Only this pageAll pages
Powered by GitBook
1 of 36

0.2

Loading...

Getting started

Loading...

Loading...

Loading...

Loading...

Loading...

Writing contracts

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Client

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Resources

Loading...

Loading...

Support

Connect wallet

What is Zama's fhEVM?

📙 White paper | 📁 Github | 💛 Community support | 🟨 Zama Bounty Program

Bring confidential smart contracts to your blockchain with Zama's fhEVM

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.

fhevmjs

fhevmjs is a javascript library that enables developers to interact with blockchains using Zama's cutting-edge technology based on TFHE (Fully Homomorphic Encryption over the Torus). This library provides a seamless integration of TFHE encryption capabilities into web3 applications, allowing for secure and private interactions with smart contracts.

Solidity library

The TFHE Solidity library we introduce is a powerful tool that empowers developers to manipulate encrypted data using TFHE within smart contracts. With this library, developers can perform computations over encrypted data, such as addition, multiplication, comparison and more, while maintaining the confidentiality of the underlying information.

Tutorials and Examples

  • Workshop during ETHcc [by Morten Dahl — 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]

  • ERC-20

  • Blind Auction

  • Governor DAO

  • Mixnet [by Remi Gai]

  • Battleship [by Owen Murovec]

  • Darkpool [by Owen Murovec]

Hardhat

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.

Faucet

Zama Devnet

You can get 10 Zama token on https://faucet.zama.ai/

CLI

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:

npm install -g fhevmjs

Once installed, fhevm command should be available. You can get all commands and options available with fhevm help or fhevm encrypt help.

Examples

Encrypt 71721075 as 32bits integer:

fhevm encrypt --node devnet.zama.ai 32 71721075

Docker

We provide a docker image to spin up a fhEVM node for local development.

Repositories

Implementation

Libraries

docker run -i -p 8545:8545 --rm --name fhevm ghcr.io/zama-ai/evmos-dev-node:v0.1.10
fhEVM on evmos
Solidity library
fhevmjs

Getting started

Welcome to the documentation for fhevmjs, a JavaScript library that enables interaction with blockchain using Zama's technology! This comprehensive guide provides developers with detailed information on encryption of data using TFHE (Fully Homomorphic Encryption over the Torus) and generation of EIP-719 tokens for reencrypt data.

Installation

To get started with fhevmjs, you need to install it as a dependency in your JavaScript project. You can do this using npm, Yarn or pnpm. Open your terminal and navigate to your project's directory, then run one of the following commands:

# Using npm
npm install fhevmjs

# Using Yarn
yarn add fhevmjs

# Using pnpm
pnpm add fhevmjs

This will download and install the fhevmjs library and its dependencies into your project.

Examples

  • ERC-20

  • Blind Auction

  • Governor DAO

  • Mixnet by Remi Gai

  • Battleship by Owen Murovec

  • Darkpool by Owen Murovec

Condition

The result of comparison operations is of type ebool. Typical boolean operations are not currently supported for this type, because it is encrypted.

That said, there are possibilities to condition on ebool with or without information leakage.

Decryption and condition

The first way to have a condition based on an ebool is to decrypt it and then either use an if statement or pass it to a require. This solution is the simplest way to do a condition but it will leak information. To illustrate this, let's take an example where a user bids for an item in a blind auction.

function bid(bytes calldata encryptedBid) internal {
  euint32 bid = TFHE.asEuint32(encryptedBid);
  ebool isAbove = TFHE.le(bid, highestBid);

  // Be sure that the bid is above current highestBid
  require(TFHE.decrypt(isAbove));

  // Replace highest bid
  highestBid = bid;
}

In this code, we first evaluate a homomorphic comparison checking that the user has bid more than the highest bid. This homomorphic comparison will return an encryption of 0 if false, or an encryption of 1 if true. Since we are decrypting this value with TFHE.decrypt, we are leaking information: if the user didn't bid enough tokens, the transaction is reverted. For example, a user can know the value of the highest bid by trying every possible values and finally bid just one token above.

Homomorphic condition

To avoid information leakage, fhEVM provides a method which acts as a ternary operator on encrypted integers. This method is called cmux.

function bid(bytes calldata encryptedBid) internal {
  euint32 bid = TFHE.asEuint32(encryptedBid);
  ebool isAbove = TFHE.le(bid, highestBid);

  // Replace highest bid
  highestBid = TFHE.cmux(isAbove, bid, highestBid);
}

It is important to keep in mind that each time we assign a value using TFHE.cmux, the value changes, even if the plaintext value remains the same.

Optimistic encrypted require statements

