Using fhevmjs for Encrypted Smart Contract Development
The transfer function for encrypted data operates securely by:
Verifying Authorization: Ensures the sender has access to the encrypted amount using TFHE access control.
Homomorphic Balance Updates: Balances are updated securely using FHE computations.
Granting Allowance: Updated balances are granted permissions for both the contract and the respective users.
Getting Started with fhevmjs
1. Installing fhevmjs
Install fhevmjs using any of the following package managers:
2. Configuring Your Project
fhevmjs uses the ECMAScript Module (ESM) format. To ensure compatibility, configure the type field in your package.json as follows:
For projects using "type": "commonjs", force the web version of fhevmjs by importing as follows:
3. Initializing fhevmjs
Before using fhevmjs, load the WebAssembly (WASM) modules required by TFHE:
4. Creating an Instance
Create an fhevmjs instance to interact with encrypted functionalities:
With the fhevmjs instance created, you can now utilize its methods to:
Encrypt Parameters: Prepare encrypted inputs for your smart contracts.
Perform Reencryption: Reencrypt ciphertexts for use on the client side.
Interact with Encrypted Smart Contracts: Execute functions that operate on encrypted data.
5. Asynchronous Decryption Operation
Decryption operations in the fhEVM framework are asynchronous. To use these capabilities, your smart contract must extend the GatewayCaller contract, which automatically imports the Gateway Solidity library. The following example demonstrates how to implement asynchronous decryption in your contract.
Example: Asynchronous Decryption in a Smart Contract
Note that a GatewayContract 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 fulfill the decryption requests. However, GatewayContract will still check the KMS signature during fulfillment, so we trust the relayer only to forward the request on time—a rogue relayer could not cheat by sending fake decryption results.
The Gateway.requestDecryption function is used to initiate an asynchronous decryption request. Its interface is as follows:
Parameters:
ct: An array of ciphertext handles (e.g., uint256 values). These could be derived from types like ebool, euintX, or eaddress. Example:
callbackSelector:
Function selector for the callback function to be invoked once the decryption is fulfilled. Example callback structure if passSignaturesToCaller is false:
If passSignaturesToCaller is true, include a bytes[] memory signatures parameter:
msgValue: This is the value in native tokens to be sent to the calling contract during fulfillment, i.e., when the callback is called with the results of decryption.
maxTimestamp: The maximum timestamp after which the callback will not be able to receive the decryption results (i.e., the fulfillment transaction will fail if this timestamp is exceeded). This is useful for time-sensitive applications where it’s preferable to reject outdated decryption results.
passSignaturesToCaller: This determines whether the callback should transmit KMS signatures. Useful if the dApp developer wants to remove trust from the Gateway service and prefers to verify KMS signatures directly within their dApp smart contract. A concrete example of verifying KMS signatures inside a dApp is available in the requestBoolTrustless function.
6. Utility Functions for Additional Parameters
If you need to pass extra arguments to be used inside the callback, you can use any of the following utility functions during the request. These functions store additional values in the storage of your smart contract:
Add Parameter Functions:
Get Parameter Functions:
7. Example: Adding Parameters to a Request
This example demonstrates attaching uint256 parameters to a decryption request and retrieving them during callback execution.
8. Hardhat Testing for Asynchronous Decryption
When the decryption request is fulfilled by the relayer, the GatewayContract contract, when calling the callback function, will also emit the following event:
event ResultCallback(uint256 indexed requestID, bool success, bytes result);
The first argument is the requestID of the corresponding decryption request.
success is a boolean assessing if the call to the callback succeeded.
result is the bytes array corresponding to the return data from the callback.
To test asynchronous decryption, use the helper functions asyncDecrypt and awaitAllDecryptionResults in your Hardhat tests.
Test Example:
You should set up 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 fulfillment will always happen as soon as you call the awaitAllDecryptionResults helper function. A good way to standardize Hardhat tests is hence to always call awaitAllDecryptionResults, which will ensure that all pending decryptions are fulfilled in both modes.
9. Re-encryption
Re-encryption allows ciphertexts encrypted with the blockchain key to be re-encrypted using a NaCl public key for secure client-side use.
Step 1: Smart Contract Implementation
Add a view function to return encrypted data:
Step 2: Client-Side Implementation
Last updated