Confidential ERC-20 Tokens Using Homomorphic Encryption and the fhEVM
This is the first post in a series dedicated to our fhEVM protocol, which enables private smart contracts using homomorphic encryption.
Fully Homomorphic Encryption (FHE) is a technology that allows computations to be performed on encrypted data without requiring its decryption. FHE can be used in blockchain to enable confidential smart contracts, where transaction inputs and states remain encrypted at all time, even during computation. You can read more about the fhEVM protocol here.
Building smart contracts for the fhEVM
Before we look at the features of the fhEVM, it is important to keep in mind a few things:
- The fhEVM is a regular EVM that can compute on both encrypted and non-encrypted values. Developers can use all the existing EVM tools without change to build their applications.
- All the computation is happening on-chain, whether they are FHE operations or regular EVM operations. This ensures the data remains available at all times.
- The transaction inputs and on-chain states are encrypted under a global network key. This is necessary for composability and multi-user interactions. The network key is split amongst validators to ensure no one can decrypt the blockchain state without first reaching a consensus.
- Smart contracts contain all the logic for the computation of states, and for the visibility of the encrypted states. The smart contract developer is the one who decides how to reveal states from his contract to the user requesting it, effectively making the contract itself perform the access control logic. Validators then simply reach consensus on what to decrypt by evaluating the contracts, and run a threshold decryption protocol to reveal the value to the user.
- To avoid malicious actors from using on-chain encrypted states as transaction inputs, and writing contracts that can decrypt it, users need to provide a ZK proof that they know the plaintext value of the encrypted inputs they sent in their transactions.
FHE data types
To make it simple for developers to use encrypted values in their contracts, the fhEVM exposes several encrypted integer data types, such as euint8, euint16, euint32. They act as the encrypted equivalent to the regular uint data types, with one major difference: smaller types are much faster to compute in FHE than bigger types, so if you only need an 8 bit encrypted value, you should not use a 256 container for it! Right now, the fhEVM only supports 8, 16 and 32 bit unsigned encrypted integers, but larger precision, signed integers and other data types are coming soon.
Computing on encrypted values
The fhEVM supports many of the traditional integer operations, such as add, sub, mul, shift, min, max, etc. We will be adding more over time, as well as provide some cryptographically optimized methods for the most common workflows.
Reverting transactions based on an encrypted condition
The require operator is an error-handling, global function in Solidity: if the condition within require comes out to be true then the compiler will execute the method, otherwise it will throw an error. In homomorphic encryption, a comparison between two ciphertexts will return an encrypted Boolean, so to evaluate it at runtime we need a specific method, similar to require, but for encrypted Booleans. Behind the scenes, this will send a request to the validators to run a threshold decryption on the control bit, and return a plaintext value for the condition.
Parsing and validating encrypted transaction inputs
When a user sends an encrypted value in a transaction, it arrives in the contract as a byte array. The contract then needs to explicitly cast it to the correct data type, which will both parse the bytes and verify the ZKPoK attached to it. It then returns an euint that can be used in the rest of the contract’s logic.
An example fhEVM contract: confidential ERC20 tokens
Now that we understand the basic building blocks of the fhEVM, we can go into a concrete example and build an ERC20 token contract where the user balances and amounts being transferred are kept hidden at all times.
This confidential ERC20 contract is a classic ERC20 contract with a few differences:
- Balances are encrypted unsigned integers;
- Total supply is also encrypted, but decryptable by any user. To avoid leaking information about balances and ensure differential privacy, we add a random number of encrypted tokens on each mint (we’ll detail this later).
Let’s start by writing our contract with its properties:
Transferring tokens
Transferring tokens involves a series of steps to ensure security and confidentiality. First, the ciphertext of the amount to be transferred is verified and the method returns an euint32. Then, the contract checks that the amount to be transferred is within the sender's balance, ensuring that there is no overspending. Finally, the transfer is executed by deducting the amount from the sender's balance and adding it to the recipient's balance, homomorphically.
Displaying a balance
In order for a user to view their balance, the contract needs to tell the network to re-encrypt the value from the network's key into the user's key. This is done directly in Solidity via a simple view function. This view function however needs to be authenticated by having the user provide a signature in the contract method call so that the network knows the user is actually the one owning the balance. The signed token respects the EIP-712 standard.
Minting tokens without leaking information about balances
When a user mints an encrypted amount of tokens, the increase in the contract's total supply can reveal the number of tokens that have been minted. To prevent this, a random number of burnable tokens are minted such that someone decrypting the total supply doesn’t know how many real tokens were minted. The tokens in the burnable pool are then regularly burned to maintain equivalence between the total supply and the actual supply. To simplify, we will reset burnable tokens every 10 mints.
In the mint method of our ERC20 contract, a user-generated random number has been used. This isn’t ideal however, which is why we are planning to add an FHE random number generator to the fhEVM itself, which would enable contracts to generate randomness on-demand.
Transaction Approved
Tokens are a simple use case, but hopefully this tutorial showed how easy it is to build with FHE. Now that we have a confidential token, we can start thinking about all kinds of applications where this would be useful, such as blind auctions, confidential DeFi and more, which we will explore in future posts.
Join the Zama x FHENIX hackathon during EthCC and take the Zama's fhEVM for a spin!
Additional links
- Follow Zama on Twitter
- Check out Zama on Github
- Deep dive into Zama fhEVM smart contract protocol with this blog post