Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
You can get 10 Zama token on https://faucet.zama.ai/.
Our library compiles seamlessly with the traditional Solidity compiler and is generally compatible with traditional Solidity tools. However, it's important to note that the execution is designed to function exclusively on a fhEVM. As a result, this library is not intended for deployment on a classic EVM, such as Goerli or Ganache.
To get started with fhEVM Solidity Library, you need to install it as a dependency in your JavaScript project. You can do this using npm (Node Package Manager) or Yarn. Open your terminal and navigate to your project's directory, then run one of the following commands:
This will download and install the fhEVM Solidity Library and its dependencies into your project.
1/ You should use our custom fhevm-hardhat-template
repository. Hardhat is a popular development environment for Solidity developers and will let you test and deploy your contracts to the fhEVM using TypeScript.
2/ A good first step is to start with an unencrypted version of the contract you want to implement, as you would usually do on a regular EVM chain. It is easier to reason first on cleartext variables, before thinking on how to add confidentiality.
3/ When you're ready, you can start to add confidentiality by using the TFHE
solidity library. Typically, this would involve converting some uintX
types to euintX
, as well as following all the detailed advices that we gave in the pitfalls to avoid and best practises section of the documentation. For inspiration, you can take a look at the examples inside the fhevm
repository. If you're using the Hardhat template, read the advices that we gave in the Hardhat section.
There used to be a dilemma in blockchain: keep your application and user data on-chain, allowing everyone to see it, or keep it privately off-chain and lose contract composability. Thanks to a breakthrough in homomorphic encryption, Zama’s fhEVM makes it possible to run confidential smart contracts on encrypted data, guaranteeing both confidentiality and composability.
fhEVM contracts are simple solidity contracts that are built using traditional solidity toolchains. Developers can use the euint data types to mark which part of their contracts should be private. All the logic for access control of encrypted states is defined by developers in their smart contracts.
Tokenization: Swap tokens and RWAs on-chain without others seeing the amounts.
Blind auctions: Bid on items without revealing the amount or the winner.
On-chain games: Keep moves, selections, cards, or items hidden until ready to reveal.
Confidential voting: Prevents bribery and blackmailing by keeping votes private.
Encrypted DIDs: Store identities on-chain and generate attestations without ZK.
Private transfers: Keep balances and amounts private, without using mixers.
🎥 Workshop during ETHcc [by Morten Dahl — Zama]
🎥 How to Write Confidential Smart Contracts Using Zama's fhEVM [by Clément Danjou (Zama)]
📃 Programmable Privacy and Onchain Compliance using Homomorphic Encryption [by Rand Hindi and Clément Danjou — Zama]
📃 Confidential ERC-20 Tokens Using Homomorphic Encryption [by [Clément Danjou — Zama]
📃 On-chain Blind Auctions Using Homomorphic Encryption [by Clément Danjou — Zama]
🖥️ Darkpool [by Owen Murovec]
The best way to start writing smart contracts with fhEVM is to use our Hardhat template. It allows you to start a fhEVM docker image and run your smart contract on it. Read the README for more information. When developing confidential contracts, we recommend to use first the mocked version of fhEVM for faster testing with pnpm test:mock
and coverage computation via pnpm coverage:mock
, this will lead to a better developer experience. However, keep in mind that the mocked fhEVM has some limitations and discrepancies compared to the real fhEVM node, as explained in the warning section at the end of this page. It's essential to run tests of the final contract version using the real fhEVM. You can do this by running pnpm test
before deployment.
For faster testing iterations, instead of launching all the tests on the local fhEVM node via pnpm test
or npx hardhat test
which could last several minutes, you could use instead a mocked version of the fhEVM. The same tests should (almost always) pass, as is, without any modification: neither the javascript files neither the solidity files need to be changed between the mocked and the real version. The mocked mode does not actually real encryption for encrypted types and runs the tests on a local hardhat node which is implementing the original EVM (i.e non-fhEVM). Additionally, the mocked mode will let you use all the hardhat related special testing/debugging methods, such as evm_mine
, evm_snapshot
, evm_revert
etc, which are very helpful for testing.
To run the mocked tests use either:
Or equivalently:
In mocked mode, all tests should pass in few seconds instead of few minutes, allowing a better developer experience. Furthermore, getting the coverage of tests is only possible in mocked mode. Just use the following command:
Or equivalently:
Then open the file coverage/index.html
to see the coverage results. This will increase security by pointing out missing branches not covered yet by the current test suite.
Notice : Due to limitations in the solidity-coverage
package, the coverage computation in fhEVM does not support tests involving the evm_snapshot
hardhat testing method, however, this method is still supported when running tests in mocked mode! In case you are using hardhat snapshots, we recommend you to end your test description by the[skip-on-coverage]
tag. Here is a concrete example for illustration purpose:
In the previous snippet, the first test will be run in every case, whether in "real" non-mocked mode (pnpm test
), testing mocked mode (pnpm test:mock
) or coverage (mocked) mode (pnpm coverage:mock
). On the other hand, the second test will be run only in testing mocked mode, i.e only when running pnpm test:mock
, since snapshots only works in that specific case. Actually, the second test will be skipped if run in coverage mode, since its description string ends with [skip-on-coverage]
and similarly, we avoid the test to fail in non-mocked mode since we check that the network name is hardhat
.
⚠️ Warning : Due to intrinsic limitations of the original EVM, the mocked version differ in few corner cases from the real fhEVM, the main difference is the difference in gas prices for the FHE operations. This means that before deploying to production, developers still need to run the tests with the original fhEVM node, as a final check in non-mocked mode, with pnpm test
or npx hardhat test
.
Zama 5-Question Developer Survey
We want to hear from you! Take 1 minute to share your thoughts and helping us enhance our documentation and libraries. 👉 Click here to participate.
We have decided to deprecate our Remix fork in favor of a new plugin we are currently developing. This plugin will enable you to develop your contracts directly on the official Remix IDE by simply loading the fhEVM plugin.
To use it:
Go to the "Plugin Manager" page
Click on "Connect to a Local Plugin"
Fill the name with "Zama" and the "Url" with "https://remix.zama.ai/"
Keep "Iframe" and "Side panel" and validate
After connecting to the Zama Plugin, you should click on the plugin button located on the left of the screen, and then add a Gateway URL to be able to request reencryption of ciphertexts, as shown in the picture below. The default recommended Gateway URL is: https://gateway.devnet.zama.ai
.
Afterwards, you will be able to deploy and use any contract that you chose to compile via this plugin interface.
Deployment on Ethereum is currently unavailable but will be supported soon.
The fhEVM does not work with Foundry as Foundry employs its own EVM, preventing us from incorporating a mock for our precompiled contract. An ongoing discussion is exploring the possibility of incorporating a plugin system for precompiles, which could potentially pave the way for the utilization of Foundry at a later stage.
However, you could still use Foundry with the mocked version of the fhEVM, but please be aware that this approach is NOT recommended, since the mocked version is not fully equivalent to the real fhEVM node's implementation (see warning in hardhat). In order to do this, you will need to rename your TFHE.sol
imports from fhevm/lib/TFHE.sol
to fhevm/mocks/TFHE.sol
in your solidity source files.
Inputs are a cornerstone of fhEVM: they allow users to push encrypted data onto the blockchain. To prevent any attacks, a user must provide proof of knowledge of the plaintext value underlying the ciphertext. This prevents the reuse of a ciphertext already stored on the blockchain.
All inputs are packed into a single ciphertext in a user-defined order, thereby minimizing the size and time required to create a zero-knowledge proof. When a function is called, there are two types of parameters: einput
, which is the index of the parameter, and bytes
, which contains the actual ciphertext and zero-knowledge proof.
For example, if a function requires 3 encrypted parameters, it could be written as follows:
On client side for the previous function, using fhevmjs, the code will be:
A contract can use an encrypted parameter by calling TFHE.asEuintXX(param, proof)
(or TFHE.asEbool
or TFHE.asEaddress
). This function will transform the input as a valid encrypted type.
We provide a docker image to spin up a fhEVM node for local development.
Type | URL |
---|---|
However, we advise developers to use directly pnpm fhevm:start
or npm run fhevm:start
commands available within the hardhat template, instead of the previous command, as this will launch a bash script which will also deploy automatically the gateway contract and launch the gateway relayer service, which are needed for asynchronous decryption requests.
WARNING: >
GatewayCaller.sol
must be imported at least once in one of your smart contracts if you wish to use the recommendedfhevm:start
command, or else the bash script will emit an error and decryptions would fail. This is needed because hardhat needs to compile the gateway predeploy contract before your initial deployment. This can be done simply by adding the following import at the top of any of the smart contracts used in your project:import "fhevm/gateway/GatewayCaller.sol";
If you need to get coins for a specific wallet, you can use the faucet as follow:
fhEVM is a technology that enables confidential smart contracts on the EVM using Fully Homomorphic Encryption (FHE).
Coming soon - Learn more about Zama's fhEVM Coprocessor. Get notified at launch ↗️
Learn the basics of fhEVM, set it up, and make it run with ease.
Start developing fhEVM smart contracts in Solidity by exploring its core features, discovering essential guides, and learning more with user-friendly tutorials.
Access to additional resources and join the Zama community.
Refer to the API and access additional resources for in-depth explanations while working with fhEVM.
Ask technical questions and discuss with the community. Our team of experts usually answers within 24 hours in working days.
Collaborate with us to advance the FHE spaces and drive innovation together.
Zama 5-Question Developer Survey
We want to hear from you! Take 1 minute to share your thoughts and helping us enhance our documentation and libraries. 👉 Click here to participate.
The TFHE
library provides encrypted integer types and a type system that is checked both at compile time and at run time.
Encrypted integers behave as much as possible as Solidity's integer types. Currently, however, behaviour such as "revert on overflow" is not supported as this would leak some information about the encrypted value. Therefore, arithmetic on e(u)int
types is unchecked, i.e. there is wrap-around on overflow.
Encrypted integers with overflow checking are coming soon to the TFHE
library. They will allow reversal in case of an overflow, but will leak some information about the operands.
In terms of implementation in the fhEVM
, encrypted integers take the form of FHE ciphertexts. The TFHE
library abstracts away that and, instead, exposes ciphertext handles to smart contract developers. The e(u)int
types are wrappers over these handles.
The following encrypted data types are defined:
type | supported |
---|---|
Higher-precision integers are supported in the TFHE-rs
library and can be added as needed to fhEVM
.
You can cast types with asEuint
/asEbool
methods.
If you require a state variable that utilizes these encrypted types, you cannot assign the value with immutable
or constant
keyword. If you're using these types, the compiler attempts to ascertain the value of TFHE.asEuintXX(yy)
during compilation, which is not feasible because asEuintXX()
invokes a precompiled contract. To address this challenge, you must not declare your encrypted state variables as immutable
or constant
. Still, you can use the following methods to set your variables:
Our devnet is up and running.
Fields | Value |
---|---|
Here are the main steps from the official guide provided by Metamask:
Add these information to access to blockchain
Validators of the blockchain do not own the blockchain's private key. Instead, the private key is owned by a Key Management Service (KMS). If the plaintext value is needed at some point, there are two ways to obtain it. Both methods are handled by a service called the Gateway.
If the plaintext is needed for some logic in a contract, the Gateway acts as an oracle service: it will listen to decryption request events and return the decrypted value through a callback function.
If the plaintext is needed by a dApp, the Gateway provides an API to reencrypt a ciphertext with the dApp's public key.
We allow explicit decryption requests for any encrypted type. The values are decrypted with the network private key.
You can read about an actual implemention in our decryption guide.
Reencryption is performed on the client side by calling the gateway service using the fhevmjs library. To do this, you need to provide a view function that returns the ciphertext to be reencrypted.
The dApp retrieves the handle from the view function (e.g., balanceOf).
The dApp generates a keypair for the user and requests the user to sign the public key.
The dApp calls the gateway, providing the handle, public key, user address, contract address, and the user's signature.
The dApp decrypts the received value with the private key.
You can read our guide explaining how to use it.
The TFHE
library defines the following operations with FHE ciphertexts:
name | function name | symbol | type |
---|---|---|---|
NOTE: The shift operators
TFHE.shr
andTFHE.shl
can take any encrypted typeeuintX
as a first operand and either auint8
or aeuint8
as a second operand, however the second operand will always be computed modulo the number of bits of the first operand. For example,TFHE.shr(euint64 x, 70)
will actually be equal toTFHE.shr(euint64 x, 6)
because70 % 64 = 6
. This is in contrast to the classical shift operators in Solidity where there is no intermediate modulo operation, so for instance anyuint64
shifted right via>>
would give a null result.
Overloaded operators +
, -
, *
, &
, ... on encrypted integers are supported (using for). As of now, overloaded operators will call the versions without an overflow check.
More information about the supported operations can be found in the function specifications page or in the TFHE-rs docs.
If you find yourself in search of a missing feature, we encourage you to consult our roadmap for upcoming developments. Alternatively, don't hesitate to reach out to us on Discord or visit our community forum.
Zama 5-Question Developer Survey
We want to hear from you! Take 1 minute to share your thoughts and helping us enhance our documentation and libraries. 👉 Click here to participate.
The decryption operation is asynchronous. To use it, your contract must extend the GatewayCaller
contract. This will import automatically the Gateway
solidity library as well. See the following example:
The interface of the Gateway.requestDecryption
function from previous snippet is the following:
The first argument, ct
, should be an array of ciphertexts handles which could be of different types, i.e uint256
values coming from unwrapping handles of type either ebool
, euint4
, euint8
, euint16
, euint32
, euint64
or eaddress
. ct
is the list of ciphertexts that are requested to be decrypted. Calling requestDecryption
will emit an EventDecryption
on the GatewayContract
contract which will be detected by a relayer. Then, the relayer will send the corresponding ciphertexts to the KMS for decryption before fulfilling the request.
callbackSelector
is the function selector of the callback function which will be called by the GatewayContract
contract once the relayer fulfils the decryption request. Notice that the callback function should always follow this convention, if passSignaturesToCaller
is set to false
:
Or, alternatively, if passSignaturesToCaller
is set to true
:
Notice that XXX
should be the decrypted type, which is a native Solidity type corresponding to the original ciphertext type, following this table of conventions:
Here callbackName
is a custom name given by the developer to the callback function, requestID
will be the request id of the decryption (could be commented if not needed in the logic, but must be present) and x_0
, x_1
, ... x_N-1
are the results of the decryption of the ct
array values, i.e their number should be the size of the ct
array.
msgValue
is the value in native tokens to be sent to the calling contract during fulfilment, i.e when the callback will be called with the results of decryption.
maxTimestamp
is the maximum timestamp after which the callback will not be able to receive the results of decryption, i.e the fulfilment transaction will fail in this case. This can be used for time-sensitive applications, where we prefer to reject decryption results on too old, out-of-date, values.
WARNING: Notice that the callback should be protected by the
onlyGateway
modifier to ensure security, as only theGatewayContract
contract should be able to call it.
Finally, if you need to pass additional arguments to be used inside the callback, you could use any of the following utility functions during the request, which would store additional values in the storage of your smart contract:
With their corresponding getter functions to be used inside the callback:
For example, see this snippet where we add two uint256
s during the request call, to make them available later during the callback:
When the decryption request is fufilled by the relayer, the GatewayContract
contract, when calling the callback function, will also emit the following event:
The first argument is the requestID
of the corresponding decryption request, success
is a boolean assessing if the call to the callback succeeded, and result
is the bytes array corresponding the to return data from the callback.
You should setup the gateway handler by calling asyncDecrypt
at the top of the before
block. Notice that when testing on the fhEVM, a decryption is fulfilled usually 2 blocks after the request, while in mocked mode the fulfilment will always happen as soon as you call the awaitAllDecryptionResults
helper function. A good way to standardize hardhat tests is hence to always callawaitAllDecryptionResults
which will ensure that all pending decryptions are fulfilled in both modes.
The reencryption process involves converting a ciphertext that was encrypted with the FHE blockchain key into one that is encrypted with the NaCl public key generated by the dApp.
First, your contract needs to implement a view function to return the ciphertext to reencrypt:
Then, you can implement the client side code. This example would run in a browser:
fhevmjs
is working out of the box and we recommend you to use it. We also provide three GitHub templates to start your project with everything set.
You can use to start an application with fhevmjs, using Vite + React + TypeScript.
You can also use to start an application with fhevmjs, using Vite + Vue + TypeScript.
You can also use to start an application with fhevmjs, using Next + TypeScript.
First, you need to install the library.
Once the WASM is loaded, you can now create an instance. An instance receives an object containing:
chainId
(optional): the chainId of the network
network
(optional): the Eip1193 object provided by window.ethereum
(used to fetch the public key and/or chain id)
networkUrl
(optional): the URL of the network (used to fetch the public key and/or chain id)
publicKey
(optional): if the public key has been fetched separately (cache), you can provide it
gatewayUrl
(optional): the URL of the gateway to retrieve a reencryption
coprocessorUrl
(optional): the URL of the coprocessor
The result of is of type ebool
. Typical boolean operations are not supported for this type, because it is an encrypted boolean.
fhEVM provides a method which acts as a ternary operator on encrypted integers. This method is called .
It is important to keep in mind that each time we assign a value using TFHE.select
, the value changes, even if the plaintext value remains the same.
If a condition is not satisfied, the transaction will not be reverted, potentially posing a challenge when attempting to communicate issues to users. A recommended approach to address this is by implementing an error handler in which the contract stores the latest error information for all wallets.
fhEVM includes an Access Control List (ACL) system that allows you to define which addresses have the right to manipulate a ciphertext. This feature prevents any address from accessing the contents of any ciphertext.
These ACLs can be adjusted in two ways:
TFHE.allow(ciphertext, address)
Permanently, on the blockchain. This allows a ciphertext to be used by a specific address at any time.
TFHE.allowTransient(ciphertext, address)
Temporarily. The ciphertext is then authorized only for the duration of the transaction.
Permanent allowance will store the ACL in a dedicated contract, while a temporary allowance will store it in , allowing developers to save gas. Transient allowance is particularly useful when calling an external function using a ciphertext as a parameter.
To illustrate, here is a simple example where one function calls another:
To simplify matters, a number of functions automatically generate temporary access (using TFHE.allowTransient
) for the contract that calls the function. This applies to:
TFHE.asEuintXX()
, TFHE.asEaddress()
, TFHE.asEbool()
TFHE.randXX()
All results from computation (TFHE.add()
, TFHE.select()
, ...)
When a function receives a ciphertext (such as ebool
, euint8
, eaddress
, ...), it needs to verify that the sender also has access to this ciphertext. This is important because otherwise, a contract could send any ciphertext authorized for the contract and potentially exploit the function to retrieve the value. For example, an attacker could transfer someone's balance as an encrypted amount. Without require(TFHE.isSenderAllowed(encryptedAmount))
, an attacker who doesn't have access to this balance could determine the value by transferring the balance between two well-funded accounts.
If a ciphertext must be reencrypted by a user, then explicit access must be granted to them. If this authorization is not given, the user will be unable to request a reencryption of this ciphertext. Due to the reencryption mechanism, a user signs a public key associated with a specific contract; therefore, the ciphertext also needs to be allowed for the contract. Let's take, for example, a transfer in an ERC20:
Fields | Value |
---|---|
Fields | Value |
---|---|
Note that a contract is already predeployed on the fhEVM testnet, and a default relayer account is added through the specification of the environment variable PRIVATE_KEY_GATEWAY_RELAYER
in the .env
file. Relayers are the only accounts authorized to fulfil the decryption requests. However GatewayContract
would still check the KMS signature during the fulfilment, so we trust the relayer only to forward the request on time, a rogue relayer could not cheat by sending fake decryption results.
Ciphertext type | Decrypted type |
---|
passSignaturesToCaller
determines whether the callback needs to transmit signatures from the KMS or not. This is useful if the dApp developer wants to remove trust from the Gateway service and prefers to check the KMS signatures directly from within his dApp smart contract. A concrete example of how to verify the KMS signatures inside a dApp is available in the requestBoolTrustless
function.
In your hardhat tests, if you sent some transactions which are requesting one or several decryptions and you wish to await the fulfilment of those decryptions, you should import the two helper methods asyncDecrypt
and awaitAllDecryptionResults
from the asyncDecrypt.ts
utility file. This would work both when testing on an fhEVM node or in mocked mode. Here is a simple hardhat test for the previous TestAsyncDecrypt
contract (more examples can be seen ):
fhevmjs
uses ESM format. You need to set the . If your node project use "type": "commonjs"
or no type, you can force the loading of the web version by using import { createInstance } from 'fhevmjs/web';
To use the library in your project, you need to load the WASM of first with initFhevm
.
You can now use your instance to or do a .
JSON-RPC
http://127.0.0.1:8545
Websocket
http://127.0.0.1:8546
ebool
yes
euint4
yes
euint8
yes
euint16
yes
euint32
yes
euint64
yes
euint128
no, coming soon
euint256
no, coming soon
eaddress
yes
ebytes64
no, coming soon
ebytes128
no, coming soon
ebytes256
yes
eint8
no, coming soon
eint16
no, coming soon
eint32
no, coming soon
eint64
no, coming soon
eint128
no, coming soon
eint256
no, coming soon
Network Name
Zama Network
New RPC URL
https://devnet.zama.ai
Chain ID
9000
Currency symbol
ZAMA
Block explorer URL (Optional)
https://explorer.devnet.zama.ai (coming soon)
Network Name
Zama Local
New RPC URL
http://localhost:8545/
Chain ID
9000
Currency symbol
ZAMA
Block explorer URL (Optional)
Add
TFHE.add
+
Binary
Sub
TFHE.sub
-
Binary
Mul
TFHE.mul
*
Binary
Div (plaintext divisor)
TFHE.div
Binary
Rem (plaintext divisor)
TFHE.rem
Binary
BitAnd
TFHE.and
&
Binary
BitOr
TFHE.or
|
Binary
BitXor
TFHE.xor
^
Binary
Shift Right
TFHE.shr
Binary
Shift Left
TFHE.shl
Binary
Rotate Right
TFHE.rotr
Binary
Rotate Left
TFHE.rotl
Binary
Equal
TFHE.eq
Binary
Not equal
TFHE.ne
Binary
Greater than or equal
TFHE.ge
Binary
Greater than
TFHE.gt
Binary
Less than or equal
TFHE.le
Binary
Less than
TFHE.lt
Binary
Min
TFHE.min
Binary
Max
TFHE.max
Binary
Neg
TFHE.neg
-
Unary
Not
TFHE.not
~
Unary
Select
TFHE.select
Ternary
Random unsigned int
TFHE.randEuintX()
Random
ebool | bool |
euint4 | uint8 |
euint8 | uint8 |
euint16 | uint16 |
euint32 | uint32 |
euint64 | uint64 |
eaddress | address |
The functions exposed by the TFHE
Solidity library come in various shapes and sizes in order to facilitate developer experience. For example, most binary operators (e.g., add
) can take as input any combination of the supported data types.
In the fhEVM
, FHE operations are only defined on same-type operands. Implicit upcasting will be done automatically, if necessary.
Most binary operators are also defined with a mix of ciphertext and plaintext operands, under the condition that the size of the plaintext operand is at most the size of the encrypted operand. For example, add(uint8 a, euint8 b)
is defined but add(uint32 a, euint16 b)
is not. Note that these ciphertext-plaintext operations may take less time to compute than ciphertext-ciphertext operations.
asEuint
The asEuint
functions serve three purposes:
verify ciphertext bytes and return a valid handle to the calling smart contract;
cast a euintX
typed ciphertext to a euintY
typed ciphertext, where X != Y
;
trivially encrypt a plaintext value.
The first case is used to process encrypted inputs, e.g. user-provided ciphertexts. Those are generally included in a transaction payload.
The second case is self-explanatory. When X > Y
, the most significant bits are dropped. When X < Y
, the ciphertext is padded to the left with trivial encryptions of 0
.
The third case is used to "encrypt" a public value so that it can be used as a ciphertext. Note that what we call a trivial encryption is not secure in any sense. When trivially encrypting a plaintext value, this value is still visible in the ciphertext bytes. More information about trivial encryption can be found here.
asEbool
The asEbool
functions behave similarly to the asEuint
functions, but for encrypted boolean values.
add
, sub
, mul
, div
, rem
)Performs the operation homomorphically.
Note that division/remainder only support plaintext divisors.
AND
, OR
, XOR
)Unlike other binary operations, bitwise operations do not natively accept a mix of ciphertext and plaintext inputs. To ease developer experience, the TFHE
library adds function overloads for these operations. Such overloads implicitely do a trivial encryption before actually calling the operation function, as shown in the examples below.
<<
, >>
)Shifts the bits of the base two representation of a
by b
positions.
Rotates the bits of the base two representation of a
by b
positions.
eq
, ne
, ge
, gt
, le
, lt
)Note that in the case of ciphertext-plaintext operations, since our backend only accepts plaintext right operands, calling the operation with a plaintext left operand will actually invert the operand order and call the opposite comparison.
The result of comparison operations is an encrypted boolean (ebool
). In the backend, the boolean is represented by an encrypted unsinged integer of bit width 8, but this is abstracted away by the Solidity library.
select
)This operator takes three inputs. The first input b
is of type ebool
and the two others of type euintX
. If b
is an encryption of true
, the first integer parameter is returned. Otherwise, the second integer parameter is returned.
min
, max
Returns the minimum (resp. maximum) of the two given values.
neg
, not
)There are two unary operators: neg
(-
) and not
(!
). Note that since we work with unsigned integers, the result of negation is interpreted as the modular opposite. The not
operator returns the value obtained after flipping all the bits of the operand.
NOTE: More information about the behavior of these operators can be found at the TFHE-rs docs.
Random encrypted integers can be generated fully on-chain.
That can only be done during transactions and not on an eth_call
RPC method, because PRNG state needs to be mutated on-chain during generation.
allow
, allowTransient
, isAllowed
, isSenderAllowed
)Allow an address to use a ciphertext, which includes computation, decryption, and reencryption. The allow
function will permanently store the allowance in a dedicated contract, while allowTransient
will temporarily store it in transient storage.
To verify whether an address is allowed, isAllowed
will return true if the specified address has permission. isSenderAllowed
is similar but uses msg.sender
as the address.
NOTE: These functions will return true if the ciphertext is authorized, regardless of whether the allowance is on the ACL contract or in transient storage.
fhevmjs include a Command-Line Interface (CLI) tool. With this handy utility, you can encrypt 8/16/32bits integer with the blockchain's FHE public key. To get started with fhevmjs CLI, first, ensure you have Node.js installed on your system. Next, install the fhevmjs package globally using the '-g' flag, which allows you to access the CLI tool from any directory:
Once installed, fhevm
command should be available. You can get all commands and options available with fhevm help
or fhevm encrypt help
.
Encrypt input 71721075
as 64bits integer and 1
as boolean for contract 0x8Fdb26641d14a80FCCBE87BF455338Dd9C539a50
and user 0xa5e1defb98EFe38EBb2D958CEe052410247F4c80
:
ERC-20: A variation of the standard ERC20 smart contract that incorporates encrypted balances, providing additional privacy for token holders. -
Governor DAO: A DAO smart contract that facilitates governance decisions through encrypted voting
Blind Auction: A smart contract for conducting blind auctions where bids are encrypted and the winning bid remains private.
Decentralized ID: A blockchain-based identity management system using smart contracts to store and manage encrypted personal data.
Build an Encrypted Wordle Game Onchain using FHE and Zama's fhEVM - February 29, 2024
Programmable Privacy and Onchain Compliance using Homomorphic Encryption - November 23, 2023
Confidential DAO Voting Using Homomorphic Encryption - October 12, 2023
Accelerate your code testing and get code coverage using fhEVM mocks - January 30, 2024
Use the CMUX operator on Zama’s fhEVM - October 27, 2023
Workshop during ETHcc: Homomorphic Encryption in the EVM - July 18, 2023
Zama 5-Question Developer Survey
We want to hear from you! Take 1 minute to share your thoughts and helping us enhance our documentation and libraries. 👉 Click here to participate.
❌ In FHE, it is not possible to break a loop based on an encrypted condition. For example, this would not work:
If your code logic requires looping on an encrypted boolean condition, we highly suggest to try to replace it by a finite loop with an appropriate constant maximum number of steps and use TFHE.select
inside the loop.
✅ For example, the previous code could maybe be replaced by the following snippet:
In this snippet, we perform 10 iterations, adding 4 to x
in each iteration as long as the iteration count is less than maxValue
. If the iteration count exceeds maxValue
, we add 0 instead for the remaining iterations because we can't break the loop.
There are two ways to contribute to the Zama fhEVM:
Open issues to report bugs and typos, or to suggest new ideas
Request to become an official contributor by emailing hello@zama.ai.
Becoming an approved contributor involves signing our Contributor License Agreement (CLA)). Only approved contributors can send pull requests, so please make sure to get in touch before you do!
fhevmjs is designed to assist in creating encrypted inputs and retrieving reencryption data off-chain through a gateway. The library works with any fhEVM and fhEVM Coprocessors.
If you are using fhevmjs
in a web application, you need to initialize it before creating an instance. To do this, you should call initFhevm
and wait for the promise to resolve.
This function returns an instance of fhevmjs, which accepts an object containing:
chainId
(optional): the chainId of the network
network
(optional): the Eip1193 object provided by window.ethereum
(used to fetch the public key and/or chain id)
networkUrl
(optional): the URL of the network (used to fetch the public key and/or chain id)
publicKey
(optional): if the public key has been fetched separately (cache), you can provide it
gatewayUrl
(optional): the URL of the gateway to retrieve a reencryption
coprocessorUrl
(optional): the URL of the coprocessor
Using window.ethereum
object:
This method creates an encrypted input and returns an input object. It requires both the user address and the contract address to ensure the encrypted input isn't reused inappropriately in a different context. An input can include multiple values of various types, resulting in a single ciphertext that packs these values.
Input object has different method to add values:
addBool
add4
add8
add16
add32
add64
addAddress
These methods process values and return the necessary data for use on the blockchain. The encrypt
method encrypts these values and provides parameters for use. The send
method encrypts, dispatches the ciphertext and proof to the coprocessor, and returns the required parameters.
A keypair consists of a private key and a public key, both generated by the dApp. These keys are used to reencrypt a blockchain ciphertext, allowing it to be securely transferred to user-specific keypairs.
Verifying that the public key used in the reencryption process belongs to the user requires the user to sign the public key linked to a specific contract address. This signature allows any ciphertext allowed for the user and the contract can be reencrypted using the signed public key. To streamline user interaction during the signature process, we utilize the EIP712 standard as the object to be signed.
This eip712
can be signed using eth_signTypedData_v4
for example in a browser:
Note: it is recommended to store the keypair and the signature in the user's browser to avoid re-requesting signature on every user connection.
Reencrypt method will use the gatewayUrl
to get the reencryption of a ciphertext and decrypt it.
First, you need to install the library.
fhevmjs
uses ESM format for web version and commonjs for node version. You need to set the type to "commonjs" in your package.json to load the correct version of fhevmjs. If your node project use "type": "module"
, you can force the loading of the Node version by using import { createInstance } from 'fhevmjs/node';
An instance receives an object containing:
chainId
(optional): the chainId of the network
network
(optional): the Eip1193 object provided by window.ethereum
(used to fetch the public key and/or chain id)
networkUrl
(optional): the URL of the network (used to fetch the public key and/or chain id)
publicKey
(optional): if the public key has been fetched separately (cache), you can provide it
gatewayUrl
(optional): the URL of the gateway to retrieve a reencryption
coprocessorUrl
(optional): the URL of the coprocessor
You can now use your instance to encrypt parameters or do a reencryption.
In the codebase, there is a new URL('tfhe_bg.wasm')
which triggers a resolve by Webpack. If you encounter an issue, you can add a fallback for this file by adding a resolve configuration in your webpack.config.js
:
If you encounter this issue with the Node Buffer object, you should offer an alternative solution. Similar issues might arise with different Node objects. In such cases, install the corresponding browserified npm package and include the fallback as follows.
With a bundler such as Webpack or Rollup, imports will be replaced with the version mentioned in the "browser"
field of the package.json
. If you encounter issue with typing, you can use this tsconfig.json using TypeScript 5.
If you encounter any other issue, you can force import of the browser package.
If you have an issue with bundling the library (for example with some SSR framework), you can use the prebundled version available in fhevmjs/bundle
. Just embed the library with a <script>
tag and you're good to go.
Never use encrypted types for constant or immutable state variables, even if they should actually stay constants, or else any transaction involving those will fail. This is because ciphertexts should always be stored in the privileged storage of the contract (see paragraph 4.4 of whitepaper) while constant and immutable variables are just appended to the bytecode of the deployed contract at construction time.
❌ So, even if a
and b
should never change after construction, this code :
✅ Should be replaced by this snippet:
The previous paragraph emphasized that branch logic should rely as much as possible on TFHE.select
instead of decryptions. It hides effectively which branch has been executed.
However, this is sometimes not enough. Enhancing the privacy of smart contracts often requires revisiting your application's logic.
For example, if implementing a simple AMM for two encrypted ERC20 tokens based on a linear constant function, it is recommended to not only hide the amounts being swapped, but also the token which is swapped in a pair.
✅ Here is a very simplified example implementations, we suppose here that the the rate between tokenA and tokenB is constant and equals to 1:
Notice that to preserve confidentiality, we had to make two inputs transfers on both tokens from the user to the AMM contract, and similarly two output transfers from the AMM to the user, even if technically most of the times it will make sense that one of the user inputs encryptedAmountAIn
or encryptedAmountBIn
is actually an encrypted zero.
This is different from a classical non-confidential AMM with regular ERC20 tokens: in this case, the user would need to just do one input transfer to the AMM on the token being sold, and receive only one output transfer from the AMM on the token being bought.
Using encrypted indexes to pick an element from an array without revealing it is not very efficient, because you would still need to loop on all the indexes to preserve confidentiality.
However, there are plans to make this kind of operation much more efficient in the future, by adding specialized operators for arrays.
For instance, imagine you have an encrypted array called encArray
and you want to update an encrypted value x
to match an item from this list, encArray[i]
, without disclosing which item you're choosing.
❌ You must loop over all the indexes and check equality homomorphically, however this pattern is very expensive in gas and should be avoided whenever possible.
Some TFHE operators exist in two versions : one where all operands are ciphertexts handles, and another where one of the operands is an unencrypted scalar. Whenever possible, use the scalar operand version, as this will save a lot of gas. See the page on Gas to discover which operators support scalar operands and compare the gas saved between both versions: all-encrypted operands vs scalar.
❌ For example, this snippet cost way more in gas:
✅ Than this one:
Despite both leading to the same encrypted result!
TFHE arithmetic operators can overflow. Do not forget to take into account such a possibility when implementing fhEVM smart contracts.
❌ For example, if you wanted to create a mint function for an encrypted ERC20 tokens with an encrypted totalSupply
state variable, this code is vulnerable to overflows:
✅ But you can fix this issue by using TFHE.select
to cancel the mint in case of an overflow:
Notice that we did not check separately the overflow on balances[msg.sender]
but only on totalSupply
variable, because totalSupply
is the sum of the balances of all the users, so balances[msg.sender]
could never overflow if totalSupply
did not.
New RPC URL
https://devnet.zama.ai
Chain ID
9000
Currency symbol
ZAMA
Gateway URL
https://gateway.devnet.zama.ai
Faucet URL
https://faucet.zama.ai
Block explorer URL
https://explorer.devnet.zama.ai (coming soon)
FHE operations are typically more computationally expensive than classical operations due to their inherent complexity. As a reference, here is an approximation of the gas cost associated with each operation.
Function name | Gas |
---|---|
function name | Gas |
---|---|
The current devnet has a gas limit of 10,000,000. If you send a transaction exceeding this limit, it won't be executed. Consequently, your wallet won't be able to emit a new transaction. To address this, emit a new transaction with the same nonce but the correct gas limit. In Metamask, you can enforce the use of a specific nonce by enabling the feature in 'Advanced Settings'.
name | description | ETA |
---|
name | function name | type | ETA |
---|
NOTE 1: Methods prefixed with
safe
will do an overflow check by decrypting an overflow bit and revert if that bit is true.
NOTE 2: Random encrypted integers that are generated fully on-chain. Currently, implemented as a mockup by using a PRNG in the plain. Not for use in production!
Function name | Gas |
---|---|
function name | euint16 |
---|---|
Function name | Gas fee |
---|---|
Function name | Gas fee |
---|---|
Function name | Gas fee |
---|---|
and
/or
/xor
26,000
not
30,000
add
/sub
65,000
add
/sub
(scalar)
65,000
mul
150,000
mul
(scalar)
88,000
div
(scalar)
139,000
rem
(scalar)
286,000
and
/or
/xor
32,000
shr
/shl
116,000
shr
/shl
(scalar)
35,000
rotr
/rotl
116,000
rotr
/rotl
(scalar)
35,000
eq
/ne
51,000
ge
/gt
/le
/lt
70,000
min
/max
121,000
min
/max
(scalar)
121,000
neg
60,000
not
33,000
select
45,000
add
/sub
94,000
add
/sub
(scalar)
94,000
mul
197,000
mul
(scalar)
159,000
div
(scalar)
238,000
rem
(scalar)
460,000
and
/or
/xor
34,000
shr
/shl
133,000
shr
/shl
(scalar)
35,000
rotr
/rotl
133,000
rotr
/rotl
(scalar)
35,000
eq
/ne
53,000
ge
/gt
/le
/lt
82,000
min
/max
128,000
min
/max
(scalar)
128,000
neg
95,000
not
34,000
select
47,000
randEuint8()
100,000
add
/sub
133,000
add
/sub
(scalar)
133,000
mul
262,000
mul
(scalar)
208,000
div
(scalar)
314,000
rem
(scalar)
622,000
and
/or
/xor
34,000
shr
/shl
153,000
shr
/shl
(scalar)
35,000
rotr
/rotl
153,000
rotr
/rotl
(scalar)
35,000
eq
/ne
54,000
ge
/gt
/le
/lt
105,000
min
/max
153,000
min
/max
(scalar)
150,000
neg
131,000
not
35,000
select
47,000
randEuint16()
100,000
add
/sub
162,000
add
/sub
(scalar)
162,000
mul
359,000
mul
(scalar)
264,000
div
(scalar)
398,000
rem
(scalar)
805,000
and
/or
/xor
35,000
shr
/shl
183,000
shr
/shl
(scalar)
35,000
rotr
/rotl
183,000
rotr
/rotl
(scalar)
35,000
eq
/ne
82,000
ge
/gt
/le
/lt
128,000
min
/max
183,000
min
/max
(scalar)
164,000
neg
160,000
not
36,000
select
50,000
randEuint32()
100,000
add
/sub
188,000
add
/sub
(scalar)
188,000
mul
641,000
mul
(scalar)
356,000
div
(scalar)
584,000
rem
(scalar)
1,095,000
and
/or
/xor
38,000
shr
/shl
227,000
shr
/shl
(scalar)
38,000
rotr
/rotl
227,000
rotr
/rotl
(scalar)
38,000
eq
/ne
86,000
ge
/gt
/le
/lt
156,000
min
/max
210,000
min
/max
(scalar)
192,000
neg
199,000
not
37,000
select
53,000
randEuint64()
100,000
eq
/ne
90,000
128bits (scalar) | Add euint128 for scalar operations | Q3 '24 |
Proof for inputs | Generate a proof for every encrypted input | Q3 '24 |
Threshold decryption | Use threshold decryption | Q3 '24 |
Random unsigned int |
| Random | Q3 '24 |
Add w/ overflow check |
| Binary, Decryption | Coming soon (1) |
Sub w/ overflow check |
| Binary, Decryption | Coming soon (1) |
Mul w/ overflow check |
| Binary, Decryption | Coming soon (1) |
Random signed int |
| Random | - |
Div |
| Binary | - |
Rem |
| Binary | - |
Set inclusion |
| Binary | - |
Fundamentals
Explore core features.
Guides
Deploy your project.
Tutorials
Learn more with tutorials.