From 5b5b8eef0cf8144134519c212d1adb3f00dd2941 Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed Date: Fri, 4 Oct 2024 17:23:41 +0800 Subject: [PATCH 1/7] docs: update Counter.sol docs --- examples/Counter.sol | 6 +++++- examples/README.md | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 examples/README.md diff --git a/examples/Counter.sol b/examples/Counter.sol index 3f353ef6..e787f267 100644 --- a/examples/Counter.sol +++ b/examples/Counter.sol @@ -1,14 +1,18 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -// dummy contract for dummy transactions just to advance blocks +// This is a simple contract named 'Counter' that maintains a single state variable 'value'. +// It provides functionality to increment the 'value' and read its current value. contract Counter { + // State variable to keep track of the count. uint32 value; + // Increases the value by 1 each time this function is called. function increment() public { value += 1; } + // Returns the current value of the counter. function currentValue() public view returns (uint32) { return value; } diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..17f1aa09 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ +> Here will go all information about specific contracts, how are they designed and what are the considerations + + +# Counter.sol +The `Counter` smart contract is a simple contract implemented in Solidity, designed to demonstrate basic state manipulation. Its main purpose is to maintain a counter (value) and provide functions to increment and view the current counter value. \ No newline at end of file From dcd75824630a36fe3e0f54986b5c3296187b4dd9 Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed Date: Fri, 4 Oct 2024 19:47:10 +0800 Subject: [PATCH 2/7] docs: encryptedERC20.sol + added some readme that will be used later --- examples/EncryptedERC20.sol | 45 ++++++++++++--------- examples/README.md | 78 ++++++++++++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 19 deletions(-) diff --git a/examples/EncryptedERC20.sol b/examples/EncryptedERC20.sol index 016d56e7..ad716726 100644 --- a/examples/EncryptedERC20.sol +++ b/examples/EncryptedERC20.sol @@ -5,24 +5,29 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; +// This contract implements an encrypted ERC20-like token with confidential balances using Zama's FHE (Fully Homomorphic Encryption) library. +// It supports typical ERC20 functionality such as transferring tokens, minting, and setting allowances, but uses encrypted data types. contract EncryptedERC20 is Ownable2Step { - event Transfer(address indexed from, address indexed to); - event Approval(address indexed owner, address indexed spender); - event Mint(address indexed to, uint64 amount); - - uint64 private _totalSupply; - string private _name; - string private _symbol; - uint8 public constant decimals = 6; - - // A mapping from address to an encrypted balance. + // Events to log important actions in the contract + event Transfer(address indexed from, address indexed to); // Emitted when tokens are transferred + event Approval(address indexed owner, address indexed spender); // Emitted when a spender is approved to spend tokens on behalf of an owner + event Mint(address indexed to, uint64 amount); // Emitted when new tokens are minted + + // Private state variables for basic token information and supply + uint64 private _totalSupply; // Stores the total supply of the token + string private _name; // Name of the token (e.g., "Confidential Token") + string private _symbol; // Symbol of the token (e.g., "CTK") + uint8 public constant decimals = 6; // Number of decimal places for the token + + // A mapping from address to an encrypted balance - tracks encrypted balances of each address mapping(address => euint64) internal balances; - // A mapping of the form mapping(owner => mapping(spender => allowance)). + // Mapping to manage encrypted allowance - of the form mapping(owner => mapping(spender => allowance)). mapping(address => mapping(address => euint64)) internal allowances; + // Constructor to initialize the token's name and symbol, and set up the owner constructor(string memory name_, string memory symbol_) Ownable(msg.sender) { - TFHE.setFHEVM(FHEVMConfig.defaultConfig()); + TFHE.setFHEVM(FHEVMConfig.defaultConfig()); // Set up the FHEVM configuration for this contract _name = name_; _symbol = symbol_; } @@ -42,7 +47,8 @@ contract EncryptedERC20 is Ownable2Step { return _totalSupply; } - // Sets the balance of the owner to the given encrypted balance. + // Mints new tokens and assigns them to the owner, increasing the total supply. + // Only the contract owner can call this function. function mint(uint64 mintedAmount) public virtual onlyOwner { balances[owner()] = TFHE.add(balances[owner()], mintedAmount); // overflow impossible because of next line TFHE.allowThis(balances[owner()]); @@ -57,7 +63,7 @@ contract EncryptedERC20 is Ownable2Step { return true; } - // Transfers an amount from the message sender address to the `to` address. + // Transfers an encrypted amount from the message sender address to the `to` address. function transfer(address to, euint64 amount) public virtual returns (bool) { require(TFHE.isSenderAllowed(amount)); // makes sure the owner has enough tokens @@ -66,18 +72,18 @@ contract EncryptedERC20 is Ownable2Step { return true; } - // Returns the balance handle of the caller. + // Returns the balance handle (encrypted) of a specific address. function balanceOf(address wallet) public view virtual returns (euint64) { return balances[wallet]; } - // Sets the `encryptedAmount` as the allowance of `spender` over the caller's tokens. + // Sets the allowance of `spender` to use a specific encrypted amount of the caller's tokens. function approve(address spender, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) { approve(spender, TFHE.asEuint64(encryptedAmount, inputProof)); return true; } - // Sets the `amount` as the allowance of `spender` over the caller's tokens. + // Sets the allowance of `spender` to use a specific amount of the caller's tokens. function approve(address spender, euint64 amount) public virtual returns (bool) { require(TFHE.isSenderAllowed(amount)); address owner = msg.sender; @@ -112,6 +118,7 @@ contract EncryptedERC20 is Ownable2Step { return true; } + // Internal function to approve a spender to use a specific amount. function _approve(address owner, address spender, euint64 amount) internal virtual { allowances[owner][spender] = amount; TFHE.allowThis(amount); @@ -119,10 +126,12 @@ contract EncryptedERC20 is Ownable2Step { TFHE.allow(amount, spender); } + // Returns the internal allowance of a spender for a specific owner. function _allowance(address owner, address spender) internal view virtual returns (euint64) { return allowances[owner][spender]; } + // Updates the allowance after a transfer and returns whether it is valid. function _updateAllowance(address owner, address spender, euint64 amount) internal virtual returns (ebool) { euint64 currentAllowance = _allowance(owner, spender); // makes sure the allowance suffices @@ -134,7 +143,7 @@ contract EncryptedERC20 is Ownable2Step { return isTransferable; } - // Transfers an encrypted amount. + // Internal function to handle the transfer of tokens between addresses. function _transfer(address from, address to, euint64 amount, ebool isTransferable) internal virtual { // Add to the balance of `to` and subract from the balance of `from`. euint64 transferValue = TFHE.select(isTransferable, amount, TFHE.asEuint64(0)); diff --git a/examples/README.md b/examples/README.md index 17f1aa09..f8713825 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,5 +1,81 @@ > Here will go all information about specific contracts, how are they designed and what are the considerations +### Overview of Encrypted and Decrypted Data Flow + +```mermaid +graph TD + A[User's Plaintext Input] -->|Encryption| B(Encrypted Input) + B -->|Stored in Contract| C{Encrypted State Variables} + + %% Using encrypted data in the contract %% + C -->|Operations on Encrypted Data| D[Contract Logic and Functions] + D -->|Maintain Confidentiality| C + + %% Encryption and Decryption operations %% + D -->|Decrypt when Necessary| E[Decrypted Values for Computations] + E -->|Logic/Verification| D +``` + +[needs work] +```mermaid +graph TD + %% User Interaction %% + subgraph User Actions + U[User] -->|1. Encrypted Input| A[User's Plaintext Input] + U -->|2. Request Re-encryption| F[Re-encryption Trigger] + U -->|3. Trigger Decryption| K[Decryption Request] + end + + %% Encryption Flow %% + A -->|Encryption| B(Encrypted Input) + B -->|Stored in Contract| C{Encrypted State Variables} + + %% Using encrypted data in the contract %% + C -->|Operations on Encrypted Data| D[Contract Logic and Functions] + D -->|Maintain Confidentiality| C + + %% Encryption and Decryption operations %% + D -->|Decrypt when Necessary| E[Decrypted Values for Computations] + E -->|Logic/Verification| D + K -->|Decrypt Specific Data| G[Gateway Service] + G -->|Return Decrypted Value| E + G <--> N{KMS} + + %% Re-encryption Flow %% + F -->|Request Re-encryption| H[Retrieve Encrypted Data from Contract] + H -->|Request Re-encryption to Gateway| G + G -->|Re-encrypt with dApp's Public Key| J[Re-encrypted Data] + J -->|Return to User| M{User's Encrypted Data} + M -->|Used in Contract Operations| C + +``` + # Counter.sol -The `Counter` smart contract is a simple contract implemented in Solidity, designed to demonstrate basic state manipulation. Its main purpose is to maintain a counter (value) and provide functions to increment and view the current counter value. \ No newline at end of file +The `Counter` smart contract is a simple contract implemented in Solidity, designed to demonstrate basic state manipulation. Its main purpose is to maintain a counter (value) and provide functions to increment and view the current counter value. + +## EncryptedERC20.sol +This contract implements an encrypted ERC20-like token with confidential balances using Zama's FHE (Fully Homomorphic Encryption) library. +It supports typical ERC20 functionality such as transferring tokens, minting, and setting allowances, but uses encrypted data types. + +### Approval and Transfer Operations + +Here's a high-level overview of what is encrypted and decrypted in the `EncryptedERC20` smart contract: + +```mermaid +graph TD + subgraph User Inputs + X1(Encrypted Amount) + X2(Encrypted Allowance) + end + subgraph Contract Logic + Y1[Check Allowance & Balance] + Y2[Update Encrypted Allowance] + Y3[Transfer Encrypted Amount] + end + X1 --> Y1 + X2 --> Y1 + Y1 --> Y2 + Y1 --> Y3 +``` + From 9c97b090b68da221706a22af8e46e425cbdb473b Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed Date: Fri, 4 Oct 2024 20:32:46 +0800 Subject: [PATCH 3/7] docs: diagram --- examples/README.md | 53 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/examples/README.md b/examples/README.md index f8713825..aa07d887 100644 --- a/examples/README.md +++ b/examples/README.md @@ -22,31 +22,68 @@ graph TD %% User Interaction %% subgraph User Actions U[User] -->|1. Encrypted Input| A[User's Plaintext Input] - U -->|2. Request Re-encryption| F[Re-encryption Trigger] + U -->|2. Request Re-encryption| F[Direct Re-encryption Request] U -->|3. Trigger Decryption| K[Decryption Request] end %% Encryption Flow %% - A -->|Encryption| B(Encrypted Input) + A -->|Sends Input to Contract| B(Encrypted Input) B -->|Stored in Contract| C{Encrypted State Variables} %% Using encrypted data in the contract %% C -->|Operations on Encrypted Data| D[Contract Logic and Functions] D -->|Maintain Confidentiality| C - %% Encryption and Decryption operations %% + %% Decryption operations %% D -->|Decrypt when Necessary| E[Decrypted Values for Computations] E -->|Logic/Verification| D - K -->|Decrypt Specific Data| G[Gateway Service] + K -->|Decrypt Request to Contract| C + C -->|Forwards Request to| G[Gateway Service] G -->|Return Decrypted Value| E - G <--> N{KMS} + G <--> N{KMS Service} %% Re-encryption Flow %% - F -->|Request Re-encryption| H[Retrieve Encrypted Data from Contract] - H -->|Request Re-encryption to Gateway| G + F -->|User Calls Gateway Directly| G G -->|Re-encrypt with dApp's Public Key| J[Re-encrypted Data] J -->|Return to User| M{User's Encrypted Data} - M -->|Used in Contract Operations| C + M -->|User Sends to Contract| C + +``` + +```mermaid +graph TD + %% User Interaction %% + subgraph User Actions + U[User] -->|1. Encrypted Input| A[User's Plaintext Input] + U -->|2. Request Re-encryption| F[Direct Re-encryption Request] + U -->|3. Trigger Decryption| K[Decryption Request] + end + + %% Smart Contract Operations %% + subgraph Smart Contract Operations + C{Encrypted State Variables} -->|Operations on Encrypted Data| D[Contract Logic and Functions] + D -->|Maintain Confidentiality| C + A -->|Sends Input to Contract| B(Encrypted Input) + B -->|Stored in Contract| C + K -->|Decrypt Request to Contract| C + C -->|Forwards Decrypt Request to| GC[Gateway Call from Contract] + J -->|Return to User| M{User's Encrypted Data} + %% Contract Decryption Flow %% + GC -->|Return Decrypted Data to Contract| E[Decrypted Values for Computations] + E -->|Logic/Verification| D + + end + + GC -->|Forward Request to| G[Gateway Service] + G -->|Return Decrypted Value| GC + G <--> N{KMS Service} + F -->|User Calls Gateway Directly| G + G -->|Re-encrypt with dApp's Public Key| J[Re-encrypted Data] + + %% Re-encrypted Data Flow %% + M -->|User Sends to Contract| C + + ``` From 176fd02f174af40a7a68e2432cbce78c0f0eee3c Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed Date: Fri, 4 Oct 2024 20:50:24 +0800 Subject: [PATCH 4/7] docs: added comments for Rand.sol, Reencrypt.sol, Regression.sol, SmartAccount.sol, TestAsyncDecrypt.sol --- examples/Rand.sol | 13 ++++++++ examples/Reencrypt.sol | 19 +++++++++++ examples/Regression1.sol | 12 +++++++ examples/SmartAccount.sol | 7 ++++ examples/TestAsyncDecrypt.sol | 61 ++++++++++++++++++++++++++++++++--- 5 files changed, 108 insertions(+), 4 deletions(-) diff --git a/examples/Rand.sol b/examples/Rand.sol index d84e62be..eb8e8dae 100644 --- a/examples/Rand.sol +++ b/examples/Rand.sol @@ -4,63 +4,76 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; +// Contract for generating random encrypted numbers contract Rand { + // Encrypted unsigned integers of various sizes euint8 public value8; euint16 public value16; euint32 public value32; euint64 public value64; euint64 public value64Bounded; + // Constructor to set FHE configuration constructor() { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); } + // Generate random 8-bit encrypted unsigned integer function generate8() public { value8 = TFHE.randEuint8(); TFHE.allowThis(value8); } + // Generate random 8-bit encrypted unsigned integer with upper bound function generate8UpperBound(uint8 upperBound) public { value8 = TFHE.randEuint8(upperBound); TFHE.allowThis(value8); } + // Generate random 16-bit encrypted unsigned integer function generate16() public { value16 = TFHE.randEuint16(); TFHE.allowThis(value16); } + // Generate random 16-bit encrypted unsigned integer with upper bound function generate16UpperBound(uint16 upperBound) public { value16 = TFHE.randEuint16(upperBound); TFHE.allowThis(value16); } + // Generate random 32-bit encrypted unsigned integer function generate32() public { value32 = TFHE.randEuint32(); TFHE.allowThis(value32); } + // Generate random 32-bit encrypted unsigned integer with upper bound function generate32UpperBound(uint32 upperBound) public { value32 = TFHE.randEuint32(upperBound); TFHE.allowThis(value32); } + // Generate random 64-bit encrypted unsigned integer function generate64() public { value64 = TFHE.randEuint64(); TFHE.allowThis(value64); } + // Generate random 64-bit encrypted unsigned integer with upper bound function generate64UpperBound(uint32 upperBound) public { value64 = TFHE.randEuint64(upperBound); TFHE.allowThis(value64); } + // Generate random 64-bit encrypted unsigned integer with error handling function generate64Reverting() public { try this.failingCall() {} catch {} value64Bounded = TFHE.randEuint64(1024); TFHE.allowThis(value64Bounded); } + // Function that always reverts after generating a random number function failingCall() public { value64 = TFHE.randEuint64(); TFHE.allowThis(value64); diff --git a/examples/Reencrypt.sol b/examples/Reencrypt.sol index 44e7ace3..957bc360 100644 --- a/examples/Reencrypt.sol +++ b/examples/Reencrypt.sol @@ -4,48 +4,67 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; +// Contract for demonstrating reencryption of various FHE data types contract Reencrypt { + // Encrypted boolean ebool public xBool; + // Encrypted 4-bit unsigned integer euint4 public xUint4; + // Encrypted 8-bit unsigned integer euint8 public xUint8; + // Encrypted 16-bit unsigned integer euint16 public xUint16; + // Encrypted 32-bit unsigned integer euint32 public xUint32; + // Encrypted 64-bit unsigned integer euint64 public xUint64; + // Encrypted Ethereum address eaddress public xAddress; + // Encrypted 256-bit bytes ebytes256 public yBytes256; + // Constructor to initialize encrypted values and set permissions constructor() { + // Set default FHE configuration TFHE.setFHEVM(FHEVMConfig.defaultConfig()); + // Initialize and set permissions for xBool xBool = TFHE.asEbool(true); TFHE.allowThis(xBool); TFHE.allow(xBool, msg.sender); + // Initialize and set permissions for xUint4 xUint4 = TFHE.asEuint4(4); TFHE.allowThis(xUint4); TFHE.allow(xUint4, msg.sender); + // Initialize and set permissions for xUint8 xUint8 = TFHE.asEuint8(42); TFHE.allowThis(xUint8); TFHE.allow(xUint8, msg.sender); + // Initialize and set permissions for xUint16 xUint16 = TFHE.asEuint16(16); TFHE.allowThis(xUint16); TFHE.allow(xUint16, msg.sender); + // Initialize and set permissions for xUint32 xUint32 = TFHE.asEuint32(32); TFHE.allowThis(xUint32); TFHE.allow(xUint32, msg.sender); + // Initialize and set permissions for xUint64 xUint64 = TFHE.asEuint64(18446744073709551600); TFHE.allowThis(xUint64); TFHE.allow(xUint64, msg.sender); + // Initialize and set permissions for xAddress xAddress = TFHE.asEaddress(0x8ba1f109551bD432803012645Ac136ddd64DBA72); TFHE.allowThis(xAddress); TFHE.allow(xAddress, msg.sender); } + // Function to set and allow access to encrypted 256-bit bytes function setEBytes256(einput inputHandleEBytes256, bytes memory inputProofEBytes256) external { yBytes256 = TFHE.asEbytes256(inputHandleEBytes256, inputProofEBytes256); TFHE.allowThis(yBytes256); diff --git a/examples/Regression1.sol b/examples/Regression1.sol index b8ae90eb..d893869b 100644 --- a/examples/Regression1.sol +++ b/examples/Regression1.sol @@ -1,21 +1,31 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; +// Contract for managing services and metadata contract Regression1 { error IndexOutOfBound(); + + // Struct to store metadata information struct Metadata { uint256 created; uint256 lastUpdated; uint256 versionId; } + + // Struct to represent a service struct Service { bytes32 id; string serviceType; string serviceEndpoint; } + + // Mapping to store metadata for each address mapping(address id => Metadata) public metadata; + + // Mapping to store services for each address mapping(address id => Service[] service) private _services; + // Function to add services function addServices(Service[] calldata services) public { for (uint256 i = 0; i < services.length; i++) { _services[msg.sender].push(services[i]); @@ -25,6 +35,7 @@ contract Regression1 { metadata[msg.sender].versionId = 1; } + // Function to remove a service function removeService(uint256 serviceIndex) public { if (serviceIndex >= _services[msg.sender].length) revert IndexOutOfBound(); for (uint256 i = serviceIndex; i < _services[msg.sender].length - 1; i++) { @@ -35,6 +46,7 @@ contract Regression1 { metadata[msg.sender].versionId++; } + // Function to get services for a given address function getServices(address id) public view returns (Service[] memory) { return _services[id]; } diff --git a/examples/SmartAccount.sol b/examples/SmartAccount.sol index b7749eac..59cf71e4 100644 --- a/examples/SmartAccount.sol +++ b/examples/SmartAccount.sol @@ -1,21 +1,28 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; +// Import Ownable2Step from OpenZeppelin import "@openzeppelin/contracts/access/Ownable2Step.sol"; +// Smart account contract with batch transaction execution contract SmartAccount is Ownable2Step { + // Structure to represent a transaction struct Transaction { address target; uint256 value; bytes data; } + // Constructor to set the initial owner constructor() Ownable(msg.sender) {} + // Function to execute multiple transactions in a batch function executeBatch(Transaction[] memory transactions) public payable onlyOwner { for (uint i = 0; i < transactions.length; i++) { Transaction memory transaction = transactions[i]; + // Execute the transaction (bool success, ) = transaction.target.call{value: transaction.value}(transaction.data); + // Ensure the transaction was successful require(success, "Transaction failed"); } } diff --git a/examples/TestAsyncDecrypt.sol b/examples/TestAsyncDecrypt.sol index 6d81b9ec..f14efb90 100644 --- a/examples/TestAsyncDecrypt.sol +++ b/examples/TestAsyncDecrypt.sol @@ -5,7 +5,9 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; import "../gateway/GatewayCaller.sol"; +// Contract for testing asynchronous decryption using the Gateway contract TestAsyncDecrypt is GatewayCaller { + // Encrypted state variables ebool xBool; euint4 xUint4; euint8 xUint8; @@ -17,6 +19,7 @@ contract TestAsyncDecrypt is GatewayCaller { eaddress xAddress; eaddress xAddress2; + // Decrypted state variables bool public yBool; uint8 public yUint4; uint8 public yUint8; @@ -29,11 +32,15 @@ contract TestAsyncDecrypt is GatewayCaller { address public yAddress2; bytes public yBytes256; + // Tracks the latest decryption request ID uint256 public latestRequestID; + // Constructor to initialize the contract and set up encrypted values constructor() { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); Gateway.setGateway(Gateway.defaultGatewayAddress()); + + // Initialize encrypted variables with sample values xBool = TFHE.asEbool(true); TFHE.allowThis(xBool); xUint4 = TFHE.asEuint4(4); @@ -56,12 +63,14 @@ contract TestAsyncDecrypt is GatewayCaller { TFHE.allowThis(xAddress2); } + // Function to request decryption of a boolean value with an infinite loop in the callback function requestBoolInfinite() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); Gateway.requestDecryption(cts, this.callbackBoolInfinite.selector, 0, block.timestamp + 100, false); } + // Callback function for the infinite loop decryption request (WARNING: This function will never complete) function callbackBoolInfinite(uint256 /*requestID*/, bool decryptedInput) public onlyGateway returns (bool) { uint256 i = 0; while (true) { @@ -71,22 +80,27 @@ contract TestAsyncDecrypt is GatewayCaller { return yBool; } + // Function to request decryption with an excessive delay (should revert) function requestBoolAboveDelay() public { - // should revert + // This should revert due to the excessive delay uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 2 days, false); } + // Request decryption of a boolean value function requestBool() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); + // Request decryption with a 100-second deadline and non-trustless mode Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100, false); } + // Request decryption of a boolean value in trustless mode function requestBoolTrustless() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); + // Request decryption with a 100-second deadline and trustless mode (true) uint256 requestID = Gateway.requestDecryption( cts, this.callbackBoolTrustless.selector, @@ -95,84 +109,102 @@ contract TestAsyncDecrypt is GatewayCaller { true ); latestRequestID = requestID; + // Save the requested handles for later verification saveRequestedHandles(requestID, cts); } + // Attempt to request decryption of a fake boolean value (should revert) function requestFakeBool() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000000); - Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100, false); // this should revert because previous ebool is not honestly obtained + // This should revert because the previous ebool is not honestly obtained + Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100, false); } + // Callback function for non-trustless boolean decryption function callbackBool(uint256, bool decryptedInput) public onlyGateway returns (bool) { yBool = decryptedInput; return yBool; } + // Callback function for trustless boolean decryption function callbackBoolTrustless( uint256 requestID, bool decryptedInput, bytes[] memory signatures ) public onlyGateway returns (bool) { + // Verify that the requestID matches the latest request require(latestRequestID == requestID, "wrong requestID passed by Gateway"); + // Load the previously saved handles for verification uint256[] memory requestedHandles = loadRequestedHandles(latestRequestID); + // Verify the signatures provided by the KMS (Key Management Service) bool isKMSVerified = Gateway.verifySignatures(requestedHandles, signatures); require(isKMSVerified, "KMS did not verify this decryption result"); + // If verification passes, store the decrypted value yBool = decryptedInput; return yBool; } - + // Function to request decryption of a 4-bit unsigned integer function requestUint4() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint4); Gateway.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100, false); } + // Function to attempt requesting decryption of a fake 4-bit unsigned integer (should revert) function requestFakeUint4() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000100); Gateway.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained } + // Callback function for 4-bit unsigned integer decryption function callbackUint4(uint256, uint8 decryptedInput) public onlyGateway returns (uint8) { yUint4 = decryptedInput; return decryptedInput; } + // Function to request decryption of an 8-bit unsigned integer function requestUint8() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint8); Gateway.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100, false); } + // Function to attempt requesting decryption of a fake 8-bit unsigned integer (should revert) function requestFakeUint8() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000200); Gateway.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained } + // Callback function for 8-bit unsigned integer decryption function callbackUint8(uint256, uint8 decryptedInput) public onlyGateway returns (uint8) { yUint8 = decryptedInput; return decryptedInput; } + // Function to request decryption of a 16-bit unsigned integer function requestUint16() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint16); Gateway.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100, false); } + // Function to attempt requesting decryption of a fake 16-bit unsigned integer (should revert) function requestFakeUint16() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000300); Gateway.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained } + // Callback function for 16-bit unsigned integer decryption function callbackUint16(uint256, uint16 decryptedInput) public onlyGateway returns (uint16) { yUint16 = decryptedInput; return decryptedInput; } + // Function to request decryption of a 32-bit unsigned integer with additional inputs function requestUint32(uint32 input1, uint32 input2) public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint32); @@ -187,12 +219,14 @@ contract TestAsyncDecrypt is GatewayCaller { addParamsUint256(requestID, input2); } + // Function to attempt requesting decryption of a fake 32-bit unsigned integer (should revert) function requestFakeUint32() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000400); Gateway.requestDecryption(cts, this.callbackUint32.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained } + // Callback function for 32-bit unsigned integer decryption function callbackUint32(uint256 requestID, uint32 decryptedInput) public onlyGateway returns (uint32) { uint256[] memory params = getParamsUint256(requestID); unchecked { @@ -202,18 +236,21 @@ contract TestAsyncDecrypt is GatewayCaller { } } + // Function to request decryption of a 64-bit unsigned integer function requestUint64() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint64); Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); } + // Function to attempt requesting decryption of a fake 64-bit unsigned integer (should revert) function requestFakeUint64() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000500); Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained } + // Function to request decryption of a non-trivial 64-bit unsigned integer function requestUint64NonTrivial(einput inputHandle, bytes calldata inputProof) public { euint64 inputNonTrivial = TFHE.asEuint64(inputHandle, inputProof); uint256[] memory cts = new uint256[](1); @@ -221,11 +258,13 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); } + // Callback function for 64-bit unsigned integer decryption function callbackUint64(uint256, uint64 decryptedInput) public onlyGateway returns (uint64) { yUint64 = decryptedInput; return decryptedInput; } + // Function to request decryption of a non-trivial 256-bit encrypted bytes function requestEbytes256NonTrivial(einput inputHandle, bytes calldata inputProof) public { ebytes256 inputNonTrivial = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](1); @@ -233,17 +272,20 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackBytes256.selector, 0, block.timestamp + 100, false); } + // Callback function for 256-bit encrypted bytes decryption function callbackBytes256(uint256, bytes calldata decryptedInput) public onlyGateway returns (bytes memory) { yBytes256 = decryptedInput; return decryptedInput; } + // Function to request decryption of an encrypted address function requestAddress() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xAddress); Gateway.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100, false); } + // Function to request decryption of multiple encrypted addresses function requestSeveralAddresses() public { uint256[] memory cts = new uint256[](2); cts[0] = Gateway.toUint256(xAddress); @@ -251,6 +293,7 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackAddresses.selector, 0, block.timestamp + 100, false); } + // Callback function for multiple address decryption function callbackAddresses( uint256 /*requestID*/, address decryptedInput1, @@ -261,17 +304,20 @@ contract TestAsyncDecrypt is GatewayCaller { return decryptedInput1; } + // Function to attempt requesting decryption of a fake address (should revert) function requestFakeAddress() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000700); Gateway.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained } + // Callback function for address decryption function callbackAddress(uint256, address decryptedInput) public onlyGateway returns (address) { yAddress = decryptedInput; return decryptedInput; } + // Function to request decryption of mixed data types function requestMixed(uint32 input1, uint32 input2) public { uint256[] memory cts = new uint256[](10); cts[0] = Gateway.toUint256(xBool); @@ -295,6 +341,7 @@ contract TestAsyncDecrypt is GatewayCaller { addParamsUint256(requestID, input2); } + // Callback function for mixed data type decryption function callbackMixed( uint256 requestID, bool decBool_1, @@ -324,6 +371,7 @@ contract TestAsyncDecrypt is GatewayCaller { return yUint4; } + // Function to request decryption of mixed data types including 256-bit encrypted bytes function requestMixedBytes256(einput inputHandle, bytes calldata inputProof) public { ebytes256 xBytes256 = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](3); @@ -333,6 +381,7 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackMixedBytes256.selector, 0, block.timestamp + 100, false); } + // Callback function for mixed data type decryption including 256-bit encrypted bytes function callbackMixedBytes256( uint256, bool decBool, @@ -344,6 +393,7 @@ contract TestAsyncDecrypt is GatewayCaller { yBytes256 = bytesRes; } + // Function to request trustless decryption of non-trivial 256-bit encrypted bytes function requestEbytes256NonTrivialTrustless(einput inputHandle, bytes calldata inputProof) public { ebytes256 inputNonTrivial = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](1); @@ -359,6 +409,7 @@ contract TestAsyncDecrypt is GatewayCaller { saveRequestedHandles(requestID, cts); } + // Callback function for trustless decryption of 256-bit encrypted bytes function callbackBytes256Trustless( uint256 requestID, bytes calldata decryptedInput, @@ -372,6 +423,7 @@ contract TestAsyncDecrypt is GatewayCaller { return decryptedInput; } + // Function to request trustless decryption of mixed data types including 256-bit encrypted bytes function requestMixedBytes256Trustless(einput inputHandle, bytes calldata inputProof) public { ebytes256 xBytes256 = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](3); @@ -390,6 +442,7 @@ contract TestAsyncDecrypt is GatewayCaller { saveRequestedHandles(requestID, cts); } + // Callback function for trustless decryption of mixed data types including 256-bit encrypted bytes function callbackMixedBytes256Trustless( uint256 requestID, bool decBool, @@ -405,4 +458,4 @@ contract TestAsyncDecrypt is GatewayCaller { yAddress = decAddress; yBytes256 = bytesRes; } -} +} \ No newline at end of file From 45bd0336b5aca99927beca04f3591c30f53cd823 Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed Date: Fri, 4 Oct 2024 21:16:50 +0800 Subject: [PATCH 5/7] docs: update --- examples/BlindAuction.sol | 16 +++++++- examples/README.md | 48 +++++++++++++++++++++--- examples/TFHEExecutorUpgradedExample.sol | 8 ++-- examples/TracingSubCalls.sol | 14 +++++++ 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/examples/BlindAuction.sol b/examples/BlindAuction.sol index c54a2e47..7c178da1 100644 --- a/examples/BlindAuction.sol +++ b/examples/BlindAuction.sol @@ -2,14 +2,18 @@ pragma solidity ^0.8.24; +// Import necessary contracts and libraries import "../lib/TFHE.sol"; import "./EncryptedERC20.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; import "../gateway/GatewayCaller.sol"; +// Main contract for the blind auction contract BlindAuction is Ownable2Step, GatewayCaller { + // Auction end time uint256 public endTime; + // Address of the beneficiary address public beneficiary; // Current highest bid. @@ -26,7 +30,7 @@ contract BlindAuction is Ownable2Step, GatewayCaller { // Mapping from bidder to their bid value. mapping(address account => euint64 bidAmount) private bids; - // Number of bid + // Number of bids uint256 public bidCounter; // The token contract used for encrypted bids. @@ -36,11 +40,13 @@ contract BlindAuction is Ownable2Step, GatewayCaller { // WARNING : if there is a draw, only first highest bidder will get the prize (an improved implementation could handle this case differently) ebool private objectClaimed; - // If the token has been transferred to the beneficiary + // Flag to check if the token has been transferred to the beneficiary bool public tokenTransferred; + // Flag to determine if the auction can be stopped manually bool public stoppable; + // Flag to check if the auction has been manually stopped bool public manuallyStopped = false; // The function has been called too early. @@ -50,6 +56,7 @@ contract BlindAuction is Ownable2Step, GatewayCaller { // It cannot be called after `time`. error TooLate(uint256 time); + // Constructor to initialize the auction constructor( address _beneficiary, EncryptedERC20 _tokenContract, @@ -127,6 +134,7 @@ contract BlindAuction is Ownable2Step, GatewayCaller { return bids[account]; } + // Function to manually stop the auction function stop() external onlyOwner { require(stoppable); manuallyStopped = true; @@ -138,12 +146,14 @@ contract BlindAuction is Ownable2Step, GatewayCaller { return userTickets[account]; } + // Function to initiate the decryption of the winning ticket function decryptWinningTicket() public onlyAfterEnd { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(winningTicket); Gateway.requestDecryption(cts, this.setDecryptedWinningTicket.selector, 0, block.timestamp + 100, false); } + // Callback function to set the decrypted winning ticket function setDecryptedWinningTicket(uint256, uint64 resultDecryption) public onlyGateway { decryptedWinningTicket = resultDecryption; } @@ -186,11 +196,13 @@ contract BlindAuction is Ownable2Step, GatewayCaller { TFHE.allow(newBid, msg.sender); } + // Modifier to ensure function is called before auction ends modifier onlyBeforeEnd() { if (block.timestamp >= endTime || manuallyStopped == true) revert TooLate(endTime); _; } + // Modifier to ensure function is called after auction ends modifier onlyAfterEnd() { if (block.timestamp < endTime && manuallyStopped == false) revert TooEarly(endTime); _; diff --git a/examples/README.md b/examples/README.md index aa07d887..6977b932 100644 --- a/examples/README.md +++ b/examples/README.md @@ -85,15 +85,14 @@ graph TD -``` -# Counter.sol -The `Counter` smart contract is a simple contract implemented in Solidity, designed to demonstrate basic state manipulation. Its main purpose is to maintain a counter (value) and provide functions to increment and view the current counter value. + 3. `Counter.sol`: + The `Counter` smart contract is a simple contract implemented in Solidity, designed to demonstrate basic state manipulation. Its main purpose is to maintain a counter (value) and provide functions to increment and view the current counter value. -## EncryptedERC20.sol -This contract implements an encrypted ERC20-like token with confidential balances using Zama's FHE (Fully Homomorphic Encryption) library. -It supports typical ERC20 functionality such as transferring tokens, minting, and setting allowances, but uses encrypted data types. + 1. `EncryptedERC20.sol`: + This contract implements an encrypted ERC20-like token with confidential balances using Zama's FHE (Fully Homomorphic Encryption) library. + It supports typical ERC20 functionality such as transferring tokens, minting, and setting allowances, but uses encrypted data types. ### Approval and Transfer Operations @@ -116,3 +115,40 @@ graph TD Y1 --> Y3 ``` +1. `TestAsyncDecrypt.sol`: + A contract for testing asynchronous decryption using the Gateway. It handles various encrypted data types and demonstrates different decryption scenarios, including trustless decryption. + +2. `FHEPaymentUpgradedExample.sol`: + An upgraded version of the FHEPayment contract, adding version information. + +4. `BlindAuction.sol`: + Implements a blind auction using encrypted bids. It manages bidding, claiming, and withdrawing processes using homomorphic encryption. + +5. `ACLUpgradedExample.sol`: + An upgraded version of the ACL (Access Control List) contract, adding version information. + +6. `KMSVerifierUpgradedExample.sol`: + An upgraded version of the KMSVerifier contract, adding version information. + +7. `ACLUpgradedExample2.sol`: + Another upgraded version of the ACL contract, with a different version number. + +8. `TFHEExecutorUpgradedExample.sol`: + An upgraded version of the TFHEExecutor contract, adding version information. + +9. `GatewayContractUpgradedExample.sol`: + An upgraded version of the GatewayContract, adding version information. + + +2. `PaymentLimit.sol`: + A contract designed to test FHE gas limits. It includes functions that perform different numbers of FHE operations to test various scenarios: well under the block FHE gas limit, close to the limit, and exceeding the limit. + +3. `KMSVerifierUpgradedExample.sol`: + An upgraded version of the KMSVerifier contract, adding version information. This contract is likely part of a key management system for the FHE operations. + +4. `TracingSubCalls.sol`: + A set of contracts designed to test various subcall scenarios in a blockchain environment. It includes: + - A main contract that initiates different types of subcalls + - Contracts that test creation with encrypted inputs + - A contract with various functions to test success, failure, out-of-gas, and self-destruct scenarios + diff --git a/examples/TFHEExecutorUpgradedExample.sol b/examples/TFHEExecutorUpgradedExample.sol index cf439ebe..f447b623 100644 --- a/examples/TFHEExecutorUpgradedExample.sol +++ b/examples/TFHEExecutorUpgradedExample.sol @@ -4,17 +4,17 @@ pragma solidity ^0.8.24; import "../lib/TFHEExecutor.sol"; +// Contract that extends TFHEExecutor with version information contract TFHEExecutorUpgradedExample is TFHEExecutor { - /// @notice Name of the contract + // Name of the contract string private constant CONTRACT_NAME = "TFHEExecutor"; - /// @notice Version of the contract + // Version numbers uint256 private constant MAJOR_VERSION = 0; uint256 private constant MINOR_VERSION = 2; uint256 private constant PATCH_VERSION = 0; - /// @notice Getter for the name and version of the contract - /// @return string representing the name and the version of the contract + // Function to get the full version string of the contract function getVersion() external pure virtual override returns (string memory) { return string( diff --git a/examples/TracingSubCalls.sol b/examples/TracingSubCalls.sol index 65f9c243..63647389 100644 --- a/examples/TracingSubCalls.sol +++ b/examples/TracingSubCalls.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; +// Main contract for testing various subcalls contract TracingSubCalls { function subCalls() external { try new SubContractCreate(400) {} catch {} // 0K @@ -22,12 +23,14 @@ contract TracingSubCalls { } } +// Contract that creates a new instance with an encrypted input contract SubContractCreate { constructor(uint256 input) { TFHE.asEuint64(input); } } +// Contract that attempts to create a new instance but always fails contract SubContractCreateFail { constructor(uint256 input) { TFHE.asEuint64(input); @@ -35,40 +38,49 @@ contract SubContractCreateFail { } } +// Contract with various test functions for success and failure scenarios contract SubContract { + // Function that always succeeds function succeed() external { TFHE.asEuint64(601); } + // Function that always fails function fail() external { TFHE.asEuint64(602); require(false); } + // Internal function that fails with a custom input function fail2(uint input) external { TFHE.asEuint64(input); require(false); } + // Function that succeeds and then calls a failing function function succeedFail() external { TFHE.asEuint64(603); try this.fail2(604) {} catch {} } + // Function that attempts to fail and then succeed function failSucceed() external { this.fail2(605); TFHE.asEuint64(606); } + // Function that runs out of gas function oogFail() external { TFHE.asEuint64(607); while (true) {} } + // Another function that always succeeds function succeed2() external { TFHE.asEuint64(608); } + // Function that fails with an invalid operation function invalidFail() external { TFHE.asEuint64(609); assembly { @@ -76,6 +88,7 @@ contract SubContract { } } + // Function that succeeds and then stops execution function succeedStop() external { TFHE.asEuint64(610); assembly { @@ -83,6 +96,7 @@ contract SubContract { } } + // Function that succeeds and then self-destructs the contract function succeedSelfDestruct() external { TFHE.asEuint64(611); selfdestruct(payable(address(1))); From 1217b9ad9cb3a068fe82db72239c06337e20e281 Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed Date: Tue, 8 Oct 2024 12:50:53 +0200 Subject: [PATCH 6/7] docs: add comments to example contracts docs: payment-limit docs docs: blind auction docs: improved the comments on example contracts, use of @dev, @params and @notice docs: readme is updated docs: imporved readme for examples docs: improve README docs: prettier docs: commit suggestions docs: shortended readme --- .gitignore | 1 + docs/tutorials/see-all-tutorials.md | 6 +- examples/BlindAuction.sol | 99 ++++++++---- examples/Counter.sol | 12 +- examples/EncryptedERC20.sol | 128 ++++++++++----- examples/PaymentLimit.sol | 13 +- examples/README.md | 166 ++++++------------- examples/Rand.sol | 29 ++-- examples/Reencrypt.sol | 24 +-- examples/Regression1.sol | 18 ++- examples/SmartAccount.sol | 14 +- examples/TFHEExecutorUpgradedExample.sol | 11 +- examples/TestAsyncDecrypt.sol | 196 +++++++++++++++-------- examples/TracingSubCalls.sol | 58 ++++--- 14 files changed, 449 insertions(+), 326 deletions(-) diff --git a/.gitignore b/.gitignore index b766d354..df4f6eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ yarn-error.log* lerna-debug.log* pnpm-lock.yaml yarn.lock +.vscode # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/docs/tutorials/see-all-tutorials.md b/docs/tutorials/see-all-tutorials.md index d6f58116..eeecdd27 100644 --- a/docs/tutorials/see-all-tutorials.md +++ b/docs/tutorials/see-all-tutorials.md @@ -8,10 +8,12 @@ ### Code examples in GitHub -- [ERC-20](https://github.com/zama-ai/fhevm/blob/main/examples/EncryptedERC20.sol): A variation of the standard ERC20 smart contract that incorporates encrypted balances, providing additional privacy for token holders. - +- [ERC-20](https://github.com/zama-ai/fhevm/blob/main/examples/EncryptedERC20.sol): A variation of the standard ERC20 smart contract that incorporates encrypted balances, providing additional privacy for token holders. - [Blind Auction](https://github.com/zama-ai/fhevm/blob/main/examples/BlindAuction.sol): A smart contract for conducting blind auctions where bids are encrypted and the winning bid remains private. -#### Legacy +For more information on individual contracts, see the [README](https://github.com/zama-ai/fhevm/blob/main/examples/README.md) in the examples folder. + +#### Legacy - Not compatible with latest fhEVM - [Governor DAO](https://github.com/zama-ai/fhevm/tree/main/examples/legacy/Governor): A DAO smart contract that facilitates governance decisions through encrypted voting - [Decentralized ID](https://github.com/zama-ai/fhevm/tree/main/examples/legacy/Identity): A blockchain-based identity management system using smart contracts to store and manage encrypted personal data. diff --git a/examples/BlindAuction.sol b/examples/BlindAuction.sol index 7c178da1..7f01c15f 100644 --- a/examples/BlindAuction.sol +++ b/examples/BlindAuction.sol @@ -2,61 +2,71 @@ pragma solidity ^0.8.24; -// Import necessary contracts and libraries import "../lib/TFHE.sol"; import "./EncryptedERC20.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; import "../gateway/GatewayCaller.sol"; -// Main contract for the blind auction +/// @notice Main contract for the blind auction contract BlindAuction is Ownable2Step, GatewayCaller { - // Auction end time + /// @notice Auction end time uint256 public endTime; - // Address of the beneficiary + /// @notice Address of the beneficiary address public beneficiary; - // Current highest bid. + /// @notice Current highest bid euint64 private highestBid; - // ticket corresponding to the highest bid, used during reencryption to know if a user has won the bid + /// @notice Ticket corresponding to the highest bid + /// @dev Used during reencryption to know if a user has won the bid euint64 private winningTicket; - uint64 private decryptedWinningTicket; // decryption of winningTicket, can be requested by anyone after auction ends - // ticket randomly sampled for each user - // WARNING : we assume probability of duplicated tickets is null (an improved implementation could simply sample 4 random euint64 tickets per user for negligible collision probability) + /// @notice Decryption of winningTicket + /// @dev Can be requested by anyone after auction ends + uint64 private decryptedWinningTicket; + + /// @notice Ticket randomly sampled for each user + /// @dev WARNING: We assume probability of duplicated tickets is null + /// @dev An improved implementation could sample 4 random euint64 tickets per user for negligible collision probability mapping(address account => euint64 ticket) private userTickets; - // Mapping from bidder to their bid value. + /// @notice Mapping from bidder to their bid value mapping(address account => euint64 bidAmount) private bids; - // Number of bids + /// @notice Number of bids uint256 public bidCounter; - // The token contract used for encrypted bids. + /// @notice The token contract used for encrypted bids EncryptedERC20 public tokenContract; - // Whether the auction object has been claimed. - // WARNING : if there is a draw, only first highest bidder will get the prize (an improved implementation could handle this case differently) + /// @notice Flag indicating whether the auction object has been claimed + /// @dev WARNING : If there is a draw, only the first highest bidder will get the prize + /// An improved implementation could handle this case differently ebool private objectClaimed; - // Flag to check if the token has been transferred to the beneficiary + /// @notice Flag to check if the token has been transferred to the beneficiary bool public tokenTransferred; - // Flag to determine if the auction can be stopped manually + /// @notice Flag to determine if the auction can be stopped manually bool public stoppable; - // Flag to check if the auction has been manually stopped + /// @notice Flag to check if the auction has been manually stopped bool public manuallyStopped = false; - // The function has been called too early. - // Try again at `time`. + /// @notice Error thrown when a function is called too early + /// @dev Includes the time when the function can be called error TooEarly(uint256 time); - // The function has been called too late. - // It cannot be called after `time`. + + /// @notice Error thrown when a function is called too late + /// @dev Includes the time after which the function cannot be called error TooLate(uint256 time); - // Constructor to initialize the auction + /// @notice Constructor to initialize the auction + /// @param _beneficiary Address of the beneficiary who will receive the highest bid + /// @param _tokenContract Address of the EncryptedERC20 token contract used for bidding + /// @param biddingTime Duration of the auction in seconds + /// @param isStoppable Flag to determine if the auction can be stopped manually constructor( address _beneficiary, EncryptedERC20 _tokenContract, @@ -75,7 +85,10 @@ contract BlindAuction is Ownable2Step, GatewayCaller { stoppable = isStoppable; } - // Bid an `encryptedValue`. + /// @notice Submit a bid with an encrypted value + /// @dev Transfers tokens from the bidder to the contract + /// @param encryptedValue The encrypted bid amount + /// @param inputProof Proof for the encrypted input function bid(einput encryptedValue, bytes calldata inputProof) external onlyBeforeEnd { euint64 value = TFHE.asEuint64(encryptedValue, inputProof); euint64 existingBid = bids[msg.sender]; @@ -129,42 +142,54 @@ contract BlindAuction is Ownable2Step, GatewayCaller { TFHE.allow(userTicket, msg.sender); } - // Returns the `account`'s encrypted bid, can be used in a reencryption request + /// @notice Get the encrypted bid of a specific account + /// @dev Can be used in a reencryption request + /// @param account The address of the bidder + /// @return The encrypted bid amount function getBid(address account) external view returns (euint64) { return bids[account]; } - // Function to manually stop the auction + /// @notice Manually stop the auction + /// @dev Can only be called by the owner and if the auction is stoppable function stop() external onlyOwner { require(stoppable); manuallyStopped = true; } - // Returns the `account`'s encrypted ticket, can be used in a reencryption request, then compared to - // `decryptedWinningTicket` when auction ends, so the user could learn if he won the auction + /// @notice Get the encrypted ticket of a specific account + /// @dev Can be used in a reencryption request + /// @param account The address of the bidder + /// @return The encrypted ticket function ticketUser(address account) external view returns (euint64) { return userTickets[account]; } - // Function to initiate the decryption of the winning ticket + /// @notice Initiate the decryption of the winning ticket + /// @dev Can only be called after the auction ends function decryptWinningTicket() public onlyAfterEnd { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(winningTicket); Gateway.requestDecryption(cts, this.setDecryptedWinningTicket.selector, 0, block.timestamp + 100, false); } - // Callback function to set the decrypted winning ticket + /// @notice Callback function to set the decrypted winning ticket + /// @dev Can only be called by the Gateway + /// @param resultDecryption The decrypted winning ticket function setDecryptedWinningTicket(uint256, uint64 resultDecryption) public onlyGateway { decryptedWinningTicket = resultDecryption; } - // if `userTickets[account]` is an encryption of decryptedWinningTicket, then `account` won and can call `claim` succesfully + /// @notice Get the decrypted winning ticket + /// @dev Can only be called after the winning ticket has been decrypted - if `userTickets[account]` is an encryption of decryptedWinningTicket, then `account` won and can call `claim` succesfully + /// @return The decrypted winning ticket function getDecryptedWinningTicket() external view returns (uint64) { require(decryptedWinningTicket != 0, "Winning ticket has not been decrypted yet"); return decryptedWinningTicket; } - // Claim the object. Succeeds only if the caller was the first to get the highest bid. + /// @notice Claim the auction object + /// @dev Succeeds only if the caller was the first to get the highest bid function claim() public onlyAfterEnd { ebool canClaim = TFHE.and(TFHE.eq(winningTicket, userTickets[msg.sender]), TFHE.not(objectClaimed)); objectClaimed = TFHE.or(canClaim, objectClaimed); @@ -175,7 +200,8 @@ contract BlindAuction is Ownable2Step, GatewayCaller { TFHE.allow(bids[msg.sender], msg.sender); } - // Transfer token to beneficiary + /// @notice Transfer the highest bid to the beneficiary + /// @dev Can only be called once after the auction ends function auctionEnd() public onlyAfterEnd { require(!tokenTransferred); tokenTransferred = true; @@ -183,7 +209,8 @@ contract BlindAuction is Ownable2Step, GatewayCaller { tokenContract.transfer(beneficiary, highestBid); } - // Withdraw a bid from the auction to the caller once the auction has stopped. + /// @notice Withdraw a bid from the auction + /// @dev Can only be called after the auction ends and by non-winning bidders function withdraw() public onlyAfterEnd { euint64 bidValue = bids[msg.sender]; ebool canWithdraw = TFHE.ne(winningTicket, userTickets[msg.sender]); @@ -196,13 +223,15 @@ contract BlindAuction is Ownable2Step, GatewayCaller { TFHE.allow(newBid, msg.sender); } - // Modifier to ensure function is called before auction ends + /// @notice Modifier to ensure function is called before auction ends + /// @dev Reverts if called after the auction end time or if manually stopped modifier onlyBeforeEnd() { if (block.timestamp >= endTime || manuallyStopped == true) revert TooLate(endTime); _; } - // Modifier to ensure function is called after auction ends + /// @notice Modifier to ensure function is called after auction ends + /// @dev Reverts if called before the auction end time and not manually stopped modifier onlyAfterEnd() { if (block.timestamp < endTime && manuallyStopped == false) revert TooEarly(endTime); _; diff --git a/examples/Counter.sol b/examples/Counter.sol index e787f267..03a66058 100644 --- a/examples/Counter.sol +++ b/examples/Counter.sol @@ -1,18 +1,20 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -// This is a simple contract named 'Counter' that maintains a single state variable 'value'. -// It provides functionality to increment the 'value' and read its current value. +/// @notice A simple contract that maintains a single state variable 'value' +/// @dev This contract provides functionality to increment the 'value' and read its current value contract Counter { - // State variable to keep track of the count. + /// @notice State variable to keep track of the count + /// @dev Stored as a uint32 to save gas uint32 value; - // Increases the value by 1 each time this function is called. + /// @notice Increases the value by 1 each time this function is called function increment() public { value += 1; } - // Returns the current value of the counter. + /// @notice Returns the current value of the counter + /// @return The current value as a uint32 function currentValue() public view returns (uint32) { return value; } diff --git a/examples/EncryptedERC20.sol b/examples/EncryptedERC20.sol index ad716726..7c96b0d7 100644 --- a/examples/EncryptedERC20.sol +++ b/examples/EncryptedERC20.sol @@ -5,50 +5,58 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; -// This contract implements an encrypted ERC20-like token with confidential balances using Zama's FHE (Fully Homomorphic Encryption) library. -// It supports typical ERC20 functionality such as transferring tokens, minting, and setting allowances, but uses encrypted data types. +/// @notice This contract implements an encrypted ERC20-like token with confidential balances using Zama's FHE (Fully Homomorphic Encryption) library. +/// @dev It supports typical ERC20 functionality such as transferring tokens, minting, and setting allowances, but uses encrypted data types. contract EncryptedERC20 is Ownable2Step { - // Events to log important actions in the contract - event Transfer(address indexed from, address indexed to); // Emitted when tokens are transferred - event Approval(address indexed owner, address indexed spender); // Emitted when a spender is approved to spend tokens on behalf of an owner - event Mint(address indexed to, uint64 amount); // Emitted when new tokens are minted - - // Private state variables for basic token information and supply - uint64 private _totalSupply; // Stores the total supply of the token - string private _name; // Name of the token (e.g., "Confidential Token") - string private _symbol; // Symbol of the token (e.g., "CTK") - uint8 public constant decimals = 6; // Number of decimal places for the token - - // A mapping from address to an encrypted balance - tracks encrypted balances of each address + /// @notice Emitted when tokens are transferred + event Transfer(address indexed from, address indexed to); + /// @notice Emitted when a spender is approved to spend tokens on behalf of an owner + event Approval(address indexed owner, address indexed spender); + /// @notice Emitted when new tokens are minted + event Mint(address indexed to, uint64 amount); + + /// @dev Stores the total supply of the token + uint64 private _totalSupply; + /// @dev Name of the token (e.g., "Confidential Token") + string private _name; + /// @dev Symbol of the token (e.g., "CTK") + string private _symbol; + /// @notice Number of decimal places for the token + uint8 public constant decimals = 6; + + /// @dev A mapping from address to an encrypted balance - tracks encrypted balances of each address mapping(address => euint64) internal balances; - // Mapping to manage encrypted allowance - of the form mapping(owner => mapping(spender => allowance)). + /// @dev Mapping to manage encrypted allowance - of the form mapping(owner => mapping(spender => allowance)). mapping(address => mapping(address => euint64)) internal allowances; - // Constructor to initialize the token's name and symbol, and set up the owner + /// @notice Constructor to initialize the token's name and symbol, and set up the owner + /// @param name_ The name of the token + /// @param symbol_ The symbol of the token constructor(string memory name_, string memory symbol_) Ownable(msg.sender) { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); // Set up the FHEVM configuration for this contract _name = name_; _symbol = symbol_; } - // Returns the name of the token. + /// @notice Returns the name of the token. function name() public view virtual returns (string memory) { return _name; } - // Returns the symbol of the token, usually a shorter version of the name. + /// @notice Returns the symbol of the token, usually a shorter version of the name. function symbol() public view virtual returns (string memory) { return _symbol; } - // Returns the total supply of the token + /// @notice Returns the total supply of the token function totalSupply() public view virtual returns (uint64) { return _totalSupply; } - // Mints new tokens and assigns them to the owner, increasing the total supply. - // Only the contract owner can call this function. + /// @notice Mints new tokens and assigns them to the owner, increasing the total supply. + /// @dev Only the contract owner can call this function. + /// @param mintedAmount The amount of tokens to mint function mint(uint64 mintedAmount) public virtual onlyOwner { balances[owner()] = TFHE.add(balances[owner()], mintedAmount); // overflow impossible because of next line TFHE.allowThis(balances[owner()]); @@ -57,33 +65,49 @@ contract EncryptedERC20 is Ownable2Step { emit Mint(owner(), mintedAmount); } - // Transfers an encrypted amount from the message sender address to the `to` address. + /// @notice Transfers an encrypted amount from the message sender address to the `to` address. + /// @param to The recipient address + /// @param encryptedAmount The encrypted amount to transfer + /// @param inputProof The proof for the encrypted input + /// @return bool indicating success of the transfer function transfer(address to, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) { transfer(to, TFHE.asEuint64(encryptedAmount, inputProof)); return true; } - // Transfers an encrypted amount from the message sender address to the `to` address. + /// @notice Transfers an encrypted amount from the message sender address to the `to` address. + /// @param to The recipient address + /// @param amount The encrypted amount to transfer + /// @return bool indicating success of the transfer function transfer(address to, euint64 amount) public virtual returns (bool) { require(TFHE.isSenderAllowed(amount)); - // makes sure the owner has enough tokens + /// @dev Makes sure the owner has enough tokens ebool canTransfer = TFHE.le(amount, balances[msg.sender]); _transfer(msg.sender, to, amount, canTransfer); return true; } - // Returns the balance handle (encrypted) of a specific address. + /// @notice Returns the balance handle (encrypted) of a specific address. + /// @param wallet The address to check the balance of + /// @return euint64 The encrypted balance of the address function balanceOf(address wallet) public view virtual returns (euint64) { return balances[wallet]; } - // Sets the allowance of `spender` to use a specific encrypted amount of the caller's tokens. + /// @notice Sets the allowance of `spender` to use a specific encrypted amount of the caller's tokens. + /// @param spender The address authorized to spend + /// @param encryptedAmount The encrypted amount to approve + /// @param inputProof The proof for the encrypted input + /// @return bool indicating success of the approval function approve(address spender, einput encryptedAmount, bytes calldata inputProof) public virtual returns (bool) { approve(spender, TFHE.asEuint64(encryptedAmount, inputProof)); return true; } - // Sets the allowance of `spender` to use a specific amount of the caller's tokens. + /// @notice Sets the allowance of `spender` to use a specific amount of the caller's tokens. + /// @param spender The address authorized to spend + /// @param amount The amount to approve + /// @return bool indicating success of the approval function approve(address spender, euint64 amount) public virtual returns (bool) { require(TFHE.isSenderAllowed(amount)); address owner = msg.sender; @@ -92,13 +116,20 @@ contract EncryptedERC20 is Ownable2Step { return true; } - // Returns the remaining number of tokens that `spender` is allowed to spend - // on behalf of the caller. + /// @notice Returns the remaining number of tokens that `spender` is allowed to spend on behalf of the caller. + /// @param owner The address that owns the tokens + /// @param spender The address authorized to spend + /// @return euint64 The remaining allowance function allowance(address owner, address spender) public view virtual returns (euint64) { return _allowance(owner, spender); } - // Transfers `encryptedAmount` tokens using the caller's allowance. + /// @notice Transfers `encryptedAmount` tokens using the caller's allowance. + /// @param from The address to transfer from + /// @param to The address to transfer to + /// @param encryptedAmount The encrypted amount to transfer + /// @param inputProof The proof for the encrypted input + /// @return bool indicating success of the transfer function transferFrom( address from, address to, @@ -109,7 +140,11 @@ contract EncryptedERC20 is Ownable2Step { return true; } - // Transfers `amount` tokens using the caller's allowance. + /// @notice Transfers `amount` tokens using the caller's allowance. + /// @param from The address to transfer from + /// @param to The address to transfer to + /// @param amount The amount to transfer + /// @return bool indicating success of the transfer function transferFrom(address from, address to, euint64 amount) public virtual returns (bool) { require(TFHE.isSenderAllowed(amount)); address spender = msg.sender; @@ -118,7 +153,11 @@ contract EncryptedERC20 is Ownable2Step { return true; } - // Internal function to approve a spender to use a specific amount. + /// @notice Internal function to approve a spender to use a specific amount. + /// @dev Updates the allowance mapping and sets appropriate permissions + /// @param owner The address that owns the tokens + /// @param spender The address authorized to spend + /// @param amount The amount to approve function _approve(address owner, address spender, euint64 amount) internal virtual { allowances[owner][spender] = amount; TFHE.allowThis(amount); @@ -126,26 +165,39 @@ contract EncryptedERC20 is Ownable2Step { TFHE.allow(amount, spender); } - // Returns the internal allowance of a spender for a specific owner. + /// @notice Returns the internal allowance of a spender for a specific owner. + /// @param owner The address that owns the tokens + /// @param spender The address authorized to spend + /// @return euint64 The current allowance function _allowance(address owner, address spender) internal view virtual returns (euint64) { return allowances[owner][spender]; } - // Updates the allowance after a transfer and returns whether it is valid. + /// @notice Updates the allowance after a transfer and returns whether it is valid. + /// @dev Checks if the transfer is allowed based on current allowance and balance + /// @param owner The address that owns the tokens + /// @param spender The address authorized to spend + /// @param amount The amount of the proposed transfer + /// @return ebool indicating whether the transfer is allowed function _updateAllowance(address owner, address spender, euint64 amount) internal virtual returns (ebool) { euint64 currentAllowance = _allowance(owner, spender); - // makes sure the allowance suffices + /// @dev Makes sure the allowance suffices ebool allowedTransfer = TFHE.le(amount, currentAllowance); - // makes sure the owner has enough tokens + /// @dev Makes sure the owner has enough tokens ebool canTransfer = TFHE.le(amount, balances[owner]); ebool isTransferable = TFHE.and(canTransfer, allowedTransfer); _approve(owner, spender, TFHE.select(isTransferable, TFHE.sub(currentAllowance, amount), currentAllowance)); return isTransferable; } - // Internal function to handle the transfer of tokens between addresses. + /// @notice Internal function to handle the transfer of tokens between addresses. + /// @dev Updates balances and sets appropriate permissions + /// @param from The address to transfer from + /// @param to The address to transfer to + /// @param amount The amount to transfer + /// @param isTransferable Boolean indicating if the transfer is allowed function _transfer(address from, address to, euint64 amount, ebool isTransferable) internal virtual { - // Add to the balance of `to` and subract from the balance of `from`. + /// @dev Add to the balance of `to` and subract from the balance of `from`. euint64 transferValue = TFHE.select(isTransferable, amount, TFHE.asEuint64(0)); euint64 newBalanceTo = TFHE.add(balances[to], transferValue); balances[to] = newBalanceTo; diff --git a/examples/PaymentLimit.sol b/examples/PaymentLimit.sol index 85d9adff..d484ca06 100644 --- a/examples/PaymentLimit.sol +++ b/examples/PaymentLimit.sol @@ -6,14 +6,19 @@ import "../lib/TFHE.sol"; import "@openzeppelin/contracts/access/Ownable2Step.sol"; import "../payment/Payment.sol"; +/// @title PaymentLimit +/// @notice A contract to demonstrate FHE gas limits in different scenarios contract PaymentLimit { + /// @notice Constructor that sets up FHE configuration and deposits initial value + /// @dev Payable to allow initial deposit constructor() payable { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); Payment.depositForThis(msg.value); } + /// @notice Performs a small number of FHE operations + /// @dev Should pass if it's the only transaction in a block function wayunderBlockFHEGasLimit() external { - // should pass if only tx in block euint64 x = TFHE.asEuint64(2); euint64 result; for (uint256 i; i < 3; i++) { @@ -21,8 +26,9 @@ contract PaymentLimit { } } + /// @notice Performs a moderate number of FHE operations + /// @dev Should pass if it's the only transaction in a block function underBlockFHEGasLimit() external { - // should pass if only tx in block euint64 x = TFHE.asEuint64(2); euint64 result; for (uint256 i; i < 15; i++) { @@ -30,8 +36,9 @@ contract PaymentLimit { } } + /// @notice Performs a large number of FHE operations + /// @dev Should revert due to exceeding the block FHE gas limit function aboveBlockFHEGasLimit() external { - // should revert due to exceeding block fheGas limit euint64 x = TFHE.asEuint64(2); euint64 result; for (uint256 i; i < 16; i++) { diff --git a/examples/README.md b/examples/README.md index 6977b932..fa611c4d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,102 +1,32 @@ -> Here will go all information about specific contracts, how are they designed and what are the considerations +# FhEVM smart contract examples -### Overview of Encrypted and Decrypted Data Flow +This directory contains example contracts that demonstrate the usage of the fhEVM (Fully Homomorphic Encryption Virtual Machine) smart contract library. These contracts showcase various features and functionalities of encrypted computations on the blockchain, enabling privacy-preserving operations and opening up new possibilities for confidential blockchain applications. -```mermaid -graph TD - A[User's Plaintext Input] -->|Encryption| B(Encrypted Input) - B -->|Stored in Contract| C{Encrypted State Variables} - - %% Using encrypted data in the contract %% - C -->|Operations on Encrypted Data| D[Contract Logic and Functions] - D -->|Maintain Confidentiality| C - - %% Encryption and Decryption operations %% - D -->|Decrypt when Necessary| E[Decrypted Values for Computations] - E -->|Logic/Verification| D -``` - -[needs work] -```mermaid -graph TD - %% User Interaction %% - subgraph User Actions - U[User] -->|1. Encrypted Input| A[User's Plaintext Input] - U -->|2. Request Re-encryption| F[Direct Re-encryption Request] - U -->|3. Trigger Decryption| K[Decryption Request] - end +## Quick overview - %% Encryption Flow %% - A -->|Sends Input to Contract| B(Encrypted Input) - B -->|Stored in Contract| C{Encrypted State Variables} +| Contract Name | Description | +| -------------------- | ---------------------------------------- | +| EncryptedERC20.sol | ERC20-like token with encrypted balances | +| TestAsyncDecrypt.sol | Asynchronous decryption testing | +| BlindAuction.sol | Blind auction using encrypted bids | - %% Using encrypted data in the contract %% - C -->|Operations on Encrypted Data| D[Contract Logic and Functions] - D -->|Maintain Confidentiality| C +## Usage - %% Decryption operations %% - D -->|Decrypt when Necessary| E[Decrypted Values for Computations] - E -->|Logic/Verification| D - K -->|Decrypt Request to Contract| C - C -->|Forwards Request to| G[Gateway Service] - G -->|Return Decrypted Value| E - G <--> N{KMS Service} +These contracts serve as examples and can be used as references when building your own fhEVM-compatible smart contracts. Make sure to have the necessary fhEVM library and dependencies set up in your development environment. - %% Re-encryption Flow %% - F -->|User Calls Gateway Directly| G - G -->|Re-encrypt with dApp's Public Key| J[Re-encrypted Data] - J -->|Return to User| M{User's Encrypted Data} - M -->|User Sends to Contract| C +For more information, refer to the [fhEVM documentation](https://docs.zama.ai/fhevm). -``` - -```mermaid -graph TD - %% User Interaction %% - subgraph User Actions - U[User] -->|1. Encrypted Input| A[User's Plaintext Input] - U -->|2. Request Re-encryption| F[Direct Re-encryption Request] - U -->|3. Trigger Decryption| K[Decryption Request] - end - - %% Smart Contract Operations %% - subgraph Smart Contract Operations - C{Encrypted State Variables} -->|Operations on Encrypted Data| D[Contract Logic and Functions] - D -->|Maintain Confidentiality| C - A -->|Sends Input to Contract| B(Encrypted Input) - B -->|Stored in Contract| C - K -->|Decrypt Request to Contract| C - C -->|Forwards Decrypt Request to| GC[Gateway Call from Contract] - J -->|Return to User| M{User's Encrypted Data} - %% Contract Decryption Flow %% - GC -->|Return Decrypted Data to Contract| E[Decrypted Values for Computations] - E -->|Logic/Verification| D - - end +## Contract summaries - GC -->|Forward Request to| G[Gateway Service] - G -->|Return Decrypted Value| GC - G <--> N{KMS Service} - F -->|User Calls Gateway Directly| G - G -->|Re-encrypt with dApp's Public Key| J[Re-encrypted Data] +### 1. **EncryptedERC20.sol** - %% Re-encrypted Data Flow %% - M -->|User Sends to Contract| C +An implementation of an ERC20-like token with encrypted balances and transfers. This contract demonstrates: +- Encrypted token balances +- Private transfer operations +- Allowance management with encryption - - - - 3. `Counter.sol`: - The `Counter` smart contract is a simple contract implemented in Solidity, designed to demonstrate basic state manipulation. Its main purpose is to maintain a counter (value) and provide functions to increment and view the current counter value. - - 1. `EncryptedERC20.sol`: - This contract implements an encrypted ERC20-like token with confidential balances using Zama's FHE (Fully Homomorphic Encryption) library. - It supports typical ERC20 functionality such as transferring tokens, minting, and setting allowances, but uses encrypted data types. - -### Approval and Transfer Operations - -Here's a high-level overview of what is encrypted and decrypted in the `EncryptedERC20` smart contract: +It showcases how traditional token systems can be made confidential using FHE techniques, allowing for private balance management on a public blockchain. ```mermaid graph TD @@ -115,40 +45,40 @@ graph TD Y1 --> Y3 ``` -1. `TestAsyncDecrypt.sol`: - A contract for testing asynchronous decryption using the Gateway. It handles various encrypted data types and demonstrates different decryption scenarios, including trustless decryption. - -2. `FHEPaymentUpgradedExample.sol`: - An upgraded version of the FHEPayment contract, adding version information. - -4. `BlindAuction.sol`: - Implements a blind auction using encrypted bids. It manages bidding, claiming, and withdrawing processes using homomorphic encryption. +### 2. **TestAsyncDecrypt.sol** -5. `ACLUpgradedExample.sol`: - An upgraded version of the ACL (Access Control List) contract, adding version information. +Tests asynchronous decryption of various encrypted data types using the Gateway. This contract is essential for understanding how to safely decrypt data when needed, without compromising the overall security of the encrypted system. -6. `KMSVerifierUpgradedExample.sol`: - An upgraded version of the KMSVerifier contract, adding version information. +### 3. **BlindAuction.sol** -7. `ACLUpgradedExample2.sol`: - Another upgraded version of the ACL contract, with a different version number. +Implements a blind auction system using encrypted bids. Key features include: -8. `TFHEExecutorUpgradedExample.sol`: - An upgraded version of the TFHEExecutor contract, adding version information. +- Encrypted bid submission +- Timed auction periods +- Winner determination without revealing losing bids +- Claim and withdrawal mechanisms -9. `GatewayContractUpgradedExample.sol`: - An upgraded version of the GatewayContract, adding version information. +This contract showcases how FHE can be used to create fair and private auction systems on the blockchain, ensuring bid confidentiality until the auction ends. +```mermaid +graph TD + subgraph Bidding Phase + A[User Submits Encrypted Bid] + B[Contract Stores Encrypted Bid] + C[Update Highest Bid & Winning Ticket] + end + subgraph Auction End + D[Decrypt Winning Ticket] + E[Winner Claims Prize] + F[Non-Winners Withdraw Bids] + G[Transfer Highest Bid to Beneficiary] + end + A --> B + B --> C + C --> |Auction Ends| D + D --> E + D --> F + D --> G +``` -2. `PaymentLimit.sol`: - A contract designed to test FHE gas limits. It includes functions that perform different numbers of FHE operations to test various scenarios: well under the block FHE gas limit, close to the limit, and exceeding the limit. - -3. `KMSVerifierUpgradedExample.sol`: - An upgraded version of the KMSVerifier contract, adding version information. This contract is likely part of a key management system for the FHE operations. - -4. `TracingSubCalls.sol`: - A set of contracts designed to test various subcall scenarios in a blockchain environment. It includes: - - A main contract that initiates different types of subcalls - - Contracts that test creation with encrypted inputs - - A contract with various functions to test success, failure, out-of-gas, and self-destruct scenarios - +This diagram illustrates the main processes in the BlindAuction contract, from bid submission to the final distribution of funds and prizes. diff --git a/examples/Rand.sol b/examples/Rand.sol index eb8e8dae..214dd07b 100644 --- a/examples/Rand.sol +++ b/examples/Rand.sol @@ -4,69 +4,74 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; -// Contract for generating random encrypted numbers +/// @notice Contract for generating random encrypted numbers contract Rand { - // Encrypted unsigned integers of various sizes + /// @notice Encrypted unsigned integers of various sizes euint8 public value8; euint16 public value16; euint32 public value32; euint64 public value64; euint64 public value64Bounded; - // Constructor to set FHE configuration + /// @notice Constructor to set FHE configuration constructor() { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); } - // Generate random 8-bit encrypted unsigned integer + /// @notice Generate random 8-bit encrypted unsigned integer function generate8() public { value8 = TFHE.randEuint8(); TFHE.allowThis(value8); } - // Generate random 8-bit encrypted unsigned integer with upper bound + /// @notice Generate random 8-bit encrypted unsigned integer with upper bound + /// @param upperBound The maximum value (exclusive) for the generated number function generate8UpperBound(uint8 upperBound) public { value8 = TFHE.randEuint8(upperBound); TFHE.allowThis(value8); } - // Generate random 16-bit encrypted unsigned integer + /// @notice Generate random 16-bit encrypted unsigned integer function generate16() public { value16 = TFHE.randEuint16(); TFHE.allowThis(value16); } - // Generate random 16-bit encrypted unsigned integer with upper bound + /// @notice Generate random 16-bit encrypted unsigned integer with upper bound + /// @param upperBound The maximum value (exclusive) for the generated number function generate16UpperBound(uint16 upperBound) public { value16 = TFHE.randEuint16(upperBound); TFHE.allowThis(value16); } - // Generate random 32-bit encrypted unsigned integer + /// @notice Generate random 32-bit encrypted unsigned integer function generate32() public { value32 = TFHE.randEuint32(); TFHE.allowThis(value32); } - // Generate random 32-bit encrypted unsigned integer with upper bound + /// @notice Generate random 32-bit encrypted unsigned integer with upper bound + /// @param upperBound The maximum value (exclusive) for the generated number function generate32UpperBound(uint32 upperBound) public { value32 = TFHE.randEuint32(upperBound); TFHE.allowThis(value32); } - // Generate random 64-bit encrypted unsigned integer + /// @notice Generate random 64-bit encrypted unsigned integer function generate64() public { value64 = TFHE.randEuint64(); TFHE.allowThis(value64); } - // Generate random 64-bit encrypted unsigned integer with upper bound + /// @notice Generate random 64-bit encrypted unsigned integer with upper bound + /// @param upperBound The maximum value (exclusive) for the generated number function generate64UpperBound(uint32 upperBound) public { value64 = TFHE.randEuint64(upperBound); TFHE.allowThis(value64); } - // Generate random 64-bit encrypted unsigned integer with error handling + /// @notice Generate random 64-bit encrypted unsigned integer with error handling + /// @dev This function attempts a failing call and then generates a bounded random number function generate64Reverting() public { try this.failingCall() {} catch {} value64Bounded = TFHE.randEuint64(1024); diff --git a/examples/Reencrypt.sol b/examples/Reencrypt.sol index 957bc360..309cbb4b 100644 --- a/examples/Reencrypt.sol +++ b/examples/Reencrypt.sol @@ -4,26 +4,26 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; -// Contract for demonstrating reencryption of various FHE data types +/// @notice Contract for demonstrating reencryption of various FHE data types contract Reencrypt { - // Encrypted boolean + /// @dev Encrypted boolean ebool public xBool; - // Encrypted 4-bit unsigned integer + /// @dev Encrypted 4-bit unsigned integer euint4 public xUint4; - // Encrypted 8-bit unsigned integer + /// @dev Encrypted 8-bit unsigned integer euint8 public xUint8; - // Encrypted 16-bit unsigned integer + /// @dev Encrypted 16-bit unsigned integer euint16 public xUint16; - // Encrypted 32-bit unsigned integer + /// @dev Encrypted 32-bit unsigned integer euint32 public xUint32; - // Encrypted 64-bit unsigned integer + /// @dev Encrypted 64-bit unsigned integer euint64 public xUint64; - // Encrypted Ethereum address + /// @dev Encrypted Ethereum address eaddress public xAddress; - // Encrypted 256-bit bytes + /// @dev Encrypted 256-bit bytes ebytes256 public yBytes256; - // Constructor to initialize encrypted values and set permissions + /// @notice Constructor to initialize encrypted values and set permissions constructor() { // Set default FHE configuration TFHE.setFHEVM(FHEVMConfig.defaultConfig()); @@ -64,7 +64,9 @@ contract Reencrypt { TFHE.allow(xAddress, msg.sender); } - // Function to set and allow access to encrypted 256-bit bytes + /// @notice Function to set and allow access to encrypted 256-bit bytes + /// @param inputHandleEBytes256 The input handle for the encrypted bytes256 + /// @param inputProofEBytes256 The input proof for the encrypted bytes256 function setEBytes256(einput inputHandleEBytes256, bytes memory inputProofEBytes256) external { yBytes256 = TFHE.asEbytes256(inputHandleEBytes256, inputProofEBytes256); TFHE.allowThis(yBytes256); diff --git a/examples/Regression1.sol b/examples/Regression1.sol index d893869b..2bf26a2f 100644 --- a/examples/Regression1.sol +++ b/examples/Regression1.sol @@ -1,31 +1,31 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -// Contract for managing services and metadata +/// @notice Contract for managing services and metadata contract Regression1 { error IndexOutOfBound(); - // Struct to store metadata information + /// @dev Struct to store metadata information struct Metadata { uint256 created; uint256 lastUpdated; uint256 versionId; } - // Struct to represent a service + /// @dev Struct to represent a service struct Service { bytes32 id; string serviceType; string serviceEndpoint; } - // Mapping to store metadata for each address + /// @notice Mapping to store metadata for each address mapping(address id => Metadata) public metadata; - // Mapping to store services for each address + /// @dev Mapping to store services for each address mapping(address id => Service[] service) private _services; - // Function to add services + /// @notice Function to add services function addServices(Service[] calldata services) public { for (uint256 i = 0; i < services.length; i++) { _services[msg.sender].push(services[i]); @@ -35,7 +35,7 @@ contract Regression1 { metadata[msg.sender].versionId = 1; } - // Function to remove a service + /// @notice Function to remove a service function removeService(uint256 serviceIndex) public { if (serviceIndex >= _services[msg.sender].length) revert IndexOutOfBound(); for (uint256 i = serviceIndex; i < _services[msg.sender].length - 1; i++) { @@ -46,7 +46,9 @@ contract Regression1 { metadata[msg.sender].versionId++; } - // Function to get services for a given address + /// @notice Function to get services for a given address + /// @param id The address to get services for + /// @return An array of Service structs function getServices(address id) public view returns (Service[] memory) { return _services[id]; } diff --git a/examples/SmartAccount.sol b/examples/SmartAccount.sol index 59cf71e4..9655dfc0 100644 --- a/examples/SmartAccount.sol +++ b/examples/SmartAccount.sol @@ -1,28 +1,28 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -// Import Ownable2Step from OpenZeppelin import "@openzeppelin/contracts/access/Ownable2Step.sol"; -// Smart account contract with batch transaction execution +/// @notice Smart account contract with batch transaction execution contract SmartAccount is Ownable2Step { - // Structure to represent a transaction + /// @dev Structure to represent a transaction struct Transaction { address target; uint256 value; bytes data; } - // Constructor to set the initial owner + /// @notice Constructor to set the initial owner constructor() Ownable(msg.sender) {} - // Function to execute multiple transactions in a batch + /// @notice Function to execute multiple transactions in a batch + /// @param transactions Array of transactions to execute function executeBatch(Transaction[] memory transactions) public payable onlyOwner { for (uint i = 0; i < transactions.length; i++) { Transaction memory transaction = transactions[i]; - // Execute the transaction + /// @dev Execute the transaction (bool success, ) = transaction.target.call{value: transaction.value}(transaction.data); - // Ensure the transaction was successful + /// @dev Ensure the transaction was successful require(success, "Transaction failed"); } } diff --git a/examples/TFHEExecutorUpgradedExample.sol b/examples/TFHEExecutorUpgradedExample.sol index f447b623..e0e83211 100644 --- a/examples/TFHEExecutorUpgradedExample.sol +++ b/examples/TFHEExecutorUpgradedExample.sol @@ -4,17 +4,20 @@ pragma solidity ^0.8.24; import "../lib/TFHEExecutor.sol"; -// Contract that extends TFHEExecutor with version information +/// @title TFHEExecutorUpgradedExample +/// @dev Contract that extends TFHEExecutor with version information contract TFHEExecutorUpgradedExample is TFHEExecutor { - // Name of the contract + /// @dev Name of the contract string private constant CONTRACT_NAME = "TFHEExecutor"; - // Version numbers + /// @dev Version numbers uint256 private constant MAJOR_VERSION = 0; uint256 private constant MINOR_VERSION = 2; uint256 private constant PATCH_VERSION = 0; - // Function to get the full version string of the contract + /// @notice Returns the full version string of the contract + /// @dev Concatenates the contract name and version numbers + /// @return A string representing the full version of the contract function getVersion() external pure virtual override returns (string memory) { return string( diff --git a/examples/TestAsyncDecrypt.sol b/examples/TestAsyncDecrypt.sol index f14efb90..277e3100 100644 --- a/examples/TestAsyncDecrypt.sol +++ b/examples/TestAsyncDecrypt.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; import "../gateway/GatewayCaller.sol"; -// Contract for testing asynchronous decryption using the Gateway +/// @notice Contract for testing asynchronous decryption using the Gateway contract TestAsyncDecrypt is GatewayCaller { - // Encrypted state variables + /// @dev Encrypted state variables ebool xBool; euint4 xUint4; euint8 xUint8; @@ -19,7 +19,7 @@ contract TestAsyncDecrypt is GatewayCaller { eaddress xAddress; eaddress xAddress2; - // Decrypted state variables + /// @dev Decrypted state variables bool public yBool; uint8 public yUint4; uint8 public yUint8; @@ -32,15 +32,15 @@ contract TestAsyncDecrypt is GatewayCaller { address public yAddress2; bytes public yBytes256; - // Tracks the latest decryption request ID + /// @dev Tracks the latest decryption request ID uint256 public latestRequestID; - // Constructor to initialize the contract and set up encrypted values + /// @notice Constructor to initialize the contract and set up encrypted values constructor() { TFHE.setFHEVM(FHEVMConfig.defaultConfig()); Gateway.setGateway(Gateway.defaultGatewayAddress()); - - // Initialize encrypted variables with sample values + + /// @dev Initialize encrypted variables with sample values xBool = TFHE.asEbool(true); TFHE.allowThis(xBool); xUint4 = TFHE.asEuint4(4); @@ -63,14 +63,14 @@ contract TestAsyncDecrypt is GatewayCaller { TFHE.allowThis(xAddress2); } - // Function to request decryption of a boolean value with an infinite loop in the callback + /// @notice Function to request decryption of a boolean value with an infinite loop in the callback function requestBoolInfinite() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); Gateway.requestDecryption(cts, this.callbackBoolInfinite.selector, 0, block.timestamp + 100, false); } - // Callback function for the infinite loop decryption request (WARNING: This function will never complete) + /// @notice Callback function for the infinite loop decryption request (WARNING: This function will never complete) function callbackBoolInfinite(uint256 /*requestID*/, bool decryptedInput) public onlyGateway returns (bool) { uint256 i = 0; while (true) { @@ -80,27 +80,27 @@ contract TestAsyncDecrypt is GatewayCaller { return yBool; } - // Function to request decryption with an excessive delay (should revert) + /// @notice Function to request decryption with an excessive delay (should revert) function requestBoolAboveDelay() public { - // This should revert due to the excessive delay + /// @dev This should revert due to the excessive delay uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 2 days, false); } - // Request decryption of a boolean value + /// @notice Request decryption of a boolean value function requestBool() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); - // Request decryption with a 100-second deadline and non-trustless mode + /// @dev Request decryption with a 100-second deadline and non-trustless mode Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100, false); } - // Request decryption of a boolean value in trustless mode + /// @notice Request decryption of a boolean value in trustless mode function requestBoolTrustless() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xBool); - // Request decryption with a 100-second deadline and trustless mode (true) + /// @dev Request decryption with a 100-second deadline and trustless mode (true) uint256 requestID = Gateway.requestDecryption( cts, this.callbackBoolTrustless.selector, @@ -109,102 +109,114 @@ contract TestAsyncDecrypt is GatewayCaller { true ); latestRequestID = requestID; - // Save the requested handles for later verification + /// @dev Save the requested handles for later verification saveRequestedHandles(requestID, cts); } - // Attempt to request decryption of a fake boolean value (should revert) + /// @notice Attempt to request decryption of a fake boolean value (should revert) function requestFakeBool() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000000); - // This should revert because the previous ebool is not honestly obtained + /// @dev This should revert because the previous ebool is not honestly obtained Gateway.requestDecryption(cts, this.callbackBool.selector, 0, block.timestamp + 100, false); } - // Callback function for non-trustless boolean decryption + /// @notice Callback function for non-trustless boolean decryption function callbackBool(uint256, bool decryptedInput) public onlyGateway returns (bool) { yBool = decryptedInput; return yBool; } - // Callback function for trustless boolean decryption + /// @notice Callback function for trustless boolean decryption function callbackBoolTrustless( uint256 requestID, bool decryptedInput, bytes[] memory signatures ) public onlyGateway returns (bool) { - // Verify that the requestID matches the latest request + /// @dev Verify that the requestID matches the latest request require(latestRequestID == requestID, "wrong requestID passed by Gateway"); - // Load the previously saved handles for verification + /// @dev Load the previously saved handles for verification uint256[] memory requestedHandles = loadRequestedHandles(latestRequestID); - // Verify the signatures provided by the KMS (Key Management Service) + /// @dev Verify the signatures provided by the KMS (Key Management Service) bool isKMSVerified = Gateway.verifySignatures(requestedHandles, signatures); require(isKMSVerified, "KMS did not verify this decryption result"); - // If verification passes, store the decrypted value + /// @dev If verification passes, store the decrypted value yBool = decryptedInput; return yBool; } - // Function to request decryption of a 4-bit unsigned integer + + /// @notice Request decryption of a 4-bit unsigned integer function requestUint4() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint4); Gateway.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100, false); } - // Function to attempt requesting decryption of a fake 4-bit unsigned integer (should revert) + /// @notice Attempt to request decryption of a fake 4-bit unsigned integer (should revert) function requestFakeUint4() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000100); - Gateway.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint4.selector, 0, block.timestamp + 100, false); } - // Callback function for 4-bit unsigned integer decryption + /// @notice Callback function for 4-bit unsigned integer decryption + /// @param decryptedInput The decrypted 4-bit unsigned integer + /// @return The decrypted value function callbackUint4(uint256, uint8 decryptedInput) public onlyGateway returns (uint8) { yUint4 = decryptedInput; return decryptedInput; } - // Function to request decryption of an 8-bit unsigned integer + /// @notice Request decryption of an 8-bit unsigned integer function requestUint8() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint8); Gateway.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100, false); } - // Function to attempt requesting decryption of a fake 8-bit unsigned integer (should revert) + /// @notice Attempt to request decryption of a fake 8-bit unsigned integer (should revert) function requestFakeUint8() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000200); - Gateway.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint8.selector, 0, block.timestamp + 100, false); } - // Callback function for 8-bit unsigned integer decryption + /// @notice Callback function for 8-bit unsigned integer decryption + /// @param decryptedInput The decrypted 8-bit unsigned integer + /// @return The decrypted value function callbackUint8(uint256, uint8 decryptedInput) public onlyGateway returns (uint8) { yUint8 = decryptedInput; return decryptedInput; } - // Function to request decryption of a 16-bit unsigned integer + /// @notice Request decryption of a 16-bit unsigned integer function requestUint16() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint16); Gateway.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100, false); } - // Function to attempt requesting decryption of a fake 16-bit unsigned integer (should revert) + /// @notice Attempt to request decryption of a fake 16-bit unsigned integer (should revert) function requestFakeUint16() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000300); - Gateway.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint16.selector, 0, block.timestamp + 100, false); } - // Callback function for 16-bit unsigned integer decryption + /// @notice Callback function for 16-bit unsigned integer decryption + /// @param decryptedInput The decrypted 16-bit unsigned integer + /// @return The decrypted value function callbackUint16(uint256, uint16 decryptedInput) public onlyGateway returns (uint16) { yUint16 = decryptedInput; return decryptedInput; } - // Function to request decryption of a 32-bit unsigned integer with additional inputs + /// @notice Request decryption of a 32-bit unsigned integer with additional inputs + /// @param input1 First additional input + /// @param input2 Second additional input function requestUint32(uint32 input1, uint32 input2) public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint32); @@ -219,14 +231,18 @@ contract TestAsyncDecrypt is GatewayCaller { addParamsUint256(requestID, input2); } - // Function to attempt requesting decryption of a fake 32-bit unsigned integer (should revert) + /// @notice Attempt to request decryption of a fake 32-bit unsigned integer (should revert) function requestFakeUint32() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000400); - Gateway.requestDecryption(cts, this.callbackUint32.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint32.selector, 0, block.timestamp + 100, false); } - // Callback function for 32-bit unsigned integer decryption + /// @notice Callback function for 32-bit unsigned integer decryption + /// @param requestID The ID of the decryption request + /// @param decryptedInput The decrypted 32-bit unsigned integer + /// @return The result of the computation function callbackUint32(uint256 requestID, uint32 decryptedInput) public onlyGateway returns (uint32) { uint256[] memory params = getParamsUint256(requestID); unchecked { @@ -236,21 +252,24 @@ contract TestAsyncDecrypt is GatewayCaller { } } - // Function to request decryption of a 64-bit unsigned integer + /// @notice Request decryption of a 64-bit unsigned integer function requestUint64() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint64); Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); } - // Function to attempt requesting decryption of a fake 64-bit unsigned integer (should revert) + /// @notice Attempt to request decryption of a fake 64-bit unsigned integer (should revert) function requestFakeUint64() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000500); - Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); } - // Function to request decryption of a non-trivial 64-bit unsigned integer + /// @notice Request decryption of a non-trivial 64-bit unsigned integer + /// @param inputHandle The input handle for the encrypted value + /// @param inputProof The input proof for the encrypted value function requestUint64NonTrivial(einput inputHandle, bytes calldata inputProof) public { euint64 inputNonTrivial = TFHE.asEuint64(inputHandle, inputProof); uint256[] memory cts = new uint256[](1); @@ -258,13 +277,17 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackUint64.selector, 0, block.timestamp + 100, false); } - // Callback function for 64-bit unsigned integer decryption + /// @notice Callback function for 64-bit unsigned integer decryption + /// @param decryptedInput The decrypted 64-bit unsigned integer + /// @return The decrypted value function callbackUint64(uint256, uint64 decryptedInput) public onlyGateway returns (uint64) { yUint64 = decryptedInput; return decryptedInput; } - // Function to request decryption of a non-trivial 256-bit encrypted bytes + /// @notice Request decryption of a non-trivial 256-bit encrypted bytes + /// @param inputHandle The input handle for the encrypted value + /// @param inputProof The input proof for the encrypted value function requestEbytes256NonTrivial(einput inputHandle, bytes calldata inputProof) public { ebytes256 inputNonTrivial = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](1); @@ -272,20 +295,22 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackBytes256.selector, 0, block.timestamp + 100, false); } - // Callback function for 256-bit encrypted bytes decryption + /// @notice Callback function for 256-bit encrypted bytes decryption + /// @param decryptedInput The decrypted 256-bit bytes + /// @return The decrypted value function callbackBytes256(uint256, bytes calldata decryptedInput) public onlyGateway returns (bytes memory) { yBytes256 = decryptedInput; return decryptedInput; } - // Function to request decryption of an encrypted address + /// @notice Request decryption of an encrypted address function requestAddress() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xAddress); Gateway.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100, false); } - // Function to request decryption of multiple encrypted addresses + /// @notice Request decryption of multiple encrypted addresses function requestSeveralAddresses() public { uint256[] memory cts = new uint256[](2); cts[0] = Gateway.toUint256(xAddress); @@ -293,7 +318,10 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackAddresses.selector, 0, block.timestamp + 100, false); } - // Callback function for multiple address decryption + /// @notice Callback function for multiple address decryption + /// @param decryptedInput1 The first decrypted address + /// @param decryptedInput2 The second decrypted address + /// @return The first decrypted address function callbackAddresses( uint256 /*requestID*/, address decryptedInput1, @@ -304,20 +332,26 @@ contract TestAsyncDecrypt is GatewayCaller { return decryptedInput1; } - // Function to attempt requesting decryption of a fake address (should revert) + /// @notice Attempt to request decryption of a fake address (should revert) function requestFakeAddress() public { uint256[] memory cts = new uint256[](1); cts[0] = uint256(0x4200000000000000000000000000000000000000000000000000000000000700); - Gateway.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100, false); // this should revert because previous handle is not honestly obtained + /// @dev This should revert because the previous handle is not honestly obtained + Gateway.requestDecryption(cts, this.callbackAddress.selector, 0, block.timestamp + 100, false); } - // Callback function for address decryption + /// @notice Callback function for address decryption + /// @param decryptedInput The decrypted address + /// @return The decrypted address function callbackAddress(uint256, address decryptedInput) public onlyGateway returns (address) { yAddress = decryptedInput; return decryptedInput; } - // Function to request decryption of mixed data types + /// @notice Request decryption of multiple encrypted data types + /// @dev This function demonstrates how to request decryption for various encrypted data types in a single call + /// @param input1 First additional input parameter for the callback function + /// @param input2 Second additional input parameter for the callback function function requestMixed(uint32 input1, uint32 input2) public { uint256[] memory cts = new uint256[](10); cts[0] = Gateway.toUint256(xBool); @@ -341,7 +375,20 @@ contract TestAsyncDecrypt is GatewayCaller { addParamsUint256(requestID, input2); } - // Callback function for mixed data type decryption + /// @notice Callback function for mixed data type decryption + /// @dev Processes the decrypted values and performs some basic checks + /// @param requestID The ID of the decryption request + /// @param decBool_1 First decrypted boolean + /// @param decBool_2 Second decrypted boolean + /// @param decUint4 Decrypted 4-bit unsigned integer + /// @param decUint8 Decrypted 8-bit unsigned integer + /// @param decUint16 Decrypted 16-bit unsigned integer + /// @param decUint32 Decrypted 32-bit unsigned integer + /// @param decUint64_1 First decrypted 64-bit unsigned integer + /// @param decUint64_2 Second decrypted 64-bit unsigned integer + /// @param decUint64_3 Third decrypted 64-bit unsigned integer + /// @param decAddress Decrypted address + /// @return The decrypted 4-bit unsigned integer function callbackMixed( uint256 requestID, bool decBool_1, @@ -371,7 +418,10 @@ contract TestAsyncDecrypt is GatewayCaller { return yUint4; } - // Function to request decryption of mixed data types including 256-bit encrypted bytes + /// @notice Request decryption of mixed data types including 256-bit encrypted bytes + /// @dev Demonstrates how to include encrypted bytes256 in a mixed decryption request + /// @param inputHandle The encrypted input handle for the bytes256 + /// @param inputProof The proof for the encrypted bytes256 function requestMixedBytes256(einput inputHandle, bytes calldata inputProof) public { ebytes256 xBytes256 = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](3); @@ -381,7 +431,11 @@ contract TestAsyncDecrypt is GatewayCaller { Gateway.requestDecryption(cts, this.callbackMixedBytes256.selector, 0, block.timestamp + 100, false); } - // Callback function for mixed data type decryption including 256-bit encrypted bytes + /// @notice Callback function for mixed data type decryption including 256-bit encrypted bytes + /// @dev Processes and stores the decrypted values + /// @param decBool Decrypted boolean + /// @param decAddress Decrypted address + /// @param bytesRes Decrypted 256-bit bytes function callbackMixedBytes256( uint256, bool decBool, @@ -393,7 +447,10 @@ contract TestAsyncDecrypt is GatewayCaller { yBytes256 = bytesRes; } - // Function to request trustless decryption of non-trivial 256-bit encrypted bytes + /// @notice Request trustless decryption of non-trivial 256-bit encrypted bytes + /// @dev Demonstrates how to request trustless decryption for complex encrypted bytes256 + /// @param inputHandle The encrypted input handle for the bytes256 + /// @param inputProof The proof for the encrypted bytes256 function requestEbytes256NonTrivialTrustless(einput inputHandle, bytes calldata inputProof) public { ebytes256 inputNonTrivial = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](1); @@ -409,7 +466,12 @@ contract TestAsyncDecrypt is GatewayCaller { saveRequestedHandles(requestID, cts); } - // Callback function for trustless decryption of 256-bit encrypted bytes + /// @notice Callback function for trustless decryption of 256-bit encrypted bytes + /// @dev Verifies the decryption result using KMS signatures + /// @param requestID The ID of the decryption request + /// @param decryptedInput The decrypted bytes256 value + /// @param signatures The signatures from the KMS for verification + /// @return The decrypted bytes256 value function callbackBytes256Trustless( uint256 requestID, bytes calldata decryptedInput, @@ -423,14 +485,16 @@ contract TestAsyncDecrypt is GatewayCaller { return decryptedInput; } - // Function to request trustless decryption of mixed data types including 256-bit encrypted bytes + /// @notice Request trustless decryption of mixed data types including 256-bit encrypted bytes + /// @dev Demonstrates how to request trustless decryption for multiple data types + /// @param inputHandle The encrypted input handle for the bytes256 + /// @param inputProof The proof for the encrypted bytes256 function requestMixedBytes256Trustless(einput inputHandle, bytes calldata inputProof) public { ebytes256 xBytes256 = TFHE.asEbytes256(inputHandle, inputProof); uint256[] memory cts = new uint256[](3); cts[0] = Gateway.toUint256(xBool); cts[1] = Gateway.toUint256(xBytes256); cts[2] = Gateway.toUint256(xAddress); - Gateway.requestDecryption(cts, this.callbackMixedBytes256Trustless.selector, 0, block.timestamp + 100, true); uint256 requestID = Gateway.requestDecryption( cts, this.callbackMixedBytes256Trustless.selector, @@ -442,7 +506,13 @@ contract TestAsyncDecrypt is GatewayCaller { saveRequestedHandles(requestID, cts); } - // Callback function for trustless decryption of mixed data types including 256-bit encrypted bytes + /// @notice Callback function for trustless decryption of mixed data types including 256-bit encrypted bytes + /// @dev Verifies and processes the decrypted values + /// @param requestID The ID of the decryption request + /// @param decBool Decrypted boolean + /// @param bytesRes Decrypted 256-bit bytes + /// @param decAddress Decrypted address + /// @param signatures The signatures from the KMS for verification function callbackMixedBytes256Trustless( uint256 requestID, bool decBool, @@ -458,4 +528,4 @@ contract TestAsyncDecrypt is GatewayCaller { yAddress = decAddress; yBytes256 = bytesRes; } -} \ No newline at end of file +} diff --git a/examples/TracingSubCalls.sol b/examples/TracingSubCalls.sol index 63647389..f1c2e587 100644 --- a/examples/TracingSubCalls.sol +++ b/examples/TracingSubCalls.sol @@ -3,84 +3,100 @@ pragma solidity ^0.8.24; import "../lib/TFHE.sol"; -// Main contract for testing various subcalls +/// @notice Main contract for testing various subcalls and their behaviors contract TracingSubCalls { + /// @notice Executes a series of subcalls to test different scenarios + /// @dev This function attempts various contract creations and function calls, + /// catching any errors to ensure the main execution continues function subCalls() external { - try new SubContractCreate(400) {} catch {} // 0K + try new SubContractCreate(400) {} catch {} // OK try new SubContractCreateFail(500) {} catch {} SubContract subc = new SubContract(); try subc.succeed() {} catch {} // OK 601 try subc.fail() {} catch {} try subc.succeedFail() {} catch {} // OK only for first 603 try subc.failSucceed() {} catch {} - try subc.oogFail{gas: 100000}() {} catch {} // this should fail out-of-gas + try subc.oogFail{gas: 100000}() {} catch {} // This should fail out-of-gas try subc.succeed2() {} catch {} // OK 608 try subc.invalidFail() {} catch {} try subc.succeedStop() {} catch {} // OK 610 try subc.succeedSelfDestruct() {} catch {} // OK 611 - try new SubContractCreate{salt: keccak256("aaa")}(700) {} catch {} // 0K 700 + try new SubContractCreate{salt: keccak256("aaa")}(700) {} catch {} // OK 700 try new SubContractCreateFail{salt: keccak256("aaa")}(800) {} catch {} } } -// Contract that creates a new instance with an encrypted input +/// @notice Contract that creates a new instance with an encrypted input contract SubContractCreate { + /// @dev Constructor that encrypts the input + /// @param input The value to be encrypted constructor(uint256 input) { TFHE.asEuint64(input); } } -// Contract that attempts to create a new instance but always fails +/// @notice Contract that attempts to create a new instance but always fails contract SubContractCreateFail { + /// @dev Constructor that encrypts the input and then fails + /// @param input The value to be encrypted before failing constructor(uint256 input) { TFHE.asEuint64(input); - require(false); + require(false, "This constructor always fails"); } } -// Contract with various test functions for success and failure scenarios +/// @notice Contract with various test functions for success and failure scenarios contract SubContract { - // Function that always succeeds + /// @notice Function that always succeeds + /// @dev Encrypts a specific value (601) function succeed() external { TFHE.asEuint64(601); } - // Function that always fails + /// @notice Function that always fails + /// @dev Encrypts a value (602) before failing function fail() external { TFHE.asEuint64(602); - require(false); + require(false, "This function always fails"); } - // Internal function that fails with a custom input + /// @notice Internal function that fails with a custom input + /// @dev Encrypts the input before failing + /// @param input The value to be encrypted before failing function fail2(uint input) external { TFHE.asEuint64(input); - require(false); + require(false, "This function always fails with custom input"); } - // Function that succeeds and then calls a failing function + /// @notice Function that succeeds and then calls a failing function + /// @dev Encrypts a value (603) and then attempts to call fail2 function succeedFail() external { TFHE.asEuint64(603); try this.fail2(604) {} catch {} } - // Function that attempts to fail and then succeed + /// @notice Function that attempts to fail and then succeed + /// @dev Calls fail2 and then attempts to encrypt a value (606) function failSucceed() external { this.fail2(605); TFHE.asEuint64(606); } - // Function that runs out of gas + /// @notice Function that runs out of gas + /// @dev Encrypts a value (607) and then enters an infinite loop function oogFail() external { TFHE.asEuint64(607); while (true) {} } - // Another function that always succeeds + /// @notice Another function that always succeeds + /// @dev Encrypts a specific value (608) function succeed2() external { TFHE.asEuint64(608); } - // Function that fails with an invalid operation + /// @notice Function that fails with an invalid operation + /// @dev Encrypts a value (609) and then executes an invalid operation function invalidFail() external { TFHE.asEuint64(609); assembly { @@ -88,7 +104,8 @@ contract SubContract { } } - // Function that succeeds and then stops execution + /// @notice Function that succeeds and then stops execution + /// @dev Encrypts a value (610) and then stops the execution function succeedStop() external { TFHE.asEuint64(610); assembly { @@ -96,7 +113,8 @@ contract SubContract { } } - // Function that succeeds and then self-destructs the contract + /// @notice Function that succeeds and then self-destructs the contract + /// @dev Encrypts a value (611) and then self-destructs the contract function succeedSelfDestruct() external { TFHE.asEuint64(611); selfdestruct(payable(address(1))); From c6536c1618ea223004963645d5bc728ffc877926 Mon Sep 17 00:00:00 2001 From: Aurora Poppyseed <30662672+poppyseedDev@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:28:08 +0200 Subject: [PATCH 7/7] docs: fix merge problems --- examples/Rand.sol | 4 +--- examples/TestAsyncDecrypt.sol | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/Rand.sol b/examples/Rand.sol index d416b29d..7593abbd 100644 --- a/examples/Rand.sol +++ b/examples/Rand.sol @@ -85,9 +85,7 @@ contract Rand { TFHE.allowThis(value64); } - /// @notice Generate random 64-bit encrypted unsigned integer with upper bound - /// @param upperBound The maximum value (exclusive) for the generated number - function generate64UpperBound(uint32 upperBound) public { + function generate64UpperBound(uint64 upperBound) public { value64 = TFHE.randEuint64(upperBound); TFHE.allowThis(value64); } diff --git a/examples/TestAsyncDecrypt.sol b/examples/TestAsyncDecrypt.sol index 617f3441..ce1fab68 100644 --- a/examples/TestAsyncDecrypt.sol +++ b/examples/TestAsyncDecrypt.sol @@ -295,9 +295,6 @@ contract TestAsyncDecrypt is GatewayCaller { return decryptedInput; } - /// @notice Request decryption of a non-trivial 256-bit encrypted bytes - /// @param inputHandle The input handle for the encrypted value - /// @param inputProof The input proof for the encrypted value function requestUint128() public { uint256[] memory cts = new uint256[](1); cts[0] = Gateway.toUint256(xUint128);