Only this pageAll pages
Powered by GitBook
1 of 31

0.1

Loading...

Getting started

Loading...

Loading...

Loading...

Loading...

Loading...

Writing contract

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Client

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Resources

Loading...

Loading...

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 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]

Faucet

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

Template

React

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

Examples

  • by

  • by

  • by

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.

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.

Zama Devnet

You can get 10 Zama token on

Tutorials

  • by Morten Dahl (Zama)

  • by Clément Danjou (Zama)

  • by Clément Danjou (Zama)

# Using npm
npm install fhevm

# Using Yarn
yarn add fhevm

# Using pnpm
pnpm add fhevm
ERC-20
Blind Auction
Governor DAO
Mixnet
Remi Gai
Battleship
Owen Murovec
Darkpool
Owen Murovec
https://faucet.zama.ai/
Workshop during ETHcc
Confidential ERC-20 Tokens Using Homomorphic Encryption
On-chain Blind Auctions Using Homomorphic Encryption

Decryption and control structures

Decryptions

We allow explicit decryption requests for any encrypted type. The values are decrypted through the distributed decryption protocol and are stored on-chain.

Examples

function decryptAmount(euint8 amount) public view returns (uint8) {
    return TFHE.decrypt(amount);
}

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

Booleans

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

The purpose of the ebool type is two-fold:

  1. control bit for the cmux operator;

  2. input for optimistic encrypted require (optReq) statements.

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.

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

const instance = await createInstance({ chainId, publicKey });
const encryptedParam = instance.encrypt8(14);

FhevmInstance.encrypt16

Parameters

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

Returns

  • Uint8Array

Example

const instance = await createInstance({ chainId, publicKey });
const encryptedParam = instance.encrypt16(1234);

FhevmInstance.encrypt32

Parameters

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

Returns

  • Uint8Array

Example

const instance = await createInstance({ chainId, publicKey });
const encryptedParam = instance.encrypt32(94839304);

Docker

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

docker run -i -p 8545:8545 --rm --name fhevm ghcr.io/zama-ai/evmos-dev-node:v0.1.9

Repositories

Implementation

  • fhEVM on evmos

Libraries

  • Solidity library

  • fhevmjs

Node

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

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

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.

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

Remix

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

To avoid this problem, we developed a version of Remix IDE with these two missing features:

  • Encryption of input

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

You can use it on https://remix.zama.ai.

Usage

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

import "fhevm/lib/TFHE.sol";

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

Choose the Zama Devnet
Choose "Injected Provider - Metamask"

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:

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

# Using npm
npm install fhevmjs

# Using Yarn
yarn add fhevmjs

# Using pnpm
pnpm add fhevmjs
How to add network from popup
How to add network
How to select correct network on Metamask

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 publicKey = await provider.call({
    to: "0x0000000000000000000000000000000000000044",
  });

  // 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);
});

Browser

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

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 publicKey = await provider.call({
    from: null,
    to: "0x0000000000000000000000000000000000000044",
  });
  return createInstance({ chainId, publicKey });
};

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

init().then((instance) => {
  console.log(instance);
});

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:

    resolve: {
      fallback: {
        'tfhe_bg.wasm': require.resolve('tfhe/tfhe_bg.wasm'),
      },
    },

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

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

import { initFhevm, createInstance } from "fhevmjs/web";

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.

const start = async () => {
  await window.fhevm.initFhevm(); // load wasm needed
  const instance = window.fhevm.createInstance({ chainId, publicKey }).then((instance) => {
    console.log(instance);
  });
};

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

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 publicKey = await window.ethereum.request({
    method: 'eth_call',
    params: [{ from: null, to: '0x0000000000000000000000000000000000000044' }],
  });

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

initInstance().then((instance) => {
  console.log(instance.serializeKeypairs());
});

Transfer tokens (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(); // Need to be a number

  // Get blockchain public key
  const publicKey = await provider.call({
    to: "0x0000000000000000000000000000000000000044",
  });

  // 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);

TFHE Library

The library exposes utility functions for TFHE operations. The goal of the library is to provide a seamless developer experience for writing smart contracts that can operate on confidential data.

Types

The library provides a type system that is checked both at compile time and at run time. The structure and operations related to these types are described in this sections.

We currently support encrypted integers of bit length up to 32 bits.

Our library provides the following types :

  • ebool

  • euint8

  • euint16

  • euint32

These encrypted integers behave as much as possible as Solidity's integer types. However, behaviour such as "revert on overflow" is not supported as this would leak some information of the encrypted integers. Therefore, arithmetic on euint types is unchecked, i.e. there is wrap-around on overlow.

In the back-end, encrypted integers are TFHE ciphertexts. The library abstracts away the ciphertexts and presents pointers to ciphertexts, or ciphertext handles, to the smart contract developer. The euint types are wrappers over these handles.

Operations

The library exposes utility functions for operations on TFHE ciphertexts. The list of supported operations is presented below.

name
function name
type

Add

TFHE.add

Binary

Sub

TFHE.sub

Binary

Mul

TFHE.mul

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

More information about the supported operations can be found at the TFHE-rs docs.

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 following types:

  • euint8

  • euint16

  • euint32

Note that in the backend, FHE operations are only defined on same-type operands. Therefore, the TFHE Solidity library will do implicit upcasting if necessary.

Most binary operators are also defined with a mix of ciphertext and plaintext inputs. In this case, operators can take as input operands of the following types

  • euint8

  • euint16

  • euint32

  • uint8

  • uint16

  • uint32

under the condition that the size of the uint operand is at most the size of the euint operand. For example, add(uint8 a, euint8 b) is defined but add(uint16 a, euint16 b) is not. Note that these ciphertext-plaintext operations may take less time to compute than ciphertext-ciphertext operations.

In the backend, FHE operations are only defined on same-type operands. Therefore, the TFHE Solidity library will do implicit upcasting if necessary.

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

Examples

// 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)

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

Examples

// 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)

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)

Performs the operation homomorphically.

Note that division is not currently supported.

Examples

// 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)

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

// 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)

Bit shift operations (<<, >>)

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

Examples

// a << b
function shl(euint16 a, euint8 b) internal view returns (euint16)
// a >> b
function shr(euint32 a, euint16 b) internal view returns (euint32)

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

// 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)

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

// 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, max

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

Examples

// 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)

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 TFHE-rs docs.

Examples

Nodejs

Transfer ERC-20 tokens

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 publicKey = await provider.call({
    to: '0x0000000000000000000000000000000000000044',
  });

  // 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);

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();

  // Get blockchain public key
  const publicKey = await provider.call({
    to: '0x0000000000000000000000000000000000000044',
  });

  // 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);
});

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',
//    }
// }