Decryption in depth
This document provides a detailed guide on implementing decryption in your smart contracts using the GatewayContract
in fhEVM. It covers the setup, usage of the Gateway.requestDecryption
function, and testing with Hardhat.
GatewayContract
set up
GatewayContract
set upThe GatewayContract
is pre-deployed on the fhEVM testnet. It uses a default relayer account specified in the PRIVATE_KEY_GATEWAY_RELAYER
or ADDRESS_GATEWAY_RELAYER
environment variable in the .env
file.
Relayers are the only accounts authorized to fulfill decryption requests. The role of the GatewayContract
, however, is to independently verify the KMS signature during execution. This ensures that the relayers cannot manipulate or send fraudulent decryption results, even if compromised. However, the relayers are still trusted to forward decryption requests on time.
Gateway.requestDecryption
function
Gateway.requestDecryption
functionThe interface of the Gateway.requestDecryption
function from previous snippet is the following:
Parameters
The first argument, ctsHandles
, 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:
ebool
bool
euint4
uint8
euint8
uint8
euint16
uint16
euint32
uint32
euint64
uint64
euint128
uint128
euint256
uint256
eaddress
address
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.
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 here in the requestBoolTrustless
function.
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.
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 initGateway
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 here):
You should initialize the gateway by calling initGateway
at the top of the before
block - more specifically, before doing any transaction which could involve a decryption request. 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 call the awaitAllDecryptionResults
function which will ensure that all pending decryptions are fulfilled in both modes.
Last updated