From e4ef46bd52f8691c0d15a09cb6a87634c0d6bd2e Mon Sep 17 00:00:00 2001 From: PacificYield <173040337+PacificYield@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:05:56 +0100 Subject: [PATCH 1/4] chore: updates IntputVerifier NatSpec --- .../contracts/InputVerifier.coprocessor.sol | 243 ++++++++++-------- contracts/contracts/InputVerifier.native.sol | 158 +++++++----- 2 files changed, 226 insertions(+), 175 deletions(-) diff --git a/contracts/contracts/InputVerifier.coprocessor.sol b/contracts/contracts/InputVerifier.coprocessor.sol index 56e24506..3fa440cc 100644 --- a/contracts/contracts/InputVerifier.coprocessor.sol +++ b/contracts/contracts/InputVerifier.coprocessor.sol @@ -1,23 +1,31 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -import "./KMSVerifier.sol"; -import "./TFHEExecutor.sol"; -import "../addresses/KMSVerifierAddress.sol"; -import "../addresses/CoprocessorAddress.sol"; +import {KMSVerifier} from "./KMSVerifier.sol"; +import {TFHEExecutor} from "./TFHEExecutor.sol"; +import {kmsVerifierAdd} from "../addresses/KMSVerifierAddress.sol"; +import {coprocessorAdd} from "../addresses/CoprocessorAddress.sol"; // Importing OpenZeppelin contracts for cryptographic signature verification and access control. -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; -/// @title InputVerifier for signature verification of users encrypted inputs -/// @notice This version is only for the Coprocessor version of fhEVM -/// @notice This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signers addresses -/// @dev The contract uses OpenZeppelin's EIP712Upgradeable for cryptographic operations +/** + * @title InputVerifier. + * @notice This contract allows signature verification of user encrypted inputs. + * This version is only for the Coprocessor version of fhEVM. + * This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signers addresses. + * @dev The contract uses OpenZeppelin's EIP712Upgradeable for cryptographic operations. + */ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradeable { + /// @param aclAddress ACL address. + /// @param hashOfCiphertext Hash of ciphertext. + /// @param handlesList List of handles. + /// @param userAddress Address of the user. + /// @param contractAddress Contract address. struct CiphertextVerificationForCopro { address aclAddress; bytes32 hashOfCiphertext; @@ -26,83 +34,54 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad address contractAddress; } - /// @notice Handle version + /// @notice Handle version. uint8 public constant HANDLE_VERSION = 0; + /// @notice KMSVerifier. KMSVerifier public constant kmsVerifier = KMSVerifier(kmsVerifierAdd); + /// @notice Ciphertext verification type. + string public constant CIPHERTEXTVERIFICATION_COPRO_TYPE = + "CiphertextVerificationForCopro(address aclAddress,bytes32 hashOfCiphertext,uint256[] handlesList,address userAddress,address contractAddress)"; + + /// @notice Ciphertext verification typehash. + bytes32 public constant CIPHERTEXT_VERIFICATION_COPRO_TYPEHASH = + keccak256(bytes(CIPHERTEXTVERIFICATION_COPRO_TYPE)); + /// @notice Name of the contract string private constant CONTRACT_NAME = "InputVerifier"; - /// @notice Version of the contract + /// @notice Major version of the contract. uint256 private constant MAJOR_VERSION = 0; - uint256 private constant MINOR_VERSION = 1; - uint256 private constant PATCH_VERSION = 0; - function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} + /// @notice Minor version of the contract. + uint256 private constant MINOR_VERSION = 1; - /// @notice Getter function for the KMSVerifier contract address - function getKMSVerifierAddress() public view virtual returns (address) { - return address(kmsVerifier); - } + /// @notice Patch version of the contract. + uint256 private constant PATCH_VERSION = 0; + /// @notice Coprocessor address. address private constant coprocessorAddress = coprocessorAdd; - string public constant CIPHERTEXTVERIFICATION_COPRO_TYPE = - "CiphertextVerificationForCopro(address aclAddress,bytes32 hashOfCiphertext,uint256[] handlesList,address userAddress,address contractAddress)"; - bytes32 private constant CIPHERTEXTVERIFICATION_COPRO_TYPE_HASH = - keccak256(bytes(CIPHERTEXTVERIFICATION_COPRO_TYPE)); - - function get_CIPHERTEXTVERIFICATION_COPRO_TYPE() public view virtual returns (string memory) { - return CIPHERTEXTVERIFICATION_COPRO_TYPE; - } - - /// @notice Getter function for the Coprocessor account address - function getCoprocessorAddress() public view virtual returns (address) { - return coprocessorAddress; - } /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } - /// @notice Initializes the contract setting `initialOwner` as the initial owner - function initialize(address initialOwner) external initializer { + /** + * @notice Initializes the contract. + * @param initialOwner Initial owner address. + */ + function initialize(address initialOwner) public initializer { __Ownable_init(initialOwner); __EIP712_init(CONTRACT_NAME, "1"); } - function typeOf(uint256 handle) internal pure virtual returns (uint8) { - uint8 typeCt = uint8(handle >> 8); - return typeCt; - } - - function checkProofCache( - bytes memory inputProof, - address userAddress, - address contractAddress, - address aclAddress - ) internal view virtual returns (bool, bytes32) { - bool isProofCached; - bytes32 key = keccak256(abi.encodePacked(contractAddress, aclAddress, userAddress, inputProof)); - assembly { - isProofCached := tload(key) - } - return (isProofCached, key); - } - - function cacheProof(bytes32 proofKey) internal virtual { - assembly { - tstore(proofKey, 1) - let length := tload(0) - let lengthPlusOne := add(length, 1) - tstore(lengthPlusOne, proofKey) - tstore(0, lengthPlusOne) - } - } - - function cleanTransientStorage() external virtual { - // this function removes the transient allowances, could be useful for integration with Account Abstraction when bundling several UserOps calling InputVerifier + /** + * @dev This function removes the transient allowances, which could be useful f + for integration with Account Abstraction when bundling several UserOps calling InputVerifier + */ + function cleanTransientStorage() public virtual { assembly { let length := tload(0) tstore(0, 0) @@ -119,12 +98,19 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad } } + /** + * @notice Verifies the ciphertext. + * @param context Context user inputs. + * @param inputHandle Input handle. + * @param inputProof Input proof. + * @return result Result. + */ function verifyCiphertext( TFHEExecutor.ContextUserInputs memory context, bytes32 inputHandle, bytes memory inputProof - ) external virtual returns (uint256) { - (bool isProofCached, bytes32 cacheKey) = checkProofCache( + ) public virtual returns (uint256) { + (bool isProofCached, bytes32 cacheKey) = _checkProofCache( inputProof, context.userAddress, context.contractAddress, @@ -134,16 +120,17 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad uint256 indexHandle = (result & 0x0000000000000000000000000000000000000000000000000000000000ff0000) >> 16; if (!isProofCached) { - // bundleCiphertext is compressedPackedCT+ZKPOK - // inputHandle is keccak256(keccak256(bundleCiphertext)+index)[0:29]+index+type+version - // and inputProof is len(list_handles) + numSignersKMS + hashCT + list_handles + signatureCopro + signatureKMSSigners (1+1+32+NUM_HANDLES*32+65+65*numSignersKMS) + /// @dev bundleCiphertext is compressedPackedCT+ZKPOK + /// inputHandle is keccak256(keccak256(bundleCiphertext)+index)[0:29]+index+type+version + /// and inputProof is len(list_handles) + numSignersKMS + hashCT + list_handles + + /// signatureCopro + signatureKMSSigners (1+1+32+NUM_HANDLES*32+65+65*numSignersKMS) uint256 inputProofLen = inputProof.length; require(inputProofLen > 0, "Empty inputProof"); uint256 numHandles = uint256(uint8(inputProof[0])); uint256 numSignersKMS = uint256(uint8(inputProof[1])); - require(numHandles > indexHandle, "Invalid index"); // @note: this checks in particular that the list is non-empty + require(numHandles > indexHandle, "Invalid index"); /// @dev this checks in particular that the list is non-empty require(inputProofLen == 99 + 32 * numHandles + 65 * numSignersKMS, "Error deserializing inputProof"); bytes32 hashCT; @@ -151,14 +138,14 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad hashCT := mload(add(inputProof, 34)) } - // deseralize handle and check they are from correct version + /// @dev Deserialize handle and check that they are from the correct version. uint256[] memory listHandles = new uint256[](numHandles); for (uint256 i = 0; i < numHandles; i++) { uint256 element; assembly { element := mload(add(inputProof, add(66, mul(i, 32)))) } - // check all handles are from correct version + /// @dev Check that all handles are from the correct version. require(uint8(element) == HANDLE_VERSION, "Wrong handle version"); listHandles[i] = element; } @@ -174,7 +161,7 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad cvCopro.handlesList = listHandles; cvCopro.userAddress = context.userAddress; cvCopro.contractAddress = context.contractAddress; - verifyEIP712Copro(cvCopro, signatureCoproc); + _verifyEIP712Copro(cvCopro, signatureCoproc); } { bytes[] memory signaturesKMS = new bytes[](numSignersKMS); @@ -192,10 +179,10 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad bool kmsCheck = kmsVerifier.verifyInputEIP712KMSSignatures(cvKMS, signaturesKMS); require(kmsCheck, "Not enough unique KMS input signatures"); } - cacheProof(cacheKey); + _cacheProof(cacheKey); require(result == listHandles[indexHandle], "Wrong inputHandle"); } else { - uint8 numHandles = uint8(inputProof[0]); // @note: we know inputProof is non-empty since it has been previously cached + uint8 numHandles = uint8(inputProof[0]); /// @dev We know inputProof is non-empty since it has been previously cached. require(numHandles > indexHandle, "Invalid index"); uint256 element; for (uint256 j = 0; j < 32; j++) { @@ -206,20 +193,71 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad return result; } - function verifyEIP712Copro(CiphertextVerificationForCopro memory cv, bytes memory signature) internal view virtual { - bytes32 digest = hashCiphertextVerificationForCopro(cv); - address signer = ECDSA.recover(digest, signature); - require(signer == coprocessorAddress, "Coprocessor address mismatch"); + /** + * @notice Getter function for the Coprocessor account address. + */ + function getCoprocessorAddress() public view virtual returns (address) { + return coprocessorAddress; + } + + /** + * @notice Getter function for the KMSVerifier contract address. + */ + function getKMSVerifierAddress() public view virtual returns (address) { + return address(kmsVerifier); } - function hashCiphertextVerificationForCopro( + /** + * @notice Getter for the name and version of the contract. + * @return string Name and the version of the contract. + */ + function getVersion() external pure virtual returns (string memory) { + return + string( + abi.encodePacked( + CONTRACT_NAME, + " v", + Strings.toString(MAJOR_VERSION), + ".", + Strings.toString(MINOR_VERSION), + ".", + Strings.toString(PATCH_VERSION) + ) + ); + } + + function _cacheProof(bytes32 proofKey) internal virtual { + assembly { + tstore(proofKey, 1) + let length := tload(0) + let lengthPlusOne := add(length, 1) + tstore(lengthPlusOne, proofKey) + tstore(0, lengthPlusOne) + } + } + + function _checkProofCache( + bytes memory inputProof, + address userAddress, + address contractAddress, + address aclAddress + ) internal view virtual returns (bool, bytes32) { + bool isProofCached; + bytes32 key = keccak256(abi.encodePacked(contractAddress, aclAddress, userAddress, inputProof)); + assembly { + isProofCached := tload(key) + } + return (isProofCached, key); + } + + function _hashCiphertextVerificationForCopro( CiphertextVerificationForCopro memory CVcopro ) internal view virtual returns (bytes32) { return _hashTypedDataV4( keccak256( abi.encode( - CIPHERTEXTVERIFICATION_COPRO_TYPE_HASH, + CIPHERTEXT_VERIFICATION_COPRO_TYPEHASH, CVcopro.aclAddress, CVcopro.hashOfCiphertext, keccak256(abi.encodePacked(CVcopro.handlesList)), @@ -230,30 +268,17 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad ); } - /// @notice recovers the signer's address from a `signature` and a `message` digest - /// @dev Utilizes ECDSA for actual address recovery - /// @param message The hash of the message that was signed - /// @param signature The signature to verify - /// @return signer The address that supposedly signed the message - function recoverSigner(bytes32 message, bytes memory signature) internal pure virtual returns (address) { - address signerRecovered = ECDSA.recover(message, signature); - return signerRecovered; + function _verifyEIP712Copro( + CiphertextVerificationForCopro memory cv, + bytes memory signature + ) internal view virtual { + bytes32 digest = _hashCiphertextVerificationForCopro(cv); + address signer = ECDSA.recover(digest, signature); + require(signer == coprocessorAddress, "Coprocessor address mismatch"); } - /// @notice Getter for the name and version of the contract - /// @return string representing the name and the version of the contract - function getVersion() external pure virtual returns (string memory) { - return - string( - abi.encodePacked( - CONTRACT_NAME, - " v", - Strings.toString(MAJOR_VERSION), - ".", - Strings.toString(MINOR_VERSION), - ".", - Strings.toString(PATCH_VERSION) - ) - ); - } + /** + * @dev Should revert when msg.sender is not authorized to upgrade the contract. + */ + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} } diff --git a/contracts/contracts/InputVerifier.native.sol b/contracts/contracts/InputVerifier.native.sol index f582cfcf..cf1b3e44 100644 --- a/contracts/contracts/InputVerifier.native.sol +++ b/contracts/contracts/InputVerifier.native.sol @@ -1,81 +1,59 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -import "./KMSVerifier.sol"; -import "./TFHEExecutor.sol"; -import "../addresses/KMSVerifierAddress.sol"; +import {KMSVerifier} from "./KMSVerifier.sol"; +import {TFHEExecutor} from "./TFHEExecutor.sol"; +import {kmsVerifierAdd} from "../addresses/KMSVerifierAddress.sol"; // Importing OpenZeppelin contracts for cryptographic signature verification and access control. -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -/// @title InputVerifier for signature verification of users encrypted inputs -/// @notice This version is only for the Native version of fhEVM -/// @notice This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signers addresses +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +/** + * @title InputVerifier. + * @notice This contract allows signature verification of user encrypted inputs. + * This version is only for the Native version of fhEVM. + * This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signers addresses. + */ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { - /// @notice Handle version + /// @notice Handle version. uint8 public constant HANDLE_VERSION = 0; + /// @notice KMSVerifier. KMSVerifier public constant kmsVerifier = KMSVerifier(kmsVerifierAdd); /// @notice Name of the contract string private constant CONTRACT_NAME = "InputVerifier"; - /// @notice Version of the contract + /// @notice Major version of the contract. uint256 private constant MAJOR_VERSION = 0; - uint256 private constant MINOR_VERSION = 1; - uint256 private constant PATCH_VERSION = 0; - function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} + /// @notice Minor version of the contract. + uint256 private constant MINOR_VERSION = 1; - /// @notice Getter function for the KMSVerifier contract address - function getKMSVerifierAddress() public view virtual returns (address) { - return address(kmsVerifier); - } + /// @notice Patch version of the contract. + uint256 private constant PATCH_VERSION = 0; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } - /// @notice Initializes the contract setting `initialOwner` as the initial owner - function initialize(address initialOwner) external initializer { + /** + * @notice Initializes the contract. + * @param initialOwner Initial owner address. + */ + function initialize(address initialOwner) public initializer { __Ownable_init(initialOwner); } - function typeOf(uint256 handle) internal pure virtual returns (uint8) { - uint8 typeCt = uint8(handle >> 8); - return typeCt; - } - - function checkProofCache( - bytes memory inputProof, - address userAddress, - address contractAddress, - address aclAddress - ) internal view virtual returns (bool, bytes32) { - bool isProofCached; - bytes32 key = keccak256(abi.encodePacked(contractAddress, aclAddress, userAddress, inputProof)); - assembly { - isProofCached := tload(key) - } - return (isProofCached, key); - } - - function cacheProof(bytes32 proofKey) internal virtual { - assembly { - tstore(proofKey, 1) - let length := tload(0) - let lengthPlusOne := add(length, 1) - tstore(lengthPlusOne, proofKey) - tstore(0, lengthPlusOne) - } - } - - function cleanTransientStorage() external virtual { - // this function removes the transient allowances, could be useful for integration with Account Abstraction when bundling several UserOps calling InputVerifier + /** + * @dev This function removes the transient allowances, which could be useful f + for integration with Account Abstraction when bundling several UserOps calling InputVerifier + */ + function cleanTransientStorage() public virtual { assembly { let length := tload(0) tstore(0, 0) @@ -92,12 +70,19 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { } } + /** + * @notice Verifies the ciphertext. + * @param context Context user inputs. + * @param inputHandle Input handle. + * @param inputProof Input proof. + * @return result Result. + */ function verifyCiphertext( TFHEExecutor.ContextUserInputs memory context, bytes32 inputHandle, bytes memory inputProof - ) external virtual returns (uint256) { - (bool isProofCached, bytes32 cacheKey) = checkProofCache( + ) public virtual returns (uint256) { + (bool isProofCached, bytes32 cacheKey) = _checkProofCache( inputProof, context.userAddress, context.contractAddress, @@ -107,21 +92,23 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { uint256 indexHandle = (result & 0x0000000000000000000000000000000000000000000000000000000000ff0000) >> 16; if (!isProofCached) { - // bundleCiphertext is compressedPackedCT+ZKPOK - // inputHandle is keccak256(keccak256(bundleCiphertext)+index)[0:29]+index+type+version - // and inputProof is len(list_handles) + numSignersKMS + list_handles + signatureKMSSigners + bundleCiphertext (1+1+NUM_HANDLES*32+65*numSignersKMS+bundleCiphertext.length) + /// @dev bundleCiphertext is compressedPackedCT+ZKPOK + /// inputHandle is keccak256(keccak256(bundleCiphertext)+index)[0:29]+index+type+version + /// and inputProof is len(list_handles) + numSignersKMS + list_handles + signatureKMSSigners + + /// bundleCiphertext (1+1+NUM_HANDLES*32+65*numSignersKMS+bundleCiphertext.length) uint256 inputProofLen = inputProof.length; require(inputProofLen > 0, "Empty inputProof"); uint256 numHandles = uint256(uint8(inputProof[0])); uint256 numSignersKMS = uint256(uint8(inputProof[1])); - require(numHandles > indexHandle, "Invalid index"); // @note: this checks in particular that the list is non-empty - // @note: on native if an invalid indexHandle above the "real" numHandles is passed, it will be mapped to a trivialEncrypt(0) by backend + require(numHandles > indexHandle, "Invalid index"); /// @dev this checks in particular that the list is non-empty. + /// @dev on native if an invalid indexHandle above the "real" numHandles is passed, it will be mapped to a trivialEncrypt(0) by backend. require(inputProofLen > 2 + 32 * numHandles + 65 * numSignersKMS, "Error deserializing inputProof"); bytes32 hashCT; + { uint256 prefixLength = 2 + 32 * numHandles + 65 * numSignersKMS; uint256 bundleCiphertextLength = inputProofLen - prefixLength; @@ -149,7 +136,8 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { require(kmsCheck, "Not enough unique KMS input signatures"); } - // deseralize handle and check they are from correct version and correct values (handles are recomputed onchain in native case) + /// @dev Deserialize handle and check they are from the correct version and correct values + /// (handles are recomputed onchain in native case). for (uint256 i = 0; i < numHandles; i++) { uint256 element; assembly { @@ -166,15 +154,18 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { (recomputedHandle & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000) == (element & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000), "Wrong handle in inputProof" - ); // @note only the before last byte corresponding to type, ie element[30] could not be checked, i.e on native type is malleable, this means it will be casted accordingly by the backend (or trivialEncrypt(0) if index is invalid) + ); + /// @dev only the before last byte corresponding to type, ie element[30] could not be checked, + /// i.e on native type is malleable, this means it will be casted accordingly by the backend + /// (or trivialEncrypt(0) if index is invalid). if (i == indexHandle) { require(result == element, "Wrong inputHandle"); } } - cacheProof(cacheKey); + _cacheProof(cacheKey); } else { - uint8 numHandles = uint8(inputProof[0]); // @note: we know inputProof is non-empty since it has been previously cached + uint8 numHandles = uint8(inputProof[0]); /// @dev we know inputProof is non-empty since it has been previously cached. require(numHandles > indexHandle, "Invalid index"); uint256 element; for (uint256 j = 0; j < 32; j++) { @@ -185,8 +176,17 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { return result; } - /// @notice Getter for the name and version of the contract - /// @return string representing the name and the version of the contract + /** + * @notice Getter function for the KMSVerifier contract address. + */ + function getKMSVerifierAddress() public view virtual returns (address) { + return address(kmsVerifier); + } + + /** + * @notice Getter for the name and version of the contract. + * @return string Name and the version of the contract. + */ function getVersion() external pure virtual returns (string memory) { return string( @@ -201,4 +201,30 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { ) ); } + + function _cacheProof(bytes32 proofKey) internal virtual { + assembly { + tstore(proofKey, 1) + let length := tload(0) + let lengthPlusOne := add(length, 1) + tstore(lengthPlusOne, proofKey) + tstore(0, lengthPlusOne) + } + } + + function _checkProofCache( + bytes memory inputProof, + address userAddress, + address contractAddress, + address aclAddress + ) internal view virtual returns (bool, bytes32) { + bool isProofCached; + bytes32 key = keccak256(abi.encodePacked(contractAddress, aclAddress, userAddress, inputProof)); + assembly { + isProofCached := tload(key) + } + return (isProofCached, key); + } + + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} } From b31c42fafb636e43eda6ae0a7db1932355791254 Mon Sep 17 00:00:00 2001 From: PacificYield <173040337+PacificYield@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:09:46 +0100 Subject: [PATCH 2/4] chore: add custom errors --- .../contracts/InputVerifier.coprocessor.sol | 68 ++++++++++----- contracts/contracts/InputVerifier.native.sol | 87 +++++++++++++------ 2 files changed, 106 insertions(+), 49 deletions(-) diff --git a/contracts/contracts/InputVerifier.coprocessor.sol b/contracts/contracts/InputVerifier.coprocessor.sol index 3fa440cc..2bea552a 100644 --- a/contracts/contracts/InputVerifier.coprocessor.sol +++ b/contracts/contracts/InputVerifier.coprocessor.sol @@ -16,11 +16,32 @@ import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/crypt /** * @title InputVerifier. * @notice This contract allows signature verification of user encrypted inputs. - * This version is only for the Coprocessor version of fhEVM. - * This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signers addresses. + * This version is only for the Coprocessor version of the fhEVM. + * This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signer addresses. * @dev The contract uses OpenZeppelin's EIP712Upgradeable for cryptographic operations. */ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradeable { + /// @notice Returned if the deserializing of the input proof fails. + error DeserializingInputProofFail(); + + /// @notice Returned if the input proof is empty. + error EmptyInputProof(); + + /// @notice Returned if the index is invalid. + error InvalidIndex(); + + /// @notice Returned if the input handle is wrong. + error InvalidInputHandle(); + + /// @notice Returned if the handle version is not the correct one. + error InvalidHandleVersion(); + + /// @notice Returned if the number of EIP712 KMS signature is not sufficient. + error KMSNumberSignaturesInsufficient(); + + /// @notice Returned if the recovered signer address is not the coprocessor address. + error SignerIsNotCoprocessor(); + /// @param aclAddress ACL address. /// @param hashOfCiphertext Hash of ciphertext. /// @param handlesList List of handles. @@ -40,16 +61,19 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad /// @notice KMSVerifier. KMSVerifier public constant kmsVerifier = KMSVerifier(kmsVerifierAdd); + /// @notice Name of the contract. + string private constant CONTRACT_NAME = "InputVerifier"; + /// @notice Ciphertext verification type. - string public constant CIPHERTEXTVERIFICATION_COPRO_TYPE = + string public constant CIPHERTEXT_VERIFICATION_COPRO_TYPE = "CiphertextVerificationForCopro(address aclAddress,bytes32 hashOfCiphertext,uint256[] handlesList,address userAddress,address contractAddress)"; /// @notice Ciphertext verification typehash. bytes32 public constant CIPHERTEXT_VERIFICATION_COPRO_TYPEHASH = - keccak256(bytes(CIPHERTEXTVERIFICATION_COPRO_TYPE)); + keccak256(bytes(CIPHERTEXT_VERIFICATION_COPRO_TYPE)); - /// @notice Name of the contract - string private constant CONTRACT_NAME = "InputVerifier"; + /// @notice Coprocessor address. + address private constant coprocessorAddress = coprocessorAdd; /// @notice Major version of the contract. uint256 private constant MAJOR_VERSION = 0; @@ -60,9 +84,6 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad /// @notice Patch version of the contract. uint256 private constant PATCH_VERSION = 0; - /// @notice Coprocessor address. - address private constant coprocessorAddress = coprocessorAdd; - /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); @@ -78,8 +99,8 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad } /** - * @dev This function removes the transient allowances, which could be useful f - for integration with Account Abstraction when bundling several UserOps calling InputVerifier + * @dev This function removes the transient allowances, which could be useful for + integration with Account Abstraction when bundling several UserOps calling InputVerifier. */ function cleanTransientStorage() public virtual { assembly { @@ -126,14 +147,16 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad /// signatureCopro + signatureKMSSigners (1+1+32+NUM_HANDLES*32+65+65*numSignersKMS) uint256 inputProofLen = inputProof.length; - require(inputProofLen > 0, "Empty inputProof"); + if (inputProofLen == 0) revert EmptyInputProof(); uint256 numHandles = uint256(uint8(inputProof[0])); uint256 numSignersKMS = uint256(uint8(inputProof[1])); - require(numHandles > indexHandle, "Invalid index"); /// @dev this checks in particular that the list is non-empty - require(inputProofLen == 99 + 32 * numHandles + 65 * numSignersKMS, "Error deserializing inputProof"); + /// @dev This checks in particular that the list is non-empty. + if (numHandles <= indexHandle) revert InvalidIndex(); + if (inputProofLen != 99 + 32 * numHandles + 65 * numSignersKMS) revert DeserializingInputProofFail(); bytes32 hashCT; + assembly { hashCT := mload(add(inputProof, 34)) } @@ -146,7 +169,7 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad element := mload(add(inputProof, add(66, mul(i, 32)))) } /// @dev Check that all handles are from the correct version. - require(uint8(element) == HANDLE_VERSION, "Wrong handle version"); + if (uint8(element) != HANDLE_VERSION) revert InvalidHandleVersion(); listHandles[i] = element; } @@ -163,6 +186,7 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad cvCopro.contractAddress = context.contractAddress; _verifyEIP712Copro(cvCopro, signatureCoproc); } + { bytes[] memory signaturesKMS = new bytes[](numSignersKMS); for (uint256 j = 0; j < numSignersKMS; j++) { @@ -177,18 +201,20 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad cvKMS.userAddress = context.userAddress; cvKMS.contractAddress = context.contractAddress; bool kmsCheck = kmsVerifier.verifyInputEIP712KMSSignatures(cvKMS, signaturesKMS); - require(kmsCheck, "Not enough unique KMS input signatures"); + if (!kmsCheck) revert KMSNumberSignaturesInsufficient(); } + _cacheProof(cacheKey); - require(result == listHandles[indexHandle], "Wrong inputHandle"); + if (result != listHandles[indexHandle]) revert InvalidInputHandle(); } else { - uint8 numHandles = uint8(inputProof[0]); /// @dev We know inputProof is non-empty since it has been previously cached. - require(numHandles > indexHandle, "Invalid index"); + uint8 numHandles = uint8(inputProof[0]); + /// @dev We know inputProof is non-empty since it has been previously cached. + if (numHandles <= indexHandle) revert InvalidIndex(); uint256 element; for (uint256 j = 0; j < 32; j++) { element |= uint256(uint8(inputProof[34 + indexHandle * 32 + j])) << (8 * (31 - j)); } - require(element == result, "Wrong inputHandle"); + if (element != result) revert InvalidInputHandle(); } return result; } @@ -274,7 +300,7 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad ) internal view virtual { bytes32 digest = _hashCiphertextVerificationForCopro(cv); address signer = ECDSA.recover(digest, signature); - require(signer == coprocessorAddress, "Coprocessor address mismatch"); + if (signer != coprocessorAddress) revert SignerIsNotCoprocessor(); } /** diff --git a/contracts/contracts/InputVerifier.native.sol b/contracts/contracts/InputVerifier.native.sol index cf1b3e44..f2d40d4a 100644 --- a/contracts/contracts/InputVerifier.native.sol +++ b/contracts/contracts/InputVerifier.native.sol @@ -12,19 +12,44 @@ import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/acces import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; /** - * @title InputVerifier. - * @notice This contract allows signature verification of user encrypted inputs. - * This version is only for the Native version of fhEVM. - * This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signers addresses. - */ +* @title InputVerifier. +* @notice This contract allows signature verification of user encrypted inputs. +* This version is only for the native version of the fhEVM. +* This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signer addresses. + +*/ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { + /// @notice Returned if the deserializing of the input proof fails. + error DeserializingInputProofFail(); + + /// @notice Returned if the input proof is empty. + error EmptyInputProof(); + + /// @notice Returned if the index is invalid. + error InvalidIndex(); + + /// @notice Returned if the input handle is wrong. + error InvalidInputHandle(); + + /// @notice Returned if the handle version is not the correct one. + error InvalidHandleVersion(); + + /// @notice Returned if the input proof handle is wrong. + error InvalidInputProofHandle(); + + /// @notice Returned if the serialized handle index is invalid. + error InvalidSerializedHandleIndex(); + + /// @notice Returned if the number of EIP712 KMS signature is not sufficient. + error KMSNumberSignaturesInsufficient(); + /// @notice Handle version. uint8 public constant HANDLE_VERSION = 0; /// @notice KMSVerifier. KMSVerifier public constant kmsVerifier = KMSVerifier(kmsVerifierAdd); - /// @notice Name of the contract + /// @notice Name of the contract. string private constant CONTRACT_NAME = "InputVerifier"; /// @notice Major version of the contract. @@ -50,8 +75,8 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { } /** - * @dev This function removes the transient allowances, which could be useful f - for integration with Account Abstraction when bundling several UserOps calling InputVerifier + * @dev This function removes the transient allowances, which could be useful for + integration with Account Abstraction when bundling several UserOps calling InputVerifier. */ function cleanTransientStorage() public virtual { assembly { @@ -98,17 +123,17 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { /// bundleCiphertext (1+1+NUM_HANDLES*32+65*numSignersKMS+bundleCiphertext.length) uint256 inputProofLen = inputProof.length; - require(inputProofLen > 0, "Empty inputProof"); + if (inputProofLen == 0) revert EmptyInputProof(); uint256 numHandles = uint256(uint8(inputProof[0])); uint256 numSignersKMS = uint256(uint8(inputProof[1])); - require(numHandles > indexHandle, "Invalid index"); /// @dev this checks in particular that the list is non-empty. - /// @dev on native if an invalid indexHandle above the "real" numHandles is passed, it will be mapped to a trivialEncrypt(0) by backend. - - require(inputProofLen > 2 + 32 * numHandles + 65 * numSignersKMS, "Error deserializing inputProof"); + /// @dev This checks in particular that the list is non-empty. + if (numHandles <= indexHandle) revert InvalidIndex(); + /// @dev On native, if an invalid indexHandle above the "real" numHandles is passed, it will be mapped to a trivialEncrypt(0) by backend. + if (inputProofLen <= 2 + 32 * numHandles + 65 * numSignersKMS) revert DeserializingInputProofFail(); bytes32 hashCT; - + { uint256 prefixLength = 2 + 32 * numHandles + 65 * numSignersKMS; uint256 bundleCiphertextLength = inputProofLen - prefixLength; @@ -133,7 +158,7 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { cvKMS.userAddress = context.userAddress; cvKMS.contractAddress = context.contractAddress; bool kmsCheck = kmsVerifier.verifyInputEIP712KMSSignatures(cvKMS, signaturesKMS); - require(kmsCheck, "Not enough unique KMS input signatures"); + if (!kmsCheck) revert KMSNumberSignaturesInsufficient(); } /// @dev Deserialize handle and check they are from the correct version and correct values @@ -143,35 +168,38 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { assembly { element := mload(add(inputProof, add(34, mul(i, 32)))) } - // check all handles are from correct version - require(uint8(element) == HANDLE_VERSION, "Wrong handle version"); + /// @dev Check all handles are from correct version. + if (uint8(element) != HANDLE_VERSION) revert InvalidHandleVersion(); uint256 indexElement = (element & 0x0000000000000000000000000000000000000000000000000000000000ff0000) >> 16; - require(indexElement == i, "Wrong index for serialized handle"); + + if (indexElement != i) revert InvalidSerializedHandleIndex(); uint256 recomputedHandle = uint256(keccak256(abi.encodePacked(hashCT, uint8(i)))); - require( - (recomputedHandle & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000) == - (element & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000), - "Wrong handle in inputProof" - ); - /// @dev only the before last byte corresponding to type, ie element[30] could not be checked, + + if ( + (recomputedHandle & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000) != + (element & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000) + ) revert InvalidInputProofHandle(); + + /// @dev Only the before last byte corresponding to type, ie element[30] could not be checked, /// i.e on native type is malleable, this means it will be casted accordingly by the backend /// (or trivialEncrypt(0) if index is invalid). if (i == indexHandle) { - require(result == element, "Wrong inputHandle"); + if (element != result) revert InvalidInputHandle(); } } _cacheProof(cacheKey); } else { - uint8 numHandles = uint8(inputProof[0]); /// @dev we know inputProof is non-empty since it has been previously cached. - require(numHandles > indexHandle, "Invalid index"); + uint8 numHandles = uint8(inputProof[0]); + /// @dev We know inputProof is non-empty since it has been previously cached. + if (numHandles <= indexHandle) revert InvalidIndex(); uint256 element; for (uint256 j = 0; j < 32; j++) { element |= uint256(uint8(inputProof[2 + indexHandle * 32 + j])) << (8 * (31 - j)); } - require(element == result, "Wrong inputHandle"); + if (element != result) revert InvalidInputHandle(); } return result; } @@ -226,5 +254,8 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable { return (isProofCached, key); } + /** + * @dev Should revert when msg.sender is not authorized to upgrade the contract. + */ function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} } From 9c3f173d496cbbe89a7606c2b6739fb9f559861b Mon Sep 17 00:00:00 2001 From: PacificYield <173040337+PacificYield@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:40:33 +0100 Subject: [PATCH 3/4] chore: add codegen --- contracts/codegen/inputVerifier.ts | 562 ++++++++++++++++++----------- 1 file changed, 348 insertions(+), 214 deletions(-) diff --git a/contracts/codegen/inputVerifier.ts b/contracts/codegen/inputVerifier.ts index 9525a792..61ecf09f 100644 --- a/contracts/codegen/inputVerifier.ts +++ b/contracts/codegen/inputVerifier.ts @@ -7,126 +7,169 @@ export function generateInputVerifiers(isCoprocessor: boolean): string { let output = `// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -import "./KMSVerifier.sol"; -import "./TFHEExecutor.sol"; -import "../addresses/KMSVerifierAddress.sol";\n`; +import {KMSVerifier} from "./KMSVerifier.sol"; +import {TFHEExecutor} from "./TFHEExecutor.sol"; +import {kmsVerifierAdd} from "../addresses/KMSVerifierAddress.sol";\n`; if (isCoprocessor) { - output += `import "../addresses/CoprocessorAddress.sol";\n`; + output += `import {coprocessorAdd} from "../addresses/CoprocessorAddress.sol";\n`; } output += `\n// Importing OpenZeppelin contracts for cryptographic signature verification and access control. -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol";\n`; +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";\n`; if (isCoprocessor) { - output += `import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";\n`; + output += `import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";\n`; } output += ` -/// @title InputVerifier for signature verification of users encrypted inputs\n`; +/** +* @title InputVerifier. +* @notice This contract allows signature verification of user encrypted inputs.\n`; if (isCoprocessor) { - output += `/// @notice This version is only for the Coprocessor version of fhEVM\n`; + output += `* This version is only for the Coprocessor version of the fhEVM.\n`; } else { - output += `/// @notice This version is only for the Native version of fhEVM\n`; + output += `* This version is only for the native version of the fhEVM.\n`; } - output += `/// @notice This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signers addresses\n`; + output += `* This contract is called by the TFHEExecutor inside verifyCiphertext function, and calls the KMSVerifier to fetch KMS signer addresses.\n`; if (isCoprocessor) { - output += `/// @dev The contract uses OpenZeppelin's EIP712Upgradeable for cryptographic operations + output += `* @dev The contract uses OpenZeppelin's EIP712Upgradeable for cryptographic operations. +*/ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradeable { +`; + } else { + output += ` +*/ + contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable {\n`; + } + + output += ` + /// @notice Returned if the deserializing of the input proof fails. + error DeserializingInputProofFail(); + + /// @notice Returned if the input proof is empty. + error EmptyInputProof(); + + /// @notice Returned if the index is invalid. + error InvalidIndex(); + + /// @notice Returned if the input handle is wrong. + error InvalidInputHandle(); + + /// @notice Returned if the handle version is not the correct one. + error InvalidHandleVersion(); + + `; + + if (isCoprocessor) { + output += ` + /// @notice Returned if the number of EIP712 KMS signature is not sufficient. + error KMSNumberSignaturesInsufficient(); + + /// @notice Returned if the recovered signer address is not the coprocessor address. + error SignerIsNotCoprocessor(); + + /// @param aclAddress ACL address. + /// @param hashOfCiphertext Hash of ciphertext. + /// @param handlesList List of handles. + /// @param userAddress Address of the user. + /// @param contractAddress Contract address. struct CiphertextVerificationForCopro { address aclAddress; bytes32 hashOfCiphertext; uint256[] handlesList; address userAddress; address contractAddress; - }\n + } `; } else { - output += `contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable {\n`; + output += ` + /// @notice Returned if the input proof handle is wrong. + error InvalidInputProofHandle(); + + /// @notice Returned if the serialized handle index is invalid. + error InvalidSerializedHandleIndex(); + + /// @notice Returned if the number of EIP712 KMS signature is not sufficient. + error KMSNumberSignaturesInsufficient(); +`; } - output += `/// @notice Handle version + output += ` + + /// @notice Handle version. uint8 public constant HANDLE_VERSION = 0; + /// @notice KMSVerifier. KMSVerifier public constant kmsVerifier = KMSVerifier(kmsVerifierAdd); - /// @notice Name of the contract + /// @notice Name of the contract. string private constant CONTRACT_NAME = "InputVerifier"; - - /// @notice Version of the contract - uint256 private constant MAJOR_VERSION = 0; - uint256 private constant MINOR_VERSION = 1; - uint256 private constant PATCH_VERSION = 0; - - function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} - - /// @notice Getter function for the KMSVerifier contract address - function getKMSVerifierAddress() public view virtual returns (address) { - return address(kmsVerifier); - }\n `; + if (isCoprocessor) { - output += ` address private constant coprocessorAddress = coprocessorAdd; - string public constant CIPHERTEXTVERIFICATION_COPRO_TYPE = + output += ` + /// @notice Ciphertext verification type. + string public constant CIPHERTEXT_VERIFICATION_COPRO_TYPE = "CiphertextVerificationForCopro(address aclAddress,bytes32 hashOfCiphertext,uint256[] handlesList,address userAddress,address contractAddress)"; - bytes32 private constant CIPHERTEXTVERIFICATION_COPRO_TYPE_HASH = - keccak256(bytes(CIPHERTEXTVERIFICATION_COPRO_TYPE)); - function get_CIPHERTEXTVERIFICATION_COPRO_TYPE() public view virtual returns (string memory) { - return CIPHERTEXTVERIFICATION_COPRO_TYPE; - } + /// @notice Ciphertext verification typehash. + bytes32 public constant CIPHERTEXT_VERIFICATION_COPRO_TYPEHASH = + keccak256(bytes(CIPHERTEXT_VERIFICATION_COPRO_TYPE)); - /// @notice Getter function for the Coprocessor account address - function getCoprocessorAddress() public view virtual returns (address) { - return coprocessorAddress; - }\n -`; + /// @notice Coprocessor address. + address private constant coprocessorAddress = coprocessorAdd; + `; } - output += `/// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - /// @notice Initializes the contract setting \`initialOwner\` as the initial owner - function initialize(address initialOwner) external initializer { - __Ownable_init(initialOwner);\n`; - if (isCoprocessor) output += `__EIP712_init(CONTRACT_NAME, "1");\n`; - output += `} + output += ` + /// @notice Major version of the contract. + uint256 private constant MAJOR_VERSION = 0; - function typeOf(uint256 handle) internal pure virtual returns (uint8) { - uint8 typeCt = uint8(handle >> 8); - return typeCt; - } - + /// @notice Minor version of the contract. + uint256 private constant MINOR_VERSION = 1; - function checkProofCache( - bytes memory inputProof, - address userAddress, - address contractAddress, - address aclAddress - ) internal view virtual returns (bool, bytes32) { - bool isProofCached; - bytes32 key = keccak256(abi.encodePacked(contractAddress, aclAddress, userAddress, inputProof)); - assembly { - isProofCached := tload(key) - } - return (isProofCached, key); + /// @notice Patch version of the contract. + uint256 private constant PATCH_VERSION = 0; + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); } + `; - function cacheProof(bytes32 proofKey) internal virtual { - assembly { - tstore(proofKey, 1) - let length := tload(0) - let lengthPlusOne := add(length, 1) - tstore(lengthPlusOne, proofKey) - tstore(0, lengthPlusOne) + if (isCoprocessor) { + output += ` + /** + * @notice Initializes the contract. + * @param initialOwner Initial owner address. + */ + function initialize(address initialOwner) public initializer { + __Ownable_init(initialOwner); + __EIP712_init(CONTRACT_NAME, "1"); } - } + `; + } else { + output += ` + /** + * @notice Initializes the contract. + * @param initialOwner Initial owner address. + */ + function initialize(address initialOwner) public initializer { + __Ownable_init(initialOwner); + } + `; + } - function cleanTransientStorage() external virtual { - // this function removes the transient allowances, could be useful for integration with Account Abstraction when bundling several UserOps calling InputVerifier + output += ` + + /** + * @dev This function removes the transient allowances, which could be useful for + integration with Account Abstraction when bundling several UserOps calling InputVerifier. + */ + function cleanTransientStorage() public virtual { assembly { let length := tload(0) tstore(0, 0) @@ -143,12 +186,19 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad } } + /** + * @notice Verifies the ciphertext. + * @param context Context user inputs. + * @param inputHandle Input handle. + * @param inputProof Input proof. + * @return result Result. + */ function verifyCiphertext( TFHEExecutor.ContextUserInputs memory context, bytes32 inputHandle, bytes memory inputProof - ) external virtual returns (uint256) { - (bool isProofCached, bytes32 cacheKey) = checkProofCache( + ) public virtual returns (uint256) { + (bool isProofCached, bytes32 cacheKey) = _checkProofCache( inputProof, context.userAddress, context.contractAddress, @@ -156,35 +206,41 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad ); uint256 result = uint256(inputHandle); uint256 indexHandle = (result & 0x0000000000000000000000000000000000000000000000000000000000ff0000) >> 16; + `; - if (!isProofCached) { - // bundleCiphertext is compressedPackedCT+ZKPOK - // inputHandle is keccak256(keccak256(bundleCiphertext)+index)[0:29]+index+type+version\n`; if (isCoprocessor) { - output += `// and inputProof is len(list_handles) + numSignersKMS + hashCT + list_handles + signatureCopro + signatureKMSSigners (1+1+32+NUM_HANDLES*32+65+65*numSignersKMS) + output += ` + + if (!isProofCached) { + /// @dev bundleCiphertext is compressedPackedCT+ZKPOK + /// inputHandle is keccak256(keccak256(bundleCiphertext)+index)[0:29]+index+type+version + /// and inputProof is len(list_handles) + numSignersKMS + hashCT + list_handles + + /// signatureCopro + signatureKMSSigners (1+1+32+NUM_HANDLES*32+65+65*numSignersKMS) uint256 inputProofLen = inputProof.length; - require(inputProofLen > 0, "Empty inputProof"); + if (inputProofLen == 0) revert EmptyInputProof(); uint256 numHandles = uint256(uint8(inputProof[0])); uint256 numSignersKMS = uint256(uint8(inputProof[1])); - require(numHandles > indexHandle, "Invalid index"); // @note: this checks in particular that the list is non-empty - require(inputProofLen == 99 + 32 * numHandles + 65 * numSignersKMS, "Error deserializing inputProof"); + /// @dev This checks in particular that the list is non-empty. + if (numHandles <= indexHandle) revert InvalidIndex(); + if (inputProofLen != 99 + 32 * numHandles + 65 * numSignersKMS) revert DeserializingInputProofFail(); bytes32 hashCT; + assembly { hashCT := mload(add(inputProof, 34)) } - // deseralize handle and check they are from correct version + /// @dev Deserialize handle and check that they are from the correct version. uint256[] memory listHandles = new uint256[](numHandles); for (uint256 i = 0; i < numHandles; i++) { uint256 element; assembly { element := mload(add(inputProof, add(66, mul(i, 32)))) } - // check all handles are from correct version - require(uint8(element) == HANDLE_VERSION, "Wrong handle version"); + /// @dev Check that all handles are from the correct version. + if (uint8(element) != HANDLE_VERSION) revert InvalidHandleVersion(); listHandles[i] = element; } @@ -199,153 +255,167 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad cvCopro.handlesList = listHandles; cvCopro.userAddress = context.userAddress; cvCopro.contractAddress = context.contractAddress; - verifyEIP712Copro(cvCopro, signatureCoproc); - } - { - bytes[] memory signaturesKMS = new bytes[](numSignersKMS); - for (uint256 j = 0; j < numSignersKMS; j++) { - signaturesKMS[j] = new bytes(65); - for (uint256 i = 0; i < 65; i++) { - signaturesKMS[j][i] = inputProof[99 + 32 * numHandles + 65 * j + i]; - } - } - KMSVerifier.CiphertextVerificationForKMS memory cvKMS; - cvKMS.aclAddress = context.aclAddress; - cvKMS.hashOfCiphertext = hashCT; - cvKMS.userAddress = context.userAddress; - cvKMS.contractAddress = context.contractAddress; - bool kmsCheck = kmsVerifier.verifyInputEIP712KMSSignatures(cvKMS, signaturesKMS); - require(kmsCheck, "Not enough unique KMS input signatures"); + _verifyEIP712Copro(cvCopro, signatureCoproc); } - cacheProof(cacheKey); - require(result == listHandles[indexHandle], "Wrong inputHandle"); - } else { - uint8 numHandles = uint8(inputProof[0]); // @note: we know inputProof is non-empty since it has been previously cached - require(numHandles > indexHandle, "Invalid index"); - uint256 element; - for (uint256 j = 0; j < 32; j++) { - element |= uint256(uint8(inputProof[34 + indexHandle * 32 + j])) << (8 * (31 - j)); + `; + } else { + output += ` + if (!isProofCached) { + /// @dev bundleCiphertext is compressedPackedCT+ZKPOK + /// inputHandle is keccak256(keccak256(bundleCiphertext)+index)[0:29]+index+type+version + /// and inputProof is len(list_handles) + numSignersKMS + list_handles + signatureKMSSigners + + /// bundleCiphertext (1+1+NUM_HANDLES*32+65*numSignersKMS+bundleCiphertext.length) + + uint256 inputProofLen = inputProof.length; + if (inputProofLen == 0) revert EmptyInputProof(); + uint256 numHandles = uint256(uint8(inputProof[0])); + uint256 numSignersKMS = uint256(uint8(inputProof[1])); + + /// @dev This checks in particular that the list is non-empty. + if (numHandles <= indexHandle) revert InvalidIndex(); + /// @dev On native, if an invalid indexHandle above the "real" numHandles is passed, it will be mapped to a trivialEncrypt(0) by backend. + if (inputProofLen <= 2 + 32 * numHandles + 65 * numSignersKMS) revert DeserializingInputProofFail(); + + bytes32 hashCT; + + { + uint256 prefixLength = 2 + 32 * numHandles + 65 * numSignersKMS; + uint256 bundleCiphertextLength = inputProofLen - prefixLength; + bytes memory bundleCiphertext = new bytes(bundleCiphertextLength); + for (uint256 i = 0; i < bundleCiphertextLength; i++) { + bundleCiphertext[i] = inputProof[prefixLength + i]; } - require(element == result, "Wrong inputHandle"); + hashCT = keccak256(bundleCiphertext); } - return result; -} - -function verifyEIP712Copro(CiphertextVerificationForCopro memory cv, bytes memory signature) internal view virtual { - bytes32 digest = hashCiphertextVerificationForCopro(cv); - address signer = ECDSA.recover(digest, signature); - require(signer == coprocessorAddress, "Coprocessor address mismatch"); -} + `; + } -function hashCiphertextVerificationForCopro( - CiphertextVerificationForCopro memory CVcopro -) internal view virtual returns (bytes32) { - return - _hashTypedDataV4( - keccak256( - abi.encode( - CIPHERTEXTVERIFICATION_COPRO_TYPE_HASH, - CVcopro.aclAddress, - CVcopro.hashOfCiphertext, - keccak256(abi.encodePacked(CVcopro.handlesList)), - CVcopro.userAddress, - CVcopro.contractAddress - ) - ) - ); -} + output += ` + { + bytes[] memory signaturesKMS = new bytes[](numSignersKMS); + for (uint256 j = 0; j < numSignersKMS; j++) { + signaturesKMS[j] = new bytes(65); + for (uint256 i = 0; i < 65; i++) { +`; -/// @notice recovers the signer's address from a \`signature\` and a \`message\` digest -/// @dev Utilizes ECDSA for actual address recovery -/// @param message The hash of the message that was signed -/// @param signature The signature to verify -/// @return signer The address that supposedly signed the message -function recoverSigner(bytes32 message, bytes memory signature) internal pure virtual returns (address) { - address signerRecovered = ECDSA.recover(message, signature); - return signerRecovered; -}\n + if (isCoprocessor) { + output += ` + signaturesKMS[j][i] = inputProof[99 + 32 * numHandles + 65 * j + i]; `; } else { - output += `// and inputProof is len(list_handles) + numSignersKMS + list_handles + signatureKMSSigners + bundleCiphertext (1+1+NUM_HANDLES*32+65*numSignersKMS+bundleCiphertext.length) + output += ` + signaturesKMS[j][i] = inputProof[2 + 32 * numHandles + 65 * j + i]; + `; + } - uint256 inputProofLen = inputProof.length; - require(inputProofLen > 0, "Empty inputProof"); - uint256 numHandles = uint256(uint8(inputProof[0])); - uint256 numSignersKMS = uint256(uint8(inputProof[1])); + output += ` + } + } + KMSVerifier.CiphertextVerificationForKMS memory cvKMS; + cvKMS.aclAddress = context.aclAddress; + cvKMS.hashOfCiphertext = hashCT; + cvKMS.userAddress = context.userAddress; + cvKMS.contractAddress = context.contractAddress; + bool kmsCheck = kmsVerifier.verifyInputEIP712KMSSignatures(cvKMS, signaturesKMS); + if (!kmsCheck) revert KMSNumberSignaturesInsufficient(); +} - require(numHandles > indexHandle, "Invalid index"); // @note: this checks in particular that the list is non-empty - // @note: on native if an invalid indexHandle above the "real" numHandles is passed, it will be mapped to a trivialEncrypt(0) by backend + `; - require(inputProofLen > 2 + 32 * numHandles + 65 * numSignersKMS, "Error deserializing inputProof"); + if (isCoprocessor) { + output += ` + _cacheProof(cacheKey); + if (result != listHandles[indexHandle]) revert InvalidInputHandle(); + `; + } else { + output += ` - bytes32 hashCT; - { - uint256 prefixLength = 2 + 32 * numHandles + 65 * numSignersKMS; - uint256 bundleCiphertextLength = inputProofLen - prefixLength; - bytes memory bundleCiphertext = new bytes(bundleCiphertextLength); - for (uint256 i = 0; i < bundleCiphertextLength; i++) { - bundleCiphertext[i] = inputProof[prefixLength + i]; - } - hashCT = keccak256(bundleCiphertext); + /// @dev Deserialize handle and check they are from the correct version and correct values + /// (handles are recomputed onchain in native case). + for (uint256 i = 0; i < numHandles; i++) { + uint256 element; + assembly { + element := mload(add(inputProof, add(34, mul(i, 32)))) } + /// @dev Check all handles are from correct version. + if (uint8(element) != HANDLE_VERSION) revert InvalidHandleVersion(); + uint256 indexElement = (element & 0x0000000000000000000000000000000000000000000000000000000000ff0000) >> + 16; - { - bytes[] memory signaturesKMS = new bytes[](numSignersKMS); - for (uint256 j = 0; j < numSignersKMS; j++) { - signaturesKMS[j] = new bytes(65); - for (uint256 i = 0; i < 65; i++) { - signaturesKMS[j][i] = inputProof[2 + 32 * numHandles + 65 * j + i]; - } - } - KMSVerifier.CiphertextVerificationForKMS memory cvKMS; - cvKMS.aclAddress = context.aclAddress; - cvKMS.hashOfCiphertext = hashCT; - cvKMS.userAddress = context.userAddress; - cvKMS.contractAddress = context.contractAddress; - bool kmsCheck = kmsVerifier.verifyInputEIP712KMSSignatures(cvKMS, signaturesKMS); - require(kmsCheck, "Not enough unique KMS input signatures"); - } + if (indexElement != i) revert InvalidSerializedHandleIndex(); - // deseralize handle and check they are from correct version and correct values (handles are recomputed onchain in native case) - for (uint256 i = 0; i < numHandles; i++) { - uint256 element; - assembly { - element := mload(add(inputProof, add(34, mul(i, 32)))) - } - // check all handles are from correct version - require(uint8(element) == HANDLE_VERSION, "Wrong handle version"); - uint256 indexElement = (element & 0x0000000000000000000000000000000000000000000000000000000000ff0000) >> - 16; - require(indexElement == i, "Wrong index for serialized handle"); - - uint256 recomputedHandle = uint256(keccak256(abi.encodePacked(hashCT, uint8(i)))); - require( - (recomputedHandle & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000) == - (element & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000), - "Wrong handle in inputProof" - ); // @note only the before last byte corresponding to type, ie element[30] could not be checked, i.e on native type is malleable, this means it will be casted accordingly by the backend (or trivialEncrypt(0) if index is invalid) - if (i == indexHandle) { - require(result == element, "Wrong inputHandle"); - } + uint256 recomputedHandle = uint256(keccak256(abi.encodePacked(hashCT, uint8(i)))); + + if ( + (recomputedHandle & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000) != + (element & 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000) + ) revert InvalidInputProofHandle(); + + /// @dev Only the before last byte corresponding to type, ie element[30] could not be checked, + /// i.e on native type is malleable, this means it will be casted accordingly by the backend + /// (or trivialEncrypt(0) if index is invalid). + if (i == indexHandle) { + if (element != result) revert InvalidInputHandle(); } + } + + _cacheProof(cacheKey); - cacheProof(cacheKey); + `; + } + + output += ` } else { - uint8 numHandles = uint8(inputProof[0]); // @note: we know inputProof is non-empty since it has been previously cached - require(numHandles > indexHandle, "Invalid index"); + uint8 numHandles = uint8(inputProof[0]); + /// @dev We know inputProof is non-empty since it has been previously cached. + if (numHandles <= indexHandle) revert InvalidIndex(); uint256 element; for (uint256 j = 0; j < 32; j++) { - element |= uint256(uint8(inputProof[2 + indexHandle * 32 + j])) << (8 * (31 - j)); +`; + + if (isCoprocessor) { + output += ` + element |= uint256(uint8(inputProof[34 + indexHandle * 32 + j])) << (8 * (31 - j)); + `; + } else { + output += ` + element |= uint256(uint8(inputProof[2 + indexHandle * 32 + j])) << (8 * (31 - j)); + `; + } + + output += ` } - require(element == result, "Wrong inputHandle"); + if (element != result) revert InvalidInputHandle(); } return result; -}\n + } `; + + if (isCoprocessor) { + output += ` + + /** + * @notice Getter function for the Coprocessor account address. + */ + function getCoprocessorAddress() public view virtual returns (address) { + return coprocessorAddress; + } + `; } - output += `/// @notice Getter for the name and version of the contract - /// @return string representing the name and the version of the contract + output += ` + + /** + * @notice Getter function for the KMSVerifier contract address. + */ + function getKMSVerifierAddress() public view virtual returns (address) { + return address(kmsVerifier); + } + + /** + * @notice Getter for the name and version of the contract. + * @return string Name and the version of the contract. + */ function getVersion() external pure virtual returns (string memory) { return string( @@ -360,7 +430,71 @@ function recoverSigner(bytes32 message, bytes memory signature) internal pure vi ) ); } -} -`; + + function _cacheProof(bytes32 proofKey) internal virtual { + assembly { + tstore(proofKey, 1) + let length := tload(0) + let lengthPlusOne := add(length, 1) + tstore(lengthPlusOne, proofKey) + tstore(0, lengthPlusOne) + } + } + + function _checkProofCache( + bytes memory inputProof, + address userAddress, + address contractAddress, + address aclAddress + ) internal view virtual returns (bool, bytes32) { + bool isProofCached; + bytes32 key = keccak256(abi.encodePacked(contractAddress, aclAddress, userAddress, inputProof)); + assembly { + isProofCached := tload(key) + } + return (isProofCached, key); + } + `; + + if (isCoprocessor) { + output += ` + function _hashCiphertextVerificationForCopro( + CiphertextVerificationForCopro memory CVcopro + ) internal view virtual returns (bytes32) { + return + _hashTypedDataV4( + keccak256( + abi.encode( + CIPHERTEXT_VERIFICATION_COPRO_TYPEHASH, + CVcopro.aclAddress, + CVcopro.hashOfCiphertext, + keccak256(abi.encodePacked(CVcopro.handlesList)), + CVcopro.userAddress, + CVcopro.contractAddress + ) + ) + ); + } + + function _verifyEIP712Copro( + CiphertextVerificationForCopro memory cv, + bytes memory signature + ) internal view virtual { + bytes32 digest = _hashCiphertextVerificationForCopro(cv); + address signer = ECDSA.recover(digest, signature); + if (signer != coprocessorAddress) revert SignerIsNotCoprocessor(); + } + `; + } + + output += ` + + /** + * @dev Should revert when msg.sender is not authorized to upgrade the contract. + */ + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} + } + `; + return output; } From 01265ff411e344f48b385c9eff58a171de6d247b Mon Sep 17 00:00:00 2001 From: PacificYield <173040337+PacificYield@users.noreply.github.com> Date: Tue, 31 Dec 2024 12:22:29 +0100 Subject: [PATCH 4/4] test: adjustment --- contracts/test/kmsVerifier/kmsVerifier.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/test/kmsVerifier/kmsVerifier.ts b/contracts/test/kmsVerifier/kmsVerifier.ts index ef225ed9..a5807621 100644 --- a/contracts/test/kmsVerifier/kmsVerifier.ts +++ b/contracts/test/kmsVerifier/kmsVerifier.ts @@ -97,9 +97,18 @@ describe('KMSVerifier', function () { // different format of inputProof for native const cheatInputProof = encryptedAmount2.inputProof + encryptedAmount2.inputProof.slice(-130); // trying to cheat by repeating the first kms signer signature const cheat = cheatInputProof.slice(0, 5) + '2' + cheatInputProof.slice(6); - await expect(contract2.requestMixedBytes256(encryptedAmount2.handles[0], cheat)).to.revertedWith( - 'Not enough unique KMS input signatures', - ); // this should fail because in this case the InputVerifier received only one KMS signature (instead of at least 2) + + // It should be reverted by InputVerifier with custom error 'SignerIsNotCoprocessor'. + // It should fail because in this case the InputVerifier received only one KMS signature (instead of at least 2). + + const orig = dotenv.parse(fs.readFileSync('addresses/.env.inputverifier')).INPUT_VERIFIER_CONTRACT_ADDRESS; + const inputVerifier = ( + await ethers.getContractFactory('contracts/InputVerifier.coprocessor.sol:InputVerifier') + ).attach(orig); + await expect(contract2.requestMixedBytes256(encryptedAmount2.handles[0], cheat)).to.revertedWithCustomError( + inputVerifier, + 'KMSNumberSignaturesInsufficient', + ); } process.env.NUM_KMS_SIGNERS = '4'; const encryptedAmount = await inputAlice.encrypt();