Exploring Zama: Building Confidential Smart Contracts w/ fhEVM
A smooth introduction to Zama and how they utilize fully homomorphic encryption
Before starting, it is worth noting that this is NOT a paid content.
Hello everyone! Welcome to the new post of my blog. Normally, I'm creating two types content:
protocol exploration posts from a developer's perspective and
cryptography posts explaining mathematical concepts and their applications.
In this post, I'll try to combine both. We'll explore Zama protocol and the cryptographic concept they utilize: fully homomorphic encryption (FHE).
Contents
Fully Homomorphic Encryption (FHE)
What does "homomorphic" mean?
Group Homomorphisms
- Example
Homomorphic Encryption
Overview
RSA, Revisited
Then, what is FHE?
What is Zama?
Introduction
fhEVM
Overview
How it works?
Use Cases
Building Confidential Smart Contracts
Encrypted Types
Operations on Encrypted Types
Decryption & Reencryption
Conclusion
Fully Homomorphic Encryption
What does "homomorphic" mean?
The term "homomorphic" emerges from the Greek roots 'homos' (same) and 'morphē' (shape or form). In the context of mathematics, it pertains to the property of a mapping between two algebraic structures (such as groups, rings, or fields) that conserves operations between those structures. This means that operations conducted on transformed data can, after reverting the data back to its original form, yield identical results to those obtained from performing operations on the original data directly.
Group Homomorphisms
To understand the notion of homomorphism, let's see how they work in groups.
Let \(G\) and \(H\) be groups and \(f: G \rightarrow H\) is a mapping (function). \(f\) is called a "group homomorphism" if \(f(g_1 \cdot_G g_2) = f(g_1) \cdot_H f(g_2) \ \forall g_1, g_2 \in G.\)
The purpose here is to create some functions preserving the algebraic structure.
Example
The function \(f: (\mathbb{N}, +) \rightarrow (\mathbb{R}, +)\) be defined by \(f(x) = \sqrt{2}x\). Notice that both \((\mathbb{N}, +)\) and \((\mathbb{R}, +)\) are groups. Let's check if \(f\) is a group homomorphism, or not.
Take two elements from the first group: \(n_1,n_2 \in \mathbb{N}\). It is time to see if the homomorphism axiom is satisfied:
$$f(n_1 + n_2) = \sqrt{2} \ (n_1 + n_2)$$
$$f(n_1 + n_2) = \sqrt{2} \ n_1 + \sqrt{2} \ n_2$$
$$f(n_1 + n_2) = f(n_1) + f(n_2).$$
Hence, \(f\) is a group homomorphism.
I hope this simple example helped you to understand the concept better.
Homomorphic Encryption
Overview
Homomorphic encryption is a form of encryption that allows computations to be performed on ciphertexts, generating an encrypted result which, when decrypted, matches the result of operations performed on the plaintext. This property makes homomorphic encryption highly valuable for privacy-preserving computations, allowing sensitive data to be encrypted and processed without exposing it to the computing entity.
But the ones being discussed in this section are partially homomorphic encryption methods. This type supports either addition or multiplication on ciphertexts, but not both.
For example, the RSA encryption scheme is homomorphic with respect to multiplication.
RSA, Revisited
If you are not familiar with the RSA encryption scheme, you can take a look at my RSA post.
Recall that, in RSA, encryption process takes the public key parameters \((N, e)\) as modulus and exponent, respectively. So if we have a message \(m\) to encrypt, the ciphertext corresponds to that is calculated in the following way:
$$c = m^e \ (mod \ N)$$
where \(c\) is the ciphertext.
Let's now assume we have two different messages \(m_1\) and \(m_2\).
$$Enc(m_1 \cdot m_2) = (m_1 \cdot m_2)^e$$
$$Enc(m_1 \cdot m_2) = m_1^e \cdot m_2^e$$
$$Enc(m_1 \cdot m_2) = Enc(m_1) \cdot Enc(m_2)$$
Therefore, the RSA encryption scheme is homomorphic with respect to multiplication.
Notice that it is NOT homomorphic with respect to addition.
Then, what is FHE?
A cryptosystem that supports arbitrary computation on ciphertexts is known as fully homomorphic encryption (FHE). As computations can be expressed as arithmetic circuits (addition & multiplication), it allows to do computations on ciphertexts in an unlimited way.
What is Zama?
Introduction
Zama is an open source cryptography company building fully homomorphic encryption solutions for blockchain and AI.
As the backbone of artificial intelligence efforts is data (a huge amount of data), a couple of possible problems, such as privacy and confidentiality, may arise. That's why I personally think that combining FHE and AI is quite relevant.
Zama has a couple of products:
TFHE-rs: Rust implementation of TFHE for boolean and small integer arithmetics over encrypted data.
Concrete: TFHE compiler that converts Python programs into their FHE equivalent.
Concrete ML: Privacy-preserving ML framework built on top of Concrete, with bindings to traditional ML frameworks.
fhEVM: Confidential smart contracts on the EVM using homomorphic encryption.
Even though I'm planning to write another post on TFHE-rs, in this one, we will focus on fhEVM.
fhEVM
Overview
While the transparency provided by blockchains is beneficial in some ways, privacy is necessary at certain points. For instance, you may not want your investments to be public to everyone. Zama, intentionally, preserves one of the key benefits of transparency on blockchains: the computation carried out remains public, while only the data that is being computed on is hidden.
The good news is that fhEVM contracts are built using Solidity, along with some additional types. So they offer a smooth transition for regular smart contract developers.
How it works?
Global FHE Key
First of all, fhEVM is a threshold cryptographic system (I'm planning to write a particular blog post on threshold cryptography btw.) where content is encrypted using the global public key and the private (secret) key is distributed among validators. If more validators than the threshold agrees on decrypting the encrypted content, the key is combined and decryption is executed.
Zama fhEVM relies on a global FHE key under which all inputs and private states are encrypted. This makes it enable both trustlessness and fault-tolerance.
Input Encryption
In order to provide an encrypted input to a transaction or view function, users are required to submit two things to a smart contract:
intended input value encrypted using the global public FHE key (ciphertext),
corresponding zero-knowledge proof of the plaintext knowledge (ZKPoK).
The second value is needed, because users have to prove if they actually know the underlying plaintext message. Thanks to ZKPoK, produced ciphertext cannot be used in another context. \((Enc_{pk}(m), ZKPoK(M))\) pair is called certified ciphertext.
The receiving smart contract needs fhEVM to verify certified ciphertext.
Use Cases
Encrypted ERC-20 Tokens
Existence of such a standard is important for blockchains. However, by the public nature of blockchain systems, the individual balances of ERC-20 token holders are public and this may lead some security and privacy concerns.
Because the fhEVM allows for the use of encrypted values in smart contracts, we can create an encrypted version of the ERC-20 token standard. To do so, it suffices to change the data type of balances from integers to encrypted integers and replace each operation with their respective FHE counterpart.
Blind Auctions
Blind auctions enable bidders to submit their bids in private, ensuring that no one knows the bid amounts. Once the bidding period ends, the highest bidder is determined and announced as the winner.
These auctions can be seamlessly conducted on-chain using encrypted values. By utilizing an encrypted ERC-20 token, bidders can transfer an encrypted amount to the blind auction smart contract. This contract then compares the bids to identify and announce the winner.
Building Confidential Smart Contracts
Encrypted Types
The Solidity TFHE library provides encrypted integer types and a type system that is checked both at compile time and at runtime:
euint8
,euint16
,euint32
.
In the fhEVM implementation, encrypted integers are represented as FHE ciphertexts. The TFHE library simplifies this by offering ciphertext handles to smart contract developers.
When a smart contract receives a certified ciphertext from a user, it must verify its corresponding proof. To do so, it converts the input into a usable euint
by calling TFHE.asEuint
. For example,
TFHE.asEuint8(bytes ciphertext)
verifies the provided ciphertext and returns aneuint8
,TFHE.asEuint16(bytes ciphertext)
verifies the provided ciphertext and returns aneuint16
,TFHE.asEuint32(bytes ciphertext)
verifies the provided ciphertext and returns aneuint32
,TFHE.asEbool(bytes ciphertext)
verifies the provided ciphertext and returns anebool
.
If the verification is successful, then the fhEVM returns an encrypted value that is ready to use by the smart contract. If not, then execution is reverted.
Example:
function verify(
bytes calldata _amount
) public returns (euint32) {
euint32 amount = TFHE.asEuint32(_amount);
return amount;
}
Encrypted Operations
The fhEVM provides different operations on encrypted data types. These operations are carried out by calling a precompiled smart contract, which then uses the TFHE-rs library.
Some of them are as follows:
addition:
TFHE.add()
,subtraction:
TFHE.sub()
,multiplication:
TFHE.mul()
,isEqual:
TFHE.eq()
,isNotEqual:
TFHE.ne()
,greaterThan:
TFHE.gt()
,lessThan:
TFHE.lt()
,decrypt:
TFHE.decrypt()
,reencrypt:
TFHE.reencrypt()
.
If you want to see the full list, see the official docs.
Example 1:
Let us see how to perform a basic computation on encrypted integers:
function arithmetic(
euint32 x,
euint32 y,
euint32 z
) public returns (euint32) {
return TFHE.mul(TFHE.add(x, y), z);
}
This code basically takes 3 encrypted integers \(x, y, z\) and performs the following calculation:
$$(x+y) \cdot z.$$
Example 2:
Now, let's try to write down a transfer()
function where the required balance is checked on an encrypted data:
function transfer(
address from,
address to,
euint32 amount
) public {
ebool condition = TFHE.lte(amount, balances[from]);
require(TFHE.decrypt(condition));
balances[from] = TFHE.sub(balances[from], amount);
balances[to] = TFHE.add(balances[to], amount);
}
The code takes three inputs:
from
: sender's address,to
: recipient's address,amount
: transfer amount;
and checks if sender has sufficient balance by decrypting the ebool
.
Decryption & Reencryption
Just as we did in the example above, the fhEVM allows explicit decryption requests for any encrypted type. As discussed, the values are decrypted with the global private key if the threshold is exceeded.
Example:
function getTotalSupply() public view returns (uint32) {
return TFHE.decrypt(totalSupply);
}
This function takes encrypted totalSupply
variable and returns its decrypted version.
Zama fhEVM also supports reencrypting. In this process, the ciphertext is first decrypted with the global private key. Next, the decrypted data is encrypted again using a public key provided by the user. This newly encrypted data is then returned to the person who requested it.
Example:
function balanceOf(
bytes32 publicKey,
) public view returns (bytes memory) {
return TFHE.reencrypt(balances[msg.sender], publicKey);
}
Conclusion
In wrapping up, Zama's fhEVM introduces a new way to ensure privacy in blockchain through fully homomorphic encryption. It keeps the transparency and trust blockchain is known for, while adding a layer of privacy where it's needed. This step forward in combining privacy with transparency opens up new possibilities for blockchain applications, making it an important development for those looking to balance openness with confidentiality.