The decryption statements described above may lead to important delays during the transaction execution as several of them may need to be processed in a single transaction. Given that those decryptions might be used for control flow by using the Solidity require function, we introduce optimistic require statements (optReq). These require statements take as input a value to type ebool and are accumulated throughout the execution of the transaction. The accumulated boolean value is decrypted via the threshold decryption protocol either when an explicit decryption is executed, or at the very end of a transaction execution. If the decryption returns false, the transaction is reverted. Otherwise, state changes are persisted as usual. Optimistic requires may be more efficient, but this efficiency comes at the price of paying the full transaction gas cost if one of the boolean predicates is false.

function transfer(address to, bytes calldata encryptedAmount) internal {
  euint32 amount = TFHE.asEuint32(encryptedAmount);

  ebool hasEnoughTokens = TFHE.le(amount, balances[msg.sender]);

  TFHE.optReq(hasEnoughTokens);

  balances[to] = balances[to] + amount;
  balances[msg.sender] = balances[msg.sender] - amount;
}

Metamask

Here are the main steps from the official guide provided by Metamask:

From the homepage of your wallet, click on the network selector in the top left, and then on 'Add network'
MetaMask will open in a new tab in fullscreen mode. From here, find and the 'Add network manually' button at the bottom of the network list.

Add these informations to access to blockchain

Fields
Value

Network Name

Zama Network

New RPC URL

https://devnet.zama.ai

Chain ID

8009

Currency symbol

ZAMA

Block explorer URL (Optional)

https://main.explorer.zama.ai

Fields
Value

Network Name

Zama Local

New RPC URL

http://localhost:8545/

Chain ID

9000

Currency symbol

ZAMA

Block explorer URL (Optional)

Choose the Zama Devnet

Troubleshooting

Transaction

How to cancel a stuck transaction?

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'.

My transaction reverts but I can't get the error message

When you include a require statement in a transaction like require(TFHE.decrypt(ebool), "It didn't work");, the revert message will not be returned if ebool is false.

My transaction seems to be reverted randomly (or silently)

When you invoke the gas estimation method, fhEVM may not provide an accurate estimation if your code includes TFHE.decrypt because it is unable to determine the actual value. In such cases, the gas estimation assumes that TFHE.decrypt() returns 1. Depending on your code, this assumption may lead to a deviation from the actual gas usage. To mitigate this, consider adding a 20% buffer to the gas estimation or more. However, be cautious not to exceed 10,000,000 as the upper limit. We've written a method, available in the hardhat template, to tackle this issue. Feel free to use it.

Contract

I've defined certain constants as properties, but it appears that I'm unable to utilize them.

First, to understand the issue, you need to know that a euint32 or a ebool are uint256 under the hood: they are a 256bits hash of the actual ciphertext. So if you set your properties directly in the contract as follows:

The TFHE.asEuint32(42) will be executed during compilation to evaluate your property MY_CONSTANT, because the compiler expect to have an actual value. Since you're not calling the precompiles which would return a trivial encryption of 42, you get a 0 value.

I return bytes of the ciphertext, but the ciphertext is very small.

In the contract, obtaining the bytes of the ciphertext is not possible. As outlined in , you manipulate the hash of the ciphertext in the EVM; the actual ciphertext is only utilized when interacting with the precompiles (TFHE.decrypt, TFHE.add, ...).

Blockchain

I get the error "fhePubKey FHE public key hash doesn't match one stored in state"

This error occurs when you haven't yet published any contracts on the blockchain. To resolve this, simply deploy a contract on the blockchain.

How can I listen to blockchain events?

You can listen for events using WebSocket on port 8546 of your local Docker image. Alternatively, if you prefer to do it on Zama's devnet, you can use the WebSocket server available at wss://devnet.ws.zama.ai/.

Setup an instance

First, you need to create an instance. An instance allows you to:

  • Encrypt inputs with blockchain public key

  • Manage user keys to reencrypt contract's encrypted data

createInstance

Parameters

  • params (required):

    • chainId (required): Id of the chain

    • publicKey (required): Public key of the blockchain

    • keypairs (optional): A list of keypairs associated with contract

Returns

  • Promise<FhevmInstance>

Example

Templates

React

You can use to start an application with fhevmjs, using Vite + React + Typescript.

VueJS

You can also use to start an application with fhevmjs, using Vite + Vue + Typescript.

Inputs

The library provides a set of functions to encrypt integers of various sizes (8, 16, and 32 bits) using the blockchain's public key. These encrypted integers can then be securely used as parameters for smart contract methods within the blockchain ecosystem.

FhevmInstance.encrypt8

Parameters

  • value: number (required): A number between 0 and 255.

Returns

  • Uint8Array

Example

FhevmInstance.encrypt16

Parameters

  • value: number (required): A number between 0 and 65535.

Returns

  • Uint8Array

Example

FhevmInstance.encrypt32

Parameters

  • value: number (required): A number between 0 and 4294967295.

Returns

  • Uint8Array

Example

import { createInstance } from "fhevmjs";

