From 4ad87af613afe27c9ca7fffef2ccf723ac9da93a Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed <30662672+poppyseedDev@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:03:52 +0100 Subject: [PATCH 01/32] docs: update README and MockZamaFHEVMConfig removed --- README.md | 64 ++++--------------- docs/fundamentals/acl/acl_examples.md | 6 +- docs/fundamentals/configure.md | 26 ++------ docs/fundamentals/decryption/decrypt.md | 16 ++--- .../decryption/decrypt_details.md | 6 +- docs/fundamentals/decryption/reencryption.md | 4 +- docs/fundamentals/inputs.md | 6 +- docs/getting_started/ethereum.md | 2 +- docs/getting_started/first_smart_contract.md | 20 ++---- docs/guides/contracts.md | 6 +- 10 files changed, 48 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 9f530431..cc2e8d3f 100644 --- a/README.md +++ b/README.md @@ -112,19 +112,25 @@ _Find more details on implementation instructions in [this repository](https://g pragma solidity ^0.8.24; import "fhevm/lib/TFHE.sol"; +import "fhevm/config/ZamaFHEVMConfig.sol"; -contract Counter { - euint32 counter; +contract Counter is SepoliaZamaFHEVMConfig { + euint8 counter; + + constructor() { + counter = TFHE.asEuint8(0); + TFHE.allowThis(counter); + } function add(einput valueInput, bytes calldata inputProof) public { - euint32 value = TFHE.asEuint32(valueInput, inputProof); + euint32 value = TFHE.asEuint8(valueInput, inputProof); counter = TFHE.add(counter, value); - TFHE.allow(counter, address(this)); + TFHE.allowThis(counter); } } ``` -_More examples are available [here](https://github.com/zama-ai/fhevm/tree/main/examples)._ +_More examples are available [here](https://docs.zama.ai/fhevm/tutorials/see-all-tutorials)._
↑ Back to top
@@ -138,7 +144,7 @@ _More examples are available [here](https://github.com/zama-ai/fhevm/tree/main/e
### White paper
-- [Confidential EVM Smart Contracts using Fully Homomorphic Encryption](https://github.com/zama-ai/fhevm/blob/main/fhevm-whitepaper.pdf)
+- [Confidential EVM Smart Contracts using Fully Homomorphic Encryption](https://github.com/zama-ai/fhevm/blob/main/fhevm-whitepaper-v2.pdf)
### Demos and Tutorials
@@ -197,57 +203,11 @@ test/tfheOperations/tfheOperations.ts
↑ Back to top
- ↑ Back to top -
- ### Citations To cite fhEVM or the whitepaper in academic papers, please use the following entries: diff --git a/docs/fundamentals/acl/acl_examples.md b/docs/fundamentals/acl/acl_examples.md index 94ce5bab..ecb63af3 100644 --- a/docs/fundamentals/acl/acl_examples.md +++ b/docs/fundamentals/acl/acl_examples.md @@ -33,9 +33,9 @@ The ACL system allows you to define two types of permissions for accessing ciphe ```solidity import "fhevm/lib/TFHE.sol"; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; -contract SecretGiver is MockZamaFHEVMConfig { +contract SecretGiver is SepoliaZamaFHEVMConfig { SecretStore public secretStore; constructor() { @@ -56,7 +56,7 @@ contract SecretGiver is MockZamaFHEVMConfig { ``` ``` -contract SecretStore is MockZamaFHEVMConfig { +contract SecretStore is SepoliaZamaFHEVMConfig { euint16 public secretResult; function storeSecret(euint16 callerSecret) public { diff --git a/docs/fundamentals/configure.md b/docs/fundamentals/configure.md index 58209bbc..3130f6d3 100644 --- a/docs/fundamentals/configure.md +++ b/docs/fundamentals/configure.md @@ -21,14 +21,8 @@ This configuration contract initializes the **fhEVM environment** with required **Import based on your environment:** ```solidity -// For Mock testnet -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; - // For Ethereum Sepolia import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; - -// For Ethereum Mainnet (when ready) -import { EthereumZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; ``` **Purpose:** @@ -36,15 +30,15 @@ import { EthereumZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; - Sets encryption parameters such as cryptographic keys and supported ciphertext types. - Ensures proper initialization of the FHEVM environment. -**Example: using mock configuration** +**Example: using Sepolia configuration** ```solidity // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; -contract MyERC20 is MockZamaFHEVMConfig { +contract MyERC20 is SepoliaZamaFHEVMConfig { constructor() { // Additional initialization logic if needed } @@ -58,14 +52,8 @@ To perform decryption or reencryption, your contract must interact with the **Ga **Import based on your environment** ```solidity -// For Mock testnet -import { MockZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; - // For Ethereum Sepolia import { SepoliaZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; - -// For Ethereum Mainnet (when ready) -import { EthereumZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; ``` **Purpose** @@ -73,15 +61,15 @@ import { EthereumZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; - Configures the Gateway for secure cryptographic operations. - Facilitates reencryption and decryption requests. -**Example: Configuring the gateway with mock settings** +**Example: Configuring the gateway with Sepolia settings** ```solidity import "fhevm/lib/TFHE.sol"; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; -import { MockZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; import "fhevm/gateway/GatewayCaller.sol"; -contract Test is MockZamaFHEVMConfig, MockZamaGatewayConfig, GatewayCaller { +contract Test is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, GatewayCaller { constructor() { // Gateway and FHEVM environment initialized automatically } diff --git a/docs/fundamentals/decryption/decrypt.md b/docs/fundamentals/decryption/decrypt.md index 066ef25a..f80d6545 100644 --- a/docs/fundamentals/decryption/decrypt.md +++ b/docs/fundamentals/decryption/decrypt.md @@ -25,11 +25,11 @@ Here’s an example of how to request decryption in a contract: pragma solidity ^0.8.24; import "fhevm/lib/TFHE.sol"; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; -import { MockZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; import "fhevm/gateway/GatewayCaller.sol"; -contract TestAsyncDecrypt is MockZamaFHEVMConfig, MockZamaGatewayConfig, GatewayCaller { +contract TestAsyncDecrypt is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, GatewayCaller { ebool xBool; bool public yBool; @@ -55,8 +55,8 @@ contract TestAsyncDecrypt is MockZamaFHEVMConfig, MockZamaGatewayConfig, Gateway 1. **Configuration imports**: The configuration contracts are imported to set up the FHEVM environment and Gateway. ```solidity - import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; - import { MockZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; + import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; + import { SepoliaZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; ``` 2. **`GatewayCaller` import**:\ @@ -75,15 +75,15 @@ Remember our [**Encrypted Counter**](../../getting_started/first_smart_contract. pragma solidity ^0.8.24; import "fhevm/lib/TFHE.sol"; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; -import { MockZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; import "fhevm/gateway/GatewayCaller.sol"; /// @title EncryptedCounter3 /// @notice A contract that maintains an encrypted counter and is meant for demonstrating how decryption works /// @dev Uses TFHE library for fully homomorphic encryption operations and Gateway for decryption /// @custom:experimental This contract is experimental and uses FHE technology with decryption capabilities -contract EncryptedCounter3 is MockZamaFHEVMConfig, MockZamaGatewayConfig, GatewayCaller { +contract EncryptedCounter3 is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, GatewayCaller { /// @dev Decrypted state variable euint8 counter; uint8 public decryptedCounter; diff --git a/docs/fundamentals/decryption/decrypt_details.md b/docs/fundamentals/decryption/decrypt_details.md index b057ef53..0b0b10da 100644 --- a/docs/fundamentals/decryption/decrypt_details.md +++ b/docs/fundamentals/decryption/decrypt_details.md @@ -114,11 +114,11 @@ For example, see this snippet where we add two `uint256`s during the request cal pragma solidity ^0.8.24; import "fhevm/lib/TFHE.sol"; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; -import { MockZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaGatewayConfig } from "fhevm/config/ZamaGatewayConfig.sol"; import "fhevm/gateway/GatewayCaller.sol"; -contract TestAsyncDecrypt is MockZamaFHEVMConfig, MockZamaGatewayConfig, GatewayCaller { +contract TestAsyncDecrypt is SepoliaZamaFHEVMConfig, SepoliaZamaGatewayConfig, GatewayCaller { euint32 xUint32; uint32 public yUint32; diff --git a/docs/fundamentals/decryption/reencryption.md b/docs/fundamentals/decryption/reencryption.md index acd681e9..38e06669 100644 --- a/docs/fundamentals/decryption/reencryption.md +++ b/docs/fundamentals/decryption/reencryption.md @@ -108,14 +108,14 @@ Here’s an enhanced **Encrypted Counter** example where each user maintains the pragma solidity ^0.8.24; import "fhevm/lib/TFHE.sol"; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; /// @title EncryptedCounter4 /// @notice A contract that maintains encrypted counters for each user and is meant for demonstrating how re-encryption works /// @dev Uses TFHE library for fully homomorphic encryption operations /// @custom:security Each user can only access and modify their own counter /// @custom:experimental This contract is experimental and uses FHE technology -contract EncryptedCounter4 is MockZamaFHEVMConfig { +contract EncryptedCounter4 is SepoliaZamaFHEVMConfig { // Mapping from user address to their encrypted counter value mapping(address => euint8) private counters; diff --git a/docs/fundamentals/inputs.md b/docs/fundamentals/inputs.md index 4e41eb1f..247ec0f1 100644 --- a/docs/fundamentals/inputs.md +++ b/docs/fundamentals/inputs.md @@ -155,16 +155,16 @@ Now that we have new knowledge on how to add encrypted inputs, let's upgrade our pragma solidity ^0.8.24; import "fhevm/lib/TFHE.sol"; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; /// @title EncryptedCounter2 /// @notice A contract that maintains an encrypted counter and is meant for demonstrating how to add encrypted types /// @dev Uses TFHE library for fully homomorphic encryption operations /// @custom:experimental This contract is experimental and uses FHE technology -contract EncryptedCounter2 { +contract EncryptedCounter2 is SepoliaZamaFHEVMConfig { euint8 counter; - constructor() is MockZamaFHEVMConfig { + constructor() { // Initialize counter with an encrypted zero value counter = TFHE.asEuint8(0); TFHE.allowThis(counter); diff --git a/docs/getting_started/ethereum.md b/docs/getting_started/ethereum.md index 6452f991..c136b227 100644 --- a/docs/getting_started/ethereum.md +++ b/docs/getting_started/ethereum.md @@ -52,7 +52,7 @@ Choose and inherit the correct configuration based on the environment: - **Testnets (e.g., Sepolia)**: For deploying to public test networks. - **Mainnet**: When deploying to production. -Ensure configuration contracts (e.g., `MockZamaFHEVMConfig`, `SepoliaZamaFHEVMConfig`) are inherited correctly to initialize encryption parameters, cryptographic keys, and Gateway addresses. See [configuration](../fundamentals/configure.md) for more details. +Ensure configuration contracts (e.g., `SepoliaZamaFHEVMConfig`, `SepoliaZamaFHEVMConfig`) are inherited correctly to initialize encryption parameters, cryptographic keys, and Gateway addresses. See [configuration](../fundamentals/configure.md) for more details. ### 4. Begin with unencrypted logic diff --git a/docs/getting_started/first_smart_contract.md b/docs/getting_started/first_smart_contract.md index e3faeb9a..e3b20e7a 100644 --- a/docs/getting_started/first_smart_contract.md +++ b/docs/getting_started/first_smart_contract.md @@ -31,7 +31,7 @@ Create a new file called `EncryptedCounter.sol` in your `contracts/` folder and pragma solidity ^0.8.24; import "fhevm/lib/TFHE.sol"; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; /// @title EncryptedCounter1 /// @notice A basic contract demonstrating the setup of encrypted types @@ -39,7 +39,7 @@ import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; /// @custom:experimental This is a minimal example contract intended only for learning purposes /// @custom:notice This contract has limited real-world utility and serves primarily as a starting point /// for understanding how to implement basic FHE operations in Solidity -contract EncryptedCounter1 is MockZamaFHEVMConfig { +contract EncryptedCounter1 is SepoliaZamaFHEVMConfig { euint8 counter; euint8 CONST_ONE; @@ -63,22 +63,14 @@ contract EncryptedCounter1 is MockZamaFHEVMConfig { #### How it works 1. **Configuring fhEVM**:\ - The contract inherits from `MockZamaFHEVMConfig` which provides the necessary configuration for local development and testing. This configuration includes the addresses of the TFHE library and Gateway contracts. + The contract inherits from `SepoliaZamaFHEVMConfig` which provides the necessary configuration for local development and testing. This configuration includes the addresses of the TFHE library and Gateway contracts. When deploying to different networks, you can use the appropriate configuration: ```solidity - // For local testing - import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; - contract MyContract is MockZamaFHEVMConfig { ... } - // For Sepolia testnet import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; contract MyContract is SepoliaZamaFHEVMConfig { ... } - - // For Ethereum (when ready) - import { EthereumZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; - contract MyContract is EthereumZamaFHEVMConfig { ... } ``` The configuration handles setting up: @@ -116,7 +108,7 @@ import { ethers } from "hardhat"; describe("EncryptedCounter1", function () { before(async function () { - await initSigners(2); // Initialize signers + await initSigners(); // Initialize signers this.signers = await getSigners(); }); @@ -125,7 +117,7 @@ describe("EncryptedCounter1", function () { this.counterContract = await CounterFactory.connect(this.signers.alice).deploy(); await this.counterContract.waitForDeployment(); this.contractAddress = await this.counterContract.getAddress(); - this.instances = await createInstances(this.signers); // Set up instances for testing + this.instances = await createInstance(); // Set up instances for testing }); it("should increment the counter", async function () { @@ -158,7 +150,7 @@ The test file demonstrates key concepts for testing fhEVM smart contracts: ``` 3. **Key components**: - - `createInstances()`: Sets up FHE instances for each signer to handle encrypted operations + - `createInstance()`: Sets up FHE instances for each signer to handle encrypted operations - `getSigners()`: Provides test accounts to interact with the contract - `contractFactory.deploy()`: Creates a new contract instance for testing - `tx.wait()`: Ensures transactions are mined before continuing diff --git a/docs/guides/contracts.md b/docs/guides/contracts.md index bacfbd52..aece68e8 100644 --- a/docs/guides/contracts.md +++ b/docs/guides/contracts.md @@ -25,16 +25,16 @@ pnpm add fhevm-contracts ### Local testing with the mock network -When testing your contracts locally, you can use the `MockZamaFHEVMConfig` which provides a mock configuration for local development and testing. This allows you to test your contracts without needing to connect to a real network: +When testing your contracts locally, you can use the `SepoliaZamaFHEVMConfig` which provides a mock configuration for local development and testing. This allows you to test your contracts without needing to connect to a real network: ```solidity // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -import { MockZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; +import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; import { EncryptedERC20 } from "fhevm-contracts/contracts/token/ERC20/EncryptedERC20.sol"; -contract MyERC20 is MockZamaFHEVMConfig, EncryptedERC20 { +contract MyERC20 is SepoliaZamaFHEVMConfig, EncryptedERC20 { constructor() EncryptedERC20("MyToken", "MYTOKEN") { _unsafeMint(1000000, msg.sender); } From a7269de3910cb7e3a379a3f35d9a60b33335967c Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed <30662672+poppyseedDev@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:22:08 +0100 Subject: [PATCH 02/32] docs: update typescript sample templates --- docs/fundamentals/decryption/decrypt.md | 12 +-- docs/fundamentals/decryption/reencryption.md | 78 ++++++++++---------- docs/fundamentals/inputs.md | 13 ++-- docs/getting_started/first_smart_contract.md | 4 +- docs/guides/contracts.md | 16 ++-- docs/guides/error_handling.md | 2 +- docs/guides/loop.md | 8 +- test/encryptedERC20/EncryptedERC20.FHEGas.ts | 2 +- test/encryptedERC20/EncryptedERC20.ts | 2 +- test/gatewayDecrypt/testAsyncDecrypt.ts | 2 +- test/kmsVerifier/kmsVerifier.ts | 2 +- test/payments/payments.ts | 2 +- test/reencryption/reencryption.ts | 2 +- 13 files changed, 70 insertions(+), 75 deletions(-) diff --git a/docs/fundamentals/decryption/decrypt.md b/docs/fundamentals/decryption/decrypt.md index f80d6545..e33919ec 100644 --- a/docs/fundamentals/decryption/decrypt.md +++ b/docs/fundamentals/decryption/decrypt.md @@ -133,14 +133,14 @@ Here’s a sample test for the Encrypted Counter contract using Hardhat: ```ts import { awaitAllDecryptionResults, initGateway } from "../asyncDecrypt"; -import { createInstances } from "../instance"; +import { createInstance } from "../instance"; import { getSigners, initSigners } from "../signers"; import { expect } from "chai"; import { ethers } from "hardhat"; describe("EncryptedCounter3", function () { before(async function () { - await initSigners(2); // Initialize signers + await initSigners(); // Initialize signers this.signers = await getSigners(); await initGateway(); // Initialize the gateway for decryption }); @@ -150,12 +150,12 @@ describe("EncryptedCounter3", function () { this.counterContract = await CounterFactory.connect(this.signers.alice).deploy(); await this.counterContract.waitForDeployment(); this.contractAddress = await this.counterContract.getAddress(); - this.instances = await createInstances(this.signers); // Set up instances for testing + this.instances = await createInstance(); // Set up instances for testing }); - it("should increment counter multiple times and decrypt the result", async function () { + it("should increment counter and decrypt the result", async function () { // Create encrypted input for amount to increment by - const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); + const input = this.instances.createEncryptedInput(this.contractAddress, this.signers.alice.address); input.add8(5); // Increment by 5 as an example const encryptedAmount = await input.encrypt(); @@ -169,7 +169,7 @@ describe("EncryptedCounter3", function () { // Wait for decryption to complete await awaitAllDecryptionResults(); - // Check decrypted value + // Check decrypted value (should be 3: initial 0 + three increments) const decryptedValue = await this.counterContract.getDecryptedCounter(); expect(decryptedValue).to.equal(5); }); diff --git a/docs/fundamentals/decryption/reencryption.md b/docs/fundamentals/decryption/reencryption.md index 38e06669..b5f340cf 100644 --- a/docs/fundamentals/decryption/reencryption.md +++ b/docs/fundamentals/decryption/reencryption.md @@ -30,7 +30,7 @@ To retrieve the ciphertext that needs to be re-encrypted, you can implement a vi ```solidity import "fhevm/lib/TFHE.sol"; -contract EncryptedERC20 { +contract ConfidentialERC20 { ... function balanceOf(account address) public view returns (bytes euint64) { return balances[msg.sender]; @@ -58,7 +58,7 @@ const provider = new BrowserProvider(window.ethereum); const accounts = await provider.send("eth_requestAccounts", []); const USER_ADDRESS = accounts[0]; -await initSigners(2); // Initialize signers +await initSigners(); // Initialize signers const signers = await getSigners(); const instance = await createInstances(this.signers); @@ -73,8 +73,8 @@ const params = [USER_ADDRESS, JSON.stringify(eip712)]; const signature = await window.ethereum.request({ method: "eth_signTypedData_v4", params }); // Get the ciphertext to reencrypt -const encryptedERC20 = new Contract(CONTRACT_ADDRESS, abi, signer).connect(provider); -const encryptedBalance = encryptedERC20.balanceOf(userAddress); +const ConfidentialERC20 = new Contract(CONTRACT_ADDRESS, abi, signer).connect(provider); +const encryptedBalance = ConfidentialERC20.balanceOf(userAddress); // This function will call the gateway and decrypt the received value with the provided private key const userBalance = instance.reencrypt( @@ -144,35 +144,15 @@ contract EncryptedCounter4 is SepoliaZamaFHEVMConfig { Here’s a sample test to verify re-encryption functionality: ```ts -import { expect } from "chai"; -import { ethers } from "hardhat"; - -import { initGateway, awaitAllReEncryptionResults } from "../asyncReEncrypt"; -import { createInstances } from "../instance"; +import { createInstance } from "../instance"; +import { reencryptEuint8 } from "../reencrypt"; import { getSigners, initSigners } from "../signers"; - -import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { expect } from "chai"; -import type { FhevmInstance } from "fhevmjs"; import { ethers } from "hardhat"; -import { createInstances } from "../instance"; -import { getSigners, initSigners } from "../signers"; - -/** - * Helper function to setup reencryption - */ -async function setupReencryption(instance: FhevmInstance, signer: HardhatEthersSigner, contractAddress: string) { - const { publicKey, privateKey } = instance.generateKeypair(); - const eip712 = instance.createEIP712(publicKey, contractAddress); - const signature = await signer.signTypedData(eip712.domain, { Reencrypt: eip712.types.Reencrypt }, eip712.message); - - return { publicKey, privateKey, signature: signature.replace("0x", "") }; -} - describe("EncryptedCounter4", function () { before(async function () { - await initSigners(2); // Initialize signers + await initSigners(); // Initialize signers this.signers = await getSigners(); }); @@ -181,11 +161,11 @@ describe("EncryptedCounter4", function () { this.counterContract = await CounterFactory.connect(this.signers.alice).deploy(); await this.counterContract.waitForDeployment(); this.contractAddress = await this.counterContract.getAddress(); - this.instances = await createInstances(this.signers); // Set up instances for testing + this.instances = await createInstance(); }); it("should allow reencryption and decryption of counter value", async function () { - const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); + const input = this.instances.createEncryptedInput(this.contractAddress, this.signers.alice.address); input.add8(1); // Increment by 1 as an example const encryptedAmount = await input.encrypt(); @@ -196,28 +176,44 @@ describe("EncryptedCounter4", function () { // Get the encrypted counter value const encryptedCounter = await this.counterContract.getCounter(); - // Set up reencryption keys and signature - const { publicKey, privateKey, signature } = await setupReencryption( - this.instances.alice, - this.signers.alice, + const decryptedValue = await reencryptEuint8( + this.signers, + this.instances, + "alice", + encryptedCounter, this.contractAddress, ); - // Perform reencryption and decryption - const decryptedValue = await this.instances.alice.reencrypt( + // Verify the decrypted value is 1 (since we incremented once) + expect(decryptedValue).to.equal(1); + }); + + it("should allow reencryption of counter value", async function () { + const input = this.instances.createEncryptedInput(this.contractAddress, this.signers.bob.address); + input.add8(1); // Increment by 1 as an example + const encryptedAmount = await input.encrypt(); + + // Call incrementBy with encrypted amount + const tx = await this.counterContract + .connect(this.signers.bob) + .incrementBy(encryptedAmount.handles[0], encryptedAmount.inputProof); + await tx.wait(); + + // Get the encrypted counter value + const encryptedCounter = await this.counterContract.connect(this.signers.bob).getCounter(); + + const decryptedValue = await reencryptEuint8( + this.signers, + this.instances, + "bob", encryptedCounter, - privateKey, - publicKey, - signature, this.contractAddress, - this.signers.alice.address, ); - // Verify the decrypted value is 1 + // Verify the decrypted value is 1 (since we incremented once) expect(decryptedValue).to.equal(1); }); }); - ``` #### Key additions in testing diff --git a/docs/fundamentals/inputs.md b/docs/fundamentals/inputs.md index 247ec0f1..ccbd0c4f 100644 --- a/docs/fundamentals/inputs.md +++ b/docs/fundamentals/inputs.md @@ -51,7 +51,7 @@ To interact with such a function, developers can use the [fhevmjs](https://githu import { createInstances } from "../instance"; import { getSigners, initSigners } from "../signers"; -await initSigners(2); // Initialize signers +await initSigners(); // Initialize signers const signers = await getSigners(); const instance = await createInstances(this.signers); @@ -182,14 +182,13 @@ contract EncryptedCounter2 is SepoliaZamaFHEVMConfig { ### Tests of for the Counter contract ```ts -import { createInstances } from "../instance"; +import { createInstance } from "../instance"; import { getSigners, initSigners } from "../signers"; -import { expect } from "chai"; import { ethers } from "hardhat"; describe("EncryptedCounter2", function () { before(async function () { - await initSigners(2); // Initialize signers + await initSigners(); // Initialize signers this.signers = await getSigners(); }); @@ -198,13 +197,13 @@ describe("EncryptedCounter2", function () { this.counterContract = await CounterFactory.connect(this.signers.alice).deploy(); await this.counterContract.waitForDeployment(); this.contractAddress = await this.counterContract.getAddress(); - this.instances = await createInstances(this.signers); // Set up instances for testing + this.instances = await createInstance(); // Set up instances for testing }); it("should increment by arbitrary encrypted amount", async function () { // Create encrypted input for amount to increment by - const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - input.add8(5); // Increment by 5 as an example + const input = this.instances.createEncryptedInput(this.contractAddress, this.signers.alice.address); + input.add8(5); const encryptedAmount = await input.encrypt(); // Call incrementBy with encrypted amount diff --git a/docs/getting_started/first_smart_contract.md b/docs/getting_started/first_smart_contract.md index e3b20e7a..aeec0f13 100644 --- a/docs/getting_started/first_smart_contract.md +++ b/docs/getting_started/first_smart_contract.md @@ -102,7 +102,7 @@ There are two notable issues with this contract: With any contracts that you write you will need to write tests as well. You can start by using something like this as a template: ```ts -import { createInstances } from "../instance"; +import { createInstance } from "../instance"; import { getSigners, initSigners } from "../signers"; import { ethers } from "hardhat"; @@ -117,7 +117,7 @@ describe("EncryptedCounter1", function () { this.counterContract = await CounterFactory.connect(this.signers.alice).deploy(); await this.counterContract.waitForDeployment(); this.contractAddress = await this.counterContract.getAddress(); - this.instances = await createInstance(); // Set up instances for testing + this.instances = await createInstance(); }); it("should increment the counter", async function () { diff --git a/docs/guides/contracts.md b/docs/guides/contracts.md index aece68e8..3106b6a2 100644 --- a/docs/guides/contracts.md +++ b/docs/guides/contracts.md @@ -32,10 +32,10 @@ When testing your contracts locally, you can use the `SepoliaZamaFHEVMConfig` wh pragma solidity ^0.8.24; import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; -import { EncryptedERC20 } from "fhevm-contracts/contracts/token/ERC20/EncryptedERC20.sol"; +import { ConfidentialERC20 } from "fhevm-contracts/contracts/token/ERC20/ConfidentialERC20.sol"; -contract MyERC20 is SepoliaZamaFHEVMConfig, EncryptedERC20 { - constructor() EncryptedERC20("MyToken", "MYTOKEN") { +contract MyERC20 is SepoliaZamaFHEVMConfig, ConfidentialERC20 { + constructor() ConfidentialERC20("MyToken", "MYTOKEN") { _unsafeMint(1000000, msg.sender); } } @@ -50,10 +50,10 @@ When deploying to Sepolia, you can use the `SepoliaZamaFHEVMConfig` which provid pragma solidity ^0.8.24; import { SepoliaZamaFHEVMConfig } from "fhevm/config/ZamaFHEVMConfig.sol"; -import { EncryptedERC20 } from "fhevm-contracts/contracts/token/ERC20/EncryptedERC20.sol"; +import { ConfidentialERC20 } from "fhevm-contracts/contracts/token/ERC20/ConfidentialERC20.sol"; -contract MyERC20 is SepoliaZamaFHEVMConfig, EncryptedERC20 { - constructor() EncryptedERC20("MyToken", "MYTOKEN") { +contract MyERC20 is SepoliaZamaFHEVMConfig, ConfidentialERC20 { + constructor() ConfidentialERC20("MyToken", "MYTOKEN") { _unsafeMint(1000000, msg.sender); } } @@ -66,13 +66,13 @@ When inheriting from configuration contracts, the order of inheritance is critic ✅ **Correct Order**: ``` -contract MyERC20 is SepoliaZamaFHEVMConfig, EncryptedERC20 { ... } +contract MyERC20 is SepoliaZamaFHEVMConfig, ConfidentialERC20 { ... } ``` ❌ **Wrong order**: ``` -contract MyERC20 is EncryptedERC20, SepoliaZamaFHEVMConfig { ... } +contract MyERC20 is ConfidentialERC20, SepoliaZamaFHEVMConfig { ... } ``` ## Available contracts diff --git a/docs/guides/error_handling.md b/docs/guides/error_handling.md index f3945ebf..9666765b 100644 --- a/docs/guides/error_handling.md +++ b/docs/guides/error_handling.md @@ -18,7 +18,7 @@ To address these challenges, implement an **error handler** that records the mos For a complete implementation of error handling, see our reference contracts: - [EncryptedErrors.sol](https://github.com/zama-ai/fhevm-contracts/blob/main/contracts/utils/EncryptedErrors.sol) - Base error handling contract -- [EncryptedERC20WithErrors.sol](https://github.com/zama-ai/fhevm-contracts/blob/main/contracts/token/ERC20/extensions/ConfidentialERC20WithErrors.sol) - Example usage in an ERC20 token +- [ConfidentialERC20WithErrors.sol](https://github.com/zama-ai/fhevm-contracts/blob/main/contracts/token/ERC20/extensions/ConfidentialERC20WithErrors.sol) - Example usage in an ERC20 token The following contract demonstrates how to implement and use an error handler: diff --git a/docs/guides/loop.md b/docs/guides/loop.md index 90d8a631..48d8a154 100644 --- a/docs/guides/loop.md +++ b/docs/guides/loop.md @@ -54,18 +54,18 @@ function swapTokensForTokens(einput encryptedAmountAIn, einput encryptedAmountBI // send tokens from user to AMM contract TFHE.allowTransient(encryptedAmountA, tokenA); - IEncryptedERC20(tokenA).transferFrom(msg.sender, address(this), encryptedAmountA); + IConfidentialERC20(tokenA).transferFrom(msg.sender, address(this), encryptedAmountA); TFHE.allowTransient(encryptedAmountB, tokenB); - IEncryptedERC20(tokenB).transferFrom(msg.sender, address(this), encryptedAmountB); + IConfidentialERC20(tokenB).transferFrom(msg.sender, address(this), encryptedAmountB); // send tokens from AMM contract to user // Price of tokenA in tokenB is constant and equal to 1, so we just swap the encrypted amounts here TFHE.allowTransient(encryptedAmountB, tokenA); - IEncryptedERC20(tokenA).transfer(msg.sender, encryptedAmountB); + IConfidentialERC20(tokenA).transfer(msg.sender, encryptedAmountB); TFHE.allowTransient(encryptedAmountA, tokenB); - IEncryptedERC20(tokenB).transferFrom(msg.sender, address(this), encryptedAmountA); + IConfidentialERC20(tokenB).transferFrom(msg.sender, address(this), encryptedAmountA); } ``` diff --git a/test/encryptedERC20/EncryptedERC20.FHEGas.ts b/test/encryptedERC20/EncryptedERC20.FHEGas.ts index 1b03c6bc..e9033393 100644 --- a/test/encryptedERC20/EncryptedERC20.FHEGas.ts +++ b/test/encryptedERC20/EncryptedERC20.FHEGas.ts @@ -7,7 +7,7 @@ import { deployEncryptedERC20Fixture } from './EncryptedERC20.fixture'; describe('EncryptedERC20:FHEGas', function () { before(async function () { - await initSigners(2); + await initSigners(); this.signers = await getSigners(); }); diff --git a/test/encryptedERC20/EncryptedERC20.ts b/test/encryptedERC20/EncryptedERC20.ts index 9d12f853..722c422b 100644 --- a/test/encryptedERC20/EncryptedERC20.ts +++ b/test/encryptedERC20/EncryptedERC20.ts @@ -6,7 +6,7 @@ import { deployEncryptedERC20Fixture } from './EncryptedERC20.fixture'; describe('EncryptedERC20', function () { before(async function () { - await initSigners(2); + await initSigners(); this.signers = await getSigners(); }); diff --git a/test/gatewayDecrypt/testAsyncDecrypt.ts b/test/gatewayDecrypt/testAsyncDecrypt.ts index 99b0c99f..e824480f 100644 --- a/test/gatewayDecrypt/testAsyncDecrypt.ts +++ b/test/gatewayDecrypt/testAsyncDecrypt.ts @@ -8,7 +8,7 @@ import { bigIntToBytes64, bigIntToBytes128, bigIntToBytes256, waitNBlocks } from describe('TestAsyncDecrypt', function () { before(async function () { - await initSigners(2); + await initSigners(); this.signers = await getSigners(); this.relayerAddress = '0x97F272ccfef4026A1F3f0e0E879d514627B84E69'; this.instances = await createInstances(this.signers); diff --git a/test/kmsVerifier/kmsVerifier.ts b/test/kmsVerifier/kmsVerifier.ts index eda8499c..b3b0543a 100644 --- a/test/kmsVerifier/kmsVerifier.ts +++ b/test/kmsVerifier/kmsVerifier.ts @@ -10,7 +10,7 @@ import { bigIntToBytes256 } from '../utils'; describe('KMSVerifier', function () { before(async function () { - await initSigners(2); + await initSigners(); this.signers = await getSigners(); this.instances = await createInstances(this.signers); this.kmsFactory = await ethers.getContractFactory('fhevmTemp/contracts/KMSVerifier.sol:KMSVerifier'); diff --git a/test/payments/payments.ts b/test/payments/payments.ts index 435737f4..6b7b619d 100644 --- a/test/payments/payments.ts +++ b/test/payments/payments.ts @@ -7,7 +7,7 @@ import { getSigners, initSigners } from '../signers'; describe('TestFHEPayment', function () { before(async function () { - await initSigners(2); + await initSigners(); this.signers = await getSigners(); this.fhePayment = await initializeFHEPayment(); }); diff --git a/test/reencryption/reencryption.ts b/test/reencryption/reencryption.ts index 74c3c5ba..4194ee35 100644 --- a/test/reencryption/reencryption.ts +++ b/test/reencryption/reencryption.ts @@ -6,7 +6,7 @@ import { getSigners, initSigners } from '../signers'; describe('Reencryption', function () { before(async function () { - await initSigners(2); + await initSigners(); this.signers = await getSigners(); this.instances = await createInstances(this.signers); const contractFactory = await ethers.getContractFactory('Reencrypt'); From 067dc794907b3dfc12f5fb2abbc5ae1e97b28a38 Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed <30662672+poppyseedDev@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:36:01 -0300 Subject: [PATCH 03/32] docs: gas added --- docs/guides/gas.md | 152 ++++++++++++++++++++++----- docs/guides/gasInfo.json | 218 +++++++++++++++++++++++++++++++++++++++ docs/guides/gasinfo.ts | 79 ++++++++++++++ package-lock.json | 9 +- package.json | 4 +- 5 files changed, 432 insertions(+), 30 deletions(-) create mode 100644 docs/guides/gasInfo.json create mode 100644 docs/guides/gasinfo.ts diff --git a/docs/guides/gas.md b/docs/guides/gas.md index 8277caaa..b3546fb9 100644 --- a/docs/guides/gas.md +++ b/docs/guides/gas.md @@ -1,23 +1,79 @@ -# Gas Estimation in fhEVM +# **Gas Estimation in fhEVM** -This guide helps you understand and estimate gas costs for Fully Homomorphic Encryption (FHE) operations in your smart contracts. +This guide explains how to estimate gas costs for Fully Homomorphic Encryption (FHE) operations in your smart contracts on Zama's fhEVM. Understanding gas consumption is critical for designing efficient confidential smart contracts. ## Overview -When working with encrypted data in fhEVM, operations consume more gas compared to regular smart contract operations. This is because FHE operations require complex mathematical computations to maintain data privacy and security. +FHE operations in fhEVM are computationally intensive, resulting in higher gas costs compared to standard Ethereum operations. This is due to the complex mathematical operations required to ensure privacy and security. -Below you'll find detailed gas cost estimates for common FHE operations across different encrypted data types. Use these as a reference when designing and optimizing your confidential smart contracts. +### Types of gas in fhEVM -> **Note**: Gas costs are approximate and may vary slightly based on network conditions and contract complexity. +1. **Native Gas**: + - Represents gas used for standard Ethereum operations. + - On fhEVM, native gas consumption is approximately 20% higher than in mocked environments. +2. **FHE Gas**: + - Represents gas consumed by FHE-specific computations. + - FHE gas is consistent across both mocked and real fhEVM environments. -## ebool +> **Note**: Gas values provided are approximate and may vary based on network conditions, implementation details, and contract complexity. -| Function name | Gas | -| ---------------- | ------ | -| `and`/`or`/`xor` | 26,000 | -| `not` | 30,000 | +--- -## euint4 +## Measuring gas consumption + +To monitor gas usage during development, use the following tools: + +1. **`getFHEGasFromTxReceipt`**: + + - Extracts FHE gas consumption from a transaction receipt. + - Works in only in mocked fhEVM environments. + +2. **`.gasUsed`**: + - Returns the native gas used during a transaction. + - In mocked mode, this value underestimates real native gas usage by ~20%. + - Works in both mocked and real fhEVM environments. + +### Example: gas measurement + +The following code demonstrates how to measure both FHE gas and native gas during a transaction: + +```typescript +const transaction = await tx.wait(); +expect(transaction?.status).to.eq(1); + +if (network.name === "hardhat") { + const FHEGasConsumed = getFHEGasFromTxReceipt(transaction); + console.log("FHE Gas Consumed:", FHEGasConsumed); +} + +console.log("Native Gas Consumed:", transaction.gasUsed); +``` + +## Gas limit + +The current devnet has a gas limit of **10,000,000**. Here's what you need to know: + +- If you send a transaction that exceeds this limit: + - The transaction will fail to execute + - Your wallet will be unable to emit new transactions + - You'll need to send a new transaction with the same nonce but correct gas limit + +## Gas costs for common operations + +### Boolean Operations (`ebool`) + +| Function Name | Gas Cost | +| ---------------- | -------- | +| `and`/`or`/`xor` | 26,000 | +| `not` | 30,000 | + +--- + +### Unsigned integer operations + +Gas costs increase with the bit-width of the encrypted integer type. Below are the detailed costs for various operations on encrypted types. + +#### **4-bit Encrypted Integers (`euint4`)** | function name | Gas | | ---------------------- | ------- | @@ -40,7 +96,7 @@ Below you'll find detailed gas cost estimates for common FHE operations across d | `not` | 33,000 | | `select` | 45,000 | -## euint8 +#### **8-bit Encrypted integers (`euint8`)** | Function name | Gas | | ---------------------- | ------- | @@ -64,9 +120,9 @@ Below you'll find detailed gas cost estimates for common FHE operations across d | `select` | 47,000 | | `randEuint8()` | 100,000 | -## euint16 +#### **16-bit Encrypted integers (`euint16`)** -| function name | euint16 | +| Function name | Gas | | ---------------------- | ------- | | `add`/`sub` | 133,000 | | `add`/`sub` (scalar) | 133,000 | @@ -88,7 +144,7 @@ Below you'll find detailed gas cost estimates for common FHE operations across d | `select` | 47,000 | | `randEuint16()` | 100,000 | -## euint32 +#### **32-bit Encrypted Integers (`euint32`)** | Function name | Gas fee | | ---------------------- | ------- | @@ -112,7 +168,7 @@ Below you'll find detailed gas cost estimates for common FHE operations across d | `select` | 50,000 | | `randEuint32()` | 100,000 | -## euint64 +#### **64-bit Encrypted integers (`euint64`)** | Function name | Gas fee | | ---------------------- | --------- | @@ -136,20 +192,68 @@ Below you'll find detailed gas cost estimates for common FHE operations across d | `select` | 53,000 | | `randEuint64()` | 100,000 | -## eaddress +#### **128-bit Encrypted integers (`euint128`)** + +| Function name | Gas fee | +| ---------------------- | --------- | +| `add`/`sub` | 218,000 | +| `add`/`sub` (scalar) | 218,000 | +| `mul` | 1,145,000 | +| `mul` (scalar) | 480,000 | +| `div` (scalar) | 857,000 | +| `rem` (scalar) | 1,499,000 | +| `and`/`or`/`xor` | 41,000 | +| `shr`/`shl` | 282,000 | +| `shr`/`shl` (scalar) | 41,000 | +| `rotr`/`rotl` | 282,000 | +| `rotr`/`rotl` (scalar) | 41,000 | +| `eq`/`ne` | 88,000 | +| `ge`/`gt`/`le`/`lt` | 190,000 | +| `min`/`max` | 241,000 | +| `min`/`max` (scalar) | 225,000 | +| `neg` | 248,000 | +| `not` | 38,000 | +| `select` | 70,000 | + +#### **256-bit Encrypted integers (`euint256`)** + +| function name | Gas fee | +| ---------------------- | --------- | +| `add`/`sub` | 253,000 | +| `add`/`sub` (scalar) | 253,000 | +| `mul` | 2,045,000 | +| `mul` (scalar) | 647,000 | +| `div` (scalar) | 1,258,000 | +| `rem` (scalar) | 2,052,000 | +| `and`/`or`/`xor` | 44,000 | +| `shr`/`shl` | 350,000 | +| `shr`/`shl` (scalar) | 44,000 | +| `rotr`/`rotl` | 350,000 | +| `rotr`/`rotl` (scalar) | 44,000 | +| `eq`/`ne` | 100,000 | +| `ge`/`gt`/`le`/`lt` | 231,000 | +| `min`/`max` | 277,000 | +| `min`/`max` (scalar) | 264,000 | +| `neg` | 309,000 | +| `not` | 39,000 | +| `select` | 90,000 | + +### eAddress | Function name | Gas fee | | ------------- | ------- | | `eq`/`ne` | 90,000 | -## Gas limit - -The current devnet has a gas limit of **10,000,000**. Here's what you need to know: +## Additional Operations -- If you send a transaction that exceeds this limit: - - The transaction will fail to execute - - Your wallet will be unable to emit new transactions - - You'll need to send a new transaction with the same nonce but correct gas limit +| Function name | Gas fee | +| --------------------------- | --------------- | +| `cast` | 200 | +| `trivialEncrypt` (basic) | 100-800 | +| `trivialEncrypt` (extended) | 1,600-6,400 | +| `randBounded` | 100,000 | +| `ifThenElse` | 43,000-300,000 | +| `rand` | 100,000-400,000 | ### Fixing Failed Transactions in MetaMask diff --git a/docs/guides/gasInfo.json b/docs/guides/gasInfo.json new file mode 100644 index 00000000..d7f31a6f --- /dev/null +++ b/docs/guides/gasInfo.json @@ -0,0 +1,218 @@ +{ + "fheAdd": { + "binary": true, + "scalar": { "1": 65000, "2": 94000, "3": 133000, "4": 162000, "5": 188000, "6": 218000, "8": 253000 }, + "nonScalar": { "1": 65000, "2": 94000, "3": 133000, "4": 162000, "5": 188000, "6": 218000, "8": 253000 } + }, + "fheSub": { + "binary": true, + "scalar": { "1": 65000, "2": 94000, "3": 133000, "4": 162000, "5": 188000, "6": 218000, "8": 253000 }, + "nonScalar": { "1": 65000, "2": 94000, "3": 133000, "4": 162000, "5": 188000, "6": 218000, "8": 253000 } + }, + "fheMul": { + "binary": true, + "scalar": { "1": 88000, "2": 159000, "3": 208000, "4": 264000, "5": 356000, "6": 480000, "8": 647000 }, + "nonScalar": { "1": 150000, "2": 197000, "3": 262000, "4": 359000, "5": 641000, "6": 1145000, "8": 2045000 } + }, + "fheDiv": { + "binary": true, + "scalar": { "1": 139000, "2": 238000, "3": 314000, "4": 398000, "5": 584000, "6": 857000, "8": 1258000 } + }, + "fheRem": { + "binary": true, + "scalar": { "1": 286000, "2": 460000, "3": 622000, "4": 805000, "5": 1095000, "6": 1499000, "8": 2052000 } + }, + "fheBitAnd": { + "binary": true, + "scalar": { "0": 26000, "1": 32000, "2": 34000, "3": 34000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 }, + "nonScalar": { "0": 26000, "1": 32000, "2": 34000, "3": 34000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 } + }, + "fheBitOr": { + "binary": true, + "scalar": { "0": 26000, "1": 32000, "2": 34000, "3": 34000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 }, + "nonScalar": { "0": 26000, "1": 32000, "2": 34000, "3": 34000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 } + }, + "fheBitXor": { + "binary": true, + "scalar": { "0": 26000, "1": 32000, "2": 34000, "3": 34000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 }, + "nonScalar": { "0": 26000, "1": 32000, "2": 34000, "3": 34000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 } + }, + "fheShl": { + "binary": true, + "scalar": { "1": 35000, "2": 35000, "3": 35000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 }, + "nonScalar": { "1": 116000, "2": 133000, "3": 153000, "4": 183000, "5": 227000, "6": 282000, "8": 350000 } + }, + "fheShr": { + "binary": true, + "scalar": { "1": 35000, "2": 35000, "3": 35000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 }, + "nonScalar": { "1": 116000, "2": 133000, "3": 153000, "4": 183000, "5": 227000, "6": 282000, "8": 350000 } + }, + "fheRotl": { + "binary": true, + "scalar": { "1": 35000, "2": 35000, "3": 35000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 }, + "nonScalar": { "1": 116000, "2": 133000, "3": 153000, "4": 183000, "5": 227000, "6": 282000, "8": 350000 } + }, + "fheRotr": { + "binary": true, + "scalar": { "1": 35000, "2": 35000, "3": 35000, "4": 35000, "5": 38000, "6": 41000, "8": 44000 }, + "nonScalar": { "1": 116000, "2": 133000, "3": 153000, "4": 183000, "5": 227000, "6": 282000, "8": 350000 } + }, + "fheEq": { + "binary": true, + "scalar": { + "0": 49000, + "1": 51000, + "2": 53000, + "3": 54000, + "4": 82000, + "5": 86000, + "6": 88000, + "7": 90000, + "8": 100000, + "9": 150000, + "10": 200000, + "11": 300000 + }, + "nonScalar": { + "0": 49000, + "1": 51000, + "2": 53000, + "3": 54000, + "4": 82000, + "5": 86000, + "6": 88000, + "7": 90000, + "8": 100000, + "9": 150000, + "10": 200000, + "11": 300000 + } + }, + "fheNe": { + "binary": true, + "scalar": { + "0": 49000, + "1": 51000, + "2": 53000, + "3": 54000, + "4": 82000, + "5": 86000, + "6": 88000, + "7": 90000, + "8": 100000, + "9": 150000, + "10": 200000, + "11": 300000 + }, + "nonScalar": { + "0": 49000, + "1": 51000, + "2": 53000, + "3": 54000, + "4": 82000, + "5": 86000, + "6": 88000, + "7": 90000, + "8": 100000, + "9": 150000, + "10": 200000, + "11": 300000 + } + }, + "fheGe": { + "binary": true, + "scalar": { "1": 70000, "2": 82000, "3": 105000, "4": 128000, "5": 156000, "6": 190000, "8": 231000 }, + "nonScalar": { "1": 70000, "2": 82000, "3": 105000, "4": 128000, "5": 156000, "6": 190000, "8": 231000 } + }, + "fheGt": { + "binary": true, + "scalar": { "1": 70000, "2": 82000, "3": 105000, "4": 128000, "5": 156000, "6": 190000, "8": 231000 }, + "nonScalar": { "1": 70000, "2": 82000, "3": 105000, "4": 128000, "5": 156000, "6": 190000, "8": 231000 } + }, + "fheLe": { + "binary": true, + "scalar": { "1": 70000, "2": 82000, "3": 105000, "4": 128000, "5": 156000, "6": 190000, "8": 231000 }, + "nonScalar": { "1": 70000, "2": 82000, "3": 105000, "4": 128000, "5": 156000, "6": 190000, "8": 231000 } + }, + "fheLt": { + "binary": true, + "scalar": { "1": 70000, "2": 82000, "3": 105000, "4": 128000, "5": 156000, "6": 190000, "8": 231000 }, + "nonScalar": { "1": 70000, "2": 82000, "3": 105000, "4": 128000, "5": 156000, "6": 190000, "8": 231000 } + }, + "fheMin": { + "binary": true, + "scalar": { "1": 121000, "2": 128000, "3": 150000, "4": 164000, "5": 192000, "6": 225000, "8": 264000 }, + "nonScalar": { "1": 121000, "2": 128000, "3": 153000, "4": 183000, "5": 210000, "6": 241000, "8": 277000 } + }, + "fheMax": { + "binary": true, + "scalar": { "1": 121000, "2": 128000, "3": 150000, "4": 164000, "5": 192000, "6": 225000, "8": 264000 }, + "nonScalar": { "1": 121000, "2": 128000, "3": 153000, "4": 183000, "5": 210000, "6": 241000, "8": 277000 } + }, + "fheNeg": { + "binary": false, + "types": { "1": 60000, "2": 95000, "3": 131000, "4": 160000, "5": 199000, "6": 248000, "8": 309000 } + }, + "fheNot": { + "binary": false, + "types": { "0": 30000, "1": 33000, "2": 34000, "3": 35000, "4": 36000, "5": 37000, "6": 38000, "8": 39000 } + }, + "cast": { + "binary": false, + "types": { "0": 200, "1": 200, "2": 200, "3": 200, "4": 200, "5": 200, "6": 200, "8": 200 } + }, + "trivialEncrypt": { + "binary": false, + "types": { + "0": 100, + "1": 100, + "2": 100, + "3": 200, + "4": 300, + "5": 600, + "6": 650, + "7": 700, + "8": 800, + "9": 1600, + "10": 3200, + "11": 6400 + } + }, + "ifThenElse": { + "binary": false, + "types": { + "0": 43000, + "1": 45000, + "2": 47000, + "3": 47000, + "4": 50000, + "5": 53000, + "6": 70000, + "7": 80000, + "8": 90000, + "9": 150000, + "10": 200000, + "11": 300000 + } + }, + "fheRand": { + "binary": false, + "types": { + "0": 100000, + "1": 100000, + "2": 100000, + "3": 100000, + "4": 100000, + "5": 100000, + "6": 100000, + "8": 100000, + "9": 200000, + "10": 300000, + "11": 400000 + } + }, + "fheRandBounded": { + "binary": false, + "types": { "1": 100000, "2": 100000, "3": 100000, "4": 100000, "5": 100000, "6": 100000, "8": 100000 } + } +} diff --git a/docs/guides/gasinfo.ts b/docs/guides/gasinfo.ts new file mode 100644 index 00000000..2e5a00a6 --- /dev/null +++ b/docs/guides/gasinfo.ts @@ -0,0 +1,79 @@ +import { expect } from 'chai'; +import { network } from 'hardhat'; + +import { getFHEGasFromTxReceipt } from '../coprocessorUtils'; +import { createInstance } from '../instance'; +import { getSigners, initSigners } from '../signers'; +import { deployConfidentialERC20Fixture } from './ConfidentialERC20.fixture'; + +describe('ConfidentialERC20:FHEGas', function () { + before(async function () { + await initSigners(); + this.signers = await getSigners(); + }); + + beforeEach(async function () { + const contract = await deployConfidentialERC20Fixture(); + this.contractAddress = await contract.getAddress(); + this.erc20 = contract; + this.fhevm = await createInstance(); + }); + + it('gas consumed during transfer', async function () { + const transaction = await this.erc20.mint(10000); + const t1 = await transaction.wait(); + expect(t1?.status).to.eq(1); + + const input = this.fhevm.createEncryptedInput(this.contractAddress, this.signers.alice.address); + input.add64(1337); + const encryptedTransferAmount = await input.encrypt(); + const tx = await this.erc20['transfer(address,bytes32,bytes)']( + this.signers.bob.address, + encryptedTransferAmount.handles[0], + encryptedTransferAmount.inputProof, + ); + const t2 = await tx.wait(); + expect(t2?.status).to.eq(1); + if (network.name === 'hardhat') { + // `getFHEGasFromTxReceipt` function only works in mocked mode but gives same exact FHEGas consumed than on the real fhEVM + const FHEGasConsumedTransfer = getFHEGasFromTxReceipt(t2); + console.log('FHEGas Consumed during transfer', FHEGasConsumedTransfer); + } + // contrarily to FHEGas, native gas in mocked mode slightly differs from the real gas consumption on fhevm (underestimated by ~20%) + console.log('Native Gas Consumed during transfer', t2.gasUsed); + }); + + it('gas consumed during transferFrom', async function () { + const transaction = await this.erc20.mint(10000); + await transaction.wait(); + + const inputAlice = this.fhevm.createEncryptedInput(this.contractAddress, this.signers.alice.address); + inputAlice.add64(1337); + const encryptedAllowanceAmount = await inputAlice.encrypt(); + const tx = await this.erc20['approve(address,bytes32,bytes)']( + this.signers.bob.address, + encryptedAllowanceAmount.handles[0], + encryptedAllowanceAmount.inputProof, + ); + await tx.wait(); + + const bobErc20 = this.erc20.connect(this.signers.bob); + const inputBob2 = this.fhevm.createEncryptedInput(this.contractAddress, this.signers.bob.address); + inputBob2.add64(1337); // below allowance so next tx should send token + const encryptedTransferAmount2 = await inputBob2.encrypt(); + const tx3 = await bobErc20['transferFrom(address,address,bytes32,bytes)']( + this.signers.alice.address, + this.signers.bob.address, + encryptedTransferAmount2.handles[0], + encryptedTransferAmount2.inputProof, + ); + const t3 = await tx3.wait(); + if (network.name === 'hardhat') { + // `getFHEGasFromTxReceipt` function only works in mocked mode but gives same exact FHEGas consumed than on the real fhEVM + const FHEGasConsumedTransferFrom = getFHEGasFromTxReceipt(t3); + console.log('FHEGas Consumed during transfer', FHEGasConsumedTransferFrom); + } + // contrarily to FHEGas, native gas in mocked mode slightly differs from the real gas consumption on fhevm (underestimated by ~20%) + console.log('Native Gas Consumed during transfer', t3.gasUsed); + }); +}); diff --git a/package-lock.json b/package-lock.json index a94c6a9e..22d0cea2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "husky": "^9.1.6", "lodash": "^4.17.21", "mocha": "^10.1.0", - "prettier": "^3.0.0", + "prettier": "^3.4.2", "prettier-plugin-solidity": "^1.1.3", "rimraf": "^4.1.2", "solidity-coverage": "0.8.12", @@ -9269,10 +9269,11 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, diff --git a/package.json b/package.json index 6f02fe2e..03b99d6d 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,8 @@ "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", "ethers": "^6.8.0", - "fhevmjs": "^0.6.0-8", "fhevm-core-contracts": "0.6.0-5", + "fhevmjs": "^0.6.0-8", "hardhat": "^2.22.10", "hardhat-deploy": "^0.11.29", "hardhat-gas-reporter": "^1.0.2", @@ -81,7 +81,7 @@ "husky": "^9.1.6", "lodash": "^4.17.21", "mocha": "^10.1.0", - "prettier": "^3.0.0", + "prettier": "^3.4.2", "prettier-plugin-solidity": "^1.1.3", "rimraf": "^4.1.2", "solidity-coverage": "0.8.12", From af6f634a2be0f549057ffbd0d9a3910a1b03f2a6 Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed <30662672+poppyseedDev@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:42:30 -0300 Subject: [PATCH 04/32] docs: resolve webpack error structure --- docs/SUMMARY.md | 3 + docs/guides/debug_decrypt.md | 125 ++++++++++++++++++ docs/guides/frontend/webpack.md | 26 ++-- docs/guides/gas.md | 4 +- docs/guides/gasInfo.json | 218 -------------------------------- docs/guides/gasinfo.ts | 79 ------------ docs/guides/trivial_encrypt.md | 109 ++++++++++++++++ 7 files changed, 256 insertions(+), 308 deletions(-) create mode 100644 docs/guides/debug_decrypt.md delete mode 100644 docs/guides/gasInfo.json delete mode 100644 docs/guides/gasinfo.ts create mode 100644 docs/guides/trivial_encrypt.md diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 21b3e06b..34281dfa 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -38,6 +38,9 @@ - [Branching in FHE](guides/conditions.md) - [Generate random numbers](guides/random.md) - [Error handling](guides/error_handling.md) + - [Gas estimation](guides/gas.md) + - [Trivial encrypt](guides/trivial_encrypt.md) + - [Debug decrypt](guides/debug_decrypt.md) - [Frontend](guides/frontend/README.md) - [Build a web application](guides/frontend/webapp.md) - [Build with Node](guides/frontend/node.md) diff --git a/docs/guides/debug_decrypt.md b/docs/guides/debug_decrypt.md new file mode 100644 index 00000000..778c7a9d --- /dev/null +++ b/docs/guides/debug_decrypt.md @@ -0,0 +1,125 @@ +# Debugging with `debug.decryptXX` + +This guide explains how to use the `debug.decryptXX` functions for debugging encrypted data in mocked environments during development with fhEVM. These functions allow developers to decrypt encrypted values locally for testing purposes. However, they should not be used in production as they rely on private keys. + +--- + +## Overview + +The `debug.decryptXX` functions allow you to decrypt encrypted handles into plaintext values. This feature is useful for debugging encrypted operations such as transfers, balance checks, and other computations involving FHE-encrypted data. + +### Key Points: + +- **Environment**: These functions work **only in mocked environments** (e.g., `hardhat` network). +- **Production Limitation**: In production, decryption is performed asynchronously via the Gateway and requires an authorized on-chain request. +- **Encrypted Types**: Supports various encrypted types, including integers, booleans, and `ebytesXX`. + +--- + +## Supported functions + +### Integer decryption + +Decrypts encrypted integers of different bit-widths (`euint4`, `euint8`, ..., `euint256`). + +| Function Name | Returns | Encrypted Type | +| ------------- | -------- | -------------- | +| `decrypt4` | `bigint` | `euint4` | +| `decrypt8` | `bigint` | `euint8` | +| `decrypt16` | `bigint` | `euint16` | +| `decrypt32` | `bigint` | `euint32` | +| `decrypt64` | `bigint` | `euint64` | +| `decrypt128` | `bigint` | `euint128` | +| `decrypt256` | `bigint` | `euint256` | + +### Boolean decryption + +Decrypts encrypted booleans (`ebool`). + +| Function Name | Returns | Encrypted Type | +| ------------- | --------- | -------------- | +| `decryptBool` | `boolean` | `ebool` | + +### Byte array decryption + +Decrypts encrypted byte arrays of various sizes (`ebytesXX`). + +| Function Name | Returns | Encrypted Type | +| ------------------ | -------- | -------------- | +| `decryptEbytes64` | `string` | `ebytes64` | +| `decryptEbytes128` | `string` | `ebytes128` | +| `decryptEbytes256` | `string` | `ebytes256` | + +### Address decryption + +Decrypts encrypted addresses. + +| Function Name | Returns | Encrypted Type | +| ---------------- | -------- | -------------- | +| `decryptAddress` | `string` | `eaddress` | + +--- + +## Function Usage + +### Example: decrypting encrypted values + +```typescript +import { debug } from "./debug"; + +// Decrypt a 64-bit encrypted integer +const handle64: bigint = await this.erc20.balanceOf(this.signers.alice); +const plaintextValue: bigint = await debug.decrypt64(handle64); +console.log("Decrypted Balance:", plaintextValue); +``` + +For a more complete example, refer to the [ConfidentialERC20 test file](https://github.com/zama-ai/fhevm-hardhat-template/blob/f9505a67db31c988f49b6f4210df47ca3ce97841/test/confidentialERC20/ConfidentialERC20.ts#L181-L205). + +### Example: decrypting byte arrays + +```typescript +// Decrypt a 128-byte encrypted value +const ebytes128Handle: bigint = ...; // Get handle for the encrypted bytes +const decryptedBytes: string = await debug.decryptEbytes128(ebytes128Handle); +console.log("Decrypted Bytes:", decryptedBytes); +``` + +--- + +## **How it works** + +### Verifying types + +Each decryption function includes a **type verification step** to ensure the provided handle matches the expected encrypted type. If the type is mismatched, an error is thrown. + +```typescript +function verifyType(handle: bigint, expectedType: number) { + const typeCt = handle >> 8n; + if (Number(typeCt % 256n) !== expectedType) { + throw "Wrong encrypted type for the handle"; + } +} +``` + +### Environment checks + +The functions only work in the `hardhat` network. Attempting to use them in a production environment will result in an error. + +```typescript +if (network.name !== "hardhat") { + throw Error("This function can only be called in mocked mode"); +} +``` + +--- + +## **Best Practices** + +1. **Use Only for Debugging**: + These functions require access to private keys and are meant exclusively for local testing and debugging. + +2. **Production Decryption**: + For production, always use the asynchronous Gateway-based decryption. + +3. **Validate Inputs**: + Ensure the provided handle is valid and corresponds to the correct encrypted type. diff --git a/docs/guides/frontend/webpack.md b/docs/guides/frontend/webpack.md index 38a2c234..533073cf 100644 --- a/docs/guides/frontend/webpack.md +++ b/docs/guides/frontend/webpack.md @@ -18,9 +18,13 @@ resolve: { }, ``` -## Error message: ReferenceError: Buffer is not defined +## Buffer not defined -If you encounter this issue with the Node Buffer object, you should offer an alternative solution. Similar issues might arise with different Node objects. In such cases, install the corresponding browserified npm package and include the fallback as follows. +**Error message:** `ReferenceError: Buffer is not defined` + +**Cause:** This error occurs when the Node.js `Buffer` object is used in a browser environment where it is not natively available. + +**Possible sultions:** To resolve this issue, you need to provide browser-compatible fallbacks for Node.js core modules. Install the necessary browserified npm packages and configure Webpack to use these fallbacks. ```javascript resolve: { @@ -31,22 +35,26 @@ resolve: { path: require.resolve('path-browserify'), }, }, - ``` ## 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](https://github.com/zama-ai/fhevmjs-react-template/blob/main/tsconfig.json) using TypeScript 5. +**Error message:** Issues with importing ESM version -If you encounter any other issue, you can force import of the browser package. +**Cause:** With a bundler such as Webpack or Rollup, imports will be replaced with the version mentioned in the `"browser"` field of the `package.json`. This can cause issues with typing. -```javascript -import { initFhevm, createInstance } from "fhevmjs/web"; -``` +**Possible solutions:** + +1. If you encounter issues with typing, you can use this [tsconfig.json](https://github.com/zama-ai/fhevmjs-react-template/blob/main/tsconfig.json) using TypeScript 5. +2. 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 `