diff --git a/contracts/contracts/KMSVerifier.sol b/contracts/contracts/KMSVerifier.sol index f75439d..7e37fa6 100644 --- a/contracts/contracts/KMSVerifier.sol +++ b/contracts/contracts/KMSVerifier.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; -// 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/utils/cryptography/EIP712Upgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -/// @title KMS Verifier for signature verification and signers management -/// @author The developer -/// @notice This contract allows for the management of signers and provides methods to verify signatures -/// @dev The contract uses OpenZeppelin's EIP712Upgradeable for cryptographic operations +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +/** + * @title KMSVerifier. + * @notice KMSVerifier (Key Management System Verifier) is a contract that allows the management of signers and provides + * signature verification functions. + * @dev The contract uses OpenZeppelin's EIP712Upgradeable for cryptographic operations and is deployed using an UUPS proxy. + */ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradeable { /// @notice Returned if the KMS signer to add is already a signer. error KMSAlreadySigner(); @@ -26,19 +27,38 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea /// @notice Returned if the KMS signer to add is the null address. error KMSSignerNull(); - /// @notice Returned if the number of signatures is inferior to the threshold. - /// @param numSignatures Number of signatures. + /// @notice Returned if the number of signatures is inferior to the threshold. + /// @param numSignatures Number of signatures. error KMSSignatureThresholdNotReached(uint256 numSignatures); /// @notice Returned if the number of signatures is equal to 0. error KMSZeroSignature(); + /// @notice Emitted when a signer is added. + /// @param signer The address of the signer that was added. + event SignerAdded(address indexed signer); + + /// @notice Emitted when a signer is removed. + /// @param signer The address of the signer that was removed. + event SignerRemoved(address indexed signer); + + /** + * @param aclAddress ACL address. + * @param handlesList List of handles. + * @param decryptedResult Decrypted result. + */ struct DecryptionResult { address aclAddress; uint256[] handlesList; bytes decryptedResult; } + /** + * @param aclAddress ACL address. + * @param hashOfCiphertext Hash of ciphertext. + * @param userAddress Address of the user. + * @param contractAddress Contract address. + */ struct CiphertextVerificationForKMS { address aclAddress; bytes32 hashOfCiphertext; @@ -46,20 +66,30 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea address contractAddress; } - string private constant DECRYPTIONRESULT_TYPE = + /// @notice Ciphertext verification type. + string public constant CIPHERTEXT_VERIFICATION_KMS_TYPE = + "CiphertextVerificationForKMS(address aclAddress,bytes32 hashOfCiphertext,address userAddress,address contractAddress)"; + + /// @notice Ciphertext verification typehash. + bytes32 public constant CIPHERTEXT_VERIFICATION_KMS_TYPEHASH = keccak256(bytes(CIPHERTEXT_VERIFICATION_KMS_TYPE)); + + /// @notice Decryption result type. + string public constant DECRYPTION_RESULT_TYPE = "DecryptionResult(address aclAddress,uint256[] handlesList,bytes decryptedResult)"; - bytes32 private constant DECRYPTIONRESULT_TYPE_HASH = keccak256(bytes(DECRYPTIONRESULT_TYPE)); - string public constant CIPHERTEXTVERIFICATION_KMS_TYPE = - "CiphertextVerificationForKMS(address aclAddress,bytes32 hashOfCiphertext,address userAddress,address contractAddress)"; - bytes32 private constant CIPHERTEXTVERIFICATION_KMS_TYPE_HASH = keccak256(bytes(CIPHERTEXTVERIFICATION_KMS_TYPE)); + /// @notice Decryption result typehash. + bytes32 public constant DECRYPTION_RESULT_TYPEHASH = keccak256(bytes(DECRYPTION_RESULT_TYPE)); - /// @notice Name of the contract + /// @notice Name of the contract. string private constant CONTRACT_NAME = "KMSVerifier"; - /// @notice Version of the contract + /// @notice Major version of the contract. uint256 private constant MAJOR_VERSION = 0; + + /// @notice Minor version of the contract. uint256 private constant MINOR_VERSION = 1; + + /// @notice Patch version of the contract. uint256 private constant PATCH_VERSION = 0; /// @custom:storage-location erc7201:fhevm.storage.KMSVerifier @@ -69,69 +99,29 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea uint256 threshold; /// @notice The threshold for the number of signers required for a signature to be valid } - // keccak256(abi.encode(uint256(keccak256("fhevm.storage.KMSVerifier")) - 1)) & ~bytes32(uint256(0xff)) + /// @dev keccak256(abi.encode(uint256(keccak256("fhevm.storage.KMSVerifier")) - 1)) & ~bytes32(uint256(0xff)) bytes32 private constant KMSVerifierStorageLocation = 0x7e81a744be86773af8644dd7304fa1dc9350ccabf16cfcaa614ddb78b4ce8900; - function _getKMSVerifierStorage() internal pure returns (KMSVerifierStorage storage $) { - assembly { - $.slot := KMSVerifierStorageLocation - } - } - - function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} - - function isSigner(address account) public view virtual returns (bool) { - KMSVerifierStorage storage $ = _getKMSVerifierStorage(); - return $.isSigner[account]; - } - - function getSigners() public view virtual returns (address[] memory) { - KMSVerifierStorage storage $ = _getKMSVerifierStorage(); - return $.signers; - } - - function getThreshold() public view virtual returns (uint256) { - KMSVerifierStorage storage $ = _getKMSVerifierStorage(); - return $.threshold; - } - - function get_DECRYPTIONRESULT_TYPE() public view virtual returns (string memory) { - return DECRYPTIONRESULT_TYPE; - } - - function get_CIPHERTEXTVERIFICATION_KMS_TYPE() public view virtual returns (string memory) { - return CIPHERTEXTVERIFICATION_KMS_TYPE; - } - - /// @notice Emitted when a signer is added - /// @param signer The address of the signer that was added - event SignerAdded(address indexed signer); - - /// @notice Emitted when a signer is removed - /// @param signer The address of the signer that was removed - event SignerRemoved(address indexed signer); - /// @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"); } - /// @notice Sets the threshold for the number of signers required for a signature to be valid - function applyThreshold() internal virtual { - KMSVerifierStorage storage $ = _getKMSVerifierStorage(); - $.threshold = ($.signers.length - 1) / 3 + 1; - } - - /// @notice Adds a new signer - /// @dev Only the owner can add a signer - /// @param signer The address to be added as a signer + /** + * @notice Adds a new signer. + * @dev Only the owner can add a signer. + * @param signer The address to be added as a signer. + */ function addSigner(address signer) public virtual onlyOwner { if (signer == address(0)) { revert KMSSignerNull(); @@ -144,81 +134,44 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea $.isSigner[signer] = true; $.signers.push(signer); - applyThreshold(); + _applyThreshold(); emit SignerAdded(signer); } - function hashDecryptionResult(DecryptionResult memory decRes) internal view virtual returns (bytes32) { - return - _hashTypedDataV4( - keccak256( - abi.encode( - DECRYPTIONRESULT_TYPE_HASH, - decRes.aclAddress, - keccak256(abi.encodePacked(decRes.handlesList)), - keccak256(decRes.decryptedResult) - ) - ) - ); - } - - function hashCiphertextVerificationForKMS( - CiphertextVerificationForKMS memory CVkms - ) internal view virtual returns (bytes32) { - return - _hashTypedDataV4( - keccak256( - abi.encode( - CIPHERTEXTVERIFICATION_KMS_TYPE_HASH, - CVkms.aclAddress, - CVkms.hashOfCiphertext, - CVkms.userAddress, - CVkms.contractAddress - ) - ) - ); - } - - /// @notice Removes an existing signer - /// @dev Only the owner can remove a signer - /// @param signer The address to be removed from signers + /** + * @notice Removes an existing signer. + * @dev Only the owner can remove a signer. + * @param signer The signer address to remove. + */ function removeSigner(address signer) public virtual onlyOwner { KMSVerifierStorage storage $ = _getKMSVerifierStorage(); if (!$.isSigner[signer]) { revert KMSNotASigner(); } - // Remove signer from the mapping + /// @dev Remove signer from the mapping. $.isSigner[signer] = false; - // Find the index of the signer and remove it from the array + /// @dev Find the index of the signer and remove it from the array. for (uint i = 0; i < $.signers.length; i++) { if ($.signers[i] == signer) { - $.signers[i] = $.signers[$.signers.length - 1]; // Move the last element into the place to delete - $.signers.pop(); // Remove the last element - applyThreshold(); + $.signers[i] = $.signers[$.signers.length - 1]; /// @dev Move the last element into the place to delete. + $.signers.pop(); /// @dev Remove the last element. + _applyThreshold(); emit SignerRemoved(signer); return; } } } - /// @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; - } - - /// @notice Verifies multiple signatures for a given handlesList and a given decryptedResult - /// @dev Calls verifySignaturesDigest internally; - /// @param handlesList The list of handles which where requested to be decrypted - /// @param decryptedResult A bytes array representing the abi-encoding of all requested decrypted values - /// @param signatures An array of signatures to verify - /// @return true if enough provided signatures are valid, false otherwise + /** + * @notice Verifies multiple signatures for a given handlesList and a given decryptedResult. + * @dev Calls verifySignaturesDigest internally. + * @param handlesList The list of handles, which where requested to be decrypted. + * @param decryptedResult A bytes array representing the abi-encoding of all requested decrypted values. + * @param signatures An array of signatures to verify. + * @return isVerified true if enough provided signatures are valid, false otherwise. + */ function verifyDecryptionEIP712KMSSignatures( address aclAddress, uint256[] memory handlesList, @@ -229,29 +182,113 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea decRes.aclAddress = aclAddress; decRes.handlesList = handlesList; decRes.decryptedResult = decryptedResult; - bytes32 digest = hashDecryptionResult(decRes); - return verifySignaturesDigest(digest, signatures); + bytes32 digest = _hashDecryptionResult(decRes); + return _verifySignaturesDigest(digest, signatures); } - /// @notice Verifies multiple signatures for a given CiphertextVerificationForKMS (user inputs) - /// @dev Calls verifySignaturesDigest internally; - /// @param cv The CiphertextVerificationForKMS struct for encrypted user inputs - /// @param signatures An array of signatures to verify - /// @return true if enough provided signatures are valid, false otherwise + /** + * @notice Verifies multiple signatures for a given CiphertextVerificationForKMS (user inputs). + * @dev Calls verifySignaturesDigest internally. + * @param cv The CiphertextVerificationForKMS struct for encrypted user inputs. + * @param signatures An array of signatures to verify. + * @return isVerified true if enough provided signatures are valid, false otherwise. + */ function verifyInputEIP712KMSSignatures( CiphertextVerificationForKMS memory cv, bytes[] memory signatures ) public virtual returns (bool) { - bytes32 digest = hashCiphertextVerificationForKMS(cv); - return verifySignaturesDigest(digest, signatures); + bytes32 digest = _hashCiphertextVerificationForKMS(cv); + return _verifySignaturesDigest(digest, signatures); + } + + /** + * @notice Returns the list of KMS signers. + * @dev If there are too many signers, it could be out-of-gas. + * @return signers List of signers. + */ + function getSigners() public view virtual returns (address[] memory) { + KMSVerifierStorage storage $ = _getKMSVerifierStorage(); + return $.signers; + } + + /** + * @notice Get the threshold for signature. + * @return threshold Threshold for signature verification. + */ + function getThreshold() public view virtual returns (uint256) { + KMSVerifierStorage storage $ = _getKMSVerifierStorage(); + return $.threshold; + } + + /** + * @notice Returns whether the account address is a valid KMS signer. + * @param account Account address. + * @return isSigner Whether the account is a valid KMS signer. + */ + function isSigner(address account) public view virtual returns (bool) { + KMSVerifierStorage storage $ = _getKMSVerifierStorage(); + return $.isSigner[account]; + } + + /** + * @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) + ) + ); + } + + /** + * @notice Sets the threshold for the number of signers required for a signature to be valid. + */ + function _applyThreshold() internal virtual { + KMSVerifierStorage storage $ = _getKMSVerifierStorage(); + $.threshold = ($.signers.length - 1) / 3 + 1; } - /// @notice Verifies multiple signatures for a given message at a certain threshold - /// @dev Calls verifySignature internally; - /// @param digest The hash of the message that was signed by all signers - /// @param signatures An array of signatures to verify - /// @return true if enough provided signatures are valid, false otherwise - function verifySignaturesDigest(bytes32 digest, bytes[] memory signatures) internal virtual returns (bool) { + /** + * @notice Cleans transient storage. + * @dev This is important to keep composability in the context of account abstraction. + * @param keys An array of keys to cleanup from transient storage. + * @param maxIndex The biggest index to take into account from the array - assumed to be less or equal to keys.length. + */ + function _cleanTransientStorage(address[] memory keys, uint256 maxIndex) internal virtual { + for (uint256 j = 0; j < maxIndex; j++) { + _tstore(keys[j], 0); + } + } + + /** + * @notice Writes to transient storage. + * @dev Uses inline assembly to access the Transient Storage's _tstore operation. + * @param location The address used as key where transient storage of the contract is written at. + * @param value An uint256 stored at location key in transient storage of the contract. + */ + function _tstore(address location, uint256 value) internal virtual { + assembly { + tstore(location, value) + } + } + + /** + * @notice Verifies multiple signatures for a given message at a certain threshold. + * @dev Calls verifySignature internally. + * @param digest The hash of the message that was signed by all signers. + * @param signatures An array of signatures to verify. + * @return isVerified true if enough provided signatures are valid, false otherwise. + */ + function _verifySignaturesDigest(bytes32 digest, bytes[] memory signatures) internal virtual returns (bool) { uint256 numSignatures = signatures.length; if (numSignatures == 0) { @@ -267,69 +304,100 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea address[] memory recoveredSigners = new address[](numSignatures); uint256 uniqueValidCount; for (uint256 i = 0; i < numSignatures; i++) { - address signerRecovered = recoverSigner(digest, signatures[i]); + address signerRecovered = _recoverSigner(digest, signatures[i]); if (!isSigner(signerRecovered)) { revert KMSInvalidSigner(signerRecovered); } - if (!tload(signerRecovered)) { + if (!_tload(signerRecovered)) { recoveredSigners[uniqueValidCount] = signerRecovered; uniqueValidCount++; - tstore(signerRecovered, 1); + _tstore(signerRecovered, 1); } if (uniqueValidCount >= threshold) { - cleanTransientStorage(recoveredSigners, uniqueValidCount); + _cleanTransientStorage(recoveredSigners, uniqueValidCount); return true; } } - cleanTransientStorage(recoveredSigners, uniqueValidCount); + _cleanTransientStorage(recoveredSigners, uniqueValidCount); return false; } - /// @notice Writes to transient storage - /// @dev Uses inline assembly to access the Transient Storage's tstore operation. - /// @param location The address used as key where transient storage of the contract is written at - /// @param value An uint256 stored at location key in transient storage of the contract - function tstore(address location, uint256 value) internal virtual { - assembly { - tstore(location, value) - } + /** + * @dev Should revert when msg.sender is not authorized to upgrade the contract. + */ + function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {} + + /** + * @notice Hashes the ciphertext verification. + * @param CVkms CiphertextVerification for KMS. + * @return hashTypedData Hash typed data. + */ + function _hashCiphertextVerificationForKMS( + CiphertextVerificationForKMS memory CVkms + ) internal view virtual returns (bytes32) { + return + _hashTypedDataV4( + keccak256( + abi.encode( + CIPHERTEXT_VERIFICATION_KMS_TYPEHASH, + CVkms.aclAddress, + CVkms.hashOfCiphertext, + CVkms.userAddress, + CVkms.contractAddress + ) + ) + ); + } + + /** + * @notice Hashes the decryption result. + * @param decRes Decryption result. + * @return hashTypedData Hash typed data. + */ + function _hashDecryptionResult(DecryptionResult memory decRes) internal view virtual returns (bytes32) { + return + _hashTypedDataV4( + keccak256( + abi.encode( + DECRYPTION_RESULT_TYPEHASH, + decRes.aclAddress, + keccak256(abi.encodePacked(decRes.handlesList)), + keccak256(decRes.decryptedResult) + ) + ) + ); } - /// @notice Reads transient storage - /// @dev Uses inline assembly to access the Transient Storage's tload operation. - /// @param location The address used as key where transient storage of the contract is read at - /// @return value true if value stored at the given location is non-null, false otherwise. - function tload(address location) internal view virtual returns (bool value) { + /** + * @notice Reads transient storage. + * @dev Uses inline assembly to access the Transient Storage's tload operation. + * @param location The address used as key where transient storage of the contract is read at. + * @return value true if value stored at the given location is non-null, false otherwise. + */ + function _tload(address location) internal view virtual returns (bool value) { assembly { value := tload(location) } } - /// @notice Cleans transient storage - /// @dev Important to keep composability in the context of account abstraction - /// @param keys An array of keys to cleanup from transient storage - /// @param maxIndex The biggest index to take into account from the array - assumed to be less or equal to keys.length - function cleanTransientStorage(address[] memory keys, uint256 maxIndex) internal virtual { - for (uint256 j = 0; j < maxIndex; j++) { - /// @note : clearing transient storage for composability - tstore(keys[j], 0); + /** + * @dev Returns the KMSVerifier storage location. + */ + function _getKMSVerifierStorage() internal pure returns (KMSVerifierStorage storage $) { + assembly { + $.slot := KMSVerifierStorageLocation } } - /// @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) - ) - ); + /** + * @notice Recovers the signer's address from a `signature` and a `message` digest. + * @dev It utilizes ECDSA for actual address recovery. It does not support contract signature (EIP-1271). + * @param message The hash of the message that was signed. + * @param signature The signature to verify. Its length can be 64 or 65 bytes. + * @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; } }