const keypairs = {
  "0x1c786b8ca49D932AFaDCEc00827352B503edf16c": {
    publicKey: "7b2352b10cb4e379fc89094c445acb8b2161ec23a3694c309e01e797ab2bae22",
    privateKey: "764d194c6c686164fa5eb3c53ef3f7f5b90985723f19e865baf0961dd28991eb",
    signature:
      "0x5668c087804bd8b2f95b17d7f60599502bf7d539b0b19a4d989c3a5e422c77de37771be1f991223088e968a7e18330c7ece973f527eec03b97f219447d4833401b",
  },
};

const initInstance = async () => {
  // 1. Get chain id
  const chainIdHex = await window.ethereum.request({ method: "eth_chainId" });
  const chainId = parseInt(chainIdHex, 16);

  // Get blockchain public key
  const ret = await provider.call({
    // fhe lib address, may need to be changed depending on network
    to: "0x000000000000000000000000000000000000005d",
    // first four bytes of keccak256('fhePubKey(bytes1)') + 1 byte for library
    data: "0xd9d47bb001",
  });
  const decoded = ethers.AbiCoder.defaultAbiCoder().decode(["bytes"], ret);
  const publicKey = decoded[0];

  // Create instance
  return createInstance({ chainId, publicKey, keypairs });
};

initInstance().then((instance) => {
  console.log(instance.serializeKeypairs());
});
const instance = await createInstance({ chainId, publicKey });
const encryptedParam = instance.encrypt8(14);
const instance = await createInstance({ chainId, publicKey });
const encryptedParam = instance.encrypt16(1234);
const instance = await createInstance({ chainId, publicKey });
const encryptedParam = instance.encrypt32(94839304);
export const createTransaction = async <A extends [...{ [I in keyof A]-?: A[I] | Typed }]>(
  method: TypedContractMethod<A>,
  ...params: A
) => {
  const gasLimit = await method.estimateGas(...params);
  const updatedParams: ContractMethodArgs<A> = [
    ...params,
    { gasLimit: Math.min(Math.round(+gasLimit.toString() * 1.2), 10000000) },
  ];
  return method(...updatedParams);
};
euint32 constant private MY_CONSTANT = TFHE.asEuint32(42);
const WEBSOCKET_URL = "ws://localhost:8546/";
// const WEBSOCKET_URL = 'wss://devnet.ws.zama.ai/';
const wsProvider = new WebsocketProvider(WEBSOCKET_URL);
const contract = new Contract(address, abi, await wsProvider.getSigner());
contract.on(contract.filters.GameLaunched, () => {
  console.log("A new game has been launched");
});
our whitepaper
this template
this template
How to add network from popup
How to add network
How to select correct network on Metamask

Decryption and reencryption

Decrypt

We allow explicit decryption requests for any encrypted type. The values are decrypted with the network private key (the threshold decryption protocol is in the works).

Example

function getTotalSupply() public view returns (uint32) {
  return TFHE.decrypt(totalSupply);
}

function revertIfConditionIsFalse(ebool condition) public {
  bool plaintextCondition = TFHE.decrypt(condition);
  require(plaintextCondition, "Condition was not met");
  // ... continue execution if `condition` is true
}

Reencrypt

The reencrypt functions takes as inputs a ciphertext and a public encryption key (namely, a NaCl box).

During reencryption, the ciphertext is decrypted using the network private key (the threshold decryption protocol is in the works). Then, the decrypted result is encrypted under the user-provided public encryption key. The result of this encryption is sent back to the caller as bytes memory.

It is also possible to provide a default value to the reencrypt function. In this case, if the provided ciphertext is not initialized (i.e., if the ciphertext handle is 0), the function will return an encryption of the provided default value.

Example

function balanceOf(bytes32 publicKey) public view returns (bytes memory) {
  return TFHE.reencrypt(balances[msg.sender], publicKey, 0);
}

NOTE: If one of the following operations is called with an uninitialized ciphertext handle as an operand, this handle will be made to point to a trivial encryption of 0 before the operation is executed.

Handle private reencryption

In the example above (balanceOf), this view function need to validate the user to prevent anyone to reencrypt any user's balance. To prevent this, the user provides a signature of the given public key. The best way to do it is to use EIP-712 standard. Since this is something very useful, fhEVM library provide an abstract to use in your contract:

import "fhevm/abstracts/EIP712WithModifier.sol";

contract EncryptedERC20 is EIP712WithModifier {
  ...
  constructor() EIP712WithModifier("Authorization token", "1") {
    contractOwner = msg.sender;
  }
  ...
}

When a contract uses EIP712WithModifier abstract, a modifier is available to check user signature.

function balanceOf(
  bytes32 publicKey,
  bytes calldata signature
) public view onlySignedPublicKey(publicKey, signature) returns (bytes memory) {
  return TFHE.reencrypt(balances[msg.sender], publicKey, 0);
}

