Decryption

When an app wants to read some piece of encrypted data from a Fhenix smart contract, that data must be converted from its encrypted form on chain to an encryption that the app can read and the user can decrypt.
The process of taking an FHE-encrypted ciphertext and converting it to standard encryption is called reencryption.
The data is returned to the user using sealed box encryption from NaCL. The gist of it is that the user provides a public key to the contract during a view function call, which the contract then uses to encrypt the data in such a way that only the owner of the private key associated with the provided public key can decrypt and read the data.
Pro Tip: Sealed box encryption is not Fhenix specific and can be implemented using any compliant library
For more information on Access Tokens, please refer to the Access Control Section.
Querying an Encrypted Value
FhenixJS includes methods to support creating parameters for values that require EIP-712 authentication. These methods can help creating ephemeral transaction keys, which are used by the smart contract to create a secure encryption channel to the caller.
Similarly to decryption, this usage can be implemented by any compliant library, but we include direct support in FhenixJS.
This is done in 4 steps: generating a token, signing the token, querying the contract and decrypting the data.

Creating a Token

const instance = fhenixjs.createInstance({
chainId: 8011,
publicKey: tfhePublicKey,
});
const { token, publicKey } = await instance.generateToken({ verifyingContract: contractAddress });
Signing the Token
In this step you must sign the created token using the app's connected Web3 wallet.
const params = [userAddress, JSON.stringify(generatedToken.token)];
const sign = await window.ethereum.request({ method: 'eth_signTypedData_v4', params });
Did you know? You can persist signed tokens in localstorage and reuse them
Querying the Contract
This step depends on the view function being called, but in general usually the generated credentials will be encoded and sent to the chain. For this example, we will use the balanceOf function from the example erc20 contract
const response = await contract.balanceOf(token.publicKey, sign);
Decrypting the Data
Now that we have the response data, we can use the decrypt function to decipher the data
instance.decrypt(contractAddress, response)

Putting it all Together

const decrypt = async (input: number): Uint8Array => {
const instance = fhenixjs.createInstance({
chainId: 5432,
publicKey: tfhePublicKey,
});
const { token, publicKey } = await instance.generateToken({
verifyingContract: contractAddress
});
const params = [userAddress, JSON.stringify(generatedToken.token)];
const sign = await window.ethereum.request({
method: 'eth_signTypedData_v4', params
});
const response = await contract.balanceOf(token.publicKey, sign);
instance.decrypt(contractAddress, response)
}

Without Using FhenixJS

Both the encryption and authentication standards here are not Fhenix-specific, so you can use a number of 3rd party libraries to achieve the same result. Some apps may want to avoid directly using FhenixJS in specific scenarios, so an example is provided here as well, using ethers.js and libsodium
async function decrypt(contract: ethers.Contract,
provider: BrowserProvider) {
// instantiate sodium library
await _sodium.ready;
const sodium = _sodium;
// generate keys
let keypair = sodium.crypto_box_keypair('hex');
let publicKey = keypair.publicKey;
let contractAddress = await contract.getAddress();
const signer = await provider.getSigner()
// create token
let domain = {
name: 'Authorization token',
version: '1',
chainId: 9000,
verifyingContract: contractAddress
};
let typedData = {
types: {
Reencrypt: [{
name: 'publicKey',
type: 'bytes32'
}]
},
domain: domain,
primaryType: 'Reencrypt',
message: {
publicKey: `0x${publicKey}`
}
};
// Sign token
let msgSig = await signer.signTypedData(typedData.domain, typedData.types, typedData.message);
// Query balance - assuming that the contract is already connected with the signer (wallet)
let msg = await contract.balanceOf(`0x${publicKey}`, msgSig);
// decrypt
const plaintext = sodium.crypto_box_seal_open(fromHexString(msg), fromHexString(keypair.publicKey), fromHexString(keypair.privateKey));
// todo: this is here from a previous library, need to check if this works
if (!plaintext) {
return ethers.toBigInt(0);
}
// big endian bytes to big int
return ethers.toBigInt(plaintext)
}
export const fromHexString = (hexString: string): Uint8Array => {
const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g);
if (!arr) return new Uint8Array();
return Uint8Array.from(arr.map((byte) => parseInt(byte, 16)));
};