Re-encryption is a client-side operation that allows only authorized users to access the underlying plaintext value by making an API call to the Gateway service using the fhevmJs library.
Steps to Perform Re-encryption on Encrypted Values
1. Access Control in Application Smart Contract
Developers need to grant access to various user addresses so that they can access specific ciphertexts. To do this, they must call TFHE.allow() each time a ciphertext changes. This call should include the ciphertext handle and the address of the user who is permitted to access it.
Note: Since the handle for each ciphertext changes whenever the ciphertext is updated, TFHE.allow() must be called after each modification to the value. Failure to do so will result in an unauthorized access error from the Gateway
// SPDX-License-Identifier: BSD-3-Clause-Clearpragmasolidity ^0.8.24;import"fhevm/lib/TFHE.sol";contract EncryptedERC20 {//Remaining logicmapping(address=> euint64) internal balances;functionmint(uint64 mintedAmount) publicvirtual { balances[owner()] = TFHE.add(balances[owner()], mintedAmount); TFHE.allow(balances[owner()],owner()); }// Transfers an encrypted amount between two addressesfunction_transfer(address from,address to, euint64 transferValue) internalvirtual { euint64 newBalanceTo = TFHE.add(balances[to], transferValue); balances[to] = newBalanceTo; euint64 newBalanceFrom = TFHE.sub(balances[from], transferValue); balances[from] = newBalanceFrom; TFHE.allow(balances[to], to); // Allow 'to' address to access new balance TFHE.allow(balances[from], from); // Allow 'from' address to access updated balance }// Returns the balance handle for a specified wallet addressfunctionbalanceOf(address wallet) publicviewvirtualreturns (euint64) {return balances[wallet]; }//Remaining logic}
2. Guide to Call the Gateway: The next step involves calling the view function from the smart contract to retrieve the unique handle for the ciphertext. Next, generate a keypair for the user and request that the user signs the public key. Finally, make an API call to the Gateway to request re-encryption, providing the ciphertext handle, public key, user address, contract address, and the user's EIP-712 signature. The dApp then decrypts the received value using the private key.
Here’s a small example of how to do this using fhevmJs on the client side for the contract above:
import abi from"./abi.json";import { Contract, BrowserProvider } from"ethers";import { createInstance } from"fhevmjs";constCONTRACT_ADDRESS="EncryptedERC20ContractAddress";constprovider=newBrowserProvider(window.ethereum);constaccounts=awaitprovider.send("eth_requestAccounts", []);constUSER_ADDRESS= accounts[0];// Create a fhevmjs instance using Inco's Rivest network and Gatewayconstinstance=awaitcreateInstance({ chainId:21097, networkUrl:"https://validator.rivest.inco.org/", gatewayUrl:"https://gateway.rivest.inco.org/", aclAddress:"0x2Fb4341027eb1d2aD8B5D9708187df8633cAFA92",});// Generate the private and public key pair for re-encryptionconst { publicKey,privateKey } =instance.generateKeypair();// Create an EIP712 object for the user to signconsteip712=instance.createEIP712(publicKey,CONTRACT_ADDRESS);// Request the user's signature on the public keyconstparams= [USER_ADDRESS,JSON.stringify(eip712)];constsignature=awaitwindow.ethereum.request({ method:"eth_signTypedData_v4", params });// Retrieve the ciphertext to re-encryptconstencryptedERC20=newContract(CONTRACT_ADDRESS, abi,provider.getSigner());constencryptedBalance=awaitencryptedERC20.balanceOf(awaitsigner.getAddress());// Request re-encryption through the Gateway and decrypt the result with the private keyconstuserBalance=awaitinstance.reencrypt( encryptedBalance,// the encrypted balance privateKey,// the private key generated by the dApp publicKey,// the public key generated by the dApp signature,// the user's signature of the public keyCONTRACT_ADDRESS,// The contract address holding the ciphertextUSER_ADDRESS// The user address for the ciphertext);console.log(userBalance);