This signature can be generated on client side using fhevmjs library.

Roadmap

Operations

name
function name
type
ETA

Add w/ overflow check

TFHE.safeAdd

Binary, Decryption

Coming soon (1)

Sub w/ overflow check

TFHE.safeSub

Binary, Decryption

Coming soon (1)

Mul w/ overflow check

TFHE.safeMul

Binary, Decryption

Coming soon (1)

Div

TFHE.div

Binary

-

Rem

TFHE.rem

Binary

-

Random unsigned int

TFHE.randEuintX()

Random

-

Random signed int

TFHE.randEintX()

Random

-

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!

Getting started

Welcome to the documentation for fhEVM Solidity Library! This comprehensive guide provides developers with detailed information on the library's functions, parameters, and usage examples. Explore how to leverage TFHE's powerful capabilities for computing over encrypted data within Solidity smart contracts, enabling secure computations and encrypted data manipulation. Unlock a new level of privacy and confidentiality in your blockchain applications with fhEVM Solidity Library.

Usage

Our library TFHE requires Solidity version 0.8.19 specifically, as we rely on features exclusive to this version and do not currently provide support for versions beyond it.

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.

Installation

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.

Transfer tokens (node)

Browser

To use the library in your project, you need to load WASM of first with initFhevm.

Troubleshooting

Webpack: "Module not found: Error: Can't resolve 'tfhe_bg.wasm'"

In the codebase, there is a new URL('tfhe_bg.wasm') which triggers a resolve by Webpack. If yo u encounter an issue, you can add a fallback for this file by adding a resolve configuration in y our webpack.config.js:

Issue with importing ESM version

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 using TypeScript 5.

If you encounter any other issue, you can force import of the browser package.

Use bundled version

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.

Remix

You can use to interact with a blockchain using fhEVM. If you want to send an encrypted input, you need to encrypt it with for example. It becomes more complex if you want to reencrypt a value directly in Remix.

To avoid this problem, we developed a with these two missing features:

  • Encryption of input

  • Generation of public key and signature for reencryption and decryption.

You can use it on .

Usage

First, read the regarding Solidity version and EVM.

To import TFHE library, simply import it at the top of your contract.

import "fhevm/lib/TFHE.sol";

UPDATE: Remix doesn't take into consideration the package.json of fhevm to fetch dependencies. If you're using fhevm/abstracts/EIP712WithModifier.sol, it will fetch the latest version of the @openzeppelin/contracts package, which runs only on the Shanghai EVM (Solidity version ^0.8.20). Since fhEVM is not compatible with versions above 0.8.19, it will fail. To fix that, go to .deps/fhevm/abstracts/EIP712WithModifier.sol and change the imports as follows:

Be sure to be on the correct network before deploying your contract

````

# Using npm
npm install fhevm

# Using Yarn
yarn add fhevm

# Using pnpm
pnpm add fhevm
const { createInstance } = require("fhevmjs");
const { Wallet, JsonRpcProvider, Contract } = require("ethers");

const contractInfo = require("./EncryptedERC20.json");

const CONTRACT_ADDRESS = "0x309cf2aae85ad8a1db70ca88cfd4225bf17a7482";

const provider = new JsonRpcProvider(`https://devnet.zama.ai/`);

const signer = new Wallet("0x92293977156de6e03b20b26708cb4496b523116190b5c32d77cee8286d0c41f6", provider);

let _instance;

const getInstance = async () => {
  if (_instance) return _instance;

  // 1. Get chain id
  const network = await provider.getNetwork();
  const chainId = +network.chainId.toString(); // Need to be a number

  // Get blockchain public key
  const ret = await provider.call({
    // fhe lib address, may need to be changed depending on network
    to: "0x000000000000000000000000000000000000005d",
    // first four bytes of keccak256('fhePubKey(bytes1)') + 1 byte for library
    data: "0xd9d47bb001",
  });
  const decoded = ethers.AbiCoder.defaultAbiCoder().decode(["bytes"], ret);
  const publicKey = decoded[0];

  // Create instance
  _instance = createInstance({ chainId, publicKey });
  return _instance;
};

const transfer = async (to, amount) => {
  // Initialize contract with ethers
  const contract = new Contract(CONTRACT_ADDRESS, contractInfo.abi, signer);
  // Get instance to encrypt amount parameter
  const instance = await getInstance();
  const encryptedAmount = instance.encrypt32(amount);

  const transaction = await contract["transfer(address,bytes)"](to, encryptedAmount);
  return transaction;
};

transfer("0xa83a498Eee26f9594E3A784f204e507a5Fae3210", 10);
import { BrowserProvider } from "ethers";
import { initFhevm, createInstance } from "fhevmjs";

const createFhevmInstance = async () => {
  const provider = new BrowserProvider(window.ethereum);
  const network = await provider.getNetwork();
  const chainId = +network.chainId.toString();
  const ret = await provider.call({
    // fhe lib address, may need to be changed depending on network
    to: "0x000000000000000000000000000000000000005d",
    // first four bytes of keccak256('fhePubKey(bytes1)') + 1 byte for library
    data: "0xd9d47bb001",
  });
  const decoded = ethers.AbiCoder.defaultAbiCoder().decode(["bytes"], ret);
  const publicKey = decoded[0];
  return createInstance({ chainId, publicKey });
};

const init = async () => {
  await initFhevm(); // Load TFHE
  return createFhevmInstance();
};

init().then((instance) => {
  console.log(instance);
});
    resolve: {
      fallback: {
        'tfhe_bg.wasm': require.resolve('tfhe/tfhe_bg.wasm'),
      },
    },
import { initFhevm, createInstance } from "fhevmjs/web";
const start = async () => {
  await window.fhevm.initFhevm(); // load wasm needed
  const instance = window.fhevm.createInstance({ chainId, publicKey }).then((instance) => {
    console.log(instance);
  });
};
TFHE
tsconfig.json
import "@openzeppelin/[email protected]/utils/cryptography/ECDSA.sol";
import "@openzeppelin/[email protected]/utils/cryptography/EIP712.sol";
Remix
fhevmjs CLI tool
version of Remix IDE
https://remix.zama.ai
usage section
Choose the Zama Devnet
Choose "Injected Provider - Metamask"

Types

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

ebool

yes (1)

euint8

yes

euint16

yes

euint32

yes

euint64

no, coming soon

eint8

no, coming soon

eint16

no, coming soon

eint32

no, coming soon

eint64

no, coming soon

Higher-precision integers are supported in the TFHE-rs library and can be added as needed to fhEVM.

NOTE 1: The ebool type is currently implemented as an euint8. A more optimized native boolean type will replace euint8.

Verification

When users send serialized ciphertexts as bytes to the blockchain, they first need to be converted to the respective encrypted integer type. Conversion verifies if the ciphertext is well-formed and includes proof verification. These steps prevent usage of arbitrary inputs. For example, following functions are provided for ebool, euint8, euint16 and euint32:

  • TFHE.asEbool(bytes ciphertext) verifies the provided ciphertext and returns an ebool

  • TFHE.asEuint8(bytes ciphertext) verifies the provided ciphertext and returns an euint8

  • TFHE.asEuint16(bytes ciphertext) verifies the provided ciphertext and returns an euint16

  • TFHE.asEuint32(bytes ciphertext) verifies the provided ciphertext and returns an euint32

  • ... more functions for the respective encrypted integer types

Example

function mint(bytes calldata encryptedAmount) public onlyContractOwner {
  euint32 amount = TFHE.asEuint32(encryptedAmount);
  balances[contractOwner] = balances[contractOwner] + amount;
  totalSupply = totalSupply + amount;
}

Examples

Nodejs

Transfer ERC-20 tokens

Get balance

const { createInstance } = require("fhevmjs");
const { Wallet, JsonRpcProvider, Contract } = require("ethers");

const contractInfo = require("./EncryptedERC20.json");

const CONTRACT_ADDRESS = "0x309cf2aae85ad8a1db70ca88cfd4225bf17a7482";

const provider = new JsonRpcProvider(`https://devnet.zama.ai/`);

const signer = new Wallet("0x92293977156de6e03b20b26708cb4496b523116190b5c32d77cee8286d0c41f6", provider);

let _instance;

const getInstance = async () => {
  if (_instance) return _instance;

  // 1. Get chain id
  const network = await provider.getNetwork();
  const chainId = +network.chainId.toString(); // Need to be a number

  // Get blockchain public key
  const ret = await provider.call({
    // fhe lib address, may need to be changed depending on network
    to: "0x000000000000000000000000000000000000005d",
    // first four bytes of keccak256('fhePubKey(bytes1)') + 1 byte for library
    data: "0xd9d47bb001",
  });
  const decoded = ethers.AbiCoder.defaultAbiCoder().decode(["bytes"], ret);
  const publicKey = decoded[0];

  // Create instance
  _instance = createInstance({ chainId, publicKey });
  return _instance;
};

const transfer = async (to, amount) => {
  // Initialize contract with ethers
  const contract = new Contract(CONTRACT_ADDRESS, contractInfo.abi, signer);
  // Get instance to encrypt amount parameter
  const instance = await getInstance();
  const encryptedAmount = instance.encrypt32(amount);

  const transaction = await contract["transfer(address,bytes)"](to, encryptedAmount);
  return transaction;
};

transfer("0xa83a498Eee26f9594E3A784f204e507a5Fae3210", 10);
const { createInstance } = require("fhevmjs");
const { Wallet, JsonRpcProvider, Contract } = require("ethers");

const contractInfo = require("./EncryptedERC20.json");

const CONTRACT_ADDRESS = "0x309cf2aae85ad8a1db70ca88cfd4225bf17a7482";

const provider = new JsonRpcProvider(`https://devnet.zama.ai/`);

const signer = new Wallet("0x92293977156de6e03b20b26708cb4496b523116190b5c32d77cee8286d0c41f6", provider);

let _instance;

const getInstance = async () => {
  if (_instance) return _instance;

  // 1. Get chain id
  const network = await provider.getNetwork();

  const chainId = +network.chainId.toString();

  // Get blockchain public key
  const ret = await provider.call({
    // fhe lib address, may need to be changed depending on network
    to: "0x000000000000000000000000000000000000005d",
    // first four bytes of keccak256('fhePubKey(bytes1)') + 1 byte for library
    data: "0xd9d47bb001",
  });
  const decoded = ethers.AbiCoder.defaultAbiCoder().decode(["bytes"], ret);
  const publicKey = decoded[0];

  // Create instance
  _instance = createInstance({ chainId, publicKey });
  return _instance;
};

const getBalance = async () => {
  // Initialize contract with ethers
  const contract = new Contract(CONTRACT_ADDRESS, contractInfo.abi, signer);

  // Get instance to encrypt amount parameter
  const instance = await getInstance();

  // Generate token to decrypt
  const generatedToken = instance.generateToken({
    verifyingContract: CONTRACT_ADDRESS,
  });

  // Sign the public key
  const signature = await signer.signTypedData(
    generatedToken.token.domain,
    { Reencrypt: generatedToken.token.types.Reencrypt }, // Need to remove EIP712Domain from types
    generatedToken.token.message,
  );
  instance.setTokenSignature(CONTRACT_ADDRESS, signature);

  // Call the method
  const encryptedBalance = await contract.balanceOf(generatedToken.publicKey, signature);

  // Decrypt the balance
  const balance = instance.decrypt(CONTRACT_ADDRESS, encryptedBalance);
  return balance;
};

getBalance().then((balance) => {
  console.log(balance);
});

Get balance (node)

const { createInstance } = require("fhevmjs");
const { Wallet, JsonRpcProvider, Contract } = require("ethers");

const contractInfo = require("./EncryptedERC20.json");

const CONTRACT_ADDRESS = "0x309cf2aae85ad8a1db70ca88cfd4225bf17a7482";

const provider = new JsonRpcProvider(`https://devnet.zama.ai/`);

const signer = new Wallet("0x92293977156de6e03b20b26708cb4496b523116190b5c32d77cee8286d0c41f6", provider);

let _instance;

const getInstance = async () => {
  if (_instance) return _instance;

  // 1. Get chain id
  const network = await provider.getNetwork();

  const chainId = +network.chainId.toString();

  // Get blockchain public key
  const ret = await provider.call({
    // fhe lib address, may need to be changed depending on network
    to: "0x000000000000000000000000000000000000005d",
    // first four bytes of keccak256('fhePubKey(bytes1)') + 1 byte for library
    data: "0xd9d47bb001",
  });
  const decoded = ethers.AbiCoder.defaultAbiCoder().decode(["bytes"], ret);
  const publicKey = decoded[0];

  // Create instance
  _instance = createInstance({ chainId, publicKey });
  return _instance;
};

const getBalance = async () => {
  // Initialize contract with ethers
  const contract = new Contract(CONTRACT_ADDRESS, contractInfo.abi, signer);

  // Get instance to encrypt amount parameter
  const instance = await getInstance();

  // Generate token to decrypt
  const generatedToken = instance.generateToken({
    verifyingContract: CONTRACT_ADDRESS,
  });

  // Sign the public key
  const signature = await signer.signTypedData(
    generatedToken.token.domain,
    { Reencrypt: generatedToken.token.types.Reencrypt }, // Need to remove EIP712Domain from types
    generatedToken.token.message,
  );
  instance.setTokenSignature(CONTRACT_ADDRESS, signature);

  // Call the method
  const encryptedBalance = await contract.balanceOf(generatedToken.publicKey, signature);

  // Decrypt the balance
  const balance = instance.decrypt(CONTRACT_ADDRESS, encryptedBalance);
  return balance;
};

getBalance().then((balance) => {
  console.log(balance);
});

Node

const { createInstance } = require("fhevmjs");
createInstance({ chainId, publicKey }).then((instance) => {
  console.log(instance);
});

Tutorials

  • Workshop during ETHcc by Morten Dahl (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)

Function specifications

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:

  1. verify ciphertext bytes and return a valid handle to the calling smart contract;

  2. cast a euintX typed ciphertext to a euintY typed ciphertext, where X != Y;

  3. 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 .

Examples

asEbool

The asEbool functions behave similarly to the asEuint functions, but for encrypted boolean values.

Reencrypt

The reencrypt functions takes as inputs a ciphertext and a public encryption key (namely, a ).

During reencryption, the ciphertext is decrypted using the network private key (the threshold decryption protocol is in the works). Then, the decrypted result is encrypted under the user-provided public encryption key. The result of this encryption is sent back to the caller as bytes memory.

It is also possible to provide a default value to the reencrypt function. In this case, if the provided ciphertext is not initialized (i.e., if the ciphertext handle is 0), the function will return an encryption of the provided default value.

Examples

NOTE: If one of the following operations is called with an uninitialized ciphertext handle as an operand, this handle will be made to point to a trivial encryption of 0 before the operation is executed.

Arithmetic operations (add, sub, mul, div, rem)

Performs the operation homomorphically.

Note that division/remainder only support plaintext divisors.

Examples

Bitwise operations (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.

Examples

Bit shift operations (<<, >>)

Shifts the bits of the base two representation of a by b positions.

Examples

Comparison operation (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.

Examples

Multiplexer operator (cmux)

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.

Example

min, max

Returns the minimum (resp. maximum) of the two given values.

Examples

Unary operators (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 .

Generating random encrypted integers

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.

WARNING: Not for use in production! Currently, integers are generated in the plain via a PRNG whose seed and state are public, with the state being on-chain. An FHE-based PRNG is coming soon, where the seed and state will be encrypted.

Example

// first case
function asEuint8(bytes memory ciphertext) internal view returns (euint8)
// second case
function asEuint16(euint8 ciphertext) internal view returns (euint16)
// third case
function asEuint16(uint16 value) internal view returns (euint16)
// returns the decryption of `ciphertext`, encrypted under `publicKey`.
function reencrypt(euint32 ciphertext, bytes32 publicKey) internal view returns (bytes memory reencrypted)

// if the handle of `ciphertext` is equal to `0`, returns `defaultValue` encrypted under `publicKey`.
// otherwise, returns as above
function reencrypt(euint32 ciphertext, bytes32 publicKey, uint32 defaultValue) internal view returns (bytes memory reencrypted)
// a + b
function add(euint8 a, euint8 b) internal view returns (euint8)
function add(euint8 a, euint16 b) internal view returns (euint16)
function add(uint32 a, euint32 b) internal view returns (euint32)

// a / b
function div(euint8 a, uint8 b) internal pure returns (euint8)
function div(euint16 a, uint16 b) internal pure returns (euint16)
function div(euint32 a, uint32 b) internal pure returns (euint32)
// a & b
function and(euint8 a, euint8 b) internal view returns (euint8)

// implicit trivial encryption of `b` before calling the operator
function and(euint8 a, uint16 b) internal view returns (euint16)
// a << b
function shl(euint16 a, euint8 b) internal view returns (euint16)
// a >> b
function shr(euint32 a, euint16 b) internal view returns (euint32)
// a == b
function eq(euint32 a, euint16 b) internal view returns (ebool)

// actually returns `lt(b, a)`
function gt(uint32 a, euint16 b) internal view returns (ebool)

// actually returns `gt(a, b)`
function gt(euint16 a, uint32 b) internal view returns (ebool)
// if (b == true) return val1 else return val2
function cmux(ebool b, euint8 val1, euint8 val2) internal view returns (euint8) {
  return TFHE.cmux(b, val1, val2);
}
// min(a, b)
function min(euint32 a, euint16 b) internal view returns (euint32)

// max(a, b)
function max(uint32 a, euint8 b) internal view returns (euint32)
// Generate a random encrypted unsigned integer `r`.
euint32 r = TFHE.randEuint32();
here
NaCl box
TFHE-rs docs

Local node

If you need to get coins for a specific wallet, you can use the faucet as follow:

docker exec -i fhevm faucet 0xa5e1defb98EFe38EBb2D958CEe052410247F4c80

Reencryption

The library provides a convenient function that generates a JSON object based on the EIP-712 standard. This JSON object includes a public key and is specifically designed to facilitate data reencryption in a smart contract environment.

By utilizing this JSON object and having it signed by the user, a secure process of reencrypting data becomes possible within a smart contract. The signed JSON includes the necessary information, including the public key, which allows for seamless reencryption of the data.

FhevmInstance.generateToken

Parameters

  • options (required):

    • verifyingContract: string (required): The address of the contract

    • name: string (optional): The name used in the EIP712

    • version: string (optional): The version used in the EIP712

Returns

  • FhevmToken

Example

FhevmInstance.decrypt

Parameters

  • contractAddress: string (required): address of the contract

  • ciphertext: Uint8Array | string (required): a ciphertext, as a binary or hex string, to decrypt

Returns

  • number

Example

FhevmInstance.setTokenSignature

This method allows you to store the signature of a public key for a specific contract, in order to retrieve it later. The signature is also serialized in serializeKeypairs.

Parameters

  • contractAddress: string (required): address of the contract

  • signature: string (required): the signature of the EIP-712 token

Example

FhevmInstance.hasKeypair

This method returns true if contract has a keypair and a signature.

Parameters

  • contractAddress: string (required): address of the contract

Returns

  • boolean

Example

FhevmInstance.getTokenSignature

This method returns the saved public key and signature for a specific contract. If the contract has no keypair or no signature, this method returns null.

Parameters

  • contractAddress: string (required): address of the contract

Returns

  • TokenSignature or null

Example

FhevmInstance.serializeKeypairs

This method is useful if you want to store contract keypairs in the user LocalStorage.

Returns

  • ExportedContractKeypairs:

Example

{
  keypair: {
    publicKey: Uint8Array;
    privateKey: Uint8Array;
  }
  token: EIP712;
}
const instance = await createInstance({ chainId, publicKey });
const encryptedParam = instance.generateToken({
  name: "Authentication",
  verifyingContract: "0x1c786b8ca49D932AFaDCEc00827352B503edf16c",
});
const params = [userAddress, JSON.stringify(generatedToken.token)];
const sign = await window.ethereum.request({
  method: "eth_signTypedData_v4",
  params,
});
const instance = await createInstance({ chainId, publicKey });
const token = instance.generateToken({
  name: 'Authentication',
  verifyingContract: '0x1c786b8ca49D932AFaDCEc00827352B503edf16c',
});
...
const response = await contract.balanceOf(token.publicKey, sign);
instance.decrypt('0x1c786b8ca49D932AFaDCEc00827352B503edf16c', response)
const instance = await createInstance({ chainId, publicKey });
const generatedToken = instance.generateToken({
  name: "Authentication",
  verifyingContract: "0x1c786b8ca49D932AFaDCEc00827352B503edf16c",
});

// Ask for user to sign the token
const params = [userAddress, JSON.stringify(generatedToken.token)];
const sign = await window.ethereum.request({
  method: "eth_signTypedData_v4",
  params,
});
// Store signature
instance.setTokenSignature(contractAddress, sign);
const contractAddress = '0x1c786b8ca49D932AFaDCEc00827352B503edf16c';
const instance = await createInstance({ chainId, publicKey });
const generatedToken = instance.generateToken({
  name: 'Authentication',
  verifyingContract: ,
});

// Ask for user to sign the token
const params = [userAddress, JSON.stringify(generatedToken.token)];
const sign = await window.ethereum.request({ method: 'eth_signTypedData_v4', params });

console.log(instance.hasKeypair(contractAddress)); // false

// Store signature
instance.setTokenSignature(contractAddress, sign);

console.log(instance.hasKeypair(contractAddress)); // true
{ publicKey: Uint8Array; signature: string; } | null
const contractAddress = "0x1c786b8ca49D932AFaDCEc00827352B503edf16c";
const instance = await createInstance({ chainId, publicKey });
const generatedToken = instance.generateToken({
  name: "Authentication",
  verifyingContract: contractAddress,
});

// Ask for user to sign the token
const params = [userAddress, JSON.stringify(generatedToken.token)];
const sign = await window.ethereum.request({
  method: "eth_signTypedData_v4",
  params,
});
// Store signature
instance.setTokenSignature(contractAddress, sign);

// Now, since the signature is stored, we can fetch the public key and signature later
const { publicKey, signature } = instance.getTokenSignature();
console.log(publicKey); // Uint8Array(32) [192, 108, 9, ...]
console.log(signature); // '0x6214e232b2dae4d8d2c99837dd1af004e1b...'

const response = await contract.balanceOf(publicKey, signature);
instance.decrypt(contractAddress, response);
{
  [contractAddress: string]: {
    publicKey: string;
    privateKey: string;
    signature: string;
  }
}
const keypairs = instance.serializeKeypairs();
console.log(keypairs);
// {
//    '0x1c786b8ca49D932AFaDCEc00827352B503edf16c': {
//      signature: '0x6214e232b2dae4d8d2c99837dd1af0...',
//      publicKey: '7b2352b10cb4e379fc89094c445acb8b2161ec23a3694c309e01e797ab2bae22',
//      privateKey: '764d194c6c686164fa5eb3c53ef3f7f5b90985723f19e865baf0961dd28991eb',
//    }
// }

Operations

The TFHE library defines the following operations with FHE ciphertexts:

name
function name
symbol
type

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

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

Cmux

TFHE.cmux

Ternary

Decrypt

TFHE.decrypt()

Decryption

Reencrypt

TFHE.reencrypt()

Reencryption

Optimistic Require

TFHE.optReq()

Decryption

Random unsigned int (mockup)

TFHE.randEuintX()

Random

NOTE 1: 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!

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.