diff --git a/host-contracts/.env.example b/host-contracts/.env.example index 6944a8632..4e86d1df3 100644 --- a/host-contracts/.env.example +++ b/host-contracts/.env.example @@ -1,3 +1,4 @@ +ENV_HOST_ADDRESSES_PATH="addresses/.env.host" MNEMONIC="adapt mosquito move limb mobile illegal tree voyage juice mosquito burger raise father hope layer" CHAIN_ID_GATEWAY="654321" DEPLOYER_PRIVATE_KEY="7697c90f7863e6057fbe25674464e14b57f2c670b1a8ee0f60fb87eb9b615c4d" # account[5] diff --git a/host-contracts/.eslintrc.json b/host-contracts/.eslintrc.json new file mode 100644 index 000000000..c6f6d7333 --- /dev/null +++ b/host-contracts/.eslintrc.json @@ -0,0 +1,30 @@ +{ + "root": true, + "extends": ["eslint:recommended", "prettier"], + "env": { + "es2022": true, + "browser": true, + "node": true, + "mocha": true + }, + "globals": { + "artifacts": "readonly", + "contract": "readonly", + "web3": "readonly", + "extendEnvironment": "readonly", + "expect": "readonly" + }, + "overrides": [ + { + "files": ["*.ts"], + "excludedFiles": ["./dist/**/*.js"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"] + } + ] +} diff --git a/host-contracts/codegen.config.json b/host-contracts/codegen.config.json index 1dcc5735b..809d72cad 100644 --- a/host-contracts/codegen.config.json +++ b/host-contracts/codegen.config.json @@ -22,7 +22,7 @@ "imports": [ ["instance", "../instance"], ["signers", "../signers"], - ["typechain", "../../types/examples/tests"] + ["typechain", "../../typechain-types/examples/tests"] ] } } diff --git a/host-contracts/eslint.config.mjs b/host-contracts/eslint.config.mjs new file mode 100644 index 000000000..400996f3f --- /dev/null +++ b/host-contracts/eslint.config.mjs @@ -0,0 +1,133 @@ +import { FlatCompat } from "@eslint/eslintrc"; +import eslint from "@eslint/js"; +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; +import importPlugin from "eslint-plugin-import"; +import globals from "globals"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: eslint.configs.recommended, + allConfig: eslint.configs.all, +}); + +const config = [ + // 0 + { + ignores: ["typechain-types/*", "tmp/*", "coverage/*"], + }, + // 1 + ...compat.extends("eslint:recommended", "prettier"), + // 2 + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + ...globals.mocha, + artifacts: "readonly", + contract: "readonly", + web3: "readonly", + extendEnvironment: "readonly", + expect: "readonly", + }, + }, + }, + // 3 + ...compat + .extends( + "eslint:recommended", + "plugin:@typescript-eslint/strict", + "plugin:@typescript-eslint/strict-type-checked", + "prettier", + ) + .map((config) => ({ + ...config, + files: ["**/*.ts"], + rules: { + "@typescript-eslint/no-floating-promises": "error", + }, + })), + // 4 + { + files: ["**/*.ts"], + plugins: { + "@typescript-eslint": typescriptEslint, + }, + languageOptions: { + parser: tsParser, + parserOptions: { + project: "./tsconfig.json", + tsconfigRootDir: __dirname, + }, + ecmaVersion: "latest", + sourceType: "module", + }, + rules: { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + vars: "all", + args: "after-used", + ignoreRestSiblings: true, + varsIgnorePattern: "^_", + argsIgnorePattern: "^_", + }, + ], + }, + }, + // 5 - Restrict "Buffer" usage + { + files: ["./src/internal/libs/**/*.{js,ts,tsx}"], + rules: { + "no-restricted-globals": [ + "error", + { + name: "Buffer", + message: "Avoid using Buffer in internal/libs. Use a custom abstraction if needed.", + }, + ], + }, + }, + + // 6 - Enforce imports must be declared in dependencies + { + files: ["**/*.ts"], + plugins: { + import: importPlugin, + }, + rules: { + "import/no-extraneous-dependencies": [ + "error", + { + devDependencies: false, + optionalDependencies: false, + peerDependencies: true, + bundledDependencies: false, + }, + ], + }, + }, + + // 7 - Relax rule 6 for tests + { + files: ["hardhat.config.ts", "**/test/**/*.{ts,js}", "**/*.test.{ts,js}"], + rules: { + "import/no-extraneous-dependencies": [ + "error", + { + devDependencies: true, + optionalDependencies: false, + peerDependencies: true, + }, + ], + }, + }, +]; + +export default config; diff --git a/host-contracts/examples/TestInput.sol b/host-contracts/examples/TestInput.sol index 6fbc17a88..2fd586499 100644 --- a/host-contracts/examples/TestInput.sol +++ b/host-contracts/examples/TestInput.sol @@ -19,6 +19,19 @@ contract TestInput { _xUint64 = inputEuint64; } + function setPublicUint64(externalEuint64 inputHandle, bytes calldata inputProof) public { + euint64 inputEuint64 = FHE.fromExternal(inputHandle, inputProof); + FHE.allowThis(inputEuint64); + FHE.makePubliclyDecryptable(inputEuint64); + _xUint64 = inputEuint64; + } + + function checkPublicUint64(bytes memory abiEncodedCleartexts, bytes memory decryptionProof) public { + bytes32[] memory cts = new bytes32[](1); + cts[0] = FHE.toBytes32(_xUint64); + FHE.checkSignatures(cts, abiEncodedCleartexts, decryptionProof); + } + function getEuint64() public view returns (euint64) { return _xUint64; } diff --git a/host-contracts/examples/Reencrypt.sol b/host-contracts/examples/UserDecrypt.sol similarity index 95% rename from host-contracts/examples/Reencrypt.sol rename to host-contracts/examples/UserDecrypt.sol index 0a299e20f..f4e9cbf90 100644 --- a/host-contracts/examples/Reencrypt.sol +++ b/host-contracts/examples/UserDecrypt.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.24; import "../lib/FHE.sol"; import {CoprocessorSetup} from "../lib/CoprocessorSetup.sol"; -/// @notice Contract for demonstrating reencryption of various FHE data types -contract Reencrypt { +/// @notice Contract for demonstrating user decryption of various FHE data types +contract UserDecrypt { /// @dev Encrypted boolean ebool public xBool; /// @dev Encrypted 8-bit unsigned integer diff --git a/host-contracts/hardhat.config.ts b/host-contracts/hardhat.config.ts index 3dc36b587..04a6b70cb 100644 --- a/host-contracts/hardhat.config.ts +++ b/host-contracts/hardhat.config.ts @@ -149,7 +149,6 @@ const config: HardhatUserConfig = { 'examples/TracingSubCalls.sol': { default: 'off' }, }, typechain: { - outDir: 'types', target: 'ethers-v6', }, }; diff --git a/host-contracts/lib/FHE.sol b/host-contracts/lib/FHE.sol index 12ef09642..84f41347f 100644 --- a/host-contracts/lib/FHE.sol +++ b/host-contracts/lib/FHE.sol @@ -27,6 +27,9 @@ library FHE { /// @notice Returned if the returned KMS signatures are not valid. error InvalidKMSSignatures(); + /// @notice This event is emitted when public decryption has been successfully verified. + event PublicDecryptionVerified(bytes32[] handlesList, bytes abiEncodedCleartexts); + /** * @notice Sets the coprocessor addresses. * @param coprocessorConfig Coprocessor config struct that contains contract addresses. @@ -9072,24 +9075,57 @@ library FHE { expirationDate = Impl.getUserDecryptionDelegationExpirationDate(delegator, delegate, contractAddress); } - /** - * @dev Internal low-level function used to verify the KMS signatures. - * @notice Warning: MUST be called directly in the callback function called by the relayer. - * @notice Warning: this function never reverts, its boolean return value must be checked. - * @dev clearTexts is the abi-encoding of the list of all decrypted values assiociated to handlesList, in same order. - * @dev Only static native solidity types for clear values are supported, so clearTexts is the concatenation of all clear values appended to 32 bytes. - * @dev decryptionProof contains KMS signatures corresponding to clearTexts and associated handlesList, and needed metadata for KMS context. - **/ - function verifySignatures( + /// @notice Reverts if the KMS signatures verification against the provided handles and public decryption data + /// fails. + /// @dev The function MUST be called inside a public decryption callback function of a dApp contract + /// to verify the signatures and prevent fake decryption results for being submitted. + /// @param handlesList The list of handles as an array of bytes32 to check + /// @param abiEncodedCleartexts The ABI-encoded list of decrypted values associated with each handle in the `handlesList`. + /// The ABI-encoded list order must match the `handlesList` order. + /// @param decryptionProof The KMS public decryption proof. It includes the KMS signatures, associated metadata, + /// and the context needed for verification. + /// @dev Reverts if any of the following conditions are met: + /// - The `decryptionProof` is empty or has an invalid length. + /// - The number of valid signatures is zero or less than the configured KMS signers threshold. + /// - Any signature is produced by an address that is not a registered KMS signer. + /// - The signatures verification returns false. + function checkSignatures( + bytes32[] memory handlesList, + bytes memory abiEncodedCleartexts, + bytes memory decryptionProof + ) internal { + bool isVerified = _verifySignatures(handlesList, abiEncodedCleartexts, decryptionProof); + if (!isVerified) { + revert InvalidKMSSignatures(); + } + emit PublicDecryptionVerified(handlesList, abiEncodedCleartexts); + } + + /// @notice Verifies KMS signatures against the provided handles and public decryption data. + /// @param handlesList The list of handles as an array of bytes32 to verify + /// @param abiEncodedCleartexts The ABI-encoded list of decrypted values associated with each handle in the `handlesList`. + /// The list order must match the list of handles in `handlesList` + /// @param decryptionProof The KMS public decryption proof computed by the KMS Signers associated to `handlesList` and + /// `abiEncodedCleartexts` + /// @return true if the signatures verification succeeds, false otherwise + /// @dev Private low-level function used to verify the KMS signatures. + /// Warning: this function never reverts, its boolean return value must be checked. + /// The decryptionProof is the numSigners + kmsSignatures + extraData (1 + 65*numSigners + extraData bytes) + /// Only static native solidity types for clear values are supported, so `abiEncodedCleartexts` is the concatenation of all clear values appended to 32 bytes. + /// @dev Reverts if any of the following conditions are met by the underlying KMS verifier: + /// - The `decryptionProof` is empty or has an invalid length. + /// - The number of valid signatures is zero or less than the configured KMS signers threshold. + /// - Any signature is produced by an address that is not a registered KMS signer. + function _verifySignatures( bytes32[] memory handlesList, - bytes memory cleartexts, + bytes memory abiEncodedCleartexts, bytes memory decryptionProof - ) internal returns (bool) { + ) private returns (bool) { CoprocessorConfig storage $ = Impl.getCoprocessorConfig(); return IKMSVerifier($.KMSVerifierAddress).verifyDecryptionEIP712KMSSignatures( handlesList, - cleartexts, + abiEncodedCleartexts, decryptionProof ); } diff --git a/host-contracts/package.json b/host-contracts/package.json index 79256c87b..ab0f914a7 100644 --- a/host-contracts/package.json +++ b/host-contracts/package.json @@ -8,16 +8,21 @@ }, "main": "contracts/ACL.sol", "scripts": { - "compile": "HARDHAT_NETWORK=hardhat npm run deploy:emptyProxies && cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile", - "deploy:emptyProxies": "hardhat task:deployEmptyUUPSProxies && hardhat compile:specific --contract 'contracts/immutable' && hardhat task:deployPauserSet", + "addresses": "npm run deploy:emptyProxies", + "tsc": "tsc --project tsconfig.json --noEmit", + "clean": "rm -rf addresses dist build artifacts cache typechain-types", + "typechain": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat typechain", + "compile:examples": "cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile:specific --contract 'examples'", + "compile": "cross-env HARDHAT_NETWORK=hardhat npm run deploy:emptyProxies && cross-env TS_NODE_TRANSPILE_ONLY=true hardhat compile", + "deploy:emptyProxies": "hardhat task:deployEmptyUUPSProxies && hardhat compile:specific --contract 'contracts/immutable' && hardhat task:deployPauserSet", "lint": "npm run lint:sol && npm run lint:ts && npm run prettier:check", "lint:ts": "eslint --ignore-path ./.eslintignore --ext .js,.ts .", "prettier:check": "prettier --check \"**/*.{js,json,md,sol,ts,yml}\"", "prettier": "prettier --write \"**/*.{js,json,md,sol,ts,yml}\"", - "test": "hardhat test", "coverage": "SOLIDITY_COVERAGE=true hardhat coverage", "codegen": "../library-solidity/codegen/codegen.mjs lib --config ./codegen.config.json --verbose", "codegen:overloads": "../library-solidity/codegen/codegen.mjs overloads --config ./codegen.config.json --verbose --force", + "test": "hardhat test", "test:gas": "hardhat test --grep Gas", "test:forge": "forge test", "forge:soldeer": "forge soldeer install" @@ -47,8 +52,7 @@ "prettier-plugin-solidity": "^1.1.3", "sha3": "^2.1.4", "solidity-coverage": "^0.8.14", - "sqlite3": "^5.1.7", - "web3-validator": "^2.0.6" + "sqlite3": "^5.1.7" }, "optionalDependencies": { "solidity-comments-darwin-arm64": "0.1.1", diff --git a/host-contracts/tasks/taskUtils.ts b/host-contracts/tasks/taskUtils.ts index 4ea59afe7..9ef75f04a 100644 --- a/host-contracts/tasks/taskUtils.ts +++ b/host-contracts/tasks/taskUtils.ts @@ -3,7 +3,7 @@ import fs from 'fs'; import { task } from 'hardhat/config'; import type { TaskArguments } from 'hardhat/types'; -import { InputVerifier, KMSVerifier } from '../types'; +import type { InputVerifier, KMSVerifier } from '../typechain-types'; //////////////////////////////////////////////////////////////////////////////// // Faucet diff --git a/host-contracts/test/coprocessorUtils.ts b/host-contracts/test/coprocessorUtils.ts index a108a01ca..f4ceced98 100644 --- a/host-contracts/test/coprocessorUtils.ts +++ b/host-contracts/test/coprocessorUtils.ts @@ -1,21 +1,15 @@ -import { FheTypeInfo } from '@fhevm/solidity/lib-js/common'; +import type { FheTypeInfo } from '@fhevm/solidity/lib-js/common'; import { ALL_FHE_TYPE_INFOS } from '@fhevm/solidity/lib-js/fheTypeInfos'; import { ALL_OPERATORS_PRICES } from '@fhevm/solidity/lib-js/operatorsPrices'; -import dotenv from 'dotenv'; -import type { Log, Result, TransactionReceipt } from 'ethers'; +import type { ethers as EthersT } from 'ethers'; import { log2 } from 'extra-bigint'; -import * as fs from 'fs'; import { ethers } from 'hardhat'; import { Database } from 'sqlite3'; -const parsedEnvCoprocessor = dotenv.parse(fs.readFileSync('addresses/.env.host')); -const coprocAddress = parsedEnvCoprocessor.FHEVM_EXECUTOR_CONTRACT_ADDRESS; - let firstBlockListening = 0; let lastBlockSnapshot = 0; let lastCounterRand = 0; let counterRand = 0; -let chainId: number; //const db = new Database('./sql.db'); // on-disk db for debugging const db = new Database(':memory:'); @@ -31,7 +25,7 @@ export function insertSQL(handle: string, clearText: BigInt | string, replace: b // Decrypt any handle, bypassing ACL // WARNING : only for testing or internal use -export const getClearText = async (handle: string | bigint): Promise => { +export const getCoprocessorClearText = async (handle: string | bigint): Promise => { return new Promise((resolve, reject) => { let attempts = 0; const maxRetries = 100; @@ -59,7 +53,7 @@ db.serialize(() => db.run('CREATE TABLE IF NOT EXISTS ciphertexts (handle BINARY interface FHEVMEvent { eventName: string; - args: Result; + args: EthersT.Result; } const NumBits = { @@ -71,9 +65,6 @@ const NumBits = { 6: 128n, //euint128 7: 160n, //eaddress 8: 256n, //euint256 - 9: 512n, //ebytes64 - 10: 1024n, //ebytes128 - 11: 2048n, //ebytes256 }; export function numberToEvenHexString(num: number) { @@ -117,11 +108,6 @@ function bitwiseNotUintBits(value: BigInt, numBits: number) { return ~value & BIT_MASK; } -export const awaitCoprocessor = async (): Promise => { - chainId = Number((await ethers.provider.getNetwork()).chainId); - await processAllPastFHEVMExecutorEvents(); -}; - const abi = [ 'event FheAdd(address indexed caller, bytes32 lhs, bytes32 rhs, bytes1 scalarByte, bytes32 result)', 'event FheSub(address indexed caller, bytes32 lhs, bytes32 rhs, bytes1 scalarByte, bytes32 result)', @@ -156,7 +142,7 @@ const abi = [ 'event FheRandBounded(address indexed caller, uint256 upperBound, uint8 randType, bytes16 seed, bytes32 result)', ]; -async function processAllPastFHEVMExecutorEvents() { +export async function processAllPastFHEVMExecutorEvents(coprocessorContractAddress: `0x${string}`) { const provider = ethers.provider; const latestBlockNumber = await provider.getBlockNumber(); @@ -169,18 +155,18 @@ async function processAllPastFHEVMExecutorEvents() { } } - const contract = new ethers.Contract(coprocAddress, abi, provider); + const contract = new ethers.Contract(coprocessorContractAddress, abi, provider); // Fetch all events emitted by the contract const filter = { - address: coprocAddress, + address: coprocessorContractAddress, fromBlock: firstBlockListening, toBlock: latestBlockNumber, }; const logs = await provider.getLogs(filter); - const events = logs + const events: FHEVMEvent[] = logs .map((log) => { try { const parsedLog = contract.interface.parseLog(log); @@ -227,12 +213,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheAdd': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) + BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) + BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -243,13 +229,13 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheSub': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) - BigInt(event.args[2]); if (clearText < 0n) clearText = clearText + 2n ** NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) - BigInt(clearRHS); if (clearText < 0n) clearText = clearText + 2n ** NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; @@ -260,12 +246,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheMul': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) * BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) * BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -275,7 +261,7 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheDiv': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) / BigInt(event.args[2]); } else { @@ -287,7 +273,7 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheRem': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) % BigInt(event.args[2]); } else { @@ -299,12 +285,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheBitAnd': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) & BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) & BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -314,12 +300,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheBitOr': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) | BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) | BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -329,12 +315,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheBitXor': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) ^ BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) ^ BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -344,12 +330,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheShl': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) << BigInt(event.args[2]) % NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) << BigInt(clearRHS) % NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -359,12 +345,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheShr': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) >> BigInt(event.args[2]) % NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) >> BigInt(clearRHS) % NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -374,14 +360,14 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheRotl': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { shift = BigInt(event.args[2]) % NumBits[resultType as keyof typeof NumBits]; clearText = (BigInt(clearLHS) << shift) | (BigInt(clearLHS) >> (NumBits[resultType as keyof typeof NumBits] - shift)); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); shift = BigInt(clearRHS) % NumBits[resultType as keyof typeof NumBits]; clearText = (BigInt(clearLHS) << shift) | (BigInt(clearLHS) >> (NumBits[resultType as keyof typeof NumBits] - shift)); @@ -393,14 +379,14 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheRotr': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { shift = BigInt(event.args[2]) % NumBits[resultType as keyof typeof NumBits]; clearText = (BigInt(clearLHS) >> shift) | (BigInt(clearLHS) << (NumBits[resultType as keyof typeof NumBits] - shift)); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); shift = BigInt(clearRHS) % NumBits[resultType as keyof typeof NumBits]; clearText = (BigInt(clearLHS) >> shift) | (BigInt(clearLHS) << (NumBits[resultType as keyof typeof NumBits] - shift)); @@ -412,12 +398,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheEq': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) === BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = clearLHS === clearRHS ? 1n : 0n; } @@ -427,11 +413,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheEqBytes': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) === BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) === BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -440,11 +426,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheNe': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) !== BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) !== BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -453,11 +439,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheNeBytes': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) !== BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) !== BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -466,11 +452,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheGe': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) >= BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) >= BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -479,11 +465,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheGt': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) > BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) > BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -492,11 +478,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheLe': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) <= BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) <= BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -505,11 +491,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheLt': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) < BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) < BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -518,11 +504,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheMax': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) > BigInt(event.args[2]) ? clearLHS : BigInt(event.args[2]); } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) > BigInt(clearRHS) ? clearLHS : clearRHS; } insertSQL(handle, clearText); @@ -531,11 +517,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheMin': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) < BigInt(event.args[2]) ? clearLHS : BigInt(event.args[2]); } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) < BigInt(clearRHS) ? clearLHS : clearRHS; } insertSQL(handle, clearText); @@ -544,14 +530,15 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'Cast': resultType = parseInt(event.args[2]); handle = ethers.toBeHex(event.args[3], 32); - clearText = BigInt(await getClearText(event.args[1])) % 2n ** NumBits[resultType as keyof typeof NumBits]; + clearText = + BigInt(await getCoprocessorClearText(event.args[1])) % 2n ** NumBits[resultType as keyof typeof NumBits]; insertSQL(handle, clearText); break; case 'FheNot': handle = ethers.toBeHex(event.args[2], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearText = BigInt(await getClearText(event.args[1])); + clearText = BigInt(await getCoprocessorClearText(event.args[1])); clearText = bitwiseNotUintBits(clearText, Number(NumBits[resultType as keyof typeof NumBits])); insertSQL(handle, clearText); break; @@ -559,7 +546,7 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheNeg': handle = ethers.toBeHex(event.args[2], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearText = BigInt(await getClearText(event.args[1])); + clearText = BigInt(await getCoprocessorClearText(event.args[1])); clearText = bitwiseNotUintBits(clearText, Number(NumBits[resultType as keyof typeof NumBits])); clearText = (clearText + 1n) % 2n ** NumBits[resultType as keyof typeof NumBits]; insertSQL(handle, clearText); @@ -568,7 +555,7 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'VerifyInput': handle = event.args[1]; try { - await getClearText(BigInt(handle)); + await getCoprocessorClearText(BigInt(handle)); } catch { throw Error(`User input handle was not found in DB: ${handle}`); } @@ -578,9 +565,9 @@ async function insertHandleFromEvent(event: FHEVMEvent) { handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); handle = ethers.toBeHex(event.args[4], 32); - const clearControl = BigInt(await getClearText(event.args[1])); - const clearIfTrue = BigInt(await getClearText(event.args[2])); - const clearIfFalse = BigInt(await getClearText(event.args[3])); + const clearControl = BigInt(await getCoprocessorClearText(event.args[1])); + const clearIfTrue = BigInt(await getCoprocessorClearText(event.args[2])); + const clearIfFalse = BigInt(await getCoprocessorClearText(event.args[3])); if (clearControl === 1n) { clearText = clearIfTrue; } else { @@ -607,9 +594,10 @@ async function insertHandleFromEvent(event: FHEVMEvent) { } } -export function getTxHCUFromTxReceipt( - receipt: TransactionReceipt, - FheTypes: FheTypeInfo[] = ALL_FHE_TYPE_INFOS, +export function getTxHCUFromCoprocessorTxReceipt( + coprocessorContractAddress: `0x${string}`, + receipt: EthersT.TransactionReceipt, + FheTypeInfos: FheTypeInfo[] = ALL_FHE_TYPE_INFOS, ): { globalTxHCU: number; maxTxHCUDepth: number; @@ -629,30 +617,30 @@ export function getTxHCUFromTxReceipt( let hcuMap: Record = {}; let handleSet: Set = new Set(); - const contract = new ethers.Contract(coprocAddress, abi, ethers.provider); - const relevantLogs = receipt.logs.filter((log: Log) => { - if (log.address.toLowerCase() !== coprocAddress.toLowerCase()) { + const contract = new ethers.Contract(coprocessorContractAddress, abi, ethers.provider); + const relevantLogs = receipt.logs.filter((log: EthersT.Log) => { + if (log.address.toLowerCase() !== coprocessorContractAddress.toLowerCase()) { return false; } try { const parsedLog = contract.interface.parseLog({ topics: log.topics, data: log.data, - }); - return abi.some((item) => item.startsWith(`event ${parsedLog!.name}`) && parsedLog!.name !== 'VerifyInput'); + })!; + return abi.some((item) => item.startsWith(`event ${parsedLog.name}`) && parsedLog.name !== 'VerifyInput'); } catch { return false; } }); - const FHELogs = relevantLogs.map((log: Log) => { + const FHELogs = relevantLogs.map((log: EthersT.Log) => { const parsedLog = contract.interface.parseLog({ topics: log.topics, data: log.data, - }); + })!; return { - name: parsedLog!.name, - args: parsedLog!.args, + name: parsedLog.name, + args: parsedLog.args, }; }); @@ -668,7 +656,7 @@ export function getTxHCUFromTxReceipt( switch (event.name) { case 'TrivialEncrypt': typeIndex = parseInt(event.args[2]); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -682,7 +670,7 @@ export function getTxHCUFromTxReceipt( case 'TrivialEncryptBytes': typeIndex = parseInt(event.args[2]); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -697,7 +685,7 @@ export function getTxHCUFromTxReceipt( case 'FheAdd': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -723,7 +711,7 @@ export function getTxHCUFromTxReceipt( case 'FheSub': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -749,7 +737,7 @@ export function getTxHCUFromTxReceipt( case 'FheMul': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -775,7 +763,7 @@ export function getTxHCUFromTxReceipt( case 'FheDiv': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -793,7 +781,7 @@ export function getTxHCUFromTxReceipt( case 'FheRem': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -810,7 +798,7 @@ export function getTxHCUFromTxReceipt( case 'FheBitAnd': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -833,7 +821,7 @@ export function getTxHCUFromTxReceipt( case 'FheBitOr': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -856,7 +844,7 @@ export function getTxHCUFromTxReceipt( case 'FheBitXor': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -879,7 +867,7 @@ export function getTxHCUFromTxReceipt( case 'FheShl': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -902,7 +890,7 @@ export function getTxHCUFromTxReceipt( case 'FheShr': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -925,7 +913,7 @@ export function getTxHCUFromTxReceipt( case 'FheRotl': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -948,7 +936,7 @@ export function getTxHCUFromTxReceipt( case 'FheRotr': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -972,7 +960,7 @@ export function getTxHCUFromTxReceipt( handleResult = ethers.toBeHex(event.args[4], 32); handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -997,7 +985,7 @@ export function getTxHCUFromTxReceipt( handleResult = ethers.toBeHex(event.args[4], 32); handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1022,7 +1010,7 @@ export function getTxHCUFromTxReceipt( handleResult = ethers.toBeHex(event.args[4], 32); handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1047,7 +1035,7 @@ export function getTxHCUFromTxReceipt( handleResult = ethers.toBeHex(event.args[4], 32); handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1072,7 +1060,7 @@ export function getTxHCUFromTxReceipt( handleResult = ethers.toBeHex(event.args[4], 32); handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1097,7 +1085,7 @@ export function getTxHCUFromTxReceipt( handleResult = ethers.toBeHex(event.args[4], 32); handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -1121,7 +1109,7 @@ export function getTxHCUFromTxReceipt( handleResult = ethers.toBeHex(event.args[4], 32); handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -1145,7 +1133,7 @@ export function getTxHCUFromTxReceipt( handleResult = ethers.toBeHex(event.args[4], 32); handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -1168,7 +1156,7 @@ export function getTxHCUFromTxReceipt( case 'FheMax': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1193,7 +1181,7 @@ export function getTxHCUFromTxReceipt( case 'FheMin': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1219,7 +1207,7 @@ export function getTxHCUFromTxReceipt( case 'Cast': handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; handleResult = ethers.toBeHex(event.args[3], 32); if (!type) { @@ -1235,7 +1223,7 @@ export function getTxHCUFromTxReceipt( case 'FheNot': handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; handleResult = ethers.toBeHex(event.args[2], 32); if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1249,7 +1237,7 @@ export function getTxHCUFromTxReceipt( case 'FheNeg': handle = ethers.toBeHex(event.args[1], 32); typeIndex = parseInt(handle.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; handleResult = ethers.toBeHex(event.args[2], 32); if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1263,7 +1251,7 @@ export function getTxHCUFromTxReceipt( case 'FheIfThenElse': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(handleResult.slice(-4, -2), 16); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); @@ -1284,7 +1272,7 @@ export function getTxHCUFromTxReceipt( case 'FheRand': handleResult = ethers.toBeHex(event.args[3], 32); typeIndex = parseInt(event.args[1]); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } @@ -1297,7 +1285,7 @@ export function getTxHCUFromTxReceipt( case 'FheRandBounded': handleResult = ethers.toBeHex(event.args[4], 32); typeIndex = parseInt(event.args[2]); - type = FheTypes.find((t) => t.value === typeIndex)?.type; + type = FheTypeInfos.find((t) => t.value === typeIndex)?.type; if (!type) { throw new Error(`Invalid FheType index: ${typeIndex}`); } diff --git a/host-contracts/test/encryptedERC20/EncryptedERC20.HCU.ts b/host-contracts/test/encryptedERC20/EncryptedERC20.HCU.ts index 72ee02264..d831d5251 100644 --- a/host-contracts/test/encryptedERC20/EncryptedERC20.HCU.ts +++ b/host-contracts/test/encryptedERC20/EncryptedERC20.HCU.ts @@ -1,7 +1,6 @@ import { expect } from 'chai'; -import { getTxHCUFromTxReceipt } from '../coprocessorUtils'; -import { createInstances } from '../instance'; +import { createInstances, getTxHCUFromTxReceipt } from '../instance'; import { getSigners, initSigners } from '../signers'; import { deployEncryptedERC20Fixture } from './EncryptedERC20.fixture'; @@ -34,10 +33,10 @@ describe('EncryptedERC20:HCU', function () { const t2 = await tx.wait(); expect(t2?.status).to.eq(1); - const { globalTxHCU: HCUTransfer, maxTxHCUDepth: HCUMaxDepthTransfer } = getTxHCUFromTxReceipt(t2); + const { globalTxHCU: HCUTransfer, maxTxHCUDepth: HCUMaxDepthTransfer } = getTxHCUFromTxReceipt(t2!); console.log('Total HCU in transfer', HCUTransfer); console.log('HCU Depth in transfer', HCUMaxDepthTransfer); - console.log('Native Gas Consumed in transfer', t2.gasUsed); + console.log('Native Gas Consumed in transfer', t2!.gasUsed); // Le euint64 (149000) + TrivialEncrypt euint64 (32) + Select euint64 (55000) + Add euint64 (162000) /// + TrivialEncrypt euint64(32) (Initialize balance to 0) + Sub euint euint64 (162000) @@ -74,10 +73,10 @@ describe('EncryptedERC20:HCU', function () { const t3 = await tx3.wait(); - const { globalTxHCU: HCUTransferFrom, maxTxHCUDepth: HCUMaxDepthTransferFrom } = getTxHCUFromTxReceipt(t3); + const { globalTxHCU: HCUTransferFrom, maxTxHCUDepth: HCUMaxDepthTransferFrom } = getTxHCUFromTxReceipt(t3!); console.log('Total HCU in transferFrom', HCUTransferFrom); console.log('HCU Depth in transferFrom', HCUMaxDepthTransferFrom); - console.log('Native Gas Consumed in transferFrom', t3.gasUsed); + console.log('Native Gas Consumed in transferFrom', t3!.gasUsed); // Le euint64 (149000) + Le euint64 (149000) + And ebool (34000) + Sub euint64 (162000) + TrivialEncrypt (32) + Select euint64 (55000) + // Select euint64 (55000) + Add ebool (25000) + TrivialEncrypt (Initialize balance to 0) (32) + Sub euint64 (162000) diff --git a/host-contracts/test/encryptedERC20/EncryptedERC20.fixture.ts b/host-contracts/test/encryptedERC20/EncryptedERC20.fixture.ts index 401bbd9c1..26588f942 100644 --- a/host-contracts/test/encryptedERC20/EncryptedERC20.fixture.ts +++ b/host-contracts/test/encryptedERC20/EncryptedERC20.fixture.ts @@ -1,6 +1,6 @@ import { ethers } from 'hardhat'; -import type { EncryptedERC20 } from '../../types'; +import type { EncryptedERC20 } from '../../typechain-types'; import { getSigners } from '../signers'; export async function deployEncryptedERC20Fixture(): Promise { diff --git a/host-contracts/test/encryptedERC20/EncryptedERC20.gas.ts b/host-contracts/test/encryptedERC20/EncryptedERC20.gas.ts index 752462784..46fd4ff71 100644 --- a/host-contracts/test/encryptedERC20/EncryptedERC20.gas.ts +++ b/host-contracts/test/encryptedERC20/EncryptedERC20.gas.ts @@ -14,13 +14,13 @@ describe('EncryptedERC20:Gas', function () { const contractFactory = await ethers.getContractFactory('EncryptedERC20'); const contract = await contractFactory.connect(this.signers.alice).deploy('Naraggara', 'NARA'); const contractAddress = await contract.getAddress(); - const deployTx = await contract.deploymentTransaction(); - const receipt = await deployTx.wait(1); - console.log('Gas consumed in EncryptedERC20 deployment:', '\x1b[1m' + receipt.gasUsed.toString() + '\x1b[0m'); + const deployTx = contract.deploymentTransaction(); + const receipt = await deployTx!.wait(1); + console.log('Gas consumed in EncryptedERC20 deployment:', '\x1b[1m' + receipt!.gasUsed.toString() + '\x1b[0m'); const transaction = await contract.mint(1000); const rcptMint = await transaction.wait(); - console.log('Gas consumed in EncryptedERC20 Mint tx:', '\x1b[1m' + rcptMint.gasUsed.toString() + '\x1b[0m'); + console.log('Gas consumed in EncryptedERC20 Mint tx:', '\x1b[1m' + rcptMint!.gasUsed.toString() + '\x1b[0m'); const input = this.instances.alice.createEncryptedInput(contractAddress, this.signers.alice.address); input.add64(1337); @@ -31,7 +31,10 @@ describe('EncryptedERC20:Gas', function () { encryptedTransferAmount.inputProof, ); const rcptTransfer = await tx.wait(); - console.log('Gas consumed in EncryptedERC20 Transfer tx:', '\x1b[1m' + rcptTransfer.gasUsed.toString() + '\x1b[0m'); + console.log( + 'Gas consumed in EncryptedERC20 Transfer tx:', + '\x1b[1m' + rcptTransfer!.gasUsed.toString() + '\x1b[0m', + ); const inputAlice = this.instances.alice.createEncryptedInput(contractAddress, this.signers.alice.address); inputAlice.add64(1337); @@ -42,7 +45,7 @@ describe('EncryptedERC20:Gas', function () { encryptedAllowanceAmount.inputProof, ); const rcptApprove = await txbis.wait(); - console.log('Gas consumed in EncryptedERC20 Approve tx:', '\x1b[1m' + rcptApprove.gasUsed.toString() + '\x1b[0m'); + console.log('Gas consumed in EncryptedERC20 Approve tx:', '\x1b[1m' + rcptApprove!.gasUsed.toString() + '\x1b[0m'); const bobErc20 = contract.connect(this.signers.bob); const inputBob1 = this.instances.bob.createEncryptedInput(contractAddress, this.signers.bob.address); @@ -57,7 +60,7 @@ describe('EncryptedERC20:Gas', function () { const rcptTransferFrom = await tx2.wait(); console.log( 'Gas consumed in EncryptedERC20 TransferFrom tx:', - '\x1b[1m' + rcptTransferFrom.gasUsed.toString() + '\x1b[0m', + '\x1b[1m' + rcptTransferFrom!.gasUsed.toString() + '\x1b[0m', ); }); }); diff --git a/host-contracts/test/encryptedERC20/EncryptedERC20.ts b/host-contracts/test/encryptedERC20/EncryptedERC20.ts index 78921eee0..a7058980f 100644 --- a/host-contracts/test/encryptedERC20/EncryptedERC20.ts +++ b/host-contracts/test/encryptedERC20/EncryptedERC20.ts @@ -114,7 +114,7 @@ describe('EncryptedERC20', function () { ); expect.fail('Expected an error to be thrown - Bob should not be able to reencrypt Alice balance'); } catch (error) { - expect(error.message).to.equal('User is not authorized to reencrypt this handle!'); + expect((error as any).message).to.equal('User is not authorized to reencrypt this handle!'); } }); diff --git a/host-contracts/test/fhevmOperations/fhevmOperations1.ts b/host-contracts/test/fhevmOperations/fhevmOperations1.ts index 5f0cda9a1..5c5b97616 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations1.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations1.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations10.ts b/host-contracts/test/fhevmOperations/fhevmOperations10.ts index d16b8c893..f6cc9fc80 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations10.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations10.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations11.ts b/host-contracts/test/fhevmOperations/fhevmOperations11.ts index 2d994a0cc..f4b3c5b42 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations11.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations11.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations12.ts b/host-contracts/test/fhevmOperations/fhevmOperations12.ts index 044dca960..eb2f3174b 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations12.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations12.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations13.ts b/host-contracts/test/fhevmOperations/fhevmOperations13.ts index b6f59fc1c..5991f1af8 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations13.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations13.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations2.ts b/host-contracts/test/fhevmOperations/fhevmOperations2.ts index de00378ad..533c74ce8 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations2.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations2.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations3.ts b/host-contracts/test/fhevmOperations/fhevmOperations3.ts index 13ffeab5d..306e60297 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations3.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations3.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations4.ts b/host-contracts/test/fhevmOperations/fhevmOperations4.ts index 4cf618f8e..7fefde376 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations4.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations4.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations5.ts b/host-contracts/test/fhevmOperations/fhevmOperations5.ts index c1455a6fb..1952e363e 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations5.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations5.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations6.ts b/host-contracts/test/fhevmOperations/fhevmOperations6.ts index a0f125fcc..a7d54a9b7 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations6.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations6.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations7.ts b/host-contracts/test/fhevmOperations/fhevmOperations7.ts index ca2f3f5c8..0620d7306 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations7.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations7.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations8.ts b/host-contracts/test/fhevmOperations/fhevmOperations8.ts index 508ec6de8..5e199d574 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations8.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations8.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/fhevmOperations9.ts b/host-contracts/test/fhevmOperations/fhevmOperations9.ts index 50fba9cc8..094d8ee08 100644 --- a/host-contracts/test/fhevmOperations/fhevmOperations9.ts +++ b/host-contracts/test/fhevmOperations/fhevmOperations9.ts @@ -2,13 +2,13 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMTestSuite1 } from '../../types/examples/tests/FHEVMTestSuite1'; -import type { FHEVMTestSuite2 } from '../../types/examples/tests/FHEVMTestSuite2'; -import type { FHEVMTestSuite3 } from '../../types/examples/tests/FHEVMTestSuite3'; -import type { FHEVMTestSuite4 } from '../../types/examples/tests/FHEVMTestSuite4'; -import type { FHEVMTestSuite5 } from '../../types/examples/tests/FHEVMTestSuite5'; -import type { FHEVMTestSuite6 } from '../../types/examples/tests/FHEVMTestSuite6'; -import type { FHEVMTestSuite7 } from '../../types/examples/tests/FHEVMTestSuite7'; +import type { FHEVMTestSuite1 } from '../../typechain-types/examples/tests/FHEVMTestSuite1'; +import type { FHEVMTestSuite2 } from '../../typechain-types/examples/tests/FHEVMTestSuite2'; +import type { FHEVMTestSuite3 } from '../../typechain-types/examples/tests/FHEVMTestSuite3'; +import type { FHEVMTestSuite4 } from '../../typechain-types/examples/tests/FHEVMTestSuite4'; +import type { FHEVMTestSuite5 } from '../../typechain-types/examples/tests/FHEVMTestSuite5'; +import type { FHEVMTestSuite6 } from '../../typechain-types/examples/tests/FHEVMTestSuite6'; +import type { FHEVMTestSuite7 } from '../../typechain-types/examples/tests/FHEVMTestSuite7'; import { createInstances, decrypt8, diff --git a/host-contracts/test/fhevmOperations/manual.ts b/host-contracts/test/fhevmOperations/manual.ts index 259b44f13..bcfdef411 100644 --- a/host-contracts/test/fhevmOperations/manual.ts +++ b/host-contracts/test/fhevmOperations/manual.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; -import type { FHEVMManualTestSuite } from '../../types/contracts/tests/FHEVMManualTestSuite'; +import type { FHEVMManualTestSuite } from '../../typechain-types/examples/tests/FHEVMManualTestSuite'; import { createInstances, decrypt8, @@ -12,12 +12,8 @@ import { decrypt256, decryptAddress, decryptBool, - decryptEbytes64, - decryptEbytes128, - decryptEbytes256, } from '../instance'; import { getSigners, initSigners } from '../signers'; -import { bigIntToBytes256 } from '../utils'; async function deployFHEVMManualTestFixture(): Promise { const signers = await getSigners(); @@ -87,57 +83,6 @@ describe('FHEVM manual operations', function () { expect(res2).to.equal(true); }); - it.skip('Select ebytes64', async function () { - const tx = await this.contract.test_select_ebytes64( - true, - '0x6798aa6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x11', - ); - await tx.wait(); - const res = await decryptEbytes64(await this.contract.resEbytes64()); - expect(res).to.equal( - ethers.toBeHex(BigInt('0x6798aa6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb'), 64), - ); - const tx2 = await this.contract.test_select_ebytes64(false, '0x42', '0xaaaaaaaa'); - await tx2.wait(); - const res2 = await decryptEbytes64(await this.contract.resEbytes64()); - expect(res2).to.equal(ethers.toBeHex(BigInt('0xaaaaaaaa'), 64)); - }); - - it.skip('Select ebytes128', async function () { - const tx = await this.contract.test_select_ebytes128( - true, - '0x6798aa6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x11', - ); - await tx.wait(); - const res = await decryptEbytes128(await this.contract.resEbytes128()); - expect(res).to.equal( - ethers.toBeHex(BigInt('0x6798aa6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb'), 128), - ); - const tx2 = await this.contract.test_select_ebytes128(false, '0x42', '0xaaaaaaaa'); - await tx2.wait(); - const res2 = await decryptEbytes128(await this.contract.resEbytes128()); - expect(res2).to.equal(ethers.toBeHex(BigInt('0xaaaaaaaa'), 128)); - }); - - it.skip('Select ebytes256', async function () { - const tx = await this.contract.test_select_ebytes256( - true, - '0x6798aa6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x11', - ); - await tx.wait(); - const res = await decryptEbytes256(await this.contract.resEbytes256()); - expect(res).to.equal( - ethers.toBeHex(BigInt('0x6798aa6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb'), 256), - ); - const tx2 = await this.contract.test_select_ebytes256(false, '0x428899', '0xaaaaaabb'); - await tx2.wait(); - const res2 = await decryptEbytes256(await this.contract.resEbytes256()); - expect(res2).to.equal(ethers.toBeHex(BigInt('0xaaaaaabb'), 256)); - }); - it('Select works for eaddress returning if false', async function () { const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); input.addBool(false); @@ -737,324 +682,6 @@ describe('FHEVM manual operations', function () { expect(res4).to.equal(true); }); - it.skip('eq ebytes256,ebytes256 true', async function () { - const inputAliceA = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - inputAliceA.addBytes256(bigIntToBytes256(18446744073709550022n)); - const encryptedAmountA = await inputAliceA.encrypt(); - - const inputAliceB = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - inputAliceB.addBytes256(bigIntToBytes256(18446744073709550022n)); - const encryptedAmountB = await inputAliceB.encrypt(); - - const tx = await this.contract.eqEbytes256( - encryptedAmountA.handles[0], - encryptedAmountA.inputProof, - encryptedAmountB.handles[0], - encryptedAmountB.inputProof, - ); - await tx.wait(); - - const res = await this.contract.resEbool(); - const decRes = await decryptBool(res); - expect(decRes).to.equal(true); - }); - - it.skip('eq ebytes256,ebytes256 false', async function () { - const inputAliceA = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - inputAliceA.addBytes256(bigIntToBytes256(18446744073709550022n)); - const encryptedAmountA = await inputAliceA.encrypt(); - - const inputAliceB = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - inputAliceB.addBytes256(bigIntToBytes256(18446744073709550021n)); - const encryptedAmountB = await inputAliceB.encrypt(); - - const tx = await this.contract.eqEbytes256( - encryptedAmountA.handles[0], - encryptedAmountA.inputProof, - encryptedAmountB.handles[0], - encryptedAmountB.inputProof, - ); - await tx.wait(); - - const res = await this.contract.resEbool(); - const decRes = await decryptBool(res); - expect(decRes).to.equal(false); - }); - - it.skip('ne ebytes256,ebytes256 true', async function () { - const inputAliceA = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - inputAliceA.addBytes256(bigIntToBytes256(18446744073709550022n)); - const encryptedAmountA = await inputAliceA.encrypt(); - - const inputAliceB = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - inputAliceB.addBytes256(bigIntToBytes256(18446744073709550021n)); - const encryptedAmountB = await inputAliceB.encrypt(); - - const tx = await this.contract.neEbytes256( - encryptedAmountA.handles[0], - encryptedAmountA.inputProof, - encryptedAmountB.handles[0], - encryptedAmountB.inputProof, - ); - await tx.wait(); - - const res = await this.contract.resEbool(); - const decRes = await decryptBool(res); - expect(decRes).to.equal(true); - }); - - it.skip('ne ebytes256,ebytes256 false', async function () { - const inputAliceA = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - inputAliceA.addBytes256(bigIntToBytes256(184467440184467440184467440184467440n)); - const encryptedAmountA = await inputAliceA.encrypt(); - - const inputAliceB = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - inputAliceB.addBytes256(bigIntToBytes256(184467440184467440184467440184467440n)); - const encryptedAmountB = await inputAliceB.encrypt(); - - const tx = await this.contract.neEbytes256( - encryptedAmountA.handles[0], - encryptedAmountA.inputProof, - encryptedAmountB.handles[0], - encryptedAmountB.inputProof, - ); - await tx.wait(); - - const res = await this.contract.resEbool(); - const decRes = await decryptBool(res); - expect(decRes).to.equal(false); - }); - - it.skip('ebytes64 eq ebytes64', async function () { - const tx = await this.contract.eqEbytes64( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(true); - const tx2 = await this.contract.eqEbytes64('0x1100', '0x0011'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(false); - }); - - it.skip('ebytes64 eq ebytes64 - scalarL', async function () { - const tx = await this.contract.eqEbytes64ScalarL( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(false); - const tx2 = await this.contract.eqEbytes64ScalarL('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(true); - }); - - it.skip('ebytes64 eq ebytes64 - scalarR', async function () { - const tx = await this.contract.eqEbytes64ScalarR( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(false); - const tx2 = await this.contract.eqEbytes64ScalarR('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(true); - }); - - it.skip('ebytes64 ne ebytes64', async function () { - const tx = await this.contract.neEbytes64( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(false); - const tx2 = await this.contract.neEbytes64('0x1100', '0x0011'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(true); - }); - - it.skip('ebytes64 ne ebytes64 - scalarL', async function () { - const tx = await this.contract.neEbytes64ScalarL( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(true); - const tx2 = await this.contract.neEbytes64ScalarL('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(false); - }); - - it.skip('ebytes64 ne ebytes64 - scalarR', async function () { - const tx = await this.contract.neEbytes64ScalarR( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(true); - const tx2 = await this.contract.neEbytes64ScalarR('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(false); - }); - - it.skip('ebytes128 eq ebytes128', async function () { - const tx = await this.contract.eqEbytes128( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbd4fdd06bd752b24ffb9f307805c4e698bf10aed0a47a103e5c1ade64bd31eb73', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbd4fdd06bd752b24ffb9f307805c4e698bf10aed0a47a103e5c1ade64bd31eb73', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(true); - const tx2 = await this.contract.eqEbytes128( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbd4fdd06bd752b24ffb9f307805c4e698bf10aed0a47a103e5c1ade64bd31eb73', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbd4fdd06bd752b24ffb9f307805c4e698bf10aed0a47a103e5c1ade64bd31eb71', - ); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(false); - }); - - it.skip('ebytes128 eq ebytes128 - scalarL', async function () { - const tx = await this.contract.eqEbytes128ScalarL( - '0x6d4b2086ba8e3d2104fbf4a8dfe9679d6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6d4b2086ba8e3d2104fbf4a8dfe9679d6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(false); - const tx2 = await this.contract.eqEbytes128ScalarL('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(true); - }); - - it.skip('ebytes128 eq ebytes128 - scalarR', async function () { - const tx = await this.contract.eqEbytes128ScalarR( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(false); - const tx2 = await this.contract.eqEbytes128ScalarR('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(true); - }); - - it.skip('ebytes128 ne ebytes128', async function () { - const tx = await this.contract.neEbytes128( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbd4fdd06bd752b24ffb9f307805c4e698bf10aed0a47a103e5c1ade64bd31eb73', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbd4fdd06bd752b24ffb9f307805c4e698bf10aed0a47a103e5c1ade64bd31eb73', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(false); - const tx2 = await this.contract.neEbytes128( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbd4fdd06bd752b24ffb9f307805c4e698bf10aed0a47a103e5c1ade64bd31eb73', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbd4fdd06bd752b24ffb9f307805c4e698bf10aed0a47a103e5c1ade64bd31eb71', - ); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(true); - }); - - it.skip('ebytes128 ne ebytes128 - scalarL', async function () { - const tx = await this.contract.neEbytes128ScalarL( - '0x6d4b2086ba8e3d2104fbf4a8dfe9679d6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6d4b2086ba8e3d2104fbf4a8dfe9679d6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(true); - const tx2 = await this.contract.neEbytes128ScalarL('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(false); - }); - - it.skip('ebytes128 ne ebytes128 - scalarR', async function () { - const tx = await this.contract.neEbytes128ScalarR( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(true); - const tx2 = await this.contract.neEbytes128ScalarR('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(false); - }); - - it.skip('ebytes256 eq ebytes256 - scalarL', async function () { - const tx = await this.contract.eqEbytes256ScalarL( - '0x6d4b2086ba8e3d2104fbf4a8dfe9679d6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6d4b2086ba8e3d2104fbf4a8dfe9679d6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(false); - const tx2 = await this.contract.eqEbytes256ScalarL('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(true); - }); - - it.skip('ebytes256 eq ebytes256 - scalarR', async function () { - const tx = await this.contract.eqEbytes256ScalarR( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbaa', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(false); - const tx2 = await this.contract.eqEbytes256ScalarR('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(true); - }); - - it.skip('ebytes256 ne ebytes256 - scalarL', async function () { - const tx = await this.contract.neEbytes256ScalarL( - '0x6d4b2086ba8e3d2104fbf4a8dfe9679d6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabb', - '0x6d4b2086ba8e3d2104fbf4a8dfe9679d6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(true); - const tx2 = await this.contract.neEbytes256ScalarL('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(false); - }); - - it.skip('ebytes256 ne ebytes256 - scalarR', async function () { - const tx = await this.contract.neEbytes256ScalarR( - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbabbaa', - '0x6bb8166128b0e7a16f60dc255c953288d03107895b0904ea18f7a242bf335fbaaaaa', - ); - await tx.wait(); - const res = await decryptBool(await this.contract.resEbool()); - expect(res).to.equal(true); - const tx2 = await this.contract.neEbytes256ScalarR('0x1100', '0x1100'); - await tx2.wait(); - const res2 = await decryptBool(await this.contract.resEbool()); - expect(res2).to.equal(false); - }); - it('ebool ne ebool', async function () { const tx = await this.contract.neEbool(true, true); await tx.wait(); diff --git a/host-contracts/test/fhevmjsMocked.ts b/host-contracts/test/fhevmjsMocked.ts index 925aa4a65..983d9bba1 100644 --- a/host-contracts/test/fhevmjsMocked.ts +++ b/host-contracts/test/fhevmjsMocked.ts @@ -1,151 +1,164 @@ -import { toBigIntBE } from 'bigint-buffer'; +import type { + ClearValueType, + ClearValues, + FhevmInstance, + HandleContractPair, + PublicDecryptResults, + UserDecryptResults, +} from '@zama-fhe/relayer-sdk/node'; +import { createEIP712, generateKeypair } from '@zama-fhe/relayer-sdk/node'; import { toBufferBE } from 'bigint-buffer'; import crypto from 'crypto'; import dotenv from 'dotenv'; -import { Wallet, ethers } from 'ethers'; -import * as fs from 'fs'; +import { ethers } from 'ethers'; +import type { ethers as EthersT } from 'ethers'; +import { readFileSync } from 'fs'; import hre from 'hardhat'; import { Keccak } from 'sha3'; -import { isAddress } from 'web3-validator'; import { getRequiredEnvVar } from '../tasks/utils/loadVariables'; -import { insertSQL } from './coprocessorUtils'; -import { awaitCoprocessor, getClearText } from './coprocessorUtils'; +import { getTxHCUFromCoprocessorTxReceipt, insertSQL } from './coprocessorUtils'; +import { getCoprocessorClearText, processAllPastFHEVMExecutorEvents } from './coprocessorUtils'; import { checkIsHardhatSigner } from './utils'; -const toHexString = (bytes: Uint8Array, with0x = false) => - `${with0x ? '0x' : ''}${bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')}`; +//////////////////////////////////////////////////////////////////////////////// +// Config +//////////////////////////////////////////////////////////////////////////////// -const fromHexString = (hexString: string): Uint8Array => { - const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g); - if (!arr) return new Uint8Array(); - return Uint8Array.from(arr.map((byte) => parseInt(byte, 16))); +export type EnvFhevmMockConfig = { + kmsContractAddress: `0x${string}`; + aclContractAddress: `0x${string}`; + coprocessorContractAddress: `0x${string}`; + inputVerifierContractAddress: `0x${string}`; + verifyingContractAddressDecryption: `0x${string}`; + verifyingContractAddressInputVerification: `0x${string}`; + gatewayChainId: number; + chainId: number; + kmsThreshold: number; + coprocessorThreshold: number; }; -async function getCoprocessorSigners() { - const coprocessorSigners = []; - const numKMSSigners = getRequiredEnvVar('NUM_COPROCESSORS'); - for (let idx = 0; idx < +numKMSSigners; idx++) { - const coprocessorSigner = await hre.ethers.getSigner(getRequiredEnvVar(`COPROCESSOR_SIGNER_ADDRESS_${idx}`)); - await checkIsHardhatSigner(coprocessorSigner); - coprocessorSigners.push(coprocessorSigner); - } - return coprocessorSigners; +function loadEnvFhevmMockConfig(): EnvFhevmMockConfig { + const hostEnvPath = getRequiredEnvVar('ENV_HOST_ADDRESSES_PATH'); + const hosts = dotenv.parse(readFileSync(hostEnvPath)); + return { + aclContractAddress: getAddress(hosts.ACL_CONTRACT_ADDRESS), + coprocessorContractAddress: getAddress(hosts.FHEVM_EXECUTOR_CONTRACT_ADDRESS), + kmsContractAddress: getAddress(hosts.KMS_VERIFIER_CONTRACT_ADDRESS), + inputVerifierContractAddress: getAddress(hosts.INPUT_VERIFIER_CONTRACT_ADDRESS), + verifyingContractAddressDecryption: getAddress(getRequiredEnvVar('DECRYPTION_ADDRESS')), + verifyingContractAddressInputVerification: getAddress(getRequiredEnvVar('INPUT_VERIFICATION_ADDRESS')), + gatewayChainId: Number(+getRequiredEnvVar('CHAIN_ID_GATEWAY')), + chainId: Number(hre.network.config.chainId), + kmsThreshold: Number(+getRequiredEnvVar('PUBLIC_DECRYPTION_THRESHOLD')), + coprocessorThreshold: Number(+getRequiredEnvVar('COPROCESSOR_THRESHOLD')), + }; } -const parsedEnvACL = dotenv.parse(fs.readFileSync('addresses/.env.host')); -const aclAdd = parsedEnvACL.ACL_CONTRACT_ADDRESS; +const envFhevmMockConfig = loadEnvFhevmMockConfig(); -enum Types { - ebool = 0, - euint8 = 2, - euint16 = 3, - euint32 = 4, - euint64 = 5, - euint128 = 6, - eaddress = 7, - euint256 = 8, - ebytes64 = 9, - ebytes128 = 10, - ebytes256 = 11, +export function getEnvFhevmMockConfig(): EnvFhevmMockConfig { + return envFhevmMockConfig; } -const sum = (arr: number[]) => arr.reduce((acc, val) => acc + val, 0); +//////////////////////////////////////////////////////////////////////////////// +// Public API +//////////////////////////////////////////////////////////////////////////////// -function bytesToBigInt(byteArray: Uint8Array): bigint { - if (!byteArray || byteArray?.length === 0) { - return BigInt(0); +export function assertNetwork(name: string) { + if (hre.network.name !== name) { + throw new Error(`Unsupported Hardhat network ${name}, expecting '${name}'`); } - const buffer = Buffer.from(byteArray); - const result = toBigIntBE(buffer); - return result; } -function createUintToUint8ArrayFunction(numBits: number) { - const numBytes = Math.ceil(numBits / 8); - return function (uint: number | bigint | boolean) { - const buffer = toBufferBE(BigInt(uint), numBytes); - - // concatenate 32 random bytes at the end of buffer to simulate encryption noise - const randomBytes = crypto.randomBytes(32); - const combinedBuffer = Buffer.concat([buffer, randomBytes]); +export function getTxHCUFromTxReceipt(receipt: EthersT.TransactionReceipt): { + globalTxHCU: number; + maxTxHCUDepth: number; + HCUDepthPerHandle: Record; +} { + return getTxHCUFromCoprocessorTxReceipt(getEnvFhevmMockConfig().coprocessorContractAddress, receipt); +} - let byteBuffer; - let totalBuffer; +export async function awaitCoprocessor() { + return processAllPastFHEVMExecutorEvents(getEnvFhevmMockConfig().coprocessorContractAddress); +} - switch (numBits) { - case 2: // ebool takes 2 bits - byteBuffer = Buffer.from([Types.ebool]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 8: - byteBuffer = Buffer.from([Types.euint8]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 16: - byteBuffer = Buffer.from([Types.euint16]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 32: - byteBuffer = Buffer.from([Types.euint32]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 64: - byteBuffer = Buffer.from([Types.euint64]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 128: - byteBuffer = Buffer.from([Types.euint128]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 160: - byteBuffer = Buffer.from([Types.eaddress]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 256: - byteBuffer = Buffer.from([Types.euint256]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 512: - byteBuffer = Buffer.from([Types.ebytes64]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 1024: - byteBuffer = Buffer.from([Types.ebytes128]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 2048: - byteBuffer = Buffer.from([Types.ebytes256]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - default: - throw Error('Non-supported numBits'); - } +export async function getClearText(handle: string | bigint): Promise { + return getCoprocessorClearText(handle); +} - return totalBuffer; +export async function createInstanceMocked(envFhevmMockConfig: EnvFhevmMockConfig): Promise { + const instance: FhevmInstance = { + userDecrypt: userDecryptRequestMocked({ + aclContractAddress: envFhevmMockConfig.aclContractAddress, + coprocessorContractAddress: envFhevmMockConfig.coprocessorContractAddress, + }), + publicDecrypt: publicDecryptRequestMocked({ + hostKMSSigners: await getHostKMSSigners(envFhevmMockConfig.kmsContractAddress), + thresholdSigners: Number(await getHostKMSThreshold(envFhevmMockConfig.kmsContractAddress)), + aclContractAddress: envFhevmMockConfig.aclContractAddress, + coprocessorContractAddress: envFhevmMockConfig.coprocessorContractAddress, + gatewayChainId: envFhevmMockConfig.gatewayChainId, + verifyingContractAddress: envFhevmMockConfig.verifyingContractAddressDecryption, + }), + createEncryptedInput: createEncryptedInputMocked, + getPublicKey: () => null, + getPublicParams: () => null, + generateKeypair: generateKeypair, + createEIP712: createEIP712(envFhevmMockConfig.verifyingContractAddressDecryption, envFhevmMockConfig.chainId), }; + return instance; } -export type HandleContractPair = { - ctHandle: Uint8Array | string; - contractAddress: string; -}; +//////////////////////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////////////////////// -export type HandleContractPairRelayer = { - ctHandle: string; - contractAddress: string; -}; +const MAX_USER_DECRYPT_CONTRACT_ADDRESSES = 10; +const MAX_USER_DECRYPT_DURATION_DAYS = BigInt(365); + +export const ENCRYPTION_TYPES = { + 2: 0, // ebool takes 2 bits + 8: 2, + 16: 3, + 32: 4, + 64: 5, + 128: 6, + 160: 7, + 256: 8, +} as const; + +const CiphertextAbiType = { + 0: 'bool', + 2: 'uint256', + 3: 'uint256', + 4: 'uint256', + 5: 'uint256', + 6: 'uint256', + 7: 'address', + 8: 'uint256', +} as const; + +type EncryptedBits = keyof typeof ENCRYPTION_TYPES; +type EncryptedType = keyof typeof CiphertextAbiType; + +const NumEncryptedBits: Record = { + 0: 2, // ebool + 2: 8, // euint8 + 3: 16, // euint16 + 4: 32, // euint32 + 5: 64, // euint64 + 6: 128, // euint128 + 7: 160, // eaddress + 8: 256, // euint256 +} as const; -export const userDecryptRequestMocked = - ( - kmsSigners: string[], - gatewayChainId: number, - chainId: number, - verifyingContractAddress: string, - aclContractAddress: string, - relayerUrl: string, - provider: ethers.JsonRpcProvider | ethers.BrowserProvider, - ) => +//////////////////////////////////////////////////////////////////////////////// +// userDecrypt +//////////////////////////////////////////////////////////////////////////////// + +const userDecryptRequestMocked = + (params: { aclContractAddress: `0x${string}`; coprocessorContractAddress: `0x${string}` }) => async ( _handles: HandleContractPair[], privateKey: string, @@ -155,13 +168,14 @@ export const userDecryptRequestMocked = userAddress: string, startTimestamp: string | number, durationDays: string | number, - ): Promise => { + ): Promise => { + const { aclContractAddress, coprocessorContractAddress } = params; // Casting handles if string - const handles: HandleContractPairRelayer[] = _handles.map((h) => ({ - ctHandle: - typeof h.ctHandle === 'string' ? toHexString(fromHexString(h.ctHandle), true) : toHexString(h.ctHandle, true), + const handleContractPairs: { handle: `0x${string}`; contractAddress: string }[] = _handles.map((h) => ({ + handle: typeof h.handle === 'string' ? toHexString(fromHexString(h.handle)) : toHexString(h.handle), contractAddress: h.contractAddress, })); + const handles: `0x${string}`[] = handleContractPairs.map((pair) => pair.handle); // Signature checking: const domain = { @@ -194,12 +208,24 @@ export const userDecryptRequestMocked = throw new Error('Invalid EIP-712 signature!'); } + checkEncryptedBits(handles); + + checkDeadlineValidity(BigInt(startTimestamp), BigInt(durationDays)); + + const contractAddressesLength = contractAddresses.length; + if (contractAddressesLength === 0) { + throw Error('contractAddresses is empty'); + } + if (contractAddressesLength > MAX_USER_DECRYPT_CONTRACT_ADDRESSES) { + throw Error(`contractAddresses max length of ${MAX_USER_DECRYPT_CONTRACT_ADDRESSES} exceeded`); + } + // ACL checking const aclFactory = await hre.ethers.getContractFactory('ACL'); - const acl = aclFactory.attach(aclAdd); - const verifications = handles.map(async ({ ctHandle, contractAddress }) => { - const userAllowed = await acl.persistAllowed(ctHandle, userAddress); - const contractAllowed = await acl.persistAllowed(ctHandle, contractAddress); + const acl = aclFactory.attach(aclContractAddress) as ethers.Contract; + const verifications = handleContractPairs.map(async ({ handle, contractAddress }) => { + const userAllowed = await acl.persistAllowed(handle, userAddress); + const contractAllowed = await acl.persistAllowed(handle, contractAddress); if (!userAllowed) { throw new Error('User is not authorized to reencrypt this handle!'); } @@ -214,14 +240,165 @@ export const userDecryptRequestMocked = await Promise.all(verifications).catch((e) => { throw e; }); - await awaitCoprocessor(); - return Promise.all( - handles.map(async (handleContractPair) => await BigInt(await getClearText(handleContractPair.ctHandle))), + await processAllPastFHEVMExecutorEvents(coprocessorContractAddress); + + const listBigIntDecryptions: bigint[] = await Promise.all( + handleContractPairs.map(async (handleContractPair) => BigInt(await getClearText(handleContractPair.handle))), ); + + const results: UserDecryptResults = buildUserDecryptResults(handles, listBigIntDecryptions); + + return results; }; -export const createEncryptedInputMocked = (contractAddress: string, userAddress: string) => { +//////////////////////////////////////////////////////////////////////////////// +// publicDecrypt +//////////////////////////////////////////////////////////////////////////////// + +const publicDecryptRequestMocked = + (params: { + hostKMSSigners: `0x${string}`[]; + thresholdSigners: number; + aclContractAddress: `0x${string}`; + coprocessorContractAddress: `0x${string}`; + gatewayChainId: number; + verifyingContractAddress: `0x${string}`; + }) => + async (_handles: (string | Uint8Array)[]): Promise => { + const { + hostKMSSigners, + thresholdSigners, + aclContractAddress, + coprocessorContractAddress, + gatewayChainId, + verifyingContractAddress, + } = params; + const extraData: `0x${string}` = '0x00'; + const aclFactory = await hre.ethers.getContractFactory('ACL'); + const acl = aclFactory.attach(aclContractAddress) as ethers.Contract; + + let handlesBytes32Hex: `0x${string}`[]; + try { + handlesBytes32Hex = await Promise.all( + _handles.map(async (_handle) => { + const handle = typeof _handle === 'string' ? toHexString(fromHexString(_handle)) : toHexString(_handle); + + const isAllowedForDecryption = await acl.isAllowedForDecryption(handle); + if (!isAllowedForDecryption) { + throw new Error(`Handle ${handle} is not allowed for public decryption!`); + } + return handle; + }), + ); + } catch (e) { + throw e; + } + + // check 2048 bits limit + checkEncryptedBits(handlesBytes32Hex); + + const payloadForRequest = { + ciphertextHandles: handlesBytes32Hex, + extraData, + }; + + const json = await _handleFhevmRelayerV1PublicDecrypt(coprocessorContractAddress, payloadForRequest); + + // verify signatures on decryption: + const domain = { + name: 'Decryption', + version: '1', + chainId: gatewayChainId, + verifyingContract: verifyingContractAddress, + }; + const types = { + PublicDecryptVerification: [ + { name: 'ctHandles', type: 'bytes32[]' }, + { name: 'decryptedResult', type: 'bytes' }, + { name: 'extraData', type: 'bytes' }, + ], + }; + const result = json.response[0]; + const decryptedResult: `0x${string}` = ensure0x(result.decrypted_value); + const kmsSignatures: `0x${string}`[] = result.signatures.map(ensure0x); + + // TODO result.extra_data (RelayerPublicDecryptJsonResponse) + const signedExtraData: `0x${string}` = '0x00'; + + const recoveredAddresses: `0x${string}`[] = kmsSignatures.map((kmsSignature: `0x${string}`) => { + const recoveredAddress = ethers.verifyTypedData( + domain, + types, + { ctHandles: handlesBytes32Hex, decryptedResult, extraData: signedExtraData }, + kmsSignature, + ) as `0x${string}`; + return recoveredAddress; + }); + + if (process.env.DISABLE_TEST_KMS_THRESHOLD !== '1') { + // use KMS Signers stored on host + const thresholdReached = isKmsThresholdReached(hostKMSSigners, recoveredAddresses, thresholdSigners); + + if (!thresholdReached) { + throw Error('KMS signers threshold is not reached'); + } + } + + const clearValues: ClearValues = deserializeClearValues(handlesBytes32Hex, decryptedResult); + + const abiEnc = abiEncodeClearValues(clearValues); + const decryptionProof = buildDecryptionProof(kmsSignatures, signedExtraData); + + return { + clearValues, + abiEncodedClearValues: abiEnc.abiEncodedClearValues, + decryptionProof, + }; + }; + +async function _handleFhevmRelayerV1PublicDecrypt( + coprocessorAddress: `0x${string}`, + payload: { + ciphertextHandles: `0x${string}`[]; + extraData: `0x${string}`; + }, +) { + const handlesBytes32Hex = payload.ciphertextHandles; + const extraData = payload.extraData; + + await processAllPastFHEVMExecutorEvents(coprocessorAddress); + + const listBigIntDecryptions: bigint[] = await Promise.all( + handlesBytes32Hex.map(async (h) => BigInt(await getClearText(h))), + ); + + const clearValues = buildClearValues(handlesBytes32Hex, listBigIntDecryptions); + const abiEnc = abiEncodeClearValues(clearValues); + + // Use env KMS Signers. Not host KMS signers. This is needed to simulate KMS signature issues in the test suite + const kmsSignatures = await envComputeDecryptSignaturesKms( + handlesBytes32Hex, + abiEnc.abiEncodedClearValues, + extraData, + ); + + // Build relayer response + return { + response: [ + { + decrypted_value: abiEnc.abiEncodedClearValues, + signatures: kmsSignatures, + }, + ], + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// createEncryptedInput +//////////////////////////////////////////////////////////////////////////////// + +const createEncryptedInputMocked = (contractAddress: string, userAddress: string) => { if (!isAddress(contractAddress)) { throw new Error('Contract address is not a valid address.'); } @@ -231,22 +408,35 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: } const values: bigint[] = []; - const bits: (keyof typeof ENCRYPTION_TYPES)[] = []; + const bits: EncryptedBits[] = []; return { addBool(value: boolean | number | bigint) { if (value == null) throw new Error('Missing value'); if (typeof value !== 'boolean' && typeof value !== 'number' && typeof value !== 'bigint') throw new Error('The value must be a boolean, a number or a bigint.'); - if ((typeof value !== 'bigint' || typeof value !== 'number') && Number(value) > 1) - throw new Error('The value must be 1 or 0.'); - values.push(BigInt(value)); + + // Convert to 0n or 1n + if (typeof value === 'boolean') { + value = value ? 1n : 0n; + } else if (typeof value === 'number') { + if (value !== 0 && value !== 1) { + throw new Error('The value must be 1 or 0.'); + } + value = value === 0 ? 0n : 1n; + } else if (typeof value === 'bigint') { + if (value !== 0n && value !== 1n) { + throw new Error('The value must be 1 or 0.'); + } + } + + values.push(value); bits.push(2); // ebool takes 2 bits instead of one: only exception in TFHE-rs if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); return this; }, add8(value: number | bigint) { - checkEncryptedValue(value, 8); + checkEncryptedUint(value, 8); values.push(BigInt(value)); bits.push(8); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -254,7 +444,7 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add16(value: number | bigint) { - checkEncryptedValue(value, 16); + checkEncryptedUint(value, 16); values.push(BigInt(value)); bits.push(16); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -262,7 +452,7 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add32(value: number | bigint) { - checkEncryptedValue(value, 32); + checkEncryptedUint(value, 32); values.push(BigInt(value)); bits.push(32); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -270,7 +460,7 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add64(value: number | bigint) { - checkEncryptedValue(value, 64); + checkEncryptedUint(value, 64); values.push(BigInt(value)); bits.push(64); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -278,7 +468,7 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add128(value: number | bigint) { - checkEncryptedValue(value, 128); + checkEncryptedUint(value, 128); values.push(BigInt(value)); bits.push(128); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -296,43 +486,13 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add256(value: number | bigint) { - checkEncryptedValue(value, 256); + checkEncryptedUint(value, 256); values.push(BigInt(value)); bits.push(256); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); return this; }, - addBytes64(value: Uint8Array) { - if (value.length !== 64) throw Error('Uncorrect length of input Uint8Array, should be 64 for an ebytes64'); - const bigIntValue = bytesToBigInt(value); - checkEncryptedValue(bigIntValue, 512); - values.push(bigIntValue); - bits.push(512); - if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); - if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); - return this; - }, - addBytes128(value: Uint8Array) { - if (value.length !== 128) throw Error('Uncorrect length of input Uint8Array, should be 128 for an ebytes128'); - const bigIntValue = bytesToBigInt(value); - checkEncryptedValue(bigIntValue, 1024); - values.push(bigIntValue); - bits.push(1024); - if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); - if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); - return this; - }, - addBytes256(value: Uint8Array) { - if (value.length !== 256) throw Error('Uncorrect length of input Uint8Array, should be 256 for an ebytes256'); - const bigIntValue = bytesToBigInt(value); - checkEncryptedValue(bigIntValue, 2048); - values.push(bigIntValue); - bits.push(2048); - if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); - if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); - return this; - }, getValues() { return values; }, @@ -344,15 +504,18 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: bits.length = 0; return this; }, - async encrypt() { - let encrypted = Buffer.alloc(0); + async encrypt(): Promise<{ + handles: Uint8Array[]; + inputProof: Uint8Array; + }> { + let mockEncryptedValues = Buffer.alloc(0); + for (let i = 0; i < bits.length; ++i) { + mockEncryptedValues = Buffer.concat([mockEncryptedValues, createRandomEncryptedValue(values[i], bits[i])]); + } - bits.map((v, i) => { - encrypted = Buffer.concat([encrypted, createUintToUint8ArrayFunction(v)(values[i])]); - }); + const mockEncryptedArray = new Uint8Array(mockEncryptedValues); + const mockHash = new Keccak(256).update(Buffer.from(mockEncryptedArray)).digest(); - const encryptedArray = new Uint8Array(encrypted); - const hash = new Keccak(256).update(Buffer.from(encryptedArray)).digest(); const extraDataV0 = ethers.solidityPacked(['uint8'], [0]); const chainId = process.env.SOLIDITY_COVERAGE === 'true' ? 31337 : hre.network.config.chainId; @@ -361,9 +524,9 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: } const handles = bits.map((v, i) => { - const dataWithIndex = new Uint8Array(hash.length + 1); - dataWithIndex.set(hash, 0); - dataWithIndex.set([i], hash.length); + const dataWithIndex = new Uint8Array(mockHash.length + 1); + dataWithIndex.set(mockHash, 0); + dataWithIndex.set([i], mockHash.length); const finalHash = new Keccak(256).update(Buffer.from(dataWithIndex)).digest(); const dataInput = new Uint8Array(32); dataInput.set(finalHash, 0); @@ -382,45 +545,98 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return dataInput; }); - let inputProof = '0x' + numberToHex(handles.length); // numHandles + numCoprocessorSigners + list_handles + signatureCoprocessorSigners (total len : 1+1+32+NUM_HANDLES*32+65*numSigners) - const numSigners = +process.env.NUM_COPROCESSORS!; - inputProof += numberToHex(numSigners); + const listHandlesHexNo0x = handles.map((handleAsBytes: Uint8Array) => uint8ArrayToHexStringNo0x(handleAsBytes)); + const listHandlesAsBigInt = listHandlesHexNo0x.map((handleNo0x: string) => BigInt('0x' + handleNo0x)); - const listHandlesStr = handles.map((i) => uint8ArrayToHexString(i)); - listHandlesStr.map((handle) => (inputProof += handle)); - const listHandles = listHandlesStr.map((i) => BigInt('0x' + i)); - const signaturesCoproc = await computeInputSignaturesCopro( - listHandles, + const signaturesCoproc = await envComputeInputSignaturesCoproc( + listHandlesAsBigInt, userAddress, contractAddress, extraDataV0, ); + + // numHandles + numCoprocessorSigners + list_handles + signatureCoprocessorSigners (total len : 1+1+32+NUM_HANDLES*32+65*numSigners) + let inputProof = '0x' + numberToHexNo0x(handles.length); + inputProof += numberToHexNo0x(signaturesCoproc.length); + listHandlesHexNo0x.map((handleNo0x) => (inputProof += handleNo0x)); signaturesCoproc.map((sigCopro) => (inputProof += sigCopro.slice(2))); - listHandlesStr.map((handle, i) => insertSQL('0x' + handle, values[i])); + listHandlesHexNo0x.map((handleNo0x, i) => insertSQL('0x' + handleNo0x, values[i])); // Append the extra data to the input proof inputProof = ethers.concat([inputProof, extraDataV0]); return { handles, - inputProof, + inputProof: hexStringToUint8Array(inputProof), }; }, }; }; -function uint8ArrayToHexString(uint8Array: Uint8Array) { +//////////////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////////////// + +// Add type checking +function getAddress(value: string): `0x${string}` { + return ethers.getAddress(value) as `0x${string}`; +} +// Add type checking +function isAddress(value: unknown): value is `0x${string}` { + return ethers.isAddress(value); +} + +function ensure0x(s: string): `0x${string}` { + return !s.startsWith('0x') ? `0x${s}` : (s as `0x${string}`); +} + +function toHexString(bytes: Uint8Array): `0x${string}` { + return `0x${bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')}`; +} + +function fromHexString(hexString: string): Uint8Array { + const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g); + if (!arr) return new Uint8Array(); + return Uint8Array.from(arr.map((byte) => parseInt(byte, 16))); +} + +function toBytes32HexString(value: EthersT.BigNumberish): `0x${string}` { + return ethers.zeroPadValue(ethers.toBeHex(value), 32) as `0x${string}`; +} + +function hexStringToUint8Array(hexString: string) { + return ethers.getBytes(hexString); +} + +function uint8ArrayToHexStringNo0x(uint8Array: Uint8Array): string { return Array.from(uint8Array) .map((byte) => byte.toString(16).padStart(2, '0')) .join(''); } -function numberToHex(num: number) { +function numberToHexNo0x(num: number): string { let hex = num.toString(16); return hex.length % 2 ? '0' + hex : hex; } -const checkEncryptedValue = (value: number | bigint, bits: number) => { +const sum = (arr: number[]) => arr.reduce((acc, val) => acc + val, 0); + +//////////////////////////////////////////////////////////////////////////////// +// Encrypted values +//////////////////////////////////////////////////////////////////////////////// + +function createRandomEncryptedValue(valueAsBigInt: bigint, numBits: EncryptedBits) { + const numBytes = Math.ceil(numBits / 8); + // Format: type + value + random 32 + // concatenate 32 random bytes at the end of buffer to simulate encryption noise + const bufferValue = toBufferBE(valueAsBigInt, numBytes); + const combinedBuffer = Buffer.concat([bufferValue, crypto.randomBytes(32)]); + const typeBuffer = Buffer.from([ENCRYPTION_TYPES[numBits]]); + const totalBuffer = Buffer.concat([typeBuffer, combinedBuffer]); + return totalBuffer; +} + +function checkEncryptedUint(value: number | bigint, bits: number) { if (value == null) throw new Error('Missing value'); let limit; if (bits >= 8) { @@ -429,59 +645,96 @@ const checkEncryptedValue = (value: number | bigint, bits: number) => { limit = BigInt(2 ** bits - 1); } if (typeof value !== 'number' && typeof value !== 'bigint') throw new Error('Value must be a number or a bigint.'); + if (typeof value === 'number') { + if (!Number.isInteger(value)) { + throw new Error('Value must be an unsigned integer.'); + } + value = BigInt(value); + } + if (value < 0) { + throw new Error('Value must be an unsigned integer.'); + } if (value > limit) { throw new Error(`The value exceeds the limit for ${bits}bits integer (${limit.toString()}).`); } -}; +} -export const ENCRYPTION_TYPES = { - 2: 0, // ebool takes 2 bits - 4: 1, - 8: 2, - 16: 3, - 32: 4, - 64: 5, - 128: 6, - 160: 7, - 256: 8, - 512: 9, - 1024: 10, - 2048: 11, -}; +//////////////////////////////////////////////////////////////////////////////// +// Env Signers (using env variables) +//////////////////////////////////////////////////////////////////////////////// -async function computeInputSignaturesCopro( - handlesList: string[], +// No env +async function getHostKMSSigners(kmsContractAddress: `0x${string}`): Promise<`0x${string}`[]> { + const abiKmsVerifier = ['function getKmsSigners() view returns (address[])']; + const kmsContract = new ethers.Contract(kmsContractAddress, abiKmsVerifier, hre.ethers.provider); + const signers: `0x${string}`[] = await kmsContract.getKmsSigners(); + return signers; +} + +// No env +async function getHostKMSThreshold(kmsContractAddress: `0x${string}`): Promise { + const abiKmsVerifier = ['function getThreshold() view returns (uint256)']; + const kmsContract = new ethers.Contract(kmsContractAddress, abiKmsVerifier, hre.ethers.provider); + return BigInt(await kmsContract.getThreshold()); +} + +// env function for testing +async function getEnvCoprocessorSigners() { + const signers = []; + const numSigners = getRequiredEnvVar('NUM_COPROCESSORS'); + for (let idx = 0; idx < +numSigners; idx++) { + const signer = await hre.ethers.getSigner(getRequiredEnvVar(`COPROCESSOR_SIGNER_ADDRESS_${idx}`)); + await checkIsHardhatSigner(signer); + signers.push(signer); + } + return signers; +} + +// env function for testing +async function getEnvKMSSigners() { + const signers = []; + const numSigners = getRequiredEnvVar('NUM_KMS_NODES'); + for (let idx = 0; idx < +numSigners; idx++) { + const signer = await hre.ethers.getSigner(getRequiredEnvVar(`KMS_SIGNER_ADDRESS_${idx}`)); + await checkIsHardhatSigner(signer); + signers.push(signer); + } + return signers; +} + +// Coprocessor Signers are dynamically determined using env variables +async function envComputeInputSignaturesCoproc( + handlesList: EthersT.BigNumberish[], userAddress: string, contractAddress: string, extraData: string, -): Promise { - const signatures: string[] = []; - const numSigners = +process.env.NUM_COPROCESSORS!; - let signers = await getCoprocessorSigners(); - - for (let idx = 0; idx < numSigners; idx++) { - const coprocSigner = signers[idx]; - const signature = await coprocSign(handlesList, userAddress, contractAddress, extraData, coprocSigner); +): Promise<`0x${string}`[]> { + const signatures: `0x${string}`[] = []; + const coprocSigners = await getEnvCoprocessorSigners(); + for (let idx = 0; idx < coprocSigners.length; idx++) { + const coprocSigner = coprocSigners[idx]; + const signature = await envCoprocSign(handlesList, userAddress, contractAddress, extraData, coprocSigner); signatures.push(signature); } return signatures; } -async function coprocSign( - handlesList: string[], +// Coprocessor Signers are dynamically determined using env variables +async function envCoprocSign( + handlesList: EthersT.BigNumberish[], userAddress: string, contractAddress: string, extraData: string, - signer: Wallet, -): Promise { + signer: EthersT.Signer, +): Promise<`0x${string}`> { const inputVerificationAdd = process.env.INPUT_VERIFICATION_ADDRESS; - const chainId = process.env.CHAIN_ID_GATEWAY; + const gatewayChainId = process.env.CHAIN_ID_GATEWAY; const hostChainId = process.env.SOLIDITY_COVERAGE === 'true' ? 31337 : hre.network.config.chainId; const domain = { name: 'InputVerification', version: '1', - chainId: chainId, + chainId: gatewayChainId, verifyingContract: inputVerificationAdd, }; @@ -511,7 +764,7 @@ async function coprocSign( }; const message = { - ctHandles: handlesList.map((handle) => ethers.zeroPadValue(ethers.toBeHex(handle), 32)), + ctHandles: handlesList.map(toBytes32HexString), userAddress: userAddress, contractAddress: contractAddress, contractChainId: hostChainId, @@ -525,5 +778,290 @@ async function coprocSign( const s = sigRSV.s; const result = r + s.substring(2) + v.toString(16); - return result; + + return result as `0x${string}`; +} + +// KMS Signers are dynamically determined using env variables +async function envComputeDecryptSignaturesKms( + handlesList: EthersT.BigNumberish[], + decryptedResult: `0x${string}`, + extraData: `0x${string}`, +): Promise<`0x${string}`[]> { + const signatures: `0x${string}`[] = []; + const kmsSigners = await getEnvKMSSigners(); + for (let i = 0; i < kmsSigners.length; i++) { + const kmsSigner = kmsSigners[i]; + const signature = await envKmsSign(handlesList, decryptedResult, extraData, kmsSigner); + signatures.push(signature); + } + return signatures; +} + +// KMS Signers are dynamically determined using env variables +async function envKmsSign( + handlesList: EthersT.BigNumberish[], + decryptedResult: `0x${string}`, + extraData: `0x${string}`, + kmsSigner: EthersT.Signer, +): Promise<`0x${string}`> { + // always keep dynamic values for testing + const decryptionAddress = process.env.DECRYPTION_ADDRESS; + const gatewayChainId = process.env.CHAIN_ID_GATEWAY; + + const domain = { + name: 'Decryption', + version: '1', + chainId: gatewayChainId, + verifyingContract: decryptionAddress, + }; + + const types = { + PublicDecryptVerification: [ + { + name: 'ctHandles', + type: 'bytes32[]', + }, + { + name: 'decryptedResult', + type: 'bytes', + }, + { + name: 'extraData', + type: 'bytes', + }, + ], + }; + const message = { + ctHandles: handlesList.map(toBytes32HexString), + decryptedResult, + extraData, + }; + + const signature = await kmsSigner.signTypedData(domain, types, message); + const sigRSV = ethers.Signature.from(signature); + const v = 27 + sigRSV.yParity; + const r = sigRSV.r; + const s = sigRSV.s; + + const result = r + s.substring(2) + v.toString(16); + + return result as `0x${string}`; +} + +// Copy/paste from relayer-sdk +function toClearValueType(clearValueAsBigInt: bigint, type: number): ClearValueType { + if (type === 0) { + // ebool + return clearValueAsBigInt === BigInt(1); + } else if (type === 7) { + // eaddress + return getAddress('0x' + clearValueAsBigInt.toString(16).padStart(40, '0')); + } else if (type > 8 || type == 1) { + // type == 1 : euint4 (not supported) + throw new Error(`Unsupported handle type ${type}`); + } + // euintXXX + return clearValueAsBigInt; +} + +// Copy/paste from relayer-sdk +function buildUserDecryptResults( + handlesBytes32Hex: `0x${string}`[], + listBigIntDecryptions: bigint[], +): UserDecryptResults { + return buildClearValues(handlesBytes32Hex, listBigIntDecryptions); +} + +function buildClearValues(handlesBytes32Hex: `0x${string}`[], listBigIntDecryptions: bigint[]): ClearValues { + const typesList = getHandlesTypes(handlesBytes32Hex); + const results: ClearValues = {}; + handlesBytes32Hex.forEach( + (handle, idx) => (results[handle] = toClearValueType(listBigIntDecryptions[idx], typesList[idx])), + ); + + return results; +} + +// Copy/paste from relayer-sdk +function checkDeadlineValidity(startTimestamp: bigint, durationDays: bigint) { + if (durationDays === BigInt(0)) { + throw Error('durationDays is null'); + } + + if (durationDays > MAX_USER_DECRYPT_DURATION_DAYS) { + throw Error(`durationDays is above max duration of ${MAX_USER_DECRYPT_DURATION_DAYS}`); + } + + const currentTimestamp = BigInt(Math.floor(Date.now() / 1000)); + if (startTimestamp > currentTimestamp) { + throw Error('startTimestamp is set in the future'); + } + + const durationInSeconds = durationDays * BigInt(86400); + if (startTimestamp + durationInSeconds < currentTimestamp) { + throw Error('User decrypt request has expired'); + } +} + +// Copy/paste from relayer-sdk +function checkEncryptedBits(handlesBytes32Hex: `0x${string}`[]): number { + let total = 0; + + for (const handleBytes32Hex of handlesBytes32Hex) { + const typeDiscriminant = getHandleType(handleBytes32Hex); + total += NumEncryptedBits[typeDiscriminant as keyof typeof NumEncryptedBits]; + // enforce 2048‑bit limit + if (total > 2048) { + throw new Error('Cannot decrypt more than 2048 encrypted bits in a single request'); + } + } + return total; +} + +// Copy/paste from relayer-sdk +function isKmsThresholdReached(kmsSigners: string[], recoveredAddresses: string[], threshold: number): boolean { + if (typeof threshold !== 'number') { + throw new Error('INTERNAL ERROR'); + } + const uniq = new Map(); + recoveredAddresses.forEach((address, index) => { + if (uniq.has(address)) { + throw new Error(`Duplicate KMS signer address found: ${address} appears multiple times in recovered addresses`); + } + uniq.set(address, index); + }); + + for (const address of recoveredAddresses) { + if (!kmsSigners.includes(address)) { + throw new Error(`Invalid address found: ${address} is not in the list of KMS signers`); + } + } + return uniq.size >= threshold; +} + +// Copy/paste from relayer-sdk +function abiEncodeClearValues(clearValues: ClearValues) { + const handlesBytes32Hex = Object.keys(clearValues) as `0x${string}`[]; + + const abiTypes: string[] = []; + const abiValues: (string | bigint)[] = []; + + for (let i = 0; i < handlesBytes32Hex.length; ++i) { + const handle = handlesBytes32Hex[i]; + const handleType: EncryptedType = getHandleType(handle); + + let clearTextValue: ClearValueType = clearValues[handle as keyof typeof clearValues]; + if (typeof clearTextValue === 'boolean') { + clearTextValue = clearTextValue ? '0x01' : '0x00'; + } + + const clearTextValueBigInt = BigInt(clearTextValue); + + //abiTypes.push(fhevmTypeInfo.solidityTypeName); + abiTypes.push('uint256'); + + switch (handleType) { + // eaddress + case 7: { + // string + abiValues.push(`0x${clearTextValueBigInt.toString(16).padStart(40, '0')}`); + break; + } + // ebool + case 0: { + // bigint (0 or 1) + if (clearTextValueBigInt !== BigInt(0) && clearTextValueBigInt !== BigInt(1)) { + throw new Error(`Invalid ebool clear text value ${clearTextValueBigInt}. Expecting 0 or 1.`); + } + abiValues.push(clearTextValueBigInt); + break; + } + case 2: //euint8 + case 3: //euint16 + case 4: //euint32 + case 5: //euint64 + case 6: //euint128 + case 7: { + //euint256 + // bigint + abiValues.push(clearTextValueBigInt); + break; + } + default: { + throw new Error(`Unsupported Fhevm primitive type id: ${handleType}`); + } + } + } + + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + + // ABI encode the decryptedResult as done in the KMS, since all decrypted values + // are native static types, thay have same abi-encoding as uint256: + const abiEncodedClearValues: `0x${string}` = abiCoder.encode(abiTypes, abiValues) as `0x${string}`; + + return { + abiTypes, + abiValues, + abiEncodedClearValues, + }; +} + +// Copy/paste from relayer-sdk +function buildDecryptionProof(kmsSignatures: `0x${string}`[], extraData: `0x${string}`): `0x${string}` { + // Build the decryptionProof as numSigners + KMS signatures + extraData + const packedNumSigners = ethers.solidityPacked(['uint8'], [kmsSignatures.length]); + const packedSignatures = ethers.solidityPacked(Array(kmsSignatures.length).fill('bytes'), kmsSignatures); + const decryptionProof: `0x${string}` = ethers.concat([ + packedNumSigners, + packedSignatures, + extraData, + ]) as `0x${string}`; + return decryptionProof; +} + +// Copy/paste from relayer-sdk +function getHandleType(handleBytes32Hex: `0x${string}`): EncryptedType { + if (handleBytes32Hex.length !== 66) { + throw new Error(`Handle ${handleBytes32Hex} is not of valid length`); + } + const hexPair = handleBytes32Hex.slice(-4, -2).toLowerCase(); + const typeDiscriminant = parseInt(hexPair, 16); + + if (!(typeDiscriminant in NumEncryptedBits)) { + throw new Error(`Handle ${handleBytes32Hex} is not of valid type`); + } + return typeDiscriminant as EncryptedType; +} + +function getHandlesTypes(handlesBytes32Hex: `0x${string}`[]): EncryptedType[] { + return handlesBytes32Hex.map(getHandleType); +} + +// Copy/paste from relayer-sdk +function deserializeClearValues(handlesBytes32Hex: `0x${string}`[], abiEncodedClearValues: `0x${string}`): ClearValues { + const typesList: EncryptedType[] = getHandlesTypes(handlesBytes32Hex); + + // TODO: dummy stuff must be removed! + const restoredEncoded = + '0x' + + '00'.repeat(32) + // dummy requestID (ignored) + abiEncodedClearValues.slice(2) + + '00'.repeat(32); // dummy empty bytes[] length (ignored) + + const abiTypes = typesList.map((t) => { + const abiType = CiphertextAbiType[t]; // all types are valid because this was supposedly checked already inside the `checkEncryptedBits` function + return abiType; + }); + + const coder = new ethers.AbiCoder(); + const decoded = coder.decode(['uint256', ...abiTypes, 'bytes[]'], restoredEncoded); + + // strip dummy first/last element + const rawValues = decoded.slice(1, 1 + typesList.length); + + const results: ClearValues = {}; + handlesBytes32Hex.forEach((handle, idx) => (results[handle] = rawValues[idx])); + + return results; } diff --git a/host-contracts/test/fhevmjsTest/fhevmjsTest.ts b/host-contracts/test/fhevmjsTest/fhevmjsTest.ts index cfb76cb2f..35614eb90 100644 --- a/host-contracts/test/fhevmjsTest/fhevmjsTest.ts +++ b/host-contracts/test/fhevmjsTest/fhevmjsTest.ts @@ -2,7 +2,6 @@ import { expect } from 'chai'; import { createInstances } from '../instance'; import { getSigners, initSigners } from '../signers'; -import { bigIntToBytes64, bigIntToBytes128, bigIntToBytes256 } from '../utils'; describe('Testing fhevmjs/fhevmjsMocked', function () { before(async function () { @@ -46,18 +45,18 @@ describe('Testing fhevmjs/fhevmjsMocked', function () { expect(() => input.add64(1n)).to.throw('Packing more than 2048 bits in a single input ciphertext is unsupported'); }); - it('should be able to pack up to 2 euint128s', async function () { + it('should be able to pack up to 8 euint256s', async function () { const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - for (let i = 0; i < 2; i++) { - input.addBytes128(bigIntToBytes128(797979n)); + for (let i = 0; i < 8; i++) { + input.add256(797979n); } await input.encrypt(); }); - it('should be unable to pack more than 2 euint128s', async function () { + it('should not be able to pack more than 8 euint256s', async function () { const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - for (let i = 0; i < 2; i++) { - input.addBytes128(bigIntToBytes128(797979n)); + for (let i = 0; i < 8; i++) { + input.add256(797979n); } expect(() => input.addBool(false)).to.throw( 'Packing more than 2048 bits in a single input ciphertext is unsupported', @@ -66,12 +65,22 @@ describe('Testing fhevmjs/fhevmjsMocked', function () { it('should be able to pack up to 2048 bits but not more', async function () { const input = this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); - input.addBytes128(bigIntToBytes128(797979n)); - input.addBytes64(bigIntToBytes64(797979n)); + input.add256(797979n); + input.add256(797978n); + input.add256(797977n); + input.add256(797976n); + input.add256(797975n); + input.add256(797974n); input.add256(6887n); input.add128(6887n); input.add64(6887n); input.add64(6887n); + let bits = input.getBits(); + let total = 0; + for (let i = 0; i < bits.length; ++i) { + total += bits[i]; + } + expect(total).to.eq(2048); expect(() => input.addBool(false)).to.throw( 'Packing more than 2048 bits in a single input ciphertext is unsupported', ); diff --git a/host-contracts/test/inputVerifier/inputVerifier.ts b/host-contracts/test/inputVerifier/inputVerifier.ts index 728429c64..a2eab9b5e 100644 --- a/host-contracts/test/inputVerifier/inputVerifier.ts +++ b/host-contracts/test/inputVerifier/inputVerifier.ts @@ -4,7 +4,7 @@ import type { ethers as EthersT } from 'ethers'; import fs from 'fs'; import { ethers } from 'hardhat'; -import { InputVerifier, InputVerifier__factory, TestInput } from '../../types'; +import { InputVerifier, InputVerifier__factory, TestInput } from '../../typechain-types'; import { createInstances } from '../instance'; import { Signers, getSigners, initSigners } from '../signers'; import { FhevmInstances } from '../types'; @@ -23,6 +23,15 @@ describe('InputVerifier', function () { privateKey: string; }; + // This pass is necessary to restore the original InputVerifier state + // If this pass is omitted, future tests may fail + afterEach(async function () { + process.env.NUM_COPROCESSORS = '1'; + const coprocessorAddressSigner0 = process.env['COPROCESSOR_SIGNER_ADDRESS_0']!; + const tx = await inputVerifier.connect(deployer).defineNewContext([coprocessorAddressSigner0], 1); + await tx.wait(); + }); + before(async function () { await initSigners(2); signers = await getSigners(); @@ -72,6 +81,11 @@ describe('InputVerifier', function () { keypair.privateKey, keypair.publicKey, ); + if (typeof clearUint64 !== 'bigint') { + throw new Error( + `Unexpected user decryption result type. Expected 'bigint', got '${typeof clearUint64}' instead.`, + ); + } return clearUint64; } diff --git a/host-contracts/test/instance.ts b/host-contracts/test/instance.ts index 7e98f3bea..c3ac4dd5f 100644 --- a/host-contracts/test/instance.ts +++ b/host-contracts/test/instance.ts @@ -1,108 +1,59 @@ -import { - clientKeyDecryptor, - createEIP712, - createInstance as createFhevmInstance, - generateKeypair, - getCiphertextCallParams, -} from '@zama-fhe/relayer-sdk/node'; -import dotenv from 'dotenv'; -import { readFileSync } from 'fs'; -import * as fs from 'fs'; -import { ethers, ethers as hethers, network } from 'hardhat'; -import { homedir } from 'os'; -import path from 'path'; +import type { FhevmInstance } from '@zama-fhe/relayer-sdk/node'; +import type { ethers as EthersT } from 'ethers'; +import { ethers } from 'hardhat'; -import { awaitCoprocessor, getClearText } from './coprocessorUtils'; -import { createEncryptedInputMocked, userDecryptRequestMocked } from './fhevmjsMocked'; +import { + assertNetwork, + awaitCoprocessor, + createInstanceMocked, + getClearText, + getEnvFhevmMockConfig, + getTxHCUFromTxReceipt, +} from './fhevmjsMocked'; import type { Signers } from './signers'; import { FhevmInstances } from './types'; -const FHE_CLIENT_KEY_PATH = process.env.FHE_CLIENT_KEY_PATH; - -let clientKey: Uint8Array | undefined; - -const abiKmsVerifier = ['function getKmsSigners() view returns (address[])']; - -const kmsAdd = dotenv.parse(fs.readFileSync('addresses/.env.host')).KMS_VERIFIER_CONTRACT_ADDRESS; -const aclAdd = dotenv.parse(fs.readFileSync('addresses/.env.host')).ACL_CONTRACT_ADDRESS; -const gatewayChainID = +process.env.CHAIN_ID_GATEWAY!; -const hostChainId = Number(network.config.chainId); -const verifyingContract = process.env.DECRYPTION_ADDRESS!; - -const getKMSSigners = async (): Promise => { - const kmsContract = new ethers.Contract(kmsAdd, abiKmsVerifier, ethers.provider); - const signers: string[] = await kmsContract.getKmsSigners(); - return signers; -}; - -const createInstanceMocked = async () => { - const kmsSigners = await getKMSSigners(); - - const instance = { - userDecrypt: userDecryptRequestMocked( - kmsSigners, - gatewayChainID, - hostChainId, - verifyingContract, - aclAdd, - 'http://localhost:3000', - ethers.provider, - ), - createEncryptedInput: createEncryptedInputMocked, - getPublicKey: () => '0xFFAA44433', - generateKeypair: generateKeypair, - createEIP712: createEIP712(verifyingContract, network.config.chainId), - }; - return instance; -}; +const abiAcl = [ + 'function delegateForUserDecryption(address,address,uint64)', + 'function revokeDelegationForUserDecryption(address,address)', +]; export const createInstances = async (accounts: Signers): Promise => { - // Create instance + assertNetwork('hardhat'); + const instances: FhevmInstances = {} as FhevmInstances; - if (network.name === 'hardhat') { - await Promise.all( - Object.keys(accounts).map(async (k) => { - instances[k as keyof FhevmInstances] = await createInstanceMocked(); - }), - ); - } else { - await Promise.all( - Object.keys(accounts).map(async (k) => { - instances[k as keyof FhevmInstances] = await createInstance(); - }), - ); - } + await Promise.all( + Object.keys(accounts).map(async (k) => { + instances[k as keyof FhevmInstances] = await createInstanceMocked(getEnvFhevmMockConfig()); + }), + ); + return instances; }; -export const createInstance = async () => { - const relayerUrl = 'http://localhost:3000'; - const instance = await createFhevmInstance({ - verifyingContractAddress: verifyingContract, - kmsContractAddress: kmsAdd, - aclContractAddress: aclAdd, - network: network.config.url, - relayerUrl: relayerUrl, - gatewayChainId: gatewayChainID || '54321', - }); +export const createInstance = async (): Promise => { + assertNetwork('hardhat'); + const instance = await createInstanceMocked(getEnvFhevmMockConfig()); return instance; }; -const getCiphertext = async (handle: string, ethers: typeof hethers): Promise => { - return ethers.provider.call(getCiphertextCallParams(handle)); +export const delegateUserDecryption = async ( + delegator: EthersT.Signer, + delegate: string, + contractAddress: string, + expirationDate: bigint, +): Promise => { + const aclContract = new ethers.Contract(getEnvFhevmMockConfig().aclContractAddress, abiAcl, delegator); + return aclContract.delegateForUserDecryption(delegate, contractAddress, expirationDate); }; -const getDecryptor = () => { - if (clientKey == null) { - if (FHE_CLIENT_KEY_PATH) { - clientKey = readFileSync(FHE_CLIENT_KEY_PATH); - } else { - const home = homedir(); - const clientKeyPath = path.join(home, 'network-fhe-keys/cks'); - clientKey = readFileSync(clientKeyPath); - } - } - return clientKeyDecryptor(clientKey); +export const revokeUserDecryptionDelegation = async ( + delegator: EthersT.Signer, + delegate: string, + contractAddress: string, +): Promise => { + const aclContract = new ethers.Contract(getEnvFhevmMockConfig().aclContractAddress, abiAcl, delegator); + return aclContract.revokeDelegationForUserDecryption(delegate, contractAddress); }; /** @@ -110,16 +61,13 @@ const getDecryptor = () => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bool} */ export const decryptBool = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return (await getClearText(handle)) === '1'; - } else { - return getDecryptor().decryptBool(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return (await getClearText(handle)) === '1'; }; /** @@ -127,16 +75,13 @@ export const decryptBool = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt8 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt8(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -144,16 +89,13 @@ export const decrypt8 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt16 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt16(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -161,16 +103,13 @@ export const decrypt16 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt32 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt32(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -178,16 +117,13 @@ export const decrypt32 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt64 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt64(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -195,16 +131,13 @@ export const decrypt64 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt128 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt128(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -212,16 +145,13 @@ export const decrypt128 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt256 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt256(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -229,67 +159,15 @@ export const decrypt256 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {string} */ export const decryptAddress = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - const bigintAdd = BigInt(await getClearText(handle)); - const handleStr = '0x' + bigintAdd.toString(16).padStart(40, '0'); - return handleStr; - } else { - return getDecryptor().decryptAddress(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + const bigintAdd = BigInt(await getClearText(handle)); + const handleStr = '0x' + bigintAdd.toString(16).padStart(40, '0'); + return handleStr; }; -/** - * @debug - * This function is intended for debugging purposes only. - * It cannot be used in production code, since it requires the FHE private key for decryption. - * - * @param {bigint} a handle to decrypt - * @returns {bigint} - */ -export const decryptEbytes64 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decryptEbytes64(await getCiphertext(handle, ethers)); - } -}; - -/** - * @debug - * This function is intended for debugging purposes only. - * It cannot be used in production code, since it requires the FHE private key for decryption. - * - * @param {bigint} a handle to decrypt - * @returns {bigint} - */ -export const decryptEbytes128 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decryptEbytes128(await getCiphertext(handle, ethers)); - } -}; - -/** - * @debug - * This function is intended for debugging purposes only. - * It cannot be used in production code, since it requires the FHE private key for decryption. - * - * @param {bigint} a handle to decrypt - * @returns {bigint} - */ -export const decryptEbytes256 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decryptEbytes256(await getCiphertext(handle, ethers)); - } -}; +export { getTxHCUFromTxReceipt, awaitCoprocessor }; diff --git a/host-contracts/test/kmsVerifier/kmsVerifier.ts b/host-contracts/test/kmsVerifier/kmsVerifier.ts index 93e5e90c5..fdcb2ecc6 100644 --- a/host-contracts/test/kmsVerifier/kmsVerifier.ts +++ b/host-contracts/test/kmsVerifier/kmsVerifier.ts @@ -1,108 +1,287 @@ -// import { expect } from 'chai'; -// import dotenv from 'dotenv'; -// import fs from 'fs'; -// import { ethers } from 'hardhat'; - -// import { createInstances } from '../instance'; -// import { getSigners, initSigners } from '../signers'; -// import { bigIntToBytes256 } from '../utils'; - -// describe('KMSVerifier', function () { -// before(async function () { -// await initSigners(2); -// this.signers = await getSigners(); -// this.instances = await createInstances(this.signers); -// this.kmsFactory = await ethers.getContractFactory('KMSVerifier'); -// }); - -// it('original owner adds one signer, then adds two more signers, then removes one signer', async function () { -// if (process.env.HARDHAT_PARALLEL !== '1') { -// // to avoid messing up other tests if used on the real node, in parallel testing - -// const origKMSAdd = dotenv.parse(fs.readFileSync('addresses/.env.host')).KMS_VERIFIER_CONTRACT_ADDRESS; -// const deployer = new ethers.Wallet(process.env.DEPLOYER_PRIVATE_KEY!).connect(ethers.provider); -// const kmsVerifier = await this.kmsFactory.attach(origKMSAdd); -// expect(await kmsVerifier.getVersion()).to.equal('KMSVerifier v0.1.0'); - -// const addressSigner = process.env['KMS_SIGNER_ADDRESS_1']!; -// let setSigners = await kmsVerifier.getKmsSigners(); -// setSigners = [...setSigners, addressSigner]; -// const tx1 = await kmsVerifier.connect(deployer).defineNewContext(setSigners, 1); -// await tx1.wait(); - -// expect((await kmsVerifier.getKmsSigners()).length).to.equal(2); // one signer has been added - -// const contractFactory = await ethers.getContractFactory('TestAsyncDecrypt'); -// const contract = (await contractFactory.connect(this.signers.alice).deploy()) as TestAsyncDecrypt; -// const tx2 = await contract.requestBool(); -// await tx2.wait(); -// await awaitAllDecryptionResults(); -// expect(await contract.yBool()).to.equal(true); // in this case, one signature still suffices to pass the decrypt (threshold is still 1) - -// setSigners = [...setSigners, addressSigner]; -// await expect(kmsVerifier.connect(deployer).defineNewContext(setSigners, 1)).to.revertedWithCustomError( -// kmsVerifier, -// 'KMSAlreadySigner', -// ); // cannot add duplicated signer -// expect((await kmsVerifier.getKmsSigners()).length).to.equal(2); - -// const kmsSigner2Address = process.env['KMS_SIGNER_ADDRESS_2']!; -// const kmsSigner3Address = process.env['KMS_SIGNER_ADDRESS_3']!; -// let setSigners2 = await kmsVerifier.getKmsSigners(); -// setSigners2 = [...setSigners2, kmsSigner2Address, kmsSigner3Address]; -// const tx3 = await kmsVerifier.connect(deployer).defineNewContext(setSigners2, 1); -// await tx3.wait(); -// expect((await kmsVerifier.getKmsSigners()).length).to.equal(4); // 3rd and 4th signer has been added successfully - -// const tx4 = await kmsVerifier.connect(deployer).setThreshold(2n); -// await tx4.wait(); -// expect(await kmsVerifier.getThreshold()).to.equal(2); - -// const tx5 = await contract.requestUint16(); -// await tx5.wait(); - -// await expect(awaitAllDecryptionResults()) -// .to.revertedWithCustomError(kmsVerifier, 'KMSSignatureThresholdNotReached') -// .withArgs(1n); // should revert because now we are below the threshold! (we receive only 1 signature but threshold is 2) - -// process.env.NUM_KMS_NODES = '4'; - -// const tx6 = await contract.requestUint8(); -// await tx6.wait(); -// await awaitAllDecryptionResults(); -// expect(await contract.yUint8()).to.equal(42); // even with more than 2 signatures decryption should still succeed - -// process.env.NUM_KMS_NODES = '2'; -// process.env.KMS_SIGNER_ADDRESS_1 = process.env.KMS_SIGNER_ADDRESS_0; -// const tx8 = await contract.requestUint16(); -// await tx8.wait(); -// await expect(awaitAllDecryptionResults()).to.revertedWithCustomError(contract, 'InvalidKMSSignatures'); // cannot use duplicated signatures if threshold is 2 -// expect(await contract.yUint16()).to.equal(0); - -// process.env.NUM_KMS_NODES = '1'; -// let setSigners3 = [...(await kmsVerifier.getKmsSigners())]; -// setSigners3.pop(); - -// const tx9 = await kmsVerifier.connect(deployer).defineNewContext(setSigners3, 1); -// await tx9.wait(); -// expect(await kmsVerifier.getThreshold()).to.equal(1); - -// const tx10 = await contract.requestUint16(); -// await tx10.wait(); -// await awaitAllDecryptionResults(); -// expect(await contract.yUint16()).to.equal(16); // after removing one of the 4 signers, one signature is enough for decryption -// } -// }); - -// it('cannot add/remove signers if not the owner', async function () { -// const origKMSAdd = dotenv.parse(fs.readFileSync('addresses/.env.host')).KMS_VERIFIER_CONTRACT_ADDRESS; -// const kmsVerifier = await this.kmsFactory.attach(origKMSAdd); -// let setSigners = await kmsVerifier.getKmsSigners(); -// const randomAccount = this.signers.carol; -// setSigners = [...setSigners, randomAccount]; -// await expect(kmsVerifier.connect(randomAccount).defineNewContext(setSigners, 2)).to.be.revertedWithCustomError( -// kmsVerifier, -// 'NotHostOwner', -// ); -// }); -// }); +import type { FhevmInstance, PublicDecryptResults } from '@zama-fhe/relayer-sdk/node'; +import { expect } from 'chai'; +import dotenv from 'dotenv'; +import type { ethers as EthersT } from 'ethers'; +import fs from 'fs'; +import { ethers } from 'hardhat'; + +import { KMSVerifier, KMSVerifier__factory, TestInput } from '../../typechain-types'; +import { createInstance } from '../instance'; +import { Signers, getSigners, initSigners } from '../signers'; + +describe('KmsVerifier', function () { + let signers: Signers; + let instance: FhevmInstance; + let kmsVerifierFactory: KMSVerifier__factory; + let kmsVerifier: KMSVerifier; + let deployer: EthersT.Wallet; + let testInputContract: TestInput; + let testInputContractAddress: string; + + // This pass is necessary to restore the original KMSVerifier state + // If this pass is omitted, future tests may fail + afterEach(async function () { + process.env.NUM_KMS_NODES = '1'; + const kmsAddressSigner0 = process.env['KMS_SIGNER_ADDRESS_0']!; + const tx = await kmsVerifier.connect(deployer).defineNewContext([kmsAddressSigner0], 1); + await tx.wait(); + }); + + before(async function () { + await initSigners(2); + signers = await getSigners(); + kmsVerifierFactory = await ethers.getContractFactory('KMSVerifier'); + }); + + beforeEach(async function () { + process.env.NUM_KMS_NODES = '1'; + const origIVAdd = dotenv.parse(fs.readFileSync('addresses/.env.host')).KMS_VERIFIER_CONTRACT_ADDRESS; + deployer = new ethers.Wallet(process.env.DEPLOYER_PRIVATE_KEY!).connect(ethers.provider); + kmsVerifier = kmsVerifierFactory.attach(origIVAdd) as KMSVerifier; + expect(await kmsVerifier.getVersion()).to.equal('KMSVerifier v0.1.0'); + await resetInstance(); + }); + + async function addSigners(params: { list: string[]; threshold: number }) { + let signersList = await kmsVerifier.getKmsSigners(); + signersList = [...signersList, ...params.list]; + const tx = await kmsVerifier.connect(deployer).defineNewContext(signersList, params.threshold); + await tx.wait(); + } + + async function removeLastSigner(params: { threshold: number }) { + let signersList = [...(await kmsVerifier.getKmsSigners())]; + signersList.pop(); + const tx = await kmsVerifier.connect(deployer).defineNewContext(signersList, params.threshold); + await tx.wait(); + } + + async function resetInstance() { + instance = await createInstance(); + } + + async function testInputSetPublicUint64(value: bigint) { + let inputAlice = instance.createEncryptedInput(testInputContractAddress, signers.alice.address); + inputAlice.add64(value); + let encryptedAmount = await inputAlice.encrypt(); + + let tx = await testInputContract.setPublicUint64(encryptedAmount.handles[0], encryptedAmount.inputProof); + await tx.wait(); + } + + async function testInputCheckPublicUint64(results: PublicDecryptResults) { + let tx = await testInputContract.checkPublicUint64(results.abiEncodedClearValues, results.decryptionProof); + await tx.wait(); + } + + async function publicDecryptUint64(): Promise<{ results: PublicDecryptResults; clearUint64: bigint }> { + const encUint64 = (await testInputContract.getEuint64()) as `0x${string}`; + if (typeof encUint64 !== 'string') { + throw new Error(`Unexpected getEuint64() return type`); + } + const res = await instance.publicDecrypt([encUint64]); + const clearUint64 = res.clearValues[encUint64]; + if (typeof clearUint64 !== 'bigint') { + throw new Error( + `Unexpected user decryption result type. Expected 'bigint', got '${typeof clearUint64}' instead.`, + ); + } + return { results: res, clearUint64 }; + } + + async function deployTestInput() { + const testInputContractFactory = await ethers.getContractFactory('TestInput'); + testInputContract = await testInputContractFactory.connect(signers.alice).deploy(); + await testInputContract.waitForDeployment(); + testInputContractAddress = await testInputContract.getAddress(); + } + + async function expectKmsSigners(params: { + numberOfKmsNodes: number; + numberOfSigners: number; + threshold: number; + signers: string[]; + }) { + const kmsSignersList = await kmsVerifier.getKmsSigners(); + expect(process.env.NUM_KMS_NODES).to.equal(params.numberOfKmsNodes.toString()); + expect(kmsSignersList.length).to.eq(params.numberOfSigners); + for (let i = 0; i < params.signers.length; ++i) { + expect(kmsSignersList.includes(params.signers[i])); + } + expect(await kmsVerifier.getThreshold()).to.equal(params.threshold); + } + + it('original owner adds one signer, then adds one more signers, then adds one more, then removes one signer', async function () { + if (process.env.HARDHAT_PARALLEL !== '1') { + // to avoid messing up other tests if used on the real node, in parallel testing + const kmsAddressSigner0 = process.env['KMS_SIGNER_ADDRESS_0']!; + const kmsAddressSigner1 = process.env['KMS_SIGNER_ADDRESS_1']!; + const kmsAddressSigner2 = process.env['KMS_SIGNER_ADDRESS_2']!; + const kmsAddressSigner3 = process.env['KMS_SIGNER_ADDRESS_3']!; + + // - 1 active kms node + // - 1 registered kms nodes + // - threshold 1 + await expectKmsSigners({ + numberOfKmsNodes: 1, + numberOfSigners: 1, + threshold: 1, + signers: [kmsAddressSigner0], + }); + + // Register a new kms signer + await addSigners({ list: [kmsAddressSigner1], threshold: 1 }); + await resetInstance(); + + // - 1 active kms signer + // - 2 registered kms signers + // - threshold 1 + await expectKmsSigners({ + numberOfKmsNodes: 1, + numberOfSigners: 2, + threshold: 1, + signers: [kmsAddressSigner0, kmsAddressSigner1], + }); + + // Deploy TestInput + await deployTestInput(); + + await testInputSetPublicUint64(18446744073709550042n); + let pubDec = await publicDecryptUint64(); + + // in this case, one signature still suffices to pass the decrypt (threshold is still 1) + expect(pubDec.clearUint64).to.equal(18446744073709550042n); + // check signatures should succeed + await testInputCheckPublicUint64(pubDec.results); + + // Try add a kms signer already registered + await expect(addSigners({ list: [kmsAddressSigner1], threshold: 1 })).to.revertedWithCustomError( + kmsVerifier, + 'KMSAlreadySigner', + ); // cannot add duplicated signer + expect((await kmsVerifier.getKmsSigners()).length).to.equal(2); + + // Add 2 new kms signers (total 4) + await addSigners({ list: [kmsAddressSigner2, kmsAddressSigner3], threshold: 1 }); + let tx = await kmsVerifier.connect(deployer).setThreshold(2n); + await tx.wait(); + await resetInstance(); + + // - 1 active kms node + // - 4 registered kms nodes + // - threshold 2 + await expectKmsSigners({ + numberOfKmsNodes: 1, + numberOfSigners: 4, + threshold: 2, + signers: [kmsAddressSigner0, kmsAddressSigner1, kmsAddressSigner2, kmsAddressSigner3], + }); + + // Let's disable KMS threshold assertion in the FhevmInstance.publicDecrypt() + process.env.DISABLE_TEST_KMS_THRESHOLD = '1'; + try { + // Let's set a new euint64 value + await testInputSetPublicUint64(19n); + // get the KMS decryption and signatures (only 1 here) + pubDec = await publicDecryptUint64(); + // decrypted value is correct, but the number of KMS signatures is not enough + expect(pubDec.clearUint64).to.equal(19n); + + // now we need at least 2 signatures (threshold is 2) but we only have 1 + await expect(testInputCheckPublicUint64(pubDec.results)) + .to.revertedWithCustomError(kmsVerifier, 'KMSSignatureThresholdNotReached') + .withArgs(1n); + } finally { + process.env.DISABLE_TEST_KMS_THRESHOLD = '0'; + } + + process.env.NUM_KMS_NODES = '4'; + + // - 4 active kms nodes + // - 4 registered kms nodes + // - threshold 2 + await expectKmsSigners({ + numberOfKmsNodes: 4, + numberOfSigners: 4, + threshold: 2, + signers: [kmsAddressSigner0, kmsAddressSigner1, kmsAddressSigner2, kmsAddressSigner3], + }); + + // even with more than 2 signatures decryption should still succeed + await testInputSetPublicUint64(1992n); + pubDec = await publicDecryptUint64(); + expect(pubDec.clearUint64).to.equal(1992n); + await testInputCheckPublicUint64(pubDec.results); + + process.env.NUM_KMS_NODES = '3'; + + // - 3 active kms nodes + // - 4 registered kms nodes + // - threshold 2 + await expectKmsSigners({ + numberOfKmsNodes: 3, + numberOfSigners: 4, + threshold: 2, + signers: [kmsAddressSigner0, kmsAddressSigner1, kmsAddressSigner2, kmsAddressSigner3], + }); + + // 3 signatures should still work + await testInputSetPublicUint64(873n); + pubDec = await publicDecryptUint64(); + expect(pubDec.clearUint64).to.equal(873n); + await testInputCheckPublicUint64(pubDec.results); + + const initial_kms_signer_address_1 = process.env['KMS_SIGNER_ADDRESS_1']!; + process.env.NUM_KMS_NODES = '2'; + // WARNING: this makes both addresses identical in env + // Force having actually 1 single registered kms signer + process.env.KMS_SIGNER_ADDRESS_1 = process.env.KMS_SIGNER_ADDRESS_0; + + // Let's disable KMS threshold assertion in the FhevmInstance.publicDecrypt() + process.env.DISABLE_TEST_KMS_THRESHOLD = '1'; + try { + await testInputSetPublicUint64(999n); + pubDec = await publicDecryptUint64(); + expect(pubDec.clearUint64).to.equal(999n); + // FHE.checkSignatures should revert with FHE.InvalidKMSSignatures error + await expect(testInputCheckPublicUint64(pubDec.results)).to.revertedWithCustomError( + testInputContract, + 'InvalidKMSSignatures', + ); + } finally { + process.env.DISABLE_TEST_KMS_THRESHOLD = '0'; + } + + // Put back the original addresses for future tests + process.env.KMS_SIGNER_ADDRESS_1 = initial_kms_signer_address_1; + + // Remove last kms signer + process.env.NUM_KMS_NODES = '1'; + await removeLastSigner({ threshold: 1 }); + await resetInstance(); + + await expectKmsSigners({ + numberOfKmsNodes: 1, + numberOfSigners: 3, + threshold: 1, + signers: [kmsAddressSigner0, kmsAddressSigner1, kmsAddressSigner2], + }); + + // after removing one of the 4 signers, one signature is enough for decryption + await testInputSetPublicUint64(1001n); + pubDec = await publicDecryptUint64(); + expect(pubDec.clearUint64).to.equal(1001n); + await testInputCheckPublicUint64(pubDec.results); + } + }); + + it('cannot add/remove signers if not the owner', async function () { + let kmsSignersList = await kmsVerifier.getKmsSigners(); + const randomAccount = signers.carol; + kmsSignersList = [...kmsSignersList, randomAccount.address]; + await expect(kmsVerifier.connect(randomAccount).defineNewContext(kmsSignersList, 2)).to.be.revertedWithCustomError( + kmsVerifier, + 'NotHostOwner', + ); + }); +}); diff --git a/host-contracts/test/rand/Rand.fixture.ts b/host-contracts/test/rand/Rand.fixture.ts index c4e5e5f6c..e724e88b3 100644 --- a/host-contracts/test/rand/Rand.fixture.ts +++ b/host-contracts/test/rand/Rand.fixture.ts @@ -1,6 +1,6 @@ import { ethers } from 'hardhat'; -import type { Rand } from '../../types'; +import type { Rand } from '../../typechain-types'; import { getSigners } from '../signers'; export async function deployRandFixture(): Promise { diff --git a/host-contracts/test/rand/Rand.ts b/host-contracts/test/rand/Rand.ts index ded391cb2..5c9de0e7e 100644 --- a/host-contracts/test/rand/Rand.ts +++ b/host-contracts/test/rand/Rand.ts @@ -10,9 +10,6 @@ import { decrypt128, decrypt256, decryptBool, - decryptEbytes64, - decryptEbytes128, - decryptEbytes256, } from '../instance'; import { getSigners, initSigners } from '../signers'; import { deployRandFixture } from './Rand.fixture'; @@ -45,7 +42,7 @@ describe('Rand', function () { }); it('8 bits generate and decrypt', async function () { - const values: number[] = []; + const values: bigint[] = []; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate8(); await txn.wait(); @@ -60,7 +57,7 @@ describe('Rand', function () { }); it('8 bits generate with upper bound and decrypt', async function () { - const values: number[] = []; + const values: bigint[] = []; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate8UpperBound(128); await txn.wait(); @@ -75,7 +72,7 @@ describe('Rand', function () { }); it('16 bits generate and decrypt', async function () { - const values: number[] = []; + const values: bigint[] = []; let has16bit: boolean = false; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate16(); @@ -96,7 +93,7 @@ describe('Rand', function () { }); it('16 bits generate with upper bound and decrypt', async function () { - const values: number[] = []; + const values: bigint[] = []; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate16UpperBound(8192); await txn.wait(); @@ -111,7 +108,7 @@ describe('Rand', function () { }); it('32 bits generate and decrypt', async function () { - const values: number[] = []; + const values: bigint[] = []; let has32bit: boolean = false; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate32(); @@ -132,7 +129,7 @@ describe('Rand', function () { }); it('32 bits generate with upper bound and decrypt', async function () { - const values: number[] = []; + const values: bigint[] = []; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate32UpperBound(262144); await txn.wait(); @@ -259,7 +256,7 @@ describe('Rand', function () { if (network.name === 'hardhat') { // snapshots are only possible in hardhat node, i.e in mocked mode this.snapshotId = await ethers.provider.send('evm_snapshot'); - const values: number[] = []; + const values: bigint[] = []; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate8(); await txn.wait(); @@ -275,7 +272,7 @@ describe('Rand', function () { await ethers.provider.send('evm_revert', [this.snapshotId]); this.snapshotId = await ethers.provider.send('evm_snapshot'); - const values2: number[] = []; + const values2: bigint[] = []; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate8(); await txn.wait(); @@ -289,7 +286,7 @@ describe('Rand', function () { expect(unique2.size).to.be.greaterThanOrEqual(2); await ethers.provider.send('evm_revert', [this.snapshotId]); - const values3: number[] = []; + const values3: bigint[] = []; let has16bit: boolean = false; for (let i = 0; i < 5; i++) { const txn = await this.rand.generate16(); diff --git a/host-contracts/test/regressions/Regression1.ts b/host-contracts/test/regressions/Regression1.ts index f695ea21c..163dcf22f 100644 --- a/host-contracts/test/regressions/Regression1.ts +++ b/host-contracts/test/regressions/Regression1.ts @@ -1,9 +1,9 @@ import { expect } from 'chai'; import { ethers } from 'hardhat'; +import { Regression1 } from '../../typechain-types/examples/Regression1'; import { createInstances } from '../instance'; import { getSigners, initSigners } from '../signers'; -import { Regression1 } from '../types/contracts/Regression1'; describe('Service', function () { before(async function () { @@ -27,7 +27,7 @@ describe('Service', function () { const testContract = await deployTestFixture(); this.testServiceContractAddress = await testContract.getAddress(); this.testService = testContract; - this.instances = await createInstances(this.testServiceContractAddress, ethers, this.signers); + this.instances = await createInstances(this.signers); }); it('should create and update Service', async function () { diff --git a/host-contracts/test/signers.ts b/host-contracts/test/signers.ts index 5928f6450..ebde5dd63 100644 --- a/host-contracts/test/signers.ts +++ b/host-contracts/test/signers.ts @@ -1,5 +1,6 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { exec as oldExec } from 'child_process'; +import { HDNodeWallet } from 'ethers'; import { config, ethers } from 'hardhat'; import { promisify } from 'util'; @@ -8,12 +9,12 @@ import { waitForBalance } from './utils'; const exec = promisify(oldExec); export interface Signers { - alice: HardhatEthersSigner; - bob: HardhatEthersSigner; - carol: HardhatEthersSigner; - dave: HardhatEthersSigner; - eve: HardhatEthersSigner; - fred: HardhatEthersSigner; + alice: HardhatEthersSigner | HDNodeWallet; + bob: HardhatEthersSigner | HDNodeWallet; + carol: HardhatEthersSigner | HDNodeWallet; + dave: HardhatEthersSigner | HDNodeWallet; + eve: HardhatEthersSigner | HDNodeWallet; + fred: HardhatEthersSigner | HDNodeWallet; } let signers: Signers; diff --git a/host-contracts/test/tasks/ownership.ts b/host-contracts/test/tasks/ownership.ts index 62b0e3882..9126928c0 100644 --- a/host-contracts/test/tasks/ownership.ts +++ b/host-contracts/test/tasks/ownership.ts @@ -4,7 +4,7 @@ import fs from 'fs'; import { ethers, run } from 'hardhat'; import { getRequiredEnvVar } from '../../tasks/utils/loadVariables'; -import { ACL } from '../../types'; +import { ACL } from '../../typechain-types'; describe('Ownership tasks', function () { let acl: ACL; diff --git a/host-contracts/test/tasks/pausing.ts b/host-contracts/test/tasks/pausing.ts index 7feb14063..4c8bddf16 100644 --- a/host-contracts/test/tasks/pausing.ts +++ b/host-contracts/test/tasks/pausing.ts @@ -3,9 +3,11 @@ import dotenv from 'dotenv'; import fs from 'fs'; import { ethers, run } from 'hardhat'; +import { ACL } from '../../typechain-types'; + describe('Pausing and Unpausing Tasks', function () { let pauserSet; - let acl; + let acl: ACL; describe('Hardhat pausing/unpausing tasks', function () { before(async function () { @@ -13,8 +15,8 @@ describe('Pausing and Unpausing Tasks', function () { let aclFactory = await ethers.getContractFactory('ACL'); const origPauserSetAdd = dotenv.parse(fs.readFileSync('addresses/.env.host')).PAUSER_SET_CONTRACT_ADDRESS; const origACLAdd = dotenv.parse(fs.readFileSync('addresses/.env.host')).ACL_CONTRACT_ADDRESS; - pauserSet = await pauserSetFactory.attach(origPauserSetAdd); - acl = await aclFactory.attach(origACLAdd); + pauserSet = pauserSetFactory.attach(origPauserSetAdd); + acl = aclFactory.attach(origACLAdd) as ACL; }); it('Should pause acl', async function () { diff --git a/host-contracts/test/tracing/tracing.ts b/host-contracts/test/tracing/tracing.ts index 137f69a7e..d6b747d42 100644 --- a/host-contracts/test/tracing/tracing.ts +++ b/host-contracts/test/tracing/tracing.ts @@ -1,7 +1,6 @@ import { ethers, network } from 'hardhat'; -import { awaitCoprocessor } from '../coprocessorUtils'; -import { createInstances } from '../instance'; +import { awaitCoprocessor, createInstances } from '../instance'; import { getSigners, initSigners } from '../signers'; describe('Tracing', function () { diff --git a/host-contracts/test/types.ts b/host-contracts/test/types.ts index 0c770229c..7bfd6e5eb 100644 --- a/host-contracts/test/types.ts +++ b/host-contracts/test/types.ts @@ -1,6 +1,6 @@ import type { FhevmInstance } from '@zama-fhe/relayer-sdk/node'; -import { EncryptedERC20, Rand } from '../types'; +import { EncryptedERC20, Rand } from '../typechain-types'; import type { Signers } from './signers'; declare module 'mocha' { diff --git a/host-contracts/test/upgrades/upgrades.ts b/host-contracts/test/upgrades/upgrades.ts index 83cafe8e5..5129a35e6 100644 --- a/host-contracts/test/upgrades/upgrades.ts +++ b/host-contracts/test/upgrades/upgrades.ts @@ -3,7 +3,7 @@ import dotenv from 'dotenv'; import fs from 'fs'; import { ethers, upgrades } from 'hardhat'; -import { ACL, ACLUpgradedExample } from '../../types'; +import { ACL, ACLUpgradedExample, FHEVMExecutorUpgradedExample } from '../../typechain-types'; import { getSigners, initSigners } from '../signers'; describe('Upgrades', function () { @@ -69,7 +69,7 @@ describe('Upgrades', function () { call: { fn: 'initializeFromEmptyProxy' }, }); await executor.waitForDeployment(); - expect(await executor.getVersion()).to.equal('FHEVMExecutor v0.1.0'); + expect(await (executor as FHEVMExecutorUpgradedExample).getVersion()).to.equal('FHEVMExecutor v0.1.0'); const executor2 = await upgrades.upgradeProxy(executor, executorFactoryUpgraded); await executor2.waitForDeployment(); expect(await executor2.getVersion()).to.equal('FHEVMExecutor v0.4.0'); diff --git a/host-contracts/test/reencryption/reencryption.ts b/host-contracts/test/userDecrypt/userDecrypt.ts similarity index 86% rename from host-contracts/test/reencryption/reencryption.ts rename to host-contracts/test/userDecrypt/userDecrypt.ts index fea98bca3..128ce04a8 100644 --- a/host-contracts/test/reencryption/reencryption.ts +++ b/host-contracts/test/userDecrypt/userDecrypt.ts @@ -5,12 +5,12 @@ import { createInstances } from '../instance'; import { getSigners, initSigners } from '../signers'; import { userDecryptSingleHandle } from '../utils'; -describe('Reencryption', function () { +describe('userDecrypt', function () { before(async function () { await initSigners(2); this.signers = await getSigners(); this.instances = await createInstances(this.signers); - const contractFactory = await ethers.getContractFactory('Reencrypt'); + const contractFactory = await ethers.getContractFactory('UserDecrypt'); this.contract = await contractFactory.connect(this.signers.alice).deploy(); await this.contract.waitForDeployment(); @@ -18,7 +18,7 @@ describe('Reencryption', function () { this.instances = await createInstances(this.signers); }); - it('test reencrypt ebool', async function () { + it('test userDecrypt ebool', async function () { const handle = await this.contract.xBool(); const { publicKey, privateKey } = this.instances.alice.generateKeypair(); const decryptedValue = await userDecryptSingleHandle( @@ -29,7 +29,7 @@ describe('Reencryption', function () { privateKey, publicKey, ); - expect(decryptedValue).to.equal(1n); + expect(decryptedValue).to.equal(true); // on the other hand, Bob should be unable to read Alice's handle try { @@ -44,14 +44,14 @@ describe('Reencryption', function () { ); expect.fail('Expected an error to be thrown - Bob should not be able to reencrypt Alice balance'); } catch (error) { - expect(error.message).to.equal('User is not authorized to reencrypt this handle!'); + expect((error as Error).message).to.equal('User is not authorized to reencrypt this handle!'); } // and should be impossible to call reencrypt if contractAddress is in list of userAddresses try { - const ctHandleContractPairs = [ + const handleContractPairs = [ { - ctHandle: handle, + handle, contractAddress: this.signers.alice.address, // this should be impossible, as expected by this test }, ]; @@ -70,7 +70,7 @@ describe('Reencryption', function () { ); await this.instances.alice.userDecrypt( - ctHandleContractPairs, + handleContractPairs, privateKey, publicKey, signature.replace('0x', ''), @@ -82,13 +82,13 @@ describe('Reencryption', function () { expect.fail('Expected an error to be thrown - userAddress and contractAddress cannot be equal'); } catch (error) { - expect(error.message).to.equal( + expect((error as Error).message).to.equal( 'userAddress should not be equal to contractAddress when requesting reencryption!', ); } }); - it('test reencrypt euint8', async function () { + it('test userDecrypt euint8', async function () { const handle = await this.contract.xUint8(); const { publicKey, privateKey } = this.instances.alice.generateKeypair(); const decryptedValue = await userDecryptSingleHandle( @@ -102,7 +102,7 @@ describe('Reencryption', function () { expect(decryptedValue).to.equal(42n); }); - it('test reencrypt euint16', async function () { + it('test userDecrypt euint16', async function () { const handle = await this.contract.xUint16(); const { publicKey, privateKey } = this.instances.alice.generateKeypair(); const decryptedValue = await userDecryptSingleHandle( @@ -116,7 +116,7 @@ describe('Reencryption', function () { expect(decryptedValue).to.equal(16n); }); - it('test reencrypt euint32', async function () { + it('test userDecrypt euint32', async function () { const handle = await this.contract.xUint32(); const { publicKey, privateKey } = this.instances.alice.generateKeypair(); const decryptedValue = await userDecryptSingleHandle( @@ -130,7 +130,7 @@ describe('Reencryption', function () { expect(decryptedValue).to.equal(32n); }); - it('test reencrypt euint64', async function () { + it('test userDecrypt euint64', async function () { const handle = await this.contract.xUint64(); const { publicKey, privateKey } = this.instances.alice.generateKeypair(); const decryptedValue = await userDecryptSingleHandle( @@ -144,7 +144,7 @@ describe('Reencryption', function () { expect(decryptedValue).to.equal(18446744073709551600n); }); - it('test reencrypt euint128', async function () { + it('test userDecrypt euint128', async function () { const handle = await this.contract.xUint128(); const { publicKey, privateKey } = this.instances.alice.generateKeypair(); const decryptedValue = await userDecryptSingleHandle( @@ -158,7 +158,7 @@ describe('Reencryption', function () { expect(decryptedValue).to.equal(145275933516363203950142179850024740765n); }); - it('test reencrypt eaddress', async function () { + it('test userDecrypt eaddress', async function () { const handle = await this.contract.xAddress(); const { publicKey, privateKey } = this.instances.alice.generateKeypair(); const decryptedValue = await userDecryptSingleHandle( @@ -169,10 +169,10 @@ describe('Reencryption', function () { privateKey, publicKey, ); - expect(decryptedValue).to.equal(BigInt('0x8ba1f109551bD432803012645Ac136ddd64DBA72')); + expect(decryptedValue).to.equal('0x8ba1f109551bD432803012645Ac136ddd64DBA72'); }); - it('test reencrypt euint256', async function () { + it('test userDecrypt euint256', async function () { const handle = await this.contract.xUint256(); const { publicKey, privateKey } = this.instances.alice.generateKeypair(); const decryptedValue = await userDecryptSingleHandle( diff --git a/host-contracts/test/utils.ts b/host-contracts/test/utils.ts index 17f79306b..311957d6c 100644 --- a/host-contracts/test/utils.ts +++ b/host-contracts/test/utils.ts @@ -1,16 +1,26 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; +import type { + ClearValueType, + FhevmInstance, + PublicDecryptResults, + UserDecryptResults, +} from '@zama-fhe/relayer-sdk/node'; import { toBufferBE } from 'bigint-buffer'; -import { ContractMethodArgs, Typed } from 'ethers'; -import { Signer } from 'ethers'; +import { Typed } from 'ethers'; +import type { ContractMethodArgs, Signer } from 'ethers'; import { ethers, network } from 'hardhat'; import hre from 'hardhat'; -import type { Counter } from '../types'; -import { TypedContractMethod } from '../types/common'; +import type { Counter } from '../typechain-types'; +import type { TypedContractMethod } from '../typechain-types/common'; import { getSigners } from './signers'; +export async function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + export async function checkIsHardhatSigner(signer: HardhatEthersSigner) { - const signers = await hre.ethers.getSigners(); + const signers: HardhatEthersSigner[] = await hre.ethers.getSigners(); if (signers.findIndex((s) => s.address === signer.address) === -1) { throw new Error( `The provided address (${signer.address}) is not the address of a valid hardhat signer. @@ -102,7 +112,7 @@ async function deployCounterContract(): Promise { const contract = await contractFactory.connect(signers.dave).deploy(); await contract.waitForDeployment(); - return contract; + return contract as unknown as Counter; } export const mineNBlocks = async (n: number) => { @@ -126,15 +136,15 @@ export const bigIntToBytes256 = (value: bigint) => { export const userDecryptSingleHandle = async ( handle: string, contractAddress: string, - instance: any, + instance: FhevmInstance, signer: Signer, privateKey: string, publicKey: string, -): Promise => { - const ctHandleContractPairs = [ +): Promise => { + const handleContractPairs = [ { - ctHandle: handle, - contractAddress: contractAddress, + handle, + contractAddress, }, ]; const startTimeStamp = Math.floor(Date.now() / 1000).toString(); @@ -153,17 +163,82 @@ export const userDecryptSingleHandle = async ( const signerAddress = await signer.getAddress(); - const decryptedValue = ( - await instance.userDecrypt( - ctHandleContractPairs, - privateKey, - publicKey, - signature.replace('0x', ''), - contractAddresses, - signerAddress, - startTimeStamp, - durationDays, - ) - )[0]; - return decryptedValue; + const results: UserDecryptResults = await instance.userDecrypt( + handleContractPairs, + privateKey, + publicKey, + signature.replace('0x', ''), + contractAddresses, + signerAddress, + startTimeStamp, + durationDays, + ); + + return results[handle as `0x${string}`]; }; + +// `delegate` performs a user decrypt on behalf of `delegator` +// export const delegatedUserDecryptSingleHandle = async (params: { +// instance: FhevmInstance; +// handle: string; +// signer: Signer; +// contractAddress: string; +// delegatorAddress: string; +// kmsPrivateKey: string; +// kmsPublicKey: string; +// }): Promise => { +// const HandleContractPairs = [ +// { +// ctHandle: params.handle, +// contractAddress: params.contractAddress, +// }, +// ]; +// const startTimeStamp = Math.floor(Date.now() / 1000).toString(); +// const durationDays = '10'; // String for consistency +// const contractAddresses = [params.contractAddress]; +// const instance = params.instance; +// const signer = params.signer; +// const userAddress = await signer.getAddress(); + +// // Use the new createEIP712 function +// const eip712 = instance.createEIP712( +// params.kmsPublicKey, +// contractAddresses, +// startTimeStamp, +// durationDays, +// params.delegatorAddress, +// ); + +// // Update the signing to match the new primaryType +// const signature = await signer.signTypedData( +// eip712.domain, +// { +// DelegatedUserDecryptRequestVerification: eip712.types.DelegatedUserDecryptRequestVerification, +// }, +// eip712.message, +// ); + +// if (!instance.delegatedUserDecrypt) { +// throw new Error(`instance.delegatedUserDecrypt not yet implemented`); +// } + +// // ======================================================== +// // +// // Todo: Call the delegate user decrypt function instead! +// // +// // ======================================================== +// const result = await instance.delegatedUserDecrypt( +// HandleContractPairs, +// params.kmsPrivateKey, +// params.kmsPublicKey, +// signature.replace('0x', ''), +// contractAddresses, +// userAddress, +// startTimeStamp, +// durationDays, +// params.delegatorAddress, +// ); + +// const decryptedValue = result[params.handle]; +// return decryptedValue; +// }; diff --git a/library-solidity/.env.example b/library-solidity/.env.example index 1f0ae8882..f9def2090 100644 --- a/library-solidity/.env.example +++ b/library-solidity/.env.example @@ -1,10 +1,12 @@ +ENV_HOST_ADDRESSES_PATH="./fhevmTemp/addresses/.env.host" MNEMONIC="adapt mosquito move limb mobile illegal tree voyage juice mosquito burger raise father hope layer" CHAIN_ID_GATEWAY="654321" DEPLOYER_PRIVATE_KEY="7697c90f7863e6057fbe25674464e14b57f2c670b1a8ee0f60fb87eb9b615c4d" # account[5] DECRYPTION_ADDRESS="0x5ffdaAB0373E62E2ea2944776209aEf29E631A64" INPUT_VERIFICATION_ADDRESS="0x812b06e1CDCE800494b79fFE4f925A504a9A9810" NUM_KMS_NODES="1" -KMS_THRESHOLD="1" +# In practice, the `PUBLIC_DECRYPTION_THRESHOLD` is currently set to `floor(n/2) + 1` with `n`` the number of KMS nodes +PUBLIC_DECRYPTION_THRESHOLD="1" COPROCESSOR_THRESHOLD="1" KMS_SIGNER_ADDRESS_0="0x9FE8958A2920985AC7ab8d320fDFaB310135a05B" # account[7] (account[6] is the relayer) KMS_SIGNER_ADDRESS_1="0x466f26442DD182C9A1b018Cd06671F9791DdE8Ef" # account[8] diff --git a/library-solidity/codegen/src/templates/FHE.sol-template b/library-solidity/codegen/src/templates/FHE.sol-template index f2afb496f..7939ffd9c 100644 --- a/library-solidity/codegen/src/templates/FHE.sol-template +++ b/library-solidity/codegen/src/templates/FHE.sol-template @@ -33,6 +33,9 @@ library FHE { /// @notice Returned if the returned KMS signatures are not valid. error InvalidKMSSignatures(); + /// @notice This event is emitted when public decryption has been successfully verified. + event PublicDecryptionVerified(bytes32[] handlesList, bytes abiEncodedCleartexts); + /** * @notice Sets the coprocessor addresses. * @param coprocessorConfig Coprocessor config struct that contains contract addresses. @@ -225,24 +228,53 @@ library FHE { expirationDate = Impl.getUserDecryptionDelegationExpirationDate(delegator, delegate, contractAddress); } - /** - * @dev Internal low-level function used to verify the KMS signatures. - * @notice Warning: MUST be called directly in the callback function called by the relayer. - * @notice Warning: this function never reverts, its boolean return value must be checked. - * @dev clearTexts is the abi-encoding of the list of all decrypted values assiociated to handlesList, in same order. - * @dev Only static native solidity types for clear values are supported, so clearTexts is the concatenation of all clear values appended to 32 bytes. - * @dev decryptionProof contains KMS signatures corresponding to clearTexts and associated handlesList, and needed metadata for KMS context. - **/ - function verifySignatures( + /// @notice Reverts if the KMS signatures verification against the provided handles and public decryption data + /// fails. + /// @dev The function MUST be called inside a public decryption callback function of a dApp contract + /// to verify the signatures and prevent fake decryption results for being submitted. + /// @param handlesList The list of handles as an array of bytes32 to check + /// @param abiEncodedCleartexts The ABI-encoded list of decrypted values associated with each handle in the `handlesList`. + /// The ABI-encoded list order must match the `handlesList` order. + /// @param decryptionProof The KMS public decryption proof. It includes the KMS signatures, associated metadata, + /// and the context needed for verification. + /// @dev Reverts if any of the following conditions are met: + /// - The `decryptionProof` is empty or has an invalid length. + /// - The number of valid signatures is zero or less than the configured KMS signers threshold. + /// - Any signature is produced by an address that is not a registered KMS signer. + /// - The signatures verification returns false. + function checkSignatures(bytes32[] memory handlesList, bytes memory abiEncodedCleartexts, bytes memory decryptionProof) internal { + bool isVerified = _verifySignatures(handlesList, abiEncodedCleartexts, decryptionProof); + if (!isVerified) { + revert InvalidKMSSignatures(); + } + emit PublicDecryptionVerified(handlesList, abiEncodedCleartexts); + } + + /// @notice Verifies KMS signatures against the provided handles and public decryption data. + /// @param handlesList The list of handles as an array of bytes32 to verify + /// @param abiEncodedCleartexts The ABI-encoded list of decrypted values associated with each handle in the `handlesList`. + /// The list order must match the list of handles in `handlesList` + /// @param decryptionProof The KMS public decryption proof computed by the KMS Signers associated to `handlesList` and + /// `abiEncodedCleartexts` + /// @return true if the signatures verification succeeds, false otherwise + /// @dev Private low-level function used to verify the KMS signatures. + /// Warning: this function never reverts, its boolean return value must be checked. + /// The decryptionProof is the numSigners + kmsSignatures + extraData (1 + 65*numSigners + extraData bytes) + /// Only static native solidity types for clear values are supported, so `abiEncodedCleartexts` is the concatenation of all clear values appended to 32 bytes. + /// @dev Reverts if any of the following conditions are met by the underlying KMS verifier: + /// - The `decryptionProof` is empty or has an invalid length. + /// - The number of valid signatures is zero or less than the configured KMS signers threshold. + /// - Any signature is produced by an address that is not a registered KMS signer. + function _verifySignatures( bytes32[] memory handlesList, - bytes memory cleartexts, + bytes memory abiEncodedCleartexts, bytes memory decryptionProof - ) internal returns (bool) { + ) private returns (bool) { CoprocessorConfig storage $ = Impl.getCoprocessorConfig(); return IKMSVerifier($.KMSVerifierAddress).verifyDecryptionEIP712KMSSignatures( handlesList, - cleartexts, + abiEncodedCleartexts, decryptionProof ); } diff --git a/library-solidity/config/ZamaConfig.sol b/library-solidity/config/ZamaConfig.sol index 99d481996..96052c050 100644 --- a/library-solidity/config/ZamaConfig.sol +++ b/library-solidity/config/ZamaConfig.sol @@ -11,12 +11,54 @@ import {CoprocessorConfig} from "../lib/Impl.sol"; * which are deployed & maintained by Zama. */ library ZamaConfig { - function getSepoliaProtocolId() internal pure returns (uint256) { - /// @note Zama Ethereum Sepolia protocol id is '10000 + Zama Ethereum protocol id' + /// @notice Returned if the Zama protocol is not supported on the current chain + error ZamaProtocolUnsupported(); + + function getEthereumCoprocessorConfig() internal view returns (CoprocessorConfig memory config) { + if (block.chainid == 1) { + config = _getEthereumConfig(); + } else if (block.chainid == 11155111) { + config = _getSepoliaConfig(); + } else if (block.chainid == 31337) { + config = _getLocalConfig(); + } else { + revert ZamaProtocolUnsupported(); + } + } + + function getConfidentialProtocolId() internal view returns (uint256) { + if (block.chainid == 1) { + return _getEthereumProtocolId(); + } else if (block.chainid == 11155111) { + return _getSepoliaProtocolId(); + } else if (block.chainid == 31337) { + return _getLocalProtocolId(); + } + return 0; + } + + /// @dev chainid == 1 + function _getEthereumProtocolId() private pure returns (uint256) { + // Zama Ethereum protocol id is '1' + return 1; + } + + /// @dev chainid == 1 + function _getEthereumConfig() private pure returns (CoprocessorConfig memory) { + // The addresses below are placeholders and should be replaced with actual addresses + // once deployed on the Ethereum mainnet. + return + CoprocessorConfig({ACLAddress: address(0), CoprocessorAddress: address(0), KMSVerifierAddress: address(0)}); + } + + /// @dev chainid == 11155111 + function _getSepoliaProtocolId() private pure returns (uint256) { + // Zama Ethereum Sepolia protocol id is '10000 + Zama Ethereum protocol id' return 10001; } - function getSepoliaConfig() internal pure returns (CoprocessorConfig memory) { + /// @dev chainid == 11155111 + function _getSepoliaConfig() private pure returns (CoprocessorConfig memory) { return CoprocessorConfig({ ACLAddress: 0xf0Ffdc93b7E186bC2f8CB3dAA75D86d1930A433D, @@ -25,41 +67,34 @@ library ZamaConfig { }); } - function getEthereumProtocolId() internal pure returns (uint256) { - /// @note Zama Ethereum protocol id is '1' - return 1; + /// @dev chainid == 31337 + function _getLocalProtocolId() private pure returns (uint256) { + return type(uint256).max; } - function getEthereumConfig() internal pure returns (CoprocessorConfig memory) { - /// @note The addresses below are placeholders and should be replaced with actual addresses - /// once deployed on the Ethereum mainnet. + function _getLocalConfig() private pure returns (CoprocessorConfig memory) { return - CoprocessorConfig({ACLAddress: address(0), CoprocessorAddress: address(0), KMSVerifierAddress: address(0)}); + CoprocessorConfig({ + ACLAddress: 0x50157CFfD6bBFA2DECe204a89ec419c23ef5755D, + CoprocessorAddress: 0xe3a9105a3a932253A70F126eb1E3b589C643dD24, + KMSVerifierAddress: 0x901F8942346f7AB3a01F6D7613119Bca447Bb030 + }); } } /** - * @title EthereumConfig. + * @title ZamaEthereumConfig. * @dev This contract can be inherited by a contract wishing to use the FHEVM contracts provided by Zama * on the Ethereum (mainnet) network (chainId = 1) or Sepolia (testnet) network (chainId = 11155111). * Other providers may offer similar contracts deployed at different addresses. * If you wish to use them, you should rely on the instructions from these providers. */ -contract EthereumConfig { +abstract contract ZamaEthereumConfig { constructor() { - if (block.chainid == 1) { - FHE.setCoprocessor(ZamaConfig.getEthereumConfig()); - } else if (block.chainid == 11155111) { - FHE.setCoprocessor(ZamaConfig.getSepoliaConfig()); - } + FHE.setCoprocessor(ZamaConfig.getEthereumCoprocessorConfig()); } - function protocolId() public view returns (uint256) { - if (block.chainid == 1) { - return ZamaConfig.getEthereumProtocolId(); - } else if (block.chainid == 11155111) { - return ZamaConfig.getSepoliaProtocolId(); - } - return 0; + function confidentialProtocolId() public view returns (uint256) { + return ZamaConfig.getConfidentialProtocolId(); } } diff --git a/library-solidity/examples/HeadsOrTails.sol b/library-solidity/examples/HeadsOrTails.sol index 2d2e756f1..dac04cbc6 100644 --- a/library-solidity/examples/HeadsOrTails.sol +++ b/library-solidity/examples/HeadsOrTails.sol @@ -2,92 +2,149 @@ pragma solidity ^0.8.24; import {FHE, ebool} from "../lib/FHE.sol"; -import {EthereumConfig} from "../config/ZamaConfig.sol"; - -// This contract implements a simple example for public decryption using a HeadsOrTails game, -// instead of using an oracle workflow to reveal the result. -contract HeadsOrTails is EthereumConfig { +import {ZamaEthereumConfig} from "../config/ZamaConfig.sol"; + +/** + * @title HeadsOrTails + * @notice Implements a simple Heads or Tails game demonstrating public, permissionless decryption + * using the FHE.makePubliclyDecryptable feature. + * @dev Inherits from ZamaEthereumConfig to access FHE functions. + */ +contract HeadsOrTails is ZamaEthereumConfig { constructor() {} + /** + * @notice Simple counter to assign a unique ID to each new game. + */ uint256 private counter = 0; + /** + * @notice Defines the entire state for a single Heads or Tails game instance. + */ struct Game { + /// @notice The address of the player who chose Heads. address headsPlayer; + /// @notice The address of the player who chose Tails. address tailsPlayer; - ebool encryptedHasHeadWon; + /// @notice The core encrypted result. This is a publicly decryptable ebool handle. + // true means Heads won; false means Tails won. + ebool encryptedHasHeadsWon; + /// @notice The clear address of the final winner, set after decryption and verification. address winner; } + /** + * @notice Mapping to store all game states, accessible by a unique game ID. + */ mapping(uint256 gameId => Game game) public games; + /** + * @notice Emitted when a new game is started, providing the encrypted handle required for decryption. + * @param gameId The unique identifier for the game. + * @param headsPlayer The address choosing Heads. + * @param tailsPlayer The address choosing Tails. + * @param encryptedHasHeadsWon The encrypted handle (ciphertext) storing the result. + */ event GameCreated( uint256 indexed gameId, address indexed headsPlayer, address indexed tailsPlayer, - ebool encryptedHasHeadWon + ebool encryptedHasHeadsWon ); - // Start a new Heads or Tails game between two players. And put the result encrypted publicly decryptable. + /** + * @notice Initiates a new Heads or Tails game, generates the result using FHE, + * and makes the result publicly available for decryption. + * @param headsPlayer The player address choosing Heads. + * @param tailsPlayer The player address choosing Tails. + */ function headsOrTails(address headsPlayer, address tailsPlayer) external { require(headsPlayer != address(0), "Heads player is address zero"); require(tailsPlayer != address(0), "Tails player is address zero"); + require(headsPlayer != tailsPlayer, "Heads player and Tails player should be different"); // true: Heads // false: Tails ebool headsOrTailsResult = FHE.randEbool(); - uint256 gameId = counter++; + counter++; + + // gameId > 0 + uint256 gameId = counter; games[gameId] = Game({ headsPlayer: headsPlayer, tailsPlayer: tailsPlayer, - encryptedHasHeadWon: headsOrTailsResult, + encryptedHasHeadsWon: headsOrTailsResult, winner: address(0) }); // Instead of calling the function FHE.requestDecryption, we make the result publicly decryptable directly. FHE.makePubliclyDecryptable(headsOrTailsResult); - // You can catch the event to get the gameId and the encryptedHasHeadWon handle + // You can catch the event to get the gameId and the encryptedHasHeadsWon handle // for further decryption requests, or create a view function. - emit GameCreated(gameId, headsPlayer, tailsPlayer, games[gameId].encryptedHasHeadWon); + emit GameCreated(gameId, headsPlayer, tailsPlayer, games[gameId].encryptedHasHeadsWon); + } + + /** + * @notice Returns the number of games created so far. + * @return The number of games created. + */ + function getGamesCount() public view returns (uint256) { + return counter; } - // Getting the handle we need to pass to relayer sdk or relayer to http public decrypt. - function hasHeadWon(uint256 gameId) public view returns (ebool) { - return games[gameId].encryptedHasHeadWon; + /** + * @notice Returns the encrypted ebool handle that stores the game result. + * @param gameId The ID of the game. + * @return The encrypted result (ebool handle). + */ + function hasHeadsWon(uint256 gameId) public view returns (ebool) { + return games[gameId].encryptedHasHeadsWon; } - // This logic was before called through an oracle worklfow after requestDecryption - // now used by the enduser directly. - function checkWinner( - // pass handles in the right order, or it will fail. + /** + * @notice Returns the address of the game winner. + * @param gameId The ID of the game. + * @return The winner's address (address(0) if not yet revealed). + */ + function getWinner(uint256 gameId) public view returns (address) { + return games[gameId].winner; + } + + /** + * @notice Verifies the provided (decryption proof, ABI-encoded clear value) pair against the stored ciphertext, + * and then stores the winner of the game. + * @param gameId The ID of the game to settle. + * @param abiEncodedClearGameResult The ABI-encoded clear value (bool) associated to the `decryptionProof`. + * @param decryptionProof The proof that validates the decryption. + */ + function recordAndVerifyWinner( uint256 gameId, - // Thoses two next arguments are provided by calling the endpoint of the relayer or the relayer sdk - // using http public decrypt. There is some changes needed on the relayer sdk returns to fits this - // new workflow. - bytes memory clearGameResult, - // Signatures + extradata relayer endpoint or relayer sdk function. + bytes memory abiEncodedClearGameResult, bytes memory decryptionProof - ) public returns (address) { - require(games[gameId].winner != address(0), "Game winner already revealed"); + ) public { + require(games[gameId].winner == address(0), "Game winner already revealed"); // 1. Decode the clear result and determine the winner's address. - bool decodedClearGameResult = abi.decode(clearGameResult, (bool)); + // In this very specific case, the function argument `abiEncodedClearGameResult` could have been a simple + // `bool` instead of an abi-encoded bool. In this case, we should have compute abi.encode on-chain + bool decodedClearGameResult = abi.decode(abiEncodedClearGameResult, (bool)); address winner = decodedClearGameResult ? games[gameId].headsPlayer : games[gameId].tailsPlayer; - // 2. Store the winner immediately to prevent re-entrancy issues in subsequent logic (if any). + // 2. Store the winner immediately to prevent re-entrancy issues games[gameId].winner = winner; - // 3. Verify that 'clearGameResult' is the legitimate decryption of the 'encryptedHasHeadWon' - // using the provided 'decryptionProof'. - // This implicitly checks that 'encryptedHasHeadWon' is a valid decryption handle. + // 3. FHE Verification: Build the list of ciphertexts (handles) and verify the proof. + // The verification checks that 'abiEncodedClearGameResult' is the true decryption + // of the 'encryptedHasHeadsWon' handle using the provided 'decryptionProof'. - // Creating the list of handles. + // Creating the list of handles in the right order! In this case the order does not matter since the proof + // only involves 1 single handle. bytes32[] memory cts = new bytes32[](1); - cts[0] = FHE.toBytes32(games[gameId].encryptedHasHeadWon); - - FHE.verifySignatures(cts, clearGameResult, decryptionProof); + cts[0] = FHE.toBytes32(games[gameId].encryptedHasHeadsWon); - return winner; + // This FHE call reverts the transaction if the decryption proof is invalid. + FHE.checkSignatures(cts, abiEncodedClearGameResult, decryptionProof); } } diff --git a/library-solidity/examples/HighestDieRoll.sol b/library-solidity/examples/HighestDieRoll.sol new file mode 100644 index 000000000..cc6c793f2 --- /dev/null +++ b/library-solidity/examples/HighestDieRoll.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, euint8} from "../lib/FHE.sol"; +import {ZamaEthereumConfig} from "../config/ZamaConfig.sol"; + +/** + * @title HighestDieRoll + * @notice Implements a simple 8-sided Die Roll game demonstrating public, permissionless decryption + * using the FHE.makePubliclyDecryptable feature. + * @dev Inherits from ZamaEthereumConfig to access FHE functions. + */ +contract HighestDieRoll is ZamaEthereumConfig { + constructor() {} + + /** + * @notice Simple counter to assign a unique ID to each new game. + */ + uint256 private counter = 0; + + /** + * @notice Defines the entire state for a single Heads or Tails game instance. + */ + struct Game { + /// @notice The address of the player who chose Heads. + address playerA; + /// @notice The address of the player who chose Tails. + address playerB; + /// @notice The core encrypted result. This is a publicly decryptable set of 4 handle. + euint8 playerAEncryptedDieRoll; + euint8 playerBEncryptedDieRoll; + /// @notice The clear address of the final winne, address(0) if draw, set after decryption and verification. + address winner; + /// @notice true if the game result is revealed + bool revealed; + } + + /** + * @notice Mapping to store all game states, accessible by a unique game ID. + */ + mapping(uint256 gameId => Game game) public games; + + /** + * @notice Emitted when a new game is started, providing the encrypted handle required for decryption. + * @param gameId The unique identifier for the game. + * @param playerA The address of playerA. + * @param playerB The address of playerB. + * @param playerAEncryptedDieRoll The encrypted die roll result of playerA. + * @param playerBEncryptedDieRoll The encrypted die roll result of playerB. + */ + event GameCreated( + uint256 indexed gameId, + address indexed playerA, + address indexed playerB, + euint8 playerAEncryptedDieRoll, + euint8 playerBEncryptedDieRoll + ); + + /** + * @notice Initiates a new highest die roll game, generates the result using FHE, + * and makes the result publicly available for decryption. + * @param playerA The player address choosing Heads. + * @param playerB The player address choosing Tails. + */ + function highestDieRoll(address playerA, address playerB) external { + require(playerA != address(0), "playerA is address zero"); + require(playerB != address(0), "playerB player is address zero"); + require(playerA != playerB, "playerA and playerB should be different"); + + euint8 playerAEncryptedDieRoll = FHE.randEuint8(); + euint8 playerBEncryptedDieRoll = FHE.randEuint8(); + + counter++; + + // gameId > 0 + uint256 gameId = counter; + games[gameId] = Game({ + playerA: playerA, + playerB: playerB, + playerAEncryptedDieRoll: playerAEncryptedDieRoll, + playerBEncryptedDieRoll: playerBEncryptedDieRoll, + winner: address(0), + revealed: false + }); + + // Instead of calling the function FHE.requestDecryption, we make the result publicly decryptable directly. + FHE.makePubliclyDecryptable(playerAEncryptedDieRoll); + FHE.makePubliclyDecryptable(playerBEncryptedDieRoll); + + // You can catch the event to get the gameId and the die rolls handles + // for further decryption requests, or create a view function. + emit GameCreated(gameId, playerA, playerB, playerAEncryptedDieRoll, playerBEncryptedDieRoll); + } + + /** + * @notice Returns the number of games created so far. + * @return The number of games created. + */ + function getGamesCount() public view returns (uint256) { + return counter; + } + + /** + * @notice Returns the encrypted euint8 handle that stores the playerA die roll. + * @param gameId The ID of the game. + * @return The encrypted result (euint8 handle). + */ + function getPlayerADieRoll(uint256 gameId) public view returns (euint8) { + return games[gameId].playerAEncryptedDieRoll; + } + + /** + * @notice Returns the encrypted euint8 handle that stores the playerB die roll. + * @param gameId The ID of the game. + * @return The encrypted result (euint8 handle). + */ + function getPlayerBDieRoll(uint256 gameId) public view returns (euint8) { + return games[gameId].playerBEncryptedDieRoll; + } + + /** + * @notice Returns the address of the game winner. If the game is finalized, the function returns `address(0)` + * if the game is a draw. + * @param gameId The ID of the game. + * @return The winner's address (address(0) if not yet revealed or draw). + */ + function getWinner(uint256 gameId) public view returns (address) { + return games[gameId].winner; + } + + /** + * @notice Returns `true` if the game result is publicly revealed, `false` otherwise. + * @param gameId The ID of the game. + * @return true if the game is publicly revealed. + */ + function isGameRevealed(uint256 gameId) public view returns (bool) { + return games[gameId].revealed; + } + + /** + * @notice Verifies the provided (decryption proof, ABI-encoded clear values) pair against the stored ciphertext, + * and then stores the winner of the game. + * @param gameId The ID of the game to settle. + * @param abiEncodedClearGameResult The ABI-encoded clear values (uint8, uint8) associated to the `decryptionProof`. + * @param decryptionProof The proof that validates the decryption. + */ + function recordAndVerifyWinner( + uint256 gameId, + bytes memory abiEncodedClearGameResult, + bytes memory decryptionProof + ) public { + require(!games[gameId].revealed, "Game already revealed"); + + // 1. Decode the clear result and determine the winner's address. + // In this very specific case, the function argument `abiEncodedClearGameResult` could have been replaced by two + // `uint8` instead of an abi-encoded uint8 pair. In this case, we should have to compute abi.encode on-chain + (uint8 decodedClearPlayerADieRoll, uint8 decodedClearPlayerBDieRoll) = abi.decode( + abiEncodedClearGameResult, + (uint8, uint8) + ); + + // The die is an 8-sided die (d8) (1..8) + decodedClearPlayerADieRoll = (decodedClearPlayerADieRoll % 8) + 1; + decodedClearPlayerBDieRoll = (decodedClearPlayerBDieRoll % 8) + 1; + + address winner = decodedClearPlayerADieRoll > decodedClearPlayerBDieRoll + ? games[gameId].playerA + : (decodedClearPlayerADieRoll < decodedClearPlayerBDieRoll ? games[gameId].playerB : address(0)); + + // 2. Store the revealed flag immediately to prevent re-entrancy issues + games[gameId].revealed = true; + games[gameId].winner = winner; + + // 3. FHE Verification: Build the list of ciphertexts (handles) and verify the proof. + // The verification checks that 'abiEncodedClearGameResult' is the true decryption + // of the '(playerAEncryptedDieRoll, playerBEncryptedDieRoll)' handle pair using + // the provided 'decryptionProof'. + + // Creating the list of handles in the right order! In this case the order does not matter since the proof + // only involves 1 single handle. + bytes32[] memory cts = new bytes32[](2); + cts[0] = FHE.toBytes32(games[gameId].playerAEncryptedDieRoll); + cts[1] = FHE.toBytes32(games[gameId].playerBEncryptedDieRoll); + + // This FHE call reverts the transaction if the decryption proof is invalid. + FHE.checkSignatures(cts, abiEncodedClearGameResult, decryptionProof); + } +} diff --git a/library-solidity/examples/TestEthereumCoprocessorConfig.sol b/library-solidity/examples/TestEthereumCoprocessorConfig.sol index 0e3b89646..e4c49356b 100644 --- a/library-solidity/examples/TestEthereumCoprocessorConfig.sol +++ b/library-solidity/examples/TestEthereumCoprocessorConfig.sol @@ -1,7 +1,12 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -import "../config/ZamaConfig.sol"; +import {CoprocessorConfig, Impl} from "../lib/Impl.sol"; +import {ZamaEthereumConfig} from "../config/ZamaConfig.sol"; /// @notice A simple contract for only testing solidity compilation -contract TestEthereumCoprocessorConfig is EthereumConfig {} +contract TestEthereumCoprocessorConfig is ZamaEthereumConfig { + function getCoprocessorConfig() public pure returns (CoprocessorConfig memory) { + return Impl.getCoprocessorConfig(); + } +} diff --git a/library-solidity/examples/TestSepoliaCoprocessorConfig.sol b/library-solidity/examples/TestSepoliaCoprocessorConfig.sol deleted file mode 100644 index d9be8fa2e..000000000 --- a/library-solidity/examples/TestSepoliaCoprocessorConfig.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause-Clear -pragma solidity ^0.8.24; - -import {EthereumConfig} from "../config/ZamaConfig.sol"; - -/// @notice A simple contract for only testing solidity compilation -contract TestSepoliaCoprocessorConfig is EthereumConfig {} diff --git a/library-solidity/lib/FHE.sol b/library-solidity/lib/FHE.sol index 07d90f2be..733b1b736 100644 --- a/library-solidity/lib/FHE.sol +++ b/library-solidity/lib/FHE.sol @@ -27,6 +27,9 @@ library FHE { /// @notice Returned if the returned KMS signatures are not valid. error InvalidKMSSignatures(); + /// @notice This event is emitted when public decryption has been successfully verified. + event PublicDecryptionVerified(bytes32[] handlesList, bytes abiEncodedCleartexts); + /** * @notice Sets the coprocessor addresses. * @param coprocessorConfig Coprocessor config struct that contains contract addresses. @@ -9072,24 +9075,57 @@ library FHE { expirationDate = Impl.getUserDecryptionDelegationExpirationDate(delegator, delegate, contractAddress); } - /** - * @dev Internal low-level function used to verify the KMS signatures. - * @notice Warning: MUST be called directly in the callback function called by the relayer. - * @notice Warning: this function never reverts, its boolean return value must be checked. - * @dev clearTexts is the abi-encoding of the list of all decrypted values assiociated to handlesList, in same order. - * @dev Only static native solidity types for clear values are supported, so clearTexts is the concatenation of all clear values appended to 32 bytes. - * @dev decryptionProof contains KMS signatures corresponding to clearTexts and associated handlesList, and needed metadata for KMS context. - **/ - function verifySignatures( + /// @notice Reverts if the KMS signatures verification against the provided handles and public decryption data + /// fails. + /// @dev The function MUST be called inside a public decryption callback function of a dApp contract + /// to verify the signatures and prevent fake decryption results for being submitted. + /// @param handlesList The list of handles as an array of bytes32 to check + /// @param abiEncodedCleartexts The ABI-encoded list of decrypted values associated with each handle in the `handlesList`. + /// The ABI-encoded list order must match the `handlesList` order. + /// @param decryptionProof The KMS public decryption proof. It includes the KMS signatures, associated metadata, + /// and the context needed for verification. + /// @dev Reverts if any of the following conditions are met: + /// - The `decryptionProof` is empty or has an invalid length. + /// - The number of valid signatures is zero or less than the configured KMS signers threshold. + /// - Any signature is produced by an address that is not a registered KMS signer. + /// - The signatures verification returns false. + function checkSignatures( + bytes32[] memory handlesList, + bytes memory abiEncodedCleartexts, + bytes memory decryptionProof + ) internal { + bool isVerified = _verifySignatures(handlesList, abiEncodedCleartexts, decryptionProof); + if (!isVerified) { + revert InvalidKMSSignatures(); + } + emit PublicDecryptionVerified(handlesList, abiEncodedCleartexts); + } + + /// @notice Verifies KMS signatures against the provided handles and public decryption data. + /// @param handlesList The list of handles as an array of bytes32 to verify + /// @param abiEncodedCleartexts The ABI-encoded list of decrypted values associated with each handle in the `handlesList`. + /// The list order must match the list of handles in `handlesList` + /// @param decryptionProof The KMS public decryption proof computed by the KMS Signers associated to `handlesList` and + /// `abiEncodedCleartexts` + /// @return true if the signatures verification succeeds, false otherwise + /// @dev Private low-level function used to verify the KMS signatures. + /// Warning: this function never reverts, its boolean return value must be checked. + /// The decryptionProof is the numSigners + kmsSignatures + extraData (1 + 65*numSigners + extraData bytes) + /// Only static native solidity types for clear values are supported, so `abiEncodedCleartexts` is the concatenation of all clear values appended to 32 bytes. + /// @dev Reverts if any of the following conditions are met by the underlying KMS verifier: + /// - The `decryptionProof` is empty or has an invalid length. + /// - The number of valid signatures is zero or less than the configured KMS signers threshold. + /// - Any signature is produced by an address that is not a registered KMS signer. + function _verifySignatures( bytes32[] memory handlesList, - bytes memory cleartexts, + bytes memory abiEncodedCleartexts, bytes memory decryptionProof - ) internal returns (bool) { + ) private returns (bool) { CoprocessorConfig storage $ = Impl.getCoprocessorConfig(); return IKMSVerifier($.KMSVerifierAddress).verifyDecryptionEIP712KMSSignatures( handlesList, - cleartexts, + abiEncodedCleartexts, decryptionProof ); } diff --git a/library-solidity/package.json b/library-solidity/package.json index 5d18e783f..190190653 100644 --- a/library-solidity/package.json +++ b/library-solidity/package.json @@ -35,7 +35,7 @@ }, "homepage": "https://github.com/zama-ai/fhevm-solidity#readme", "devDependencies": { - "@zama-fhe/relayer-sdk": "^0.3.0-2", + "@zama-fhe/relayer-sdk": "^0.3.0-5", "@fhevm/host-contracts": "*", "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", "@nomicfoundation/hardhat-ethers": "^3.0.4", @@ -78,8 +78,7 @@ "sqlite3": "^5.1.7", "ts-generator": "^0.1.1", "typechain": "^8.2.0", - "typescript": "^5.1.6", - "web3-validator": "^2.0.6" + "typescript": "^5.1.6" }, "optionalDependencies": { "solidity-comments-darwin-arm64": "0.1.1", diff --git a/library-solidity/tasks/taskDeploy.ts b/library-solidity/tasks/taskDeploy.ts index fadfaa5c9..210525461 100644 --- a/library-solidity/tasks/taskDeploy.ts +++ b/library-solidity/tasks/taskDeploy.ts @@ -143,13 +143,13 @@ task('task:deployKMSVerifier').setAction(async function (taskArguments: TaskArgu const deployer = new ethers.Wallet(privateKey).connect(ethers.provider); const currentImplementation = await ethers.getContractFactory('EmptyUUPSProxy', deployer); const newImplem = await ethers.getContractFactory('fhevmTemp/contracts/KMSVerifier.sol:KMSVerifier', deployer); - const parsedEnv = dotenv.parse(fs.readFileSync('fhevmTemp/addresses/.env.host')); - const proxyAddress = parsedEnv.KMS_VERIFIER_CONTRACT_ADDRESS; + const parsedEnvHostAddresses = dotenv.parse(fs.readFileSync('fhevmTemp/addresses/.env.host')); + const proxyAddress = parsedEnvHostAddresses.KMS_VERIFIER_CONTRACT_ADDRESS; const proxy = await upgrades.forceImport(proxyAddress, currentImplementation); - const verifyingContractSource = process.env.DECRYPTION_ADDRESS!; - const chainIDSource = +process.env.CHAIN_ID_GATEWAY!; - const initialThreshold = +process.env.KMS_THRESHOLD!; + const verifyingContractSource = getRequiredEnvVar('DECRYPTION_ADDRESS'); + const chainIDSource = +getRequiredEnvVar('CHAIN_ID_GATEWAY'); + const initialThreshold = +getRequiredEnvVar('PUBLIC_DECRYPTION_THRESHOLD'); let initialSigners: string[] = []; const numSigners = getRequiredEnvVar('NUM_KMS_NODES'); diff --git a/library-solidity/test/EthereumConfig.t.sol b/library-solidity/test/EthereumConfig.t.sol index 21d574f3f..9338ec3a9 100644 --- a/library-solidity/test/EthereumConfig.t.sol +++ b/library-solidity/test/EthereumConfig.t.sol @@ -2,15 +2,25 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; -import {EthereumConfig, ZamaConfig, CoprocessorConfig} from "../config/ZamaConfig.sol"; +import {ZamaEthereumConfig, ZamaConfig, CoprocessorConfig} from "../config/ZamaConfig.sol"; import {CoprocessorConfig, Impl} from "../lib/Impl.sol"; -contract TestFHEVMContract is EthereumConfig { +contract TestFHEVMContract is ZamaEthereumConfig { function getCoprocessorConfig() public pure returns (CoprocessorConfig memory) { return Impl.getCoprocessorConfig(); } } +contract TestContract { + function getEthereumCoprocessorConfig() public view returns (CoprocessorConfig memory) { + CoprocessorConfig memory cfg = ZamaConfig.getEthereumCoprocessorConfig(); + return cfg; + } + function getConfidentialProtocolId() public view returns (uint256) { + return ZamaConfig.getConfidentialProtocolId(); + } +} + contract EthereumConfigTest is Test { function setUp() public { vm.warp(1_000_000); @@ -21,7 +31,8 @@ contract EthereumConfigTest is Test { TestFHEVMContract testFhevmContract = new TestFHEVMContract(); CoprocessorConfig memory cfg = testFhevmContract.getCoprocessorConfig(); - CoprocessorConfig memory ethCfg = ZamaConfig.getEthereumConfig(); + // chainid == 1 + CoprocessorConfig memory ethCfg = ZamaConfig.getEthereumCoprocessorConfig(); assertTrue(cfg.ACLAddress == ethCfg.ACLAddress); assertTrue(cfg.CoprocessorAddress == ethCfg.CoprocessorAddress); @@ -36,7 +47,8 @@ contract EthereumConfigTest is Test { vm.chainId(1); TestFHEVMContract testFhevmContract = new TestFHEVMContract(); - assertTrue(testFhevmContract.protocolId() == 1); + assertTrue(testFhevmContract.confidentialProtocolId() == 1); + assertTrue(testFhevmContract.confidentialProtocolId() == ZamaConfig.getConfidentialProtocolId()); } function test_ZamaConfigSepolia() public { @@ -44,7 +56,8 @@ contract EthereumConfigTest is Test { TestFHEVMContract testFhevmContract = new TestFHEVMContract(); CoprocessorConfig memory cfg = testFhevmContract.getCoprocessorConfig(); - CoprocessorConfig memory sepoliaCfg = ZamaConfig.getSepoliaConfig(); + // chainid == 11155111 + CoprocessorConfig memory sepoliaCfg = ZamaConfig.getEthereumCoprocessorConfig(); assertTrue(cfg.ACLAddress == 0xf0Ffdc93b7E186bC2f8CB3dAA75D86d1930A433D); assertTrue(cfg.CoprocessorAddress == 0x92C920834Ec8941d2C77D188936E1f7A6f49c127); @@ -59,24 +72,55 @@ contract EthereumConfigTest is Test { vm.chainId(11155111); TestFHEVMContract testFhevmContract = new TestFHEVMContract(); - assertTrue(testFhevmContract.protocolId() == 10001); + assertTrue(testFhevmContract.confidentialProtocolId() == 10001); + assertTrue(testFhevmContract.confidentialProtocolId() == ZamaConfig.getConfidentialProtocolId()); } - function test_ZamaConfigUnknownChainId() public { - vm.chainId(123); + function test_ZamaConfigLocalChainId() public { + vm.chainId(31337); TestFHEVMContract testFhevmContract = new TestFHEVMContract(); CoprocessorConfig memory cfg = testFhevmContract.getCoprocessorConfig(); + // chainid == 31337 + CoprocessorConfig memory localCfg = ZamaConfig.getEthereumCoprocessorConfig(); - assertTrue(cfg.ACLAddress == address(0)); - assertTrue(cfg.CoprocessorAddress == address(0)); - assertTrue(cfg.KMSVerifierAddress == address(0)); + assertTrue(cfg.ACLAddress == 0x50157CFfD6bBFA2DECe204a89ec419c23ef5755D); + assertTrue(cfg.CoprocessorAddress == 0xe3a9105a3a932253A70F126eb1E3b589C643dD24); + assertTrue(cfg.KMSVerifierAddress == 0x901F8942346f7AB3a01F6D7613119Bca447Bb030); + + assertTrue(cfg.ACLAddress == localCfg.ACLAddress); + assertTrue(cfg.CoprocessorAddress == localCfg.CoprocessorAddress); + assertTrue(cfg.KMSVerifierAddress == localCfg.KMSVerifierAddress); } - function test_ZamaProtocolIdUnknownChainId() public { - vm.chainId(123); + function test_ZamaProtocolIdLocalChainId() public { + vm.chainId(31337); TestFHEVMContract testFhevmContract = new TestFHEVMContract(); - assertTrue(testFhevmContract.protocolId() == 0); + assertTrue(testFhevmContract.confidentialProtocolId() == 100001); + assertTrue(testFhevmContract.confidentialProtocolId() == ZamaConfig.getConfidentialProtocolId()); + } + + function test_ZamaConfigUnknownChainId() public { + vm.chainId(123); + + vm.expectRevert(abi.encodeWithSelector(ZamaConfig.ZamaProtocolUnsupported.selector)); + new TestFHEVMContract(); + } + + function test_ZamaConfigGetEthereumCoprocessorConfigUnknownChainId() public { + vm.chainId(123); + + TestContract testContract = new TestContract(); + + vm.expectRevert(abi.encodeWithSelector(ZamaConfig.ZamaProtocolUnsupported.selector)); + testContract.getEthereumCoprocessorConfig(); + } + + function test_ZamaConfigGetConfidentialProtocolIdUnknownChainId() public { + vm.chainId(123); + + TestContract testContract = new TestContract(); + assertTrue(testContract.getConfidentialProtocolId() == 0); } } diff --git a/library-solidity/test/checkSignatures/HeadsOrTails.ts b/library-solidity/test/checkSignatures/HeadsOrTails.ts new file mode 100644 index 000000000..3254a2e96 --- /dev/null +++ b/library-solidity/test/checkSignatures/HeadsOrTails.ts @@ -0,0 +1,151 @@ +import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; +import type { FhevmInstance } from '@zama-fhe/relayer-sdk/node'; +import { expect } from 'chai'; +import { ethers as EthersT } from 'ethers'; +import { ethers } from 'hardhat'; + +import { HeadsOrTails, HeadsOrTails__factory } from '../../typechain-types'; +import { createInstance } from '../instance'; +import { Signers, getSigners, initSigners } from '../signers'; + +async function deployFixture() { + // Contracts are deployed using the first signer/account by default + const factory = (await ethers.getContractFactory('HeadsOrTails')) as unknown as HeadsOrTails__factory; + const headsOrTails = (await factory.deploy()) as HeadsOrTails; + const headsOrTails_address = await headsOrTails.getAddress(); + + return { headsOrTails, headsOrTails_address }; +} + +describe('BBB HeadsOrTails', function () { + let contract: HeadsOrTails; + let contractAddress: string; + let signers: Signers; + let owner: HardhatEthersSigner; + let playerA: HardhatEthersSigner; + let playerB: HardhatEthersSigner; + let instance: FhevmInstance; + + before(async function () { + await initSigners(5); + signers = await getSigners(); + instance = await createInstance(); + + owner = signers.alice; + playerA = signers.bob; + playerB = signers.carol; + }); + + beforeEach(async function () { + // Deploy a new contract each time we run a new test + const deployment = await deployFixture(); + contractAddress = deployment.headsOrTails_address; + contract = deployment.headsOrTails; + }); + + function parseGameCreatedEvent(txReceipt: EthersT.ContractTransactionReceipt | null): + | { + gameId: number; + headsPlayer: string; + tailsPlayer: string; + encryptedHasHeadWon: string; + } + | undefined { + let gameCreatedEvent: + | { gameId: number; headsPlayer: string; tailsPlayer: string; encryptedHasHeadWon: string } + | undefined = undefined; + + if (txReceipt) { + console.log(`✅ New game created tx:${txReceipt.hash}`); + const logs = Array.isArray(txReceipt.logs) ? txReceipt.logs : [txReceipt.logs]; + for (let i = 0; i < logs.length; ++i) { + const parsedLog = contract.interface.parseLog(logs[i]); + if (!parsedLog) { + continue; + } + if (parsedLog.name !== 'GameCreated') { + continue; + } + + gameCreatedEvent = { + gameId: Number(parsedLog.args[0]), + headsPlayer: parsedLog.args[1], + tailsPlayer: parsedLog.args[2], + encryptedHasHeadWon: parsedLog.args[3], + }; + } + } + + return gameCreatedEvent; + } + + // ✅ Test should succeed + it('decryption should succeed', async function () { + console.log(``); + console.log(`🎲 HeadsOrTails Game contract address: ${contractAddress}`); + console.log(` 🤖 playerA.address: ${playerA.address}`); + console.log(` 🎃 playerB.address: ${playerB.address}`); + console.log(``); + + // Starts a new Heads or Tails game. This will emit a `GameCreated` event + const tx = await contract.connect(owner).headsOrTails(playerA, playerB); + + // Parse the `GameCreated` event + const gameCreatedEvent = parseGameCreatedEvent(await tx.wait())!; + + // GameId is 1 since we are playing the first game + expect(gameCreatedEvent !== undefined).to.eq(true); + expect(gameCreatedEvent.gameId).to.eq(1); + expect(gameCreatedEvent.headsPlayer).to.eq(playerA.address); + expect(gameCreatedEvent.tailsPlayer).to.eq(playerB.address); + expect(await contract.getGamesCount()).to.eq(1); + + console.log(JSON.stringify(gameCreatedEvent, null, 2)); + + const gameId = gameCreatedEvent.gameId; + const encryptedBool: string = gameCreatedEvent.encryptedHasHeadWon; + + // Call the Zama Relayer to compute the decryption + const publicDecryptResults = await instance.publicDecrypt([encryptedBool]); + + // The Relayer returns a `PublicDecryptResults` object containing: + // - the ORDERED clear values (here we have only one single value) + // - the ORDERED clear values in ABI-encoded form + // - the KMS decryption proof associated with the ORDERED clear values in ABI-encoded form + const abiEncodedClearGameResult = publicDecryptResults.abiEncodedClearValues; + const decryptionProof = publicDecryptResults.decryptionProof; + + // Let's forward the `PublicDecryptResults` content to the on-chain contract whose job + // will simply be to verify the proof and declare the final winner of the game + await contract.recordAndVerifyWinner(gameId, abiEncodedClearGameResult, decryptionProof); + + const winner = await contract.getWinner(gameId); + + expect(winner === playerA.address || winner === playerB.address).to.eq(true); + + console.log(``); + if (winner === playerA.address) { + console.log(`🤖 playerA is the winner 🥇🥇`); + } else if (winner === playerB.address) { + console.log(`🎃 playerB is the winner 🥇🥇`); + } + }); + + // ❌ Test should fail if proof is invalid + it('decryption should fail', async function () { + const tx = await contract.connect(owner).headsOrTails(playerA, playerB); + const gameCreatedEvent = parseGameCreatedEvent(await tx.wait())!; + const publicDecryptResults = await instance.publicDecrypt([gameCreatedEvent.encryptedHasHeadWon]); + // ❌ Call `contract.recordAndVerifyWinner` using order (B, A) + await expect( + contract.recordAndVerifyWinner( + gameCreatedEvent.gameId, + publicDecryptResults.abiEncodedClearValues, + publicDecryptResults.decryptionProof + 'dead', + ), + ).to.be.revertedWithCustomError( + { interface: new EthersT.Interface(['error KMSInvalidSigner(address invalidSigner)']) }, + 'KMSInvalidSigner', + ); + }); +}); diff --git a/library-solidity/test/checkSignatures/HighestDieRoll.ts b/library-solidity/test/checkSignatures/HighestDieRoll.ts new file mode 100644 index 000000000..6ec1ebb61 --- /dev/null +++ b/library-solidity/test/checkSignatures/HighestDieRoll.ts @@ -0,0 +1,202 @@ +import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; +import type { ClearValueType, FhevmInstance } from '@zama-fhe/relayer-sdk/node'; +import { expect } from 'chai'; +import { ethers as EthersT } from 'ethers'; +import { ethers } from 'hardhat'; + +import { HighestDieRoll, HighestDieRoll__factory } from '../../typechain-types'; +import { createInstance } from '../instance'; +import { Signers, getSigners, initSigners } from '../signers'; + +async function deployFixture() { + // Contracts are deployed using the first signer/account by default + const factory = (await ethers.getContractFactory('HighestDieRoll')) as unknown as HighestDieRoll__factory; + const highestDiceRoll = (await factory.deploy()) as HighestDieRoll; + const highestDiceRoll_address = await highestDiceRoll.getAddress(); + + return { highestDiceRoll, highestDiceRoll_address }; +} + +describe('HighestDieRoll', function () { + let contract: HighestDieRoll; + let contractAddress: string; + let signers: Signers; + let owner: HardhatEthersSigner; + let playerA: HardhatEthersSigner; + let playerB: HardhatEthersSigner; + let instance: FhevmInstance; + + before(async function () { + await initSigners(5); + signers = await getSigners(); + instance = await createInstance(); + + owner = signers.alice; + playerA = signers.bob; + playerB = signers.carol; + }); + + beforeEach(async function () { + // Deploy a new contract each time we run a new test + const deployment = await deployFixture(); + contractAddress = deployment.highestDiceRoll_address; + contract = deployment.highestDiceRoll; + }); + + function parseGameCreatedEvent(txReceipt: EthersT.ContractTransactionReceipt | null): + | { + gameId: number; + playerA: string; + playerB: string; + playerAEncryptedDiceRoll: `0x${string}`; + playerBEncryptedDiceRoll: `0x${string}`; + } + | undefined { + let gameCreatedEvent: + | { + gameId: number; + playerA: string; + playerB: string; + playerAEncryptedDiceRoll: `0x${string}`; + playerBEncryptedDiceRoll: `0x${string}`; + } + | undefined = undefined; + + if (txReceipt) { + console.log(`✅ New game created tx:${txReceipt.hash}`); + const logs = Array.isArray(txReceipt.logs) ? txReceipt.logs : [txReceipt.logs]; + for (let i = 0; i < logs.length; ++i) { + const parsedLog = contract.interface.parseLog(logs[i]); + if (!parsedLog) { + continue; + } + if (parsedLog.name !== 'GameCreated') { + continue; + } + + gameCreatedEvent = { + gameId: Number(parsedLog.args[0]), + playerA: parsedLog.args[1], + playerB: parsedLog.args[2], + playerAEncryptedDiceRoll: parsedLog.args[3], + playerBEncryptedDiceRoll: parsedLog.args[4], + }; + } + } + + return gameCreatedEvent; + } + + // ✅ Test should succeed + it('decryption should succeed', async function () { + console.log(``); + console.log(`🎲 HighestDieRoll Game contract address: ${contractAddress}`); + console.log(` 🤖 playerA.address: ${playerA.address}`); + console.log(` 🎃 playerB.address: ${playerB.address}`); + console.log(``); + + // Starts a new Heads or Tails game. This will emit a `GameCreated` event + const tx = await contract.connect(owner).highestDieRoll(playerA, playerB); + + // Parse the `GameCreated` event + const gameCreatedEvent = parseGameCreatedEvent(await tx.wait())!; + + // GameId is 1 since we are playing the first game + expect(gameCreatedEvent !== undefined).to.eq(true); + expect(gameCreatedEvent.gameId).to.eq(1); + expect(gameCreatedEvent.playerA).to.eq(playerA.address); + expect(gameCreatedEvent.playerB).to.eq(playerB.address); + expect(await contract.getGamesCount()).to.eq(1); + + console.log(JSON.stringify(gameCreatedEvent, null, 2)); + + const gameId = gameCreatedEvent.gameId; + const playerADiceRoll = gameCreatedEvent.playerAEncryptedDiceRoll; + const playerBDiceRoll = gameCreatedEvent.playerBEncryptedDiceRoll; + + // Call the Zama Relayer to compute the decryption + const publicDecryptResults = await instance.publicDecrypt([playerADiceRoll, playerBDiceRoll]); + + // The Relayer returns a `PublicDecryptResults` object containing: + // - the ORDERED clear values (here we have only one single value) + // - the ORDERED clear values in ABI-encoded form + // - the KMS decryption proof associated with the ORDERED clear values in ABI-encoded form + const abiEncodedClearGameResult = publicDecryptResults.abiEncodedClearValues; + const decryptionProof = publicDecryptResults.decryptionProof; + + const clearValueA: ClearValueType = publicDecryptResults.clearValues[playerADiceRoll]; + const clearValueB: ClearValueType = publicDecryptResults.clearValues[playerBDiceRoll]; + + expect(typeof clearValueA).to.eq('bigint'); + expect(typeof clearValueB).to.eq('bigint'); + + const a = (Number(clearValueA) % 8) + 1; + const b = (Number(clearValueB) % 8) + 1; + + const isDraw = a === b; + const playerAWon = a > b; + const playerBWon = a < b; + + console.log('🎲 playerA die roll is ' + a); + console.log('🎲 playerB die roll is ' + b); + + // Let's forward the `PublicDecryptResults` content to the on-chain contract whose job + // will simply be to verify the proof and store the final winner of the game + await contract.recordAndVerifyWinner(gameId, abiEncodedClearGameResult, decryptionProof); + + const isRevealed = await contract.isGameRevealed(gameId); + const winner = await contract.getWinner(gameId); + + expect(isRevealed).to.eq(true); + expect(winner === playerA.address || winner === playerB.address || winner === EthersT.ZeroAddress).to.eq(true); + + expect(isDraw).to.eq(winner === EthersT.ZeroAddress); + expect(playerAWon).to.eq(winner === playerA.address); + expect(playerBWon).to.eq(winner === playerB.address); + + console.log(``); + if (winner === playerA.address) { + console.log(`🤖 playerA is the winner 🥇🥇`); + } else if (winner === playerB.address) { + console.log(`🎃 playerB is the winner 🥇🥇`); + } else if (winner === EthersT.ZeroAddress) { + console.log(`Game is a draw!`); + } + }); + + // ❌ Test should fail because clear values are ABI-encoded in the wrong order. + it('decryption should fail', async function () { + // Test Case: Verify strict ordering is enforced for cryptographic proof generation. + // The `decryptionProof` is generated based on the expected order (A, B). By ABI-encoding + // the clear values in the **reverse order** (B, A), we create a mismatch when the contract + // internally verifies the proof (e.g., checks a signature against a newly computed hash). + // This intentional failure is expected to revert with the `KMSInvalidSigner` error, + // confirming the proof's order dependency. + const tx = await contract.connect(owner).highestDieRoll(playerA, playerB); + const gameCreatedEvent = parseGameCreatedEvent(await tx.wait())!; + const gameId = gameCreatedEvent.gameId; + const playerADiceRoll = gameCreatedEvent.playerAEncryptedDiceRoll; + const playerBDiceRoll = gameCreatedEvent.playerBEncryptedDiceRoll; + // Call `fhevm.publicDecrypt` using order (A, B) + const publicDecryptResults = await instance.publicDecrypt([playerADiceRoll, playerBDiceRoll]); + const clearValueA: ClearValueType = publicDecryptResults.clearValues[playerADiceRoll]; + const clearValueB: ClearValueType = publicDecryptResults.clearValues[playerBDiceRoll]; + const decryptionProof = publicDecryptResults.decryptionProof; + expect(typeof clearValueA).to.eq('bigint'); + expect(typeof clearValueB).to.eq('bigint'); + expect(ethers.AbiCoder.defaultAbiCoder().encode(['uint256', 'uint256'], [clearValueA, clearValueB])).to.eq( + publicDecryptResults.abiEncodedClearValues, + ); + const wrongOrderBAInsteadOfABAbiEncodedValues = ethers.AbiCoder.defaultAbiCoder().encode( + ['uint256', 'uint256'], + [clearValueB, clearValueA], + ); + // ❌ Call `contract.recordAndVerifyWinner` using order (B, A) + await expect( + contract.recordAndVerifyWinner(gameId, wrongOrderBAInsteadOfABAbiEncodedValues, decryptionProof), + ).to.be.revertedWithCustomError( + { interface: new EthersT.Interface(['error KMSInvalidSigner(address invalidSigner)']) }, + 'KMSInvalidSigner', + ); + }); +}); diff --git a/library-solidity/test/coprocessorConfig/testEthereumCoprocessorConfig.ts b/library-solidity/test/coprocessorConfig/testEthereumCoprocessorConfig.ts index e17f7ec67..7e8ba1b33 100644 --- a/library-solidity/test/coprocessorConfig/testEthereumCoprocessorConfig.ts +++ b/library-solidity/test/coprocessorConfig/testEthereumCoprocessorConfig.ts @@ -18,8 +18,18 @@ describe('TestEthereumCoprocessorConfig', function () { this.instances = await createInstances(this.signers); }); - it('test protocolId', async function () { - const protocolId = await this.contract.connect(this.signers.carol).protocolId(); - expect(protocolId).to.equal(0); // Zama protocolId == 0 on any chain other than mainnet/sepolia + it('test local confidentialProtocolId', async function () { + const confidentialProtocolId = await this.contract.connect(this.signers.carol).confidentialProtocolId(); + expect(confidentialProtocolId).to.equal(100001); // Zama confidentialProtocolId == 0 on any chain other than mainnet/sepolia + }); + + it('test local CoprocessorConfig', async function () { + const cfg = await this.contract.connect(this.signers.carol).getCoprocessorConfig(); + // ACL + expect(cfg[0]).to.eq('0x50157CFfD6bBFA2DECe204a89ec419c23ef5755D'); + // FHEVMExecutor + expect(cfg[1]).to.eq('0xe3a9105a3a932253A70F126eb1E3b589C643dD24'); + // KMSVerifier + expect(cfg[2]).to.eq('0x901F8942346f7AB3a01F6D7613119Bca447Bb030'); }); }); diff --git a/library-solidity/test/coprocessorConfig/testSepoliaCoprocessorConfig.ts b/library-solidity/test/coprocessorConfig/testSepoliaCoprocessorConfig.ts deleted file mode 100644 index 2ee2cf482..000000000 --- a/library-solidity/test/coprocessorConfig/testSepoliaCoprocessorConfig.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; - -import { createInstances } from '../instance'; -import { getSigners, initSigners } from '../signers'; - -describe('TestSepoliaCoprocessorConfig', function () { - before(async function () { - await initSigners(2); - this.signers = await getSigners(); - }); - - beforeEach(async function () { - const contractFactory = await ethers.getContractFactory('TestSepoliaCoprocessorConfig'); - this.contract = await contractFactory.connect(this.signers.alice).deploy(); - await this.contract.waitForDeployment(); - this.contractAddress = await this.contract.getAddress(); - this.instances = await createInstances(this.signers); - }); - - it('test protocolId', async function () { - const protocolId = await this.contract.connect(this.signers.carol).protocolId(); - expect(protocolId).to.equal(0); // Zama protocolId == 0 on any chain other than mainnet/sepolia - }); -}); diff --git a/library-solidity/test/coprocessorUtils.ts b/library-solidity/test/coprocessorUtils.ts index 16e808d73..f4ceced98 100644 --- a/library-solidity/test/coprocessorUtils.ts +++ b/library-solidity/test/coprocessorUtils.ts @@ -1,22 +1,15 @@ -import dotenv from 'dotenv'; +import type { FheTypeInfo } from '@fhevm/solidity/lib-js/common'; +import { ALL_FHE_TYPE_INFOS } from '@fhevm/solidity/lib-js/fheTypeInfos'; +import { ALL_OPERATORS_PRICES } from '@fhevm/solidity/lib-js/operatorsPrices'; import type { ethers as EthersT } from 'ethers'; import { log2 } from 'extra-bigint'; -import * as fs from 'fs'; import { ethers } from 'hardhat'; import { Database } from 'sqlite3'; -import type { FheTypeInfo } from '../lib-js/common'; -import { ALL_FHE_TYPE_INFOS } from '../lib-js/fheTypeInfos'; -import { ALL_OPERATORS_PRICES } from '../lib-js/operatorsPrices'; - -const parsedEnvCoprocessor = dotenv.parse(fs.readFileSync('./fhevmTemp/addresses/.env.host')); -const coprocAddress = parsedEnvCoprocessor.FHEVM_EXECUTOR_CONTRACT_ADDRESS; - let firstBlockListening = 0; let lastBlockSnapshot = 0; let lastCounterRand = 0; let counterRand = 0; -let chainId: number; //const db = new Database('./sql.db'); // on-disk db for debugging const db = new Database(':memory:'); @@ -32,7 +25,7 @@ export function insertSQL(handle: string, clearText: BigInt | string, replace: b // Decrypt any handle, bypassing ACL // WARNING : only for testing or internal use -export const getClearText = async (handle: string | bigint): Promise => { +export const getCoprocessorClearText = async (handle: string | bigint): Promise => { return new Promise((resolve, reject) => { let attempts = 0; const maxRetries = 100; @@ -72,9 +65,6 @@ const NumBits = { 6: 128n, //euint128 7: 160n, //eaddress 8: 256n, //euint256 - 9: 512n, //ebytes64 - 10: 1024n, //ebytes128 - 11: 2048n, //ebytes256 }; export function numberToEvenHexString(num: number) { @@ -118,11 +108,6 @@ function bitwiseNotUintBits(value: BigInt, numBits: number) { return ~value & BIT_MASK; } -export const awaitCoprocessor = async (): Promise => { - chainId = Number((await ethers.provider.getNetwork()).chainId); - await processAllPastFHEVMExecutorEvents(); -}; - const abi = [ 'event FheAdd(address indexed caller, bytes32 lhs, bytes32 rhs, bytes1 scalarByte, bytes32 result)', 'event FheSub(address indexed caller, bytes32 lhs, bytes32 rhs, bytes1 scalarByte, bytes32 result)', @@ -157,7 +142,7 @@ const abi = [ 'event FheRandBounded(address indexed caller, uint256 upperBound, uint8 randType, bytes16 seed, bytes32 result)', ]; -async function processAllPastFHEVMExecutorEvents() { +export async function processAllPastFHEVMExecutorEvents(coprocessorContractAddress: `0x${string}`) { const provider = ethers.provider; const latestBlockNumber = await provider.getBlockNumber(); @@ -170,11 +155,11 @@ async function processAllPastFHEVMExecutorEvents() { } } - const contract = new ethers.Contract(coprocAddress, abi, provider); + const contract = new ethers.Contract(coprocessorContractAddress, abi, provider); // Fetch all events emitted by the contract const filter = { - address: coprocAddress, + address: coprocessorContractAddress, fromBlock: firstBlockListening, toBlock: latestBlockNumber, }; @@ -205,12 +190,12 @@ async function processAllPastFHEVMExecutorEvents() { } async function insertHandleFromEvent(event: FHEVMEvent) { - let handle; - let clearText; - let clearLHS; - let clearRHS; + let handle: string; + let clearText: bigint | string; + let clearLHS: string | bigint; + let clearRHS: string | bigint; let resultType: number; - let shift; + let shift: bigint; switch (event.eventName) { case 'TrivialEncrypt': @@ -228,12 +213,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheAdd': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) + BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) + BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -244,13 +229,13 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheSub': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) - BigInt(event.args[2]); if (clearText < 0n) clearText = clearText + 2n ** NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) - BigInt(clearRHS); if (clearText < 0n) clearText = clearText + 2n ** NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; @@ -261,12 +246,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheMul': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) * BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) * BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -276,7 +261,7 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheDiv': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) / BigInt(event.args[2]); } else { @@ -288,7 +273,7 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheRem': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) % BigInt(event.args[2]); } else { @@ -300,12 +285,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheBitAnd': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) & BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) & BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -315,12 +300,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheBitOr': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) | BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) | BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -330,12 +315,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheBitXor': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) ^ BigInt(event.args[2]); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) ^ BigInt(clearRHS); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -345,12 +330,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheShl': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) << BigInt(event.args[2]) % NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) << BigInt(clearRHS) % NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -360,12 +345,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheShr': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) >> BigInt(event.args[2]) % NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) >> BigInt(clearRHS) % NumBits[resultType as keyof typeof NumBits]; clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } @@ -375,14 +360,14 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheRotl': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { shift = BigInt(event.args[2]) % NumBits[resultType as keyof typeof NumBits]; clearText = (BigInt(clearLHS) << shift) | (BigInt(clearLHS) >> (NumBits[resultType as keyof typeof NumBits] - shift)); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); shift = BigInt(clearRHS) % NumBits[resultType as keyof typeof NumBits]; clearText = (BigInt(clearLHS) << shift) | (BigInt(clearLHS) >> (NumBits[resultType as keyof typeof NumBits] - shift)); @@ -394,14 +379,14 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheRotr': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { shift = BigInt(event.args[2]) % NumBits[resultType as keyof typeof NumBits]; clearText = (BigInt(clearLHS) >> shift) | (BigInt(clearLHS) << (NumBits[resultType as keyof typeof NumBits] - shift)); clearText = clearText % 2n ** NumBits[resultType as keyof typeof NumBits]; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); shift = BigInt(clearRHS) % NumBits[resultType as keyof typeof NumBits]; clearText = (BigInt(clearLHS) >> shift) | (BigInt(clearLHS) << (NumBits[resultType as keyof typeof NumBits] - shift)); @@ -413,12 +398,12 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheEq': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) === BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = clearLHS === clearRHS ? 1n : 0n; } @@ -428,11 +413,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheEqBytes': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) === BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) === BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -441,11 +426,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheNe': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) !== BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) !== BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -454,11 +439,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheNeBytes': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) !== BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) !== BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -467,11 +452,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheGe': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) >= BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) >= BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -480,11 +465,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheGt': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) > BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) > BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -493,11 +478,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheLe': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) <= BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) <= BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -506,11 +491,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheLt': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) < BigInt(event.args[2]) ? 1n : 0n; } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) < BigInt(clearRHS) ? 1n : 0n; } insertSQL(handle, clearText); @@ -519,11 +504,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheMax': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) > BigInt(event.args[2]) ? clearLHS : BigInt(event.args[2]); } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) > BigInt(clearRHS) ? clearLHS : clearRHS; } insertSQL(handle, clearText); @@ -532,11 +517,11 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheMin': handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearLHS = await getClearText(event.args[1]); + clearLHS = await getCoprocessorClearText(event.args[1]); if (event.args[3] === '0x01') { clearText = BigInt(clearLHS) < BigInt(event.args[2]) ? clearLHS : BigInt(event.args[2]); } else { - clearRHS = await getClearText(event.args[2]); + clearRHS = await getCoprocessorClearText(event.args[2]); clearText = BigInt(clearLHS) < BigInt(clearRHS) ? clearLHS : clearRHS; } insertSQL(handle, clearText); @@ -545,14 +530,15 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'Cast': resultType = parseInt(event.args[2]); handle = ethers.toBeHex(event.args[3], 32); - clearText = BigInt(await getClearText(event.args[1])) % 2n ** NumBits[resultType as keyof typeof NumBits]; + clearText = + BigInt(await getCoprocessorClearText(event.args[1])) % 2n ** NumBits[resultType as keyof typeof NumBits]; insertSQL(handle, clearText); break; case 'FheNot': handle = ethers.toBeHex(event.args[2], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearText = BigInt(await getClearText(event.args[1])); + clearText = BigInt(await getCoprocessorClearText(event.args[1])); clearText = bitwiseNotUintBits(clearText, Number(NumBits[resultType as keyof typeof NumBits])); insertSQL(handle, clearText); break; @@ -560,7 +546,7 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'FheNeg': handle = ethers.toBeHex(event.args[2], 32); resultType = parseInt(handle.slice(-4, -2), 16); - clearText = BigInt(await getClearText(event.args[1])); + clearText = BigInt(await getCoprocessorClearText(event.args[1])); clearText = bitwiseNotUintBits(clearText, Number(NumBits[resultType as keyof typeof NumBits])); clearText = (clearText + 1n) % 2n ** NumBits[resultType as keyof typeof NumBits]; insertSQL(handle, clearText); @@ -569,9 +555,9 @@ async function insertHandleFromEvent(event: FHEVMEvent) { case 'VerifyInput': handle = event.args[1]; try { - await getClearText(BigInt(handle)); + await getCoprocessorClearText(BigInt(handle)); } catch { - throw Error('User input was not found in DB'); + throw Error(`User input handle was not found in DB: ${handle}`); } break; @@ -579,9 +565,9 @@ async function insertHandleFromEvent(event: FHEVMEvent) { handle = ethers.toBeHex(event.args[4], 32); resultType = parseInt(handle.slice(-4, -2), 16); handle = ethers.toBeHex(event.args[4], 32); - const clearControl = BigInt(await getClearText(event.args[1])); - const clearIfTrue = BigInt(await getClearText(event.args[2])); - const clearIfFalse = BigInt(await getClearText(event.args[3])); + const clearControl = BigInt(await getCoprocessorClearText(event.args[1])); + const clearIfTrue = BigInt(await getCoprocessorClearText(event.args[2])); + const clearIfFalse = BigInt(await getCoprocessorClearText(event.args[3])); if (clearControl === 1n) { clearText = clearIfTrue; } else { @@ -608,7 +594,8 @@ async function insertHandleFromEvent(event: FHEVMEvent) { } } -export function getTxHCUFromTxReceipt( +export function getTxHCUFromCoprocessorTxReceipt( + coprocessorContractAddress: `0x${string}`, receipt: EthersT.TransactionReceipt, FheTypeInfos: FheTypeInfo[] = ALL_FHE_TYPE_INFOS, ): { @@ -630,9 +617,9 @@ export function getTxHCUFromTxReceipt( let hcuMap: Record = {}; let handleSet: Set = new Set(); - const contract = new ethers.Contract(coprocAddress, abi, ethers.provider); + const contract = new ethers.Contract(coprocessorContractAddress, abi, ethers.provider); const relevantLogs = receipt.logs.filter((log: EthersT.Log) => { - if (log.address.toLowerCase() !== coprocAddress.toLowerCase()) { + if (log.address.toLowerCase() !== coprocessorContractAddress.toLowerCase()) { return false; } try { diff --git a/library-solidity/test/encryptedERC20/EncryptedERC20.HCU.ts b/library-solidity/test/encryptedERC20/EncryptedERC20.HCU.ts index 9eb0b608d..d831d5251 100644 --- a/library-solidity/test/encryptedERC20/EncryptedERC20.HCU.ts +++ b/library-solidity/test/encryptedERC20/EncryptedERC20.HCU.ts @@ -1,7 +1,6 @@ import { expect } from 'chai'; -import { getTxHCUFromTxReceipt } from '../coprocessorUtils'; -import { createInstances } from '../instance'; +import { createInstances, getTxHCUFromTxReceipt } from '../instance'; import { getSigners, initSigners } from '../signers'; import { deployEncryptedERC20Fixture } from './EncryptedERC20.fixture'; diff --git a/library-solidity/test/fhevmjsMocked.ts b/library-solidity/test/fhevmjsMocked.ts index 036217383..4ac3343f1 100644 --- a/library-solidity/test/fhevmjsMocked.ts +++ b/library-solidity/test/fhevmjsMocked.ts @@ -1,150 +1,164 @@ -import { toBigIntBE } from 'bigint-buffer'; +import type { + ClearValueType, + ClearValues, + FhevmInstance, + HandleContractPair, + PublicDecryptResults, + UserDecryptResults, +} from '@zama-fhe/relayer-sdk/node'; +import { createEIP712, generateKeypair } from '@zama-fhe/relayer-sdk/node'; import { toBufferBE } from 'bigint-buffer'; import crypto from 'crypto'; import dotenv from 'dotenv'; -import { type BigNumberish, type Signer, ethers } from 'ethers'; -import * as fs from 'fs'; +import { ethers } from 'ethers'; +import type { ethers as EthersT } from 'ethers'; +import { readFileSync } from 'fs'; import hre from 'hardhat'; import { Keccak } from 'sha3'; -import { isAddress } from 'web3-validator'; import { getRequiredEnvVar } from '../tasks/utils/loadVariables'; -import { insertSQL } from './coprocessorUtils'; -import { awaitCoprocessor, getClearText } from './coprocessorUtils'; +import { getTxHCUFromCoprocessorTxReceipt, insertSQL } from './coprocessorUtils'; +import { getCoprocessorClearText, processAllPastFHEVMExecutorEvents } from './coprocessorUtils'; import { checkIsHardhatSigner } from './utils'; -const toHexString = (bytes: Uint8Array, with0x = false) => - `${with0x ? '0x' : ''}${bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')}`; +//////////////////////////////////////////////////////////////////////////////// +// Config +//////////////////////////////////////////////////////////////////////////////// -const fromHexString = (hexString: string): Uint8Array => { - const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g); - if (!arr) return new Uint8Array(); - return Uint8Array.from(arr.map((byte) => parseInt(byte, 16))); +export type EnvFhevmMockConfig = { + kmsContractAddress: `0x${string}`; + aclContractAddress: `0x${string}`; + coprocessorContractAddress: `0x${string}`; + inputVerifierContractAddress: `0x${string}`; + verifyingContractAddressDecryption: `0x${string}`; + verifyingContractAddressInputVerification: `0x${string}`; + gatewayChainId: number; + chainId: number; + kmsThreshold: number; + coprocessorThreshold: number; }; -async function getCoprocessorSigners() { - const coprocessorSigners = []; - const numKMSSigners = getRequiredEnvVar('NUM_COPROCESSORS'); - for (let idx = 0; idx < +numKMSSigners; idx++) { - const coprocessorSigner = await hre.ethers.getSigner(getRequiredEnvVar(`COPROCESSOR_SIGNER_ADDRESS_${idx}`)); - await checkIsHardhatSigner(coprocessorSigner); - coprocessorSigners.push(coprocessorSigner); - } - return coprocessorSigners; +function loadEnvFhevmMockConfig(): EnvFhevmMockConfig { + const hostEnvPath = getRequiredEnvVar('ENV_HOST_ADDRESSES_PATH'); + const hosts = dotenv.parse(readFileSync(hostEnvPath)); + return { + aclContractAddress: getAddress(hosts.ACL_CONTRACT_ADDRESS), + coprocessorContractAddress: getAddress(hosts.FHEVM_EXECUTOR_CONTRACT_ADDRESS), + kmsContractAddress: getAddress(hosts.KMS_VERIFIER_CONTRACT_ADDRESS), + inputVerifierContractAddress: getAddress(hosts.INPUT_VERIFIER_CONTRACT_ADDRESS), + verifyingContractAddressDecryption: getAddress(getRequiredEnvVar('DECRYPTION_ADDRESS')), + verifyingContractAddressInputVerification: getAddress(getRequiredEnvVar('INPUT_VERIFICATION_ADDRESS')), + gatewayChainId: Number(+getRequiredEnvVar('CHAIN_ID_GATEWAY')), + chainId: Number(hre.network.config.chainId), + kmsThreshold: Number(+getRequiredEnvVar('PUBLIC_DECRYPTION_THRESHOLD')), + coprocessorThreshold: Number(+getRequiredEnvVar('COPROCESSOR_THRESHOLD')), + }; } -const aclAdd = dotenv.parse(fs.readFileSync('./fhevmTemp/addresses/.env.host')).ACL_CONTRACT_ADDRESS; +const envFhevmMockConfig = loadEnvFhevmMockConfig(); -enum Types { - ebool = 0, - euint8 = 2, - euint16 = 3, - euint32 = 4, - euint64 = 5, - euint128 = 6, - eaddress = 7, - euint256 = 8, - ebytes64 = 9, - ebytes128 = 10, - ebytes256 = 11, +export function getEnvFhevmMockConfig(): EnvFhevmMockConfig { + return envFhevmMockConfig; } -const sum = (arr: number[]) => arr.reduce((acc, val) => acc + val, 0); +//////////////////////////////////////////////////////////////////////////////// +// Public API +//////////////////////////////////////////////////////////////////////////////// -function bytesToBigInt(byteArray: Uint8Array): bigint { - if (!byteArray || byteArray?.length === 0) { - return BigInt(0); +export function assertNetwork(name: string) { + if (hre.network.name !== name) { + throw new Error(`Unsupported Hardhat network ${name}, expecting '${name}'`); } - const buffer = Buffer.from(byteArray); - const result = toBigIntBE(buffer); - return result; } -function createUintToUint8ArrayFunction(numBits: number) { - const numBytes = Math.ceil(numBits / 8); - return function (uint: number | bigint | boolean) { - const buffer = toBufferBE(BigInt(uint), numBytes); - - // concatenate 32 random bytes at the end of buffer to simulate encryption noise - const randomBytes = crypto.randomBytes(32); - const combinedBuffer = Buffer.concat([buffer, randomBytes]); +export function getTxHCUFromTxReceipt(receipt: EthersT.TransactionReceipt): { + globalTxHCU: number; + maxTxHCUDepth: number; + HCUDepthPerHandle: Record; +} { + return getTxHCUFromCoprocessorTxReceipt(getEnvFhevmMockConfig().coprocessorContractAddress, receipt); +} - let byteBuffer; - let totalBuffer; +export async function awaitCoprocessor() { + return processAllPastFHEVMExecutorEvents(getEnvFhevmMockConfig().coprocessorContractAddress); +} - switch (numBits) { - case 2: // ebool takes 2 bits - byteBuffer = Buffer.from([Types.ebool]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 8: - byteBuffer = Buffer.from([Types.euint8]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 16: - byteBuffer = Buffer.from([Types.euint16]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 32: - byteBuffer = Buffer.from([Types.euint32]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 64: - byteBuffer = Buffer.from([Types.euint64]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 128: - byteBuffer = Buffer.from([Types.euint128]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 160: - byteBuffer = Buffer.from([Types.eaddress]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 256: - byteBuffer = Buffer.from([Types.euint256]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 512: - byteBuffer = Buffer.from([Types.ebytes64]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 1024: - byteBuffer = Buffer.from([Types.ebytes128]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - case 2048: - byteBuffer = Buffer.from([Types.ebytes256]); - totalBuffer = Buffer.concat([byteBuffer, combinedBuffer]); - break; - default: - throw Error('Non-supported numBits'); - } +export async function getClearText(handle: string | bigint): Promise { + return getCoprocessorClearText(handle); +} - return totalBuffer; +export async function createInstanceMocked(envFhevmMockConfig: EnvFhevmMockConfig): Promise { + const instance: FhevmInstance = { + userDecrypt: userDecryptRequestMocked({ + aclContractAddress: envFhevmMockConfig.aclContractAddress, + coprocessorContractAddress: envFhevmMockConfig.coprocessorContractAddress, + }), + publicDecrypt: publicDecryptRequestMocked({ + hostKMSSigners: await getHostKMSSigners(envFhevmMockConfig.kmsContractAddress), + thresholdSigners: envFhevmMockConfig.kmsThreshold, // maybe host value instead of env ? + aclContractAddress: envFhevmMockConfig.aclContractAddress, + coprocessorContractAddress: envFhevmMockConfig.coprocessorContractAddress, + gatewayChainId: envFhevmMockConfig.gatewayChainId, + verifyingContractAddress: envFhevmMockConfig.verifyingContractAddressDecryption, + }), + createEncryptedInput: createEncryptedInputMocked, + getPublicKey: () => null, + getPublicParams: () => null, + generateKeypair: generateKeypair, + createEIP712: createEIP712(envFhevmMockConfig.verifyingContractAddressDecryption, envFhevmMockConfig.chainId), }; + return instance; } -export type HandleContractPair = { - ctHandle: Uint8Array | string; - contractAddress: string; -}; +//////////////////////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////////////////////// -export type HandleContractPairRelayer = { - ctHandle: string; - contractAddress: string; -}; +const MAX_USER_DECRYPT_CONTRACT_ADDRESSES = 10; +const MAX_USER_DECRYPT_DURATION_DAYS = BigInt(365); -export const userDecryptRequestMocked = - ( - kmsSigners: string[], - gatewayChainId: number, - chainId: number, - verifyingContractAddress: string, - aclContractAddress: string, - relayerUrl: string, - provider: ethers.JsonRpcProvider | ethers.BrowserProvider, - ) => +export const ENCRYPTION_TYPES = { + 2: 0, // ebool takes 2 bits + 8: 2, + 16: 3, + 32: 4, + 64: 5, + 128: 6, + 160: 7, + 256: 8, +} as const; + +const CiphertextAbiType = { + 0: 'bool', + 2: 'uint256', + 3: 'uint256', + 4: 'uint256', + 5: 'uint256', + 6: 'uint256', + 7: 'address', + 8: 'uint256', +} as const; + +type EncryptedBits = keyof typeof ENCRYPTION_TYPES; +type EncryptedType = keyof typeof CiphertextAbiType; + +const NumEncryptedBits: Record = { + 0: 2, // ebool + 2: 8, // euint8 + 3: 16, // euint16 + 4: 32, // euint32 + 5: 64, // euint64 + 6: 128, // euint128 + 7: 160, // eaddress + 8: 256, // euint256 +} as const; + +//////////////////////////////////////////////////////////////////////////////// +// userDecrypt +//////////////////////////////////////////////////////////////////////////////// + +const userDecryptRequestMocked = + (params: { aclContractAddress: `0x${string}`; coprocessorContractAddress: `0x${string}` }) => async ( _handles: HandleContractPair[], privateKey: string, @@ -154,13 +168,14 @@ export const userDecryptRequestMocked = userAddress: string, startTimestamp: string | number, durationDays: string | number, - ): Promise => { + ): Promise => { + const { aclContractAddress, coprocessorContractAddress } = params; // Casting handles if string - const handles: HandleContractPairRelayer[] = _handles.map((h) => ({ - ctHandle: - typeof h.ctHandle === 'string' ? toHexString(fromHexString(h.ctHandle), true) : toHexString(h.ctHandle, true), + const handleContractPairs: { handle: `0x${string}`; contractAddress: string }[] = _handles.map((h) => ({ + handle: typeof h.handle === 'string' ? toHexString(fromHexString(h.handle)) : toHexString(h.handle), contractAddress: h.contractAddress, })); + const handles: `0x${string}`[] = handleContractPairs.map((pair) => pair.handle); // Signature checking: const domain = { @@ -193,12 +208,24 @@ export const userDecryptRequestMocked = throw new Error('Invalid EIP-712 signature!'); } + checkEncryptedBits(handles); + + checkDeadlineValidity(BigInt(startTimestamp), BigInt(durationDays)); + + const contractAddressesLength = contractAddresses.length; + if (contractAddressesLength === 0) { + throw Error('contractAddresses is empty'); + } + if (contractAddressesLength > MAX_USER_DECRYPT_CONTRACT_ADDRESSES) { + throw Error(`contractAddresses max length of ${MAX_USER_DECRYPT_CONTRACT_ADDRESSES} exceeded`); + } + // ACL checking const aclFactory = await hre.ethers.getContractFactory('ACL'); - const acl = aclFactory.attach(aclAdd); - const verifications = handles.map(async ({ ctHandle, contractAddress }) => { - const userAllowed = await acl.persistAllowed(ctHandle, userAddress); - const contractAllowed = await acl.persistAllowed(ctHandle, contractAddress); + const acl = aclFactory.attach(aclContractAddress) as ethers.Contract; + const verifications = handleContractPairs.map(async ({ handle, contractAddress }) => { + const userAllowed = await acl.persistAllowed(handle, userAddress); + const contractAllowed = await acl.persistAllowed(handle, contractAddress); if (!userAllowed) { throw new Error('User is not authorized to reencrypt this handle!'); } @@ -213,14 +240,163 @@ export const userDecryptRequestMocked = await Promise.all(verifications).catch((e) => { throw e; }); - await awaitCoprocessor(); - return Promise.all( - handles.map(async (handleContractPair) => await BigInt(await getClearText(handleContractPair.ctHandle))), + await processAllPastFHEVMExecutorEvents(coprocessorContractAddress); + + const listBigIntDecryptions: bigint[] = await Promise.all( + handleContractPairs.map(async (handleContractPair) => BigInt(await getClearText(handleContractPair.handle))), ); + + const results: UserDecryptResults = buildUserDecryptResults(handles, listBigIntDecryptions); + + return results; + }; + +//////////////////////////////////////////////////////////////////////////////// +// publicDecrypt +//////////////////////////////////////////////////////////////////////////////// + +const publicDecryptRequestMocked = + (params: { + hostKMSSigners: `0x${string}`[]; + thresholdSigners: number; + aclContractAddress: `0x${string}`; + coprocessorContractAddress: `0x${string}`; + gatewayChainId: number; + verifyingContractAddress: `0x${string}`; + }) => + async (_handles: (string | Uint8Array)[]): Promise => { + const { + hostKMSSigners, + thresholdSigners, + aclContractAddress, + coprocessorContractAddress, + gatewayChainId, + verifyingContractAddress, + } = params; + const extraData: `0x${string}` = '0x00'; + const aclFactory = await hre.ethers.getContractFactory('ACL'); + const acl = aclFactory.attach(aclContractAddress) as ethers.Contract; + + let handlesBytes32Hex: `0x${string}`[]; + try { + handlesBytes32Hex = await Promise.all( + _handles.map(async (_handle) => { + const handle = typeof _handle === 'string' ? toHexString(fromHexString(_handle)) : toHexString(_handle); + + const isAllowedForDecryption = await acl.isAllowedForDecryption(handle); + if (!isAllowedForDecryption) { + throw new Error(`Handle ${handle} is not allowed for public decryption!`); + } + return handle; + }), + ); + } catch (e) { + throw e; + } + + // check 2048 bits limit + checkEncryptedBits(handlesBytes32Hex); + + const payloadForRequest = { + ciphertextHandles: handlesBytes32Hex, + extraData, + }; + + const json = await _handleFhevmRelayerV1PublicDecrypt(coprocessorContractAddress, payloadForRequest); + + // verify signatures on decryption: + const domain = { + name: 'Decryption', + version: '1', + chainId: gatewayChainId, + verifyingContract: verifyingContractAddress, + }; + const types = { + PublicDecryptVerification: [ + { name: 'ctHandles', type: 'bytes32[]' }, + { name: 'decryptedResult', type: 'bytes' }, + { name: 'extraData', type: 'bytes' }, + ], + }; + const result = json.response[0]; + const decryptedResult: `0x${string}` = ensure0x(result.decrypted_value); + const kmsSignatures: `0x${string}`[] = result.signatures.map(ensure0x); + + // TODO result.extra_data (RelayerPublicDecryptJsonResponse) + const signedExtraData: `0x${string}` = '0x00'; + + const recoveredAddresses: `0x${string}`[] = kmsSignatures.map((kmsSignature: `0x${string}`) => { + const recoveredAddress = ethers.verifyTypedData( + domain, + types, + { ctHandles: handlesBytes32Hex, decryptedResult, extraData: signedExtraData }, + kmsSignature, + ) as `0x${string}`; + return recoveredAddress; + }); + + // use KMS Signers stored on host + const thresholdReached = isKmsThresholdReached(hostKMSSigners, recoveredAddresses, thresholdSigners); + + if (!thresholdReached) { + throw Error('KMS signers threshold is not reached'); + } + + const clearValues: ClearValues = deserializeClearValues(handlesBytes32Hex, decryptedResult); + + const abiEnc = abiEncodeClearValues(clearValues); + const decryptionProof = buildDecryptionProof(kmsSignatures, signedExtraData); + + return { + clearValues, + abiEncodedClearValues: abiEnc.abiEncodedClearValues, + decryptionProof, + }; + }; + +async function _handleFhevmRelayerV1PublicDecrypt( + coprocessorAddress: `0x${string}`, + payload: { + ciphertextHandles: `0x${string}`[]; + extraData: `0x${string}`; + }, +) { + const handlesBytes32Hex = payload.ciphertextHandles; + const extraData = payload.extraData; + + await processAllPastFHEVMExecutorEvents(coprocessorAddress); + + const listBigIntDecryptions: bigint[] = await Promise.all( + handlesBytes32Hex.map(async (h) => BigInt(await getClearText(h))), + ); + + const clearValues = buildClearValues(handlesBytes32Hex, listBigIntDecryptions); + const abiEnc = abiEncodeClearValues(clearValues); + + // Use env KMS Signers. Not host KMS signers. This is needed to simulate KMS signature issues in the test suite + const kmsSignatures = await envComputeDecryptSignaturesKms( + handlesBytes32Hex, + abiEnc.abiEncodedClearValues, + extraData, + ); + + // Build relayer response + return { + response: [ + { + decrypted_value: abiEnc.abiEncodedClearValues, + signatures: kmsSignatures, + }, + ], }; +} -export const createEncryptedInputMocked = (contractAddress: string, userAddress: string) => { +//////////////////////////////////////////////////////////////////////////////// +// createEncryptedInput +//////////////////////////////////////////////////////////////////////////////// + +const createEncryptedInputMocked = (contractAddress: string, userAddress: string) => { if (!isAddress(contractAddress)) { throw new Error('Contract address is not a valid address.'); } @@ -230,22 +406,35 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: } const values: bigint[] = []; - const bits: (keyof typeof ENCRYPTION_TYPES)[] = []; + const bits: EncryptedBits[] = []; return { addBool(value: boolean | number | bigint) { if (value == null) throw new Error('Missing value'); if (typeof value !== 'boolean' && typeof value !== 'number' && typeof value !== 'bigint') throw new Error('The value must be a boolean, a number or a bigint.'); - if ((typeof value !== 'bigint' || typeof value !== 'number') && Number(value) > 1) - throw new Error('The value must be 1 or 0.'); - values.push(BigInt(value)); + + // Convert to 0n or 1n + if (typeof value === 'boolean') { + value = value ? 1n : 0n; + } else if (typeof value === 'number') { + if (value !== 0 && value !== 1) { + throw new Error('The value must be 1 or 0.'); + } + value = value === 0 ? 0n : 1n; + } else if (typeof value === 'bigint') { + if (value !== 0n && value !== 1n) { + throw new Error('The value must be 1 or 0.'); + } + } + + values.push(value); bits.push(2); // ebool takes 2 bits instead of one: only exception in TFHE-rs if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); return this; }, add8(value: number | bigint) { - checkEncryptedValue(value, 8); + checkEncryptedUint(value, 8); values.push(BigInt(value)); bits.push(8); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -253,7 +442,7 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add16(value: number | bigint) { - checkEncryptedValue(value, 16); + checkEncryptedUint(value, 16); values.push(BigInt(value)); bits.push(16); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -261,7 +450,7 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add32(value: number | bigint) { - checkEncryptedValue(value, 32); + checkEncryptedUint(value, 32); values.push(BigInt(value)); bits.push(32); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -269,7 +458,7 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add64(value: number | bigint) { - checkEncryptedValue(value, 64); + checkEncryptedUint(value, 64); values.push(BigInt(value)); bits.push(64); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -277,7 +466,7 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add128(value: number | bigint) { - checkEncryptedValue(value, 128); + checkEncryptedUint(value, 128); values.push(BigInt(value)); bits.push(128); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); @@ -295,43 +484,13 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return this; }, add256(value: number | bigint) { - checkEncryptedValue(value, 256); + checkEncryptedUint(value, 256); values.push(BigInt(value)); bits.push(256); if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); return this; }, - addBytes64(value: Uint8Array) { - if (value.length !== 64) throw Error('Uncorrect length of input Uint8Array, should be 64 for an ebytes64'); - const bigIntValue = bytesToBigInt(value); - checkEncryptedValue(bigIntValue, 512); - values.push(bigIntValue); - bits.push(512); - if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); - if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); - return this; - }, - addBytes128(value: Uint8Array) { - if (value.length !== 128) throw Error('Uncorrect length of input Uint8Array, should be 128 for an ebytes128'); - const bigIntValue = bytesToBigInt(value); - checkEncryptedValue(bigIntValue, 1024); - values.push(bigIntValue); - bits.push(1024); - if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); - if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); - return this; - }, - addBytes256(value: Uint8Array) { - if (value.length !== 256) throw Error('Uncorrect length of input Uint8Array, should be 256 for an ebytes256'); - const bigIntValue = bytesToBigInt(value); - checkEncryptedValue(bigIntValue, 2048); - values.push(bigIntValue); - bits.push(2048); - if (sum(bits) > 2048) throw Error('Packing more than 2048 bits in a single input ciphertext is unsupported'); - if (bits.length > 256) throw Error('Packing more than 256 variables in a single input ciphertext is unsupported'); - return this; - }, getValues() { return values; }, @@ -343,15 +502,18 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: bits.length = 0; return this; }, - async encrypt() { - let encrypted = Buffer.alloc(0); + async encrypt(): Promise<{ + handles: Uint8Array[]; + inputProof: Uint8Array; + }> { + let mockEncryptedValues = Buffer.alloc(0); + for (let i = 0; i < bits.length; ++i) { + mockEncryptedValues = Buffer.concat([mockEncryptedValues, createRandomEncryptedValue(values[i], bits[i])]); + } - bits.map((v, i) => { - encrypted = Buffer.concat([encrypted, createUintToUint8ArrayFunction(v)(values[i])]); - }); + const mockEncryptedArray = new Uint8Array(mockEncryptedValues); + const mockHash = new Keccak(256).update(Buffer.from(mockEncryptedArray)).digest(); - const encryptedArray = new Uint8Array(encrypted); - const hash = new Keccak(256).update(Buffer.from(encryptedArray)).digest(); const extraDataV0 = ethers.solidityPacked(['uint8'], [0]); const chainId = process.env.SOLIDITY_COVERAGE === 'true' ? 31337 : hre.network.config.chainId; @@ -360,9 +522,9 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: } const handles = bits.map((v, i) => { - const dataWithIndex = new Uint8Array(hash.length + 1); - dataWithIndex.set(hash, 0); - dataWithIndex.set([i], hash.length); + const dataWithIndex = new Uint8Array(mockHash.length + 1); + dataWithIndex.set(mockHash, 0); + dataWithIndex.set([i], mockHash.length); const finalHash = new Keccak(256).update(Buffer.from(dataWithIndex)).digest(); const dataInput = new Uint8Array(32); dataInput.set(finalHash, 0); @@ -381,45 +543,98 @@ export const createEncryptedInputMocked = (contractAddress: string, userAddress: return dataInput; }); - let inputProof = '0x' + numberToHex(handles.length); // numHandles + numCoprocessorSigners + list_handles + signatureCoprocessorSigners (total len : 1+1+32+NUM_HANDLES*32+65*numSigners) - const numSigners = +process.env.NUM_COPROCESSORS!; - inputProof += numberToHex(numSigners); + const listHandlesHexNo0x = handles.map((handleAsBytes: Uint8Array) => uint8ArrayToHexStringNo0x(handleAsBytes)); + const listHandlesAsBigInt = listHandlesHexNo0x.map((handleNo0x: string) => BigInt('0x' + handleNo0x)); - const listHandlesStr = handles.map((i) => uint8ArrayToHexString(i)); - listHandlesStr.map((handle) => (inputProof += handle)); - const listHandles = listHandlesStr.map((i) => BigInt('0x' + i)); - const signaturesCoproc = await computeInputSignaturesCopro( - listHandles, + const signaturesCoproc = await envComputeInputSignaturesCoproc( + listHandlesAsBigInt, userAddress, contractAddress, extraDataV0, ); + + // numHandles + numCoprocessorSigners + list_handles + signatureCoprocessorSigners (total len : 1+1+32+NUM_HANDLES*32+65*numSigners) + let inputProof = '0x' + numberToHexNo0x(handles.length); + inputProof += numberToHexNo0x(signaturesCoproc.length); + listHandlesHexNo0x.map((handleNo0x) => (inputProof += handleNo0x)); signaturesCoproc.map((sigCopro) => (inputProof += sigCopro.slice(2))); - listHandlesStr.map((handle, i) => insertSQL('0x' + handle, values[i])); + listHandlesHexNo0x.map((handleNo0x, i) => insertSQL('0x' + handleNo0x, values[i])); // Append the extra data to the input proof inputProof = ethers.concat([inputProof, extraDataV0]); return { handles, - inputProof, + inputProof: hexStringToUint8Array(inputProof), }; }, }; }; -function uint8ArrayToHexString(uint8Array: Uint8Array) { +//////////////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////////////// + +// Add type checking +function getAddress(value: string): `0x${string}` { + return ethers.getAddress(value) as `0x${string}`; +} +// Add type checking +function isAddress(value: unknown): value is `0x${string}` { + return ethers.isAddress(value); +} + +function ensure0x(s: string): `0x${string}` { + return !s.startsWith('0x') ? `0x${s}` : (s as `0x${string}`); +} + +function toHexString(bytes: Uint8Array): `0x${string}` { + return `0x${bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')}`; +} + +function fromHexString(hexString: string): Uint8Array { + const arr = hexString.replace(/^(0x)/, '').match(/.{1,2}/g); + if (!arr) return new Uint8Array(); + return Uint8Array.from(arr.map((byte) => parseInt(byte, 16))); +} + +function toBytes32HexString(value: EthersT.BigNumberish): `0x${string}` { + return ethers.zeroPadValue(ethers.toBeHex(value), 32) as `0x${string}`; +} + +function hexStringToUint8Array(hexString: string) { + return ethers.getBytes(hexString); +} + +function uint8ArrayToHexStringNo0x(uint8Array: Uint8Array): string { return Array.from(uint8Array) .map((byte) => byte.toString(16).padStart(2, '0')) .join(''); } -function numberToHex(num: number) { +function numberToHexNo0x(num: number): string { let hex = num.toString(16); return hex.length % 2 ? '0' + hex : hex; } -const checkEncryptedValue = (value: number | bigint, bits: number) => { +const sum = (arr: number[]) => arr.reduce((acc, val) => acc + val, 0); + +//////////////////////////////////////////////////////////////////////////////// +// Encrypted values +//////////////////////////////////////////////////////////////////////////////// + +function createRandomEncryptedValue(valueAsBigInt: bigint, numBits: EncryptedBits) { + const numBytes = Math.ceil(numBits / 8); + // Format: type + value + random 32 + // concatenate 32 random bytes at the end of buffer to simulate encryption noise + const bufferValue = toBufferBE(valueAsBigInt, numBytes); + const combinedBuffer = Buffer.concat([bufferValue, crypto.randomBytes(32)]); + const typeBuffer = Buffer.from([ENCRYPTION_TYPES[numBits]]); + const totalBuffer = Buffer.concat([typeBuffer, combinedBuffer]); + return totalBuffer; +} + +function checkEncryptedUint(value: number | bigint, bits: number) { if (value == null) throw new Error('Missing value'); let limit; if (bits >= 8) { @@ -428,59 +643,89 @@ const checkEncryptedValue = (value: number | bigint, bits: number) => { limit = BigInt(2 ** bits - 1); } if (typeof value !== 'number' && typeof value !== 'bigint') throw new Error('Value must be a number or a bigint.'); + if (typeof value === 'number') { + if (!Number.isInteger(value)) { + throw new Error('Value must be an unsigned integer.'); + } + value = BigInt(value); + } + if (value < 0) { + throw new Error('Value must be an unsigned integer.'); + } if (value > limit) { throw new Error(`The value exceeds the limit for ${bits}bits integer (${limit.toString()}).`); } -}; +} -export const ENCRYPTION_TYPES = { - 2: 0, // ebool takes 2 bits - 4: 1, - 8: 2, - 16: 3, - 32: 4, - 64: 5, - 128: 6, - 160: 7, - 256: 8, - 512: 9, - 1024: 10, - 2048: 11, -}; +//////////////////////////////////////////////////////////////////////////////// +// Dynamic Signers (using env variables) +//////////////////////////////////////////////////////////////////////////////// -async function computeInputSignaturesCopro( - handlesList: BigNumberish[], +// Not dynamic +async function getHostKMSSigners(kmsContractAddress: `0x${string}`): Promise<`0x${string}`[]> { + const abiKmsVerifier = ['function getKmsSigners() view returns (address[])']; + const kmsContract = new ethers.Contract(kmsContractAddress, abiKmsVerifier, hre.ethers.provider); + const signers: `0x${string}`[] = await kmsContract.getKmsSigners(); + return signers; +} + +// dynamic function for testing +async function getEnvCoprocessorSigners() { + const signers = []; + const numSigners = getRequiredEnvVar('NUM_COPROCESSORS'); + for (let idx = 0; idx < +numSigners; idx++) { + const signer = await hre.ethers.getSigner(getRequiredEnvVar(`COPROCESSOR_SIGNER_ADDRESS_${idx}`)); + await checkIsHardhatSigner(signer); + signers.push(signer); + } + return signers; +} + +// dynamic function for testing +async function getEnvKMSSigners() { + const signers = []; + const numSigners = getRequiredEnvVar('NUM_KMS_NODES'); + for (let idx = 0; idx < +numSigners; idx++) { + const signer = await hre.ethers.getSigner(getRequiredEnvVar(`KMS_SIGNER_ADDRESS_${idx}`)); + await checkIsHardhatSigner(signer); + signers.push(signer); + } + return signers; +} + +// Coprocessor Signers are dynamically determined using env variables +async function envComputeInputSignaturesCoproc( + handlesList: EthersT.BigNumberish[], userAddress: string, contractAddress: string, extraData: string, -): Promise { - const signatures: string[] = []; - const numSigners = +process.env.NUM_COPROCESSORS!; - let signers = await getCoprocessorSigners(); - - for (let idx = 0; idx < numSigners; idx++) { - const coprocSigner = signers[idx]; - const signature = await coprocSign(handlesList, userAddress, contractAddress, extraData, coprocSigner); +): Promise<`0x${string}`[]> { + const signatures: `0x${string}`[] = []; + const coprocSigners = await getEnvCoprocessorSigners(); + for (let idx = 0; idx < coprocSigners.length; idx++) { + const coprocSigner = coprocSigners[idx]; + const signature = await envCoprocSign(handlesList, userAddress, contractAddress, extraData, coprocSigner); signatures.push(signature); } return signatures; } -async function coprocSign( - handlesList: BigNumberish[], +// Coprocessor Signers are dynamically determined using env variables +async function envCoprocSign( + handlesList: EthersT.BigNumberish[], userAddress: string, contractAddress: string, extraData: string, - signer: Signer, -): Promise { + signer: EthersT.Signer, +): Promise<`0x${string}`> { const inputVerificationAdd = process.env.INPUT_VERIFICATION_ADDRESS; - const chainId = process.env.CHAIN_ID_GATEWAY; + const gatewayChainId = process.env.CHAIN_ID_GATEWAY; const hostChainId = process.env.SOLIDITY_COVERAGE === 'true' ? 31337 : hre.network.config.chainId; const domain = { name: 'InputVerification', version: '1', - chainId: chainId, + chainId: gatewayChainId, verifyingContract: inputVerificationAdd, }; @@ -510,7 +755,7 @@ async function coprocSign( }; const message = { - ctHandles: handlesList.map((handle) => ethers.zeroPadValue(ethers.toBeHex(handle), 32)), + ctHandles: handlesList.map(toBytes32HexString), userAddress: userAddress, contractAddress: contractAddress, contractChainId: hostChainId, @@ -524,5 +769,291 @@ async function coprocSign( const s = sigRSV.s; const result = r + s.substring(2) + v.toString(16); - return result; + + return result as `0x${string}`; +} + +// KMS Signers are dynamically determined using env variables +async function envComputeDecryptSignaturesKms( + handlesList: EthersT.BigNumberish[], + decryptedResult: `0x${string}`, + extraData: `0x${string}`, +): Promise<`0x${string}`[]> { + const signatures: `0x${string}`[] = []; + const kmsSigners = await getEnvKMSSigners(); + + for (let i = 0; i < kmsSigners.length; i++) { + const kmsSigner = kmsSigners[i]; + const signature = await envKmsSign(handlesList, decryptedResult, extraData, kmsSigner); + signatures.push(signature); + } + return signatures; +} + +// KMS Signers are dynamically determined using env variables +async function envKmsSign( + handlesList: EthersT.BigNumberish[], + decryptedResult: `0x${string}`, + extraData: `0x${string}`, + kmsSigner: EthersT.Signer, +): Promise<`0x${string}`> { + // always keep dynamic values for testing + const decryptionAddress = process.env.DECRYPTION_ADDRESS; + const gatewayChainId = process.env.CHAIN_ID_GATEWAY; + + const domain = { + name: 'Decryption', + version: '1', + chainId: gatewayChainId, + verifyingContract: decryptionAddress, + }; + + const types = { + PublicDecryptVerification: [ + { + name: 'ctHandles', + type: 'bytes32[]', + }, + { + name: 'decryptedResult', + type: 'bytes', + }, + { + name: 'extraData', + type: 'bytes', + }, + ], + }; + const message = { + ctHandles: handlesList.map(toBytes32HexString), + decryptedResult, + extraData, + }; + + const signature = await kmsSigner.signTypedData(domain, types, message); + const sigRSV = ethers.Signature.from(signature); + const v = 27 + sigRSV.yParity; + const r = sigRSV.r; + const s = sigRSV.s; + + const result = r + s.substring(2) + v.toString(16); + + return result as `0x${string}`; +} + +// Copy/paste from relayer-sdk +function toClearValueType(clearValueAsBigInt: bigint, type: number): ClearValueType { + if (type === 0) { + // ebool + return clearValueAsBigInt === BigInt(1); + } else if (type === 7) { + // eaddress + return getAddress('0x' + clearValueAsBigInt.toString(16).padStart(40, '0')); + } else if (type > 8 || type == 1) { + // type == 1 : euint4 (not supported) + throw new Error(`Unsupported handle type ${type}`); + } + // euintXXX + return clearValueAsBigInt; +} + +// Copy/paste from relayer-sdk +function buildUserDecryptResults( + handlesBytes32Hex: `0x${string}`[], + listBigIntDecryptions: bigint[], +): UserDecryptResults { + return buildClearValues(handlesBytes32Hex, listBigIntDecryptions); +} + +function buildClearValues(handlesBytes32Hex: `0x${string}`[], listBigIntDecryptions: bigint[]): ClearValues { + const typesList = getHandlesTypes(handlesBytes32Hex); + const results: ClearValues = {}; + handlesBytes32Hex.forEach( + (handle, idx) => (results[handle] = toClearValueType(listBigIntDecryptions[idx], typesList[idx])), + ); + + return results; +} + +// Copy/paste from relayer-sdk +function checkDeadlineValidity(startTimestamp: bigint, durationDays: bigint) { + if (durationDays === BigInt(0)) { + throw Error('durationDays is null'); + } + + if (durationDays > MAX_USER_DECRYPT_DURATION_DAYS) { + throw Error(`durationDays is above max duration of ${MAX_USER_DECRYPT_DURATION_DAYS}`); + } + + const currentTimestamp = BigInt(Math.floor(Date.now() / 1000)); + if (startTimestamp > currentTimestamp) { + throw Error('startTimestamp is set in the future'); + } + + const durationInSeconds = durationDays * BigInt(86400); + if (startTimestamp + durationInSeconds < currentTimestamp) { + throw Error('User decrypt request has expired'); + } +} + +// Copy/paste from relayer-sdk +function checkEncryptedBits(handlesBytes32Hex: `0x${string}`[]): number { + let total = 0; + + for (const handleBytes32Hex of handlesBytes32Hex) { + const typeDiscriminant = getHandleType(handleBytes32Hex); + total += NumEncryptedBits[typeDiscriminant as keyof typeof NumEncryptedBits]; + // enforce 2048‑bit limit + if (total > 2048) { + throw new Error('Cannot decrypt more than 2048 encrypted bits in a single request'); + } + } + return total; +} + +// Copy/paste from relayer-sdk +function isKmsThresholdReached(kmsSigners: string[], recoveredAddresses: string[], threshold: number): boolean { + if (typeof threshold !== 'number') { + throw new Error('INTERNAL ERROR'); + } + const uniq = new Map(); + recoveredAddresses.forEach((address, index) => { + if (uniq.has(address)) { + throw new Error(`Duplicate KMS signer address found: ${address} appears multiple times in recovered addresses`); + } + uniq.set(address, index); + }); + + for (const address of recoveredAddresses) { + if (!kmsSigners.includes(address)) { + throw new Error(`Invalid address found: ${address} is not in the list of KMS signers`); + } + } + return uniq.size >= threshold; +} + +// Copy/paste from relayer-sdk +function abiEncodeClearValues(clearValues: ClearValues) { + const handlesBytes32Hex = Object.keys(clearValues) as `0x${string}`[]; + + const abiTypes: string[] = []; + const abiValues: (string | bigint)[] = []; + + for (let i = 0; i < handlesBytes32Hex.length; ++i) { + const handle = handlesBytes32Hex[i]; + const handleType: EncryptedType = getHandleType(handle); + + let clearTextValue: ClearValueType = clearValues[handle as keyof typeof clearValues]; + if (typeof clearTextValue === 'boolean') { + clearTextValue = clearTextValue ? '0x01' : '0x00'; + } + + const clearTextValueBigInt = BigInt(clearTextValue); + + //abiTypes.push(fhevmTypeInfo.solidityTypeName); + abiTypes.push('uint256'); + + switch (handleType) { + // eaddress + case 7: { + // string + abiValues.push(`0x${clearTextValueBigInt.toString(16).padStart(40, '0')}`); + break; + } + // ebool + case 0: { + // bigint (0 or 1) + if (clearTextValueBigInt !== BigInt(0) && clearTextValueBigInt !== BigInt(1)) { + throw new Error(`Invalid ebool clear text value ${clearTextValueBigInt}. Expecting 0 or 1.`); + } + abiValues.push(clearTextValueBigInt); + break; + } + case 2: //euint8 + case 3: //euint16 + case 4: //euint32 + case 5: //euint64 + case 6: //euint128 + case 7: { + //euint256 + // bigint + abiValues.push(clearTextValueBigInt); + break; + } + default: { + throw new Error(`Unsupported Fhevm primitive type id: ${handleType}`); + } + } + } + + const abiCoder = ethers.AbiCoder.defaultAbiCoder(); + + // ABI encode the decryptedResult as done in the KMS, since all decrypted values + // are native static types, thay have same abi-encoding as uint256: + const abiEncodedClearValues: `0x${string}` = abiCoder.encode(abiTypes, abiValues) as `0x${string}`; + + return { + abiTypes, + abiValues, + abiEncodedClearValues, + }; +} + +// Copy/paste from relayer-sdk +function buildDecryptionProof(kmsSignatures: `0x${string}`[], extraData: `0x${string}`): `0x${string}` { + // Build the decryptionProof as numSigners + KMS signatures + extraData + const packedNumSigners = ethers.solidityPacked(['uint8'], [kmsSignatures.length]); + const packedSignatures = ethers.solidityPacked(Array(kmsSignatures.length).fill('bytes'), kmsSignatures); + const decryptionProof: `0x${string}` = ethers.concat([ + packedNumSigners, + packedSignatures, + extraData, + ]) as `0x${string}`; + return decryptionProof; +} + +// Copy/paste from relayer-sdk +function getHandleType(handleBytes32Hex: `0x${string}`): EncryptedType { + if (handleBytes32Hex.length !== 66) { + throw new Error(`Handle ${handleBytes32Hex} is not of valid length`); + } + const hexPair = handleBytes32Hex.slice(-4, -2).toLowerCase(); + const typeDiscriminant = parseInt(hexPair, 16); + + if (!(typeDiscriminant in NumEncryptedBits)) { + throw new Error(`Handle ${handleBytes32Hex} is not of valid type`); + } + return typeDiscriminant as EncryptedType; +} + +function getHandlesTypes(handlesBytes32Hex: `0x${string}`[]): EncryptedType[] { + return handlesBytes32Hex.map(getHandleType); +} + +// Copy/paste from relayer-sdk +function deserializeClearValues(handlesBytes32Hex: `0x${string}`[], abiEncodedClearValues: `0x${string}`): ClearValues { + const typesList: EncryptedType[] = getHandlesTypes(handlesBytes32Hex); + + // TODO: dummy stuff must be removed! + const restoredEncoded = + '0x' + + '00'.repeat(32) + // dummy requestID (ignored) + abiEncodedClearValues.slice(2) + + '00'.repeat(32); // dummy empty bytes[] length (ignored) + + const abiTypes = typesList.map((t) => { + const abiType = CiphertextAbiType[t]; // all types are valid because this was supposedly checked already inside the `checkEncryptedBits` function + return abiType; + }); + + const coder = new ethers.AbiCoder(); + const decoded = coder.decode(['uint256', ...abiTypes, 'bytes[]'], restoredEncoded); + + // strip dummy first/last element + const rawValues = decoded.slice(1, 1 + typesList.length); + + const results: ClearValues = {}; + handlesBytes32Hex.forEach((handle, idx) => (results[handle] = rawValues[idx])); + + return results; } diff --git a/library-solidity/test/instance.ts b/library-solidity/test/instance.ts index e14d151e0..c3ac4dd5f 100644 --- a/library-solidity/test/instance.ts +++ b/library-solidity/test/instance.ts @@ -1,46 +1,40 @@ -import { - FhevmInstance, - clientKeyDecryptor, - createEIP712, - createInstance as createFhevmInstance, - generateKeypair, - getCiphertextCallParams, -} from '@zama-fhe/relayer-sdk/node'; -import dotenv from 'dotenv'; +import type { FhevmInstance } from '@zama-fhe/relayer-sdk/node'; import type { ethers as EthersT } from 'ethers'; -import { readFileSync } from 'fs'; -import * as fs from 'fs'; -import { ethers, ethers as hethers, network } from 'hardhat'; -import { homedir } from 'os'; -import path from 'path'; +import { ethers } from 'hardhat'; -import { awaitCoprocessor, getClearText } from './coprocessorUtils'; -import { createEncryptedInputMocked, userDecryptRequestMocked } from './fhevmjsMocked'; +import { + assertNetwork, + awaitCoprocessor, + createInstanceMocked, + getClearText, + getEnvFhevmMockConfig, + getTxHCUFromTxReceipt, +} from './fhevmjsMocked'; import type { Signers } from './signers'; import { FhevmInstances } from './types'; -const FHE_CLIENT_KEY_PATH = process.env.FHE_CLIENT_KEY_PATH; - -let clientKey: Uint8Array | undefined; - -const abiKmsVerifier = ['function getKmsSigners() view returns (address[])']; const abiAcl = [ 'function delegateForUserDecryption(address,address,uint64)', 'function revokeDelegationForUserDecryption(address,address)', ]; -const parsedEnv = dotenv.parse(fs.readFileSync('./fhevmTemp/addresses/.env.host')); -const kmsAdd = parsedEnv.KMS_VERIFIER_CONTRACT_ADDRESS; -const aclAdd = parsedEnv.ACL_CONTRACT_ADDRESS; -const inputVerificationAdd = parsedEnv.INPUT_VERIFIER_CONTRACT_ADDRESS; -const gatewayChainID = +process.env.CHAIN_ID_GATEWAY!; -const hostChainId = Number(network.config.chainId); -const verifyingContractAddressDecryption = process.env.DECRYPTION_ADDRESS!; +export const createInstances = async (accounts: Signers): Promise => { + assertNetwork('hardhat'); + + const instances: FhevmInstances = {} as FhevmInstances; + await Promise.all( + Object.keys(accounts).map(async (k) => { + instances[k as keyof FhevmInstances] = await createInstanceMocked(getEnvFhevmMockConfig()); + }), + ); + + return instances; +}; -const getKMSSigners = async (): Promise => { - const kmsContract = new ethers.Contract(kmsAdd, abiKmsVerifier, ethers.provider); - const signers: string[] = await kmsContract.getKmsSigners(); - return signers; +export const createInstance = async (): Promise => { + assertNetwork('hardhat'); + const instance = await createInstanceMocked(getEnvFhevmMockConfig()); + return instance; }; export const delegateUserDecryption = async ( @@ -49,7 +43,7 @@ export const delegateUserDecryption = async ( contractAddress: string, expirationDate: bigint, ): Promise => { - const aclContract = new ethers.Contract(aclAdd, abiAcl, delegator); + const aclContract = new ethers.Contract(getEnvFhevmMockConfig().aclContractAddress, abiAcl, delegator); return aclContract.delegateForUserDecryption(delegate, contractAddress, expirationDate); }; @@ -58,97 +52,22 @@ export const revokeUserDecryptionDelegation = async ( delegate: string, contractAddress: string, ): Promise => { - const aclContract = new ethers.Contract(aclAdd, abiAcl, delegator); + const aclContract = new ethers.Contract(getEnvFhevmMockConfig().aclContractAddress, abiAcl, delegator); return aclContract.revokeDelegationForUserDecryption(delegate, contractAddress); }; -const createInstanceMocked = async (): FhevmInstance => { - const kmsSigners = await getKMSSigners(); - - const instance: FhevmInstance = { - userDecrypt: userDecryptRequestMocked( - kmsSigners, - gatewayChainID, - hostChainId, - verifyingContractAddressDecryption, - aclAdd, - 'http://localhost:3000', - ethers.provider, - ), - createEncryptedInput: createEncryptedInputMocked, - getPublicKey: () => '0xFFAA44433', - generateKeypair: generateKeypair, - createEIP712: createEIP712(verifyingContractAddressDecryption, network.config.chainId!), - }; - return instance; -}; - -export const createInstances = async (accounts: Signers): Promise => { - // Create instance - const instances: FhevmInstances = {} as FhevmInstances; - if (network.name === 'hardhat') { - await Promise.all( - Object.keys(accounts).map(async (k) => { - instances[k as keyof FhevmInstances] = await createInstanceMocked(); - }), - ); - } else { - await Promise.all( - Object.keys(accounts).map(async (k) => { - instances[k as keyof FhevmInstances] = await createInstance(); - }), - ); - } - return instances; -}; - -export const createInstance = async () => { - const relayerUrl = 'http://localhost:3000'; - const instance = await createFhevmInstance({ - verifyingContractAddressDecryption, - verifyingContractAddressInputVerification: ethers.ZeroAddress, - kmsContractAddress: kmsAdd, - aclContractAddress: aclAdd, - inputVerifierContractAddress: inputVerificationAdd, - network: (network.config as any).url, - relayerUrl: relayerUrl, - gatewayChainId: gatewayChainID || 54321, - }); - return instance; -}; - -const getCiphertext = async (handle: string, ethers: typeof hethers): Promise => { - return ethers.provider.call(getCiphertextCallParams(handle)); -}; - -const getDecryptor = () => { - if (clientKey == null) { - if (FHE_CLIENT_KEY_PATH) { - clientKey = readFileSync(FHE_CLIENT_KEY_PATH); - } else { - const home = homedir(); - const clientKeyPath = path.join(home, 'network-fhe-keys/cks'); - clientKey = readFileSync(clientKeyPath); - } - } - return clientKeyDecryptor(clientKey); -}; - /** * @debug * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bool} */ export const decryptBool = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return (await getClearText(handle)) === '1'; - } else { - return getDecryptor().decryptBool(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return (await getClearText(handle)) === '1'; }; /** @@ -156,16 +75,13 @@ export const decryptBool = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt8 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt8(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -173,16 +89,13 @@ export const decrypt8 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt16 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt16(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -190,16 +103,13 @@ export const decrypt16 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt32 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt32(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -207,16 +117,13 @@ export const decrypt32 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt64 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt64(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -224,16 +131,13 @@ export const decrypt64 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt128 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt128(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -241,16 +145,13 @@ export const decrypt128 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {bigint} */ export const decrypt256 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decrypt256(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + return BigInt(await getClearText(handle)); }; /** @@ -258,67 +159,15 @@ export const decrypt256 = async (handle: string): Promise => { * This function is intended for debugging purposes only. * It cannot be used in production code, since it requires the FHE private key for decryption. * - * @param {bigint} a handle to decrypt + * @param {bigint} handle handle to decrypt * @returns {string} */ export const decryptAddress = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - const bigintAdd = BigInt(await getClearText(handle)); - const handleStr = '0x' + bigintAdd.toString(16).padStart(40, '0'); - return handleStr; - } else { - return getDecryptor().decryptAddress(await getCiphertext(handle, ethers)); - } + assertNetwork('hardhat'); + await awaitCoprocessor(); + const bigintAdd = BigInt(await getClearText(handle)); + const handleStr = '0x' + bigintAdd.toString(16).padStart(40, '0'); + return handleStr; }; -/** - * @debug - * This function is intended for debugging purposes only. - * It cannot be used in production code, since it requires the FHE private key for decryption. - * - * @param {bigint} a handle to decrypt - * @returns {bigint} - */ -export const decryptEbytes64 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decryptEbytes64(await getCiphertext(handle, ethers)); - } -}; - -/** - * @debug - * This function is intended for debugging purposes only. - * It cannot be used in production code, since it requires the FHE private key for decryption. - * - * @param {bigint} a handle to decrypt - * @returns {bigint} - */ -export const decryptEbytes128 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decryptEbytes128(await getCiphertext(handle, ethers)); - } -}; - -/** - * @debug - * This function is intended for debugging purposes only. - * It cannot be used in production code, since it requires the FHE private key for decryption. - * - * @param {bigint} a handle to decrypt - * @returns {bigint} - */ -export const decryptEbytes256 = async (handle: string): Promise => { - if (network.name === 'hardhat') { - await awaitCoprocessor(); - return BigInt(await getClearText(handle)); - } else { - return getDecryptor().decryptEbytes256(await getCiphertext(handle, ethers)); - } -}; +export { getTxHCUFromTxReceipt, awaitCoprocessor }; diff --git a/library-solidity/test/tracing/tracing.ts b/library-solidity/test/tracing/tracing.ts index 137f69a7e..d6b747d42 100644 --- a/library-solidity/test/tracing/tracing.ts +++ b/library-solidity/test/tracing/tracing.ts @@ -1,7 +1,6 @@ import { ethers, network } from 'hardhat'; -import { awaitCoprocessor } from '../coprocessorUtils'; -import { createInstances } from '../instance'; +import { awaitCoprocessor, createInstances } from '../instance'; import { getSigners, initSigners } from '../signers'; describe('Tracing', function () { diff --git a/library-solidity/test/utils.ts b/library-solidity/test/utils.ts index 9b8398249..dd306117d 100644 --- a/library-solidity/test/utils.ts +++ b/library-solidity/test/utils.ts @@ -1,4 +1,5 @@ import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; +import type { ClearValueType, FhevmInstance, UserDecryptResults } from '@zama-fhe/relayer-sdk/node'; import { toBufferBE } from 'bigint-buffer'; import { Typed } from 'ethers'; import type { ContractMethodArgs, Signer } from 'ethers'; @@ -6,7 +7,7 @@ import { ethers, network } from 'hardhat'; import hre from 'hardhat'; import type { Counter } from '../typechain-types'; -import { TypedContractMethod } from '../typechain-types/common'; +import type { TypedContractMethod } from '../typechain-types/common'; import { getSigners } from './signers'; export async function sleep(ms: number): Promise { @@ -130,15 +131,15 @@ export const bigIntToBytes256 = (value: bigint) => { export const userDecryptSingleHandle = async ( handle: string, contractAddress: string, - instance: any, + instance: FhevmInstance, signer: Signer, privateKey: string, publicKey: string, -): Promise => { - const ctHandleContractPairs = [ +): Promise => { + const handleContractPairs = [ { - ctHandle: handle, - contractAddress: contractAddress, + handle, + contractAddress, }, ]; const startTimeStamp = Math.floor(Date.now() / 1000).toString(); @@ -157,83 +158,82 @@ export const userDecryptSingleHandle = async ( const signerAddress = await signer.getAddress(); - const decryptedValue = ( - await instance.userDecrypt( - ctHandleContractPairs, - privateKey, - publicKey, - signature.replace('0x', ''), - contractAddresses, - signerAddress, - startTimeStamp, - durationDays, - ) - )[0]; - return decryptedValue; -}; - -// `delegate` performs a user decrypt on behalf of `delegator` -export const delegatedUserDecryptSingleHandle = async (params: { - instance: any; - handle: string; - signer: Signer; - contractAddress: string; - delegatorAddress: string; - kmsPrivateKey: string; - kmsPublicKey: string; -}): Promise => { - const HandleContractPairs = [ - { - ctHandle: params.handle, - contractAddress: params.contractAddress, - }, - ]; - const startTimeStamp = Math.floor(Date.now() / 1000).toString(); - const durationDays = '10'; // String for consistency - const contractAddresses = [params.contractAddress]; - const instance = params.instance; - const signer = params.signer; - const userAddress = await signer.getAddress(); - - // Use the new createEIP712 function - const eip712 = instance.createEIP712( - params.kmsPublicKey, - contractAddresses, - startTimeStamp, - durationDays, - params.delegatorAddress, - ); - - // Update the signing to match the new primaryType - const signature = await signer.signTypedData( - eip712.domain, - { - DelegatedUserDecryptRequestVerification: eip712.types.DelegatedUserDecryptRequestVerification, - }, - eip712.message, - ); - - if (!instance.delegatedUserDecrypt) { - throw new Error(`instance.delegatedUserDecrypt not yet implemented`); - } - - // ======================================================== - // - // Todo: Call the delegate user decrypt function instead! - // - // ======================================================== - const result = await instance.delegatedUserDecrypt( - HandleContractPairs, - params.kmsPrivateKey, - params.kmsPublicKey, + const results: UserDecryptResults = await instance.userDecrypt( + handleContractPairs, + privateKey, + publicKey, signature.replace('0x', ''), contractAddresses, - userAddress, + signerAddress, startTimeStamp, durationDays, - params.delegatorAddress, ); - const decryptedValue = result[params.handle]; - return decryptedValue; + return results[handle as `0x${string}`]; }; + +// `delegate` performs a user decrypt on behalf of `delegator` +// export const delegatedUserDecryptSingleHandle = async (params: { +// instance: FhevmInstance; +// handle: string; +// signer: Signer; +// contractAddress: string; +// delegatorAddress: string; +// kmsPrivateKey: string; +// kmsPublicKey: string; +// }): Promise => { +// const HandleContractPairs = [ +// { +// ctHandle: params.handle, +// contractAddress: params.contractAddress, +// }, +// ]; +// const startTimeStamp = Math.floor(Date.now() / 1000).toString(); +// const durationDays = '10'; // String for consistency +// const contractAddresses = [params.contractAddress]; +// const instance = params.instance; +// const signer = params.signer; +// const userAddress = await signer.getAddress(); + +// // Use the new createEIP712 function +// const eip712 = instance.createEIP712( +// params.kmsPublicKey, +// contractAddresses, +// startTimeStamp, +// durationDays, +// params.delegatorAddress, +// ); + +// // Update the signing to match the new primaryType +// const signature = await signer.signTypedData( +// eip712.domain, +// { +// DelegatedUserDecryptRequestVerification: eip712.types.DelegatedUserDecryptRequestVerification, +// }, +// eip712.message, +// ); + +// if (!instance.delegatedUserDecrypt) { +// throw new Error(`instance.delegatedUserDecrypt not yet implemented`); +// } + +// // ======================================================== +// // +// // Todo: Call the delegate user decrypt function instead! +// // +// // ======================================================== +// const result = await instance.delegatedUserDecrypt( +// HandleContractPairs, +// params.kmsPrivateKey, +// params.kmsPublicKey, +// signature.replace('0x', ''), +// contractAddresses, +// userAddress, +// startTimeStamp, +// durationDays, +// params.delegatorAddress, +// ); + +// const decryptedValue = result[params.handle]; +// return decryptedValue; +// }; diff --git a/package-lock.json b/package-lock.json index 41d6d4a9e..c0d8b51f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,8 +46,7 @@ "prettier-plugin-solidity": "^1.1.3", "sha3": "^2.1.4", "solidity-coverage": "^0.8.14", - "sqlite3": "^5.1.7", - "web3-validator": "^2.0.6" + "sqlite3": "^5.1.7" }, "optionalDependencies": { "solidity-comments-darwin-arm64": "0.1.1", @@ -4053,9 +4052,9 @@ "license": "ISC" }, "host-contracts/node_modules/@zama-fhe/relayer-sdk": { - "version": "0.3.0-3", - "resolved": "https://registry.npmjs.org/@zama-fhe/relayer-sdk/-/relayer-sdk-0.3.0-3.tgz", - "integrity": "sha512-afum+H/sLdEeD3gGimbWrp1zI1TGyPIhkdaG3JlwS4Hqfnndt2CrVg65YbdeV7iAOpN+qcQmvSCiu3dNMTNNAA==", + "version": "0.3.0-5", + "resolved": "https://registry.npmjs.org/@zama-fhe/relayer-sdk/-/relayer-sdk-0.3.0-5.tgz", + "integrity": "sha512-DIPlN9z5tfSCXqUlhQN2MEv57HmAUHSGV9LPtGs6LWmM7POj6WsMd1hU7Qci4AWPsor7k3IqMfqGGthIvot/DQ==", "dev": true, "license": "BSD-3-Clause-Clear", "dependencies": { @@ -5737,19 +5736,6 @@ "node": ">=8" } }, - "host-contracts/node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "host-contracts/node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -5815,32 +5801,6 @@ "dev": true, "license": "MIT" }, - "host-contracts/node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "host-contracts/node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "host-contracts/node_modules/encrypted-types": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/encrypted-types/-/encrypted-types-0.0.4.tgz", @@ -6171,6 +6131,19 @@ "concat-map": "0.0.1" } }, + "host-contracts/node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "host-contracts/node_modules/eslint/node_modules/globals": { "version": "13.24.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", @@ -6982,16 +6955,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "host-contracts/node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "host-contracts/node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -8213,23 +8176,6 @@ "node": ">= 12" } }, - "host-contracts/node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "host-contracts/node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -8276,26 +8222,6 @@ "node": ">=8" } }, - "host-contracts/node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "host-contracts/node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -8358,25 +8284,6 @@ "node": ">=8" } }, - "host-contracts/node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "host-contracts/node_modules/is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -10427,24 +10334,6 @@ ], "license": "MIT" }, - "host-contracts/node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "host-contracts/node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -12540,20 +12429,6 @@ "dev": true, "license": "MIT" }, - "host-contracts/node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, "host-contracts/node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -12585,31 +12460,6 @@ "dev": true, "license": "Apache-2.0" }, - "host-contracts/node_modules/web3-errors": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/web3-errors/-/web3-errors-1.3.1.tgz", - "integrity": "sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ==", - "dev": true, - "license": "LGPL-3.0", - "dependencies": { - "web3-types": "^1.10.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6.12.0" - } - }, - "host-contracts/node_modules/web3-types": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/web3-types/-/web3-types-1.10.0.tgz", - "integrity": "sha512-0IXoaAFtFc8Yin7cCdQfB9ZmjafrbP6BO0f0KT/khMhXKUpoJ6yShrVhiNpyRBo8QQjuOagsWzwSK2H49I7sbw==", - "dev": true, - "license": "LGPL-3.0", - "engines": { - "node": ">=14", - "npm": ">=6.12.0" - } - }, "host-contracts/node_modules/web3-utils": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", @@ -12697,63 +12547,6 @@ "@scure/bip39": "1.3.0" } }, - "host-contracts/node_modules/web3-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/web3-validator/-/web3-validator-2.0.6.tgz", - "integrity": "sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg==", - "dev": true, - "license": "LGPL-3.0", - "dependencies": { - "ethereum-cryptography": "^2.0.0", - "util": "^0.12.5", - "web3-errors": "^1.2.0", - "web3-types": "^1.6.0", - "zod": "^3.21.4" - }, - "engines": { - "node": ">=14", - "npm": ">=6.12.0" - } - }, - "host-contracts/node_modules/web3-validator/node_modules/@noble/curves": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", - "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "host-contracts/node_modules/web3-validator/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "host-contracts/node_modules/web3-validator/node_modules/ethereum-cryptography": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", - "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/curves": "1.4.2", - "@noble/hashes": "1.4.0", - "@scure/bip32": "1.4.0", - "@scure/bip39": "1.3.0" - } - }, "host-contracts/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -13014,16 +12807,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "host-contracts/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "library-solidity": { "name": "@fhevm/solidity", "version": "0.9.0-1", @@ -13050,7 +12833,7 @@ "@types/node": "^20.14.9", "@typescript-eslint/eslint-plugin": "^5.44.0", "@typescript-eslint/parser": "^5.44.0", - "@zama-fhe/relayer-sdk": "^0.3.0-2", + "@zama-fhe/relayer-sdk": "^0.3.0-5", "bigint-buffer": "^1.1.5", "chai": "^4.3.7", "chalk": "^4.1.2", @@ -16042,9 +15825,9 @@ "license": "ISC" }, "library-solidity/node_modules/@zama-fhe/relayer-sdk": { - "version": "0.3.0-3", - "resolved": "https://registry.npmjs.org/@zama-fhe/relayer-sdk/-/relayer-sdk-0.3.0-3.tgz", - "integrity": "sha512-afum+H/sLdEeD3gGimbWrp1zI1TGyPIhkdaG3JlwS4Hqfnndt2CrVg65YbdeV7iAOpN+qcQmvSCiu3dNMTNNAA==", + "version": "0.3.0-5", + "resolved": "https://registry.npmjs.org/@zama-fhe/relayer-sdk/-/relayer-sdk-0.3.0-5.tgz", + "integrity": "sha512-DIPlN9z5tfSCXqUlhQN2MEv57HmAUHSGV9LPtGs6LWmM7POj6WsMd1hU7Qci4AWPsor7k3IqMfqGGthIvot/DQ==", "dev": true, "license": "BSD-3-Clause-Clear", "dependencies": { @@ -17792,32 +17575,6 @@ "dev": true, "license": "MIT" }, - "library-solidity/node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "library-solidity/node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "library-solidity/node_modules/encrypted-types": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/encrypted-types/-/encrypted-types-0.0.4.tgz", @@ -25658,7 +25415,7 @@ "dependencies": { "@fhevm/solidity": "*", "@openzeppelin/contracts": "^5.3.0", - "@zama-fhe/relayer-sdk": "^0.3.0-2", + "@zama-fhe/relayer-sdk": "^0.3.0-5", "bigint-buffer": "^1.1.5", "dotenv": "^16.0.3", "encrypted-types": "^0.0.4" @@ -27474,9 +27231,9 @@ } }, "test-suite/e2e/node_modules/@zama-fhe/relayer-sdk": { - "version": "0.3.0-3", - "resolved": "https://registry.npmjs.org/@zama-fhe/relayer-sdk/-/relayer-sdk-0.3.0-3.tgz", - "integrity": "sha512-afum+H/sLdEeD3gGimbWrp1zI1TGyPIhkdaG3JlwS4Hqfnndt2CrVg65YbdeV7iAOpN+qcQmvSCiu3dNMTNNAA==", + "version": "0.3.0-5", + "resolved": "https://registry.npmjs.org/@zama-fhe/relayer-sdk/-/relayer-sdk-0.3.0-5.tgz", + "integrity": "sha512-DIPlN9z5tfSCXqUlhQN2MEv57HmAUHSGV9LPtGs6LWmM7POj6WsMd1hU7Qci4AWPsor7k3IqMfqGGthIvot/DQ==", "license": "BSD-3-Clause-Clear", "dependencies": { "commander": "^14.0.0", diff --git a/test-suite/e2e/package.json b/test-suite/e2e/package.json index 7b9ece94a..ecf8c8743 100644 --- a/test-suite/e2e/package.json +++ b/test-suite/e2e/package.json @@ -16,7 +16,7 @@ "dependencies": { "@fhevm/solidity": "*", "@openzeppelin/contracts": "^5.3.0", - "@zama-fhe/relayer-sdk": "^0.3.0-2", + "@zama-fhe/relayer-sdk": "^0.3.0-5", "bigint-buffer": "^1.1.5", "dotenv": "^16.0.3", "encrypted-types": "^0.0.4"