Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: native InputVerifier #547

Merged
merged 1 commit into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export PRIVATE_KEY_KMS_SIGNER_1="e5b998ce1e664718772fa35e8d02b2b6a267a03a8ecadab
export PRIVATE_KEY_KMS_SIGNER_2="dca817bfe824b12c92d61e56056b956617da156bcd730379cb9203c822c9ba8e"
export PRIVATE_KEY_KMS_SIGNER_3="7ac1a2886ca07b3b7393ea5ff3613bb94d72129e2c7cbedc807eb55ff971394c"
export PRIVATE_KEY_COPROCESSOR_ACCOUNT="7ec8ada6642fc4ccfb7729bc29c17cf8d21b61abd5642d1db992c0b8672ab901"
export IS_COPROCESSOR="true"

# Block explorer API keys
export ARBISCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
Expand Down
6 changes: 6 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '@nomicfoundation/hardhat-toolbox';
import '@openzeppelin/hardhat-upgrades';
import dotenv from 'dotenv';
import { promises as fs } from 'fs';
import 'hardhat-deploy';
import 'hardhat-ignore-warnings';
import type { HardhatUserConfig, extendProvider } from 'hardhat/config';
Expand Down Expand Up @@ -93,6 +94,11 @@ task('test', async (taskArgs, hre, runSuper) => {
await hre.run('task:computeKMSVerifierAddress');
await hre.run('task:computeInputVerifierAddress');
await hre.run('task:computeFHEPaymentAddress');
if (process.env.IS_COPROCESSOR === 'true') {
await fs.copyFile('lib/InputVerifier.sol.coprocessor', 'lib/InputVerifier.sol');
} else {
await fs.copyFile('lib/InputVerifier.sol.native', 'lib/InputVerifier.sol');
}
await hre.run('compile:specific', { contract: 'lib' });
await hre.run('compile:specific', { contract: 'gateway' });
await hre.run('compile:specific', { contract: 'payment' });
Expand Down
15 changes: 2 additions & 13 deletions lib/InputVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad
uint256 indexHandle = (result & 0x0000000000000000000000000000000000000000000000000000000000ff0000) >> 16;

if (!isProofCached) {
// inputHandle is keccak256(bundleCiphertext)[0:29]+index+type+version
// 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;
Expand Down Expand Up @@ -179,18 +180,6 @@ contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgrad
require(signer == coprocessorAddress, "Coprocessor address mismatch");
}

function tstore(address location, uint256 value) internal virtual {
assembly {
tstore(location, value)
}
}

function tload(address location) internal view virtual returns (bool value) {
assembly {
value := tload(location)
}
}

function hashCiphertextVerificationForCopro(
CiphertextVerificationForCopro memory CVcopro
) internal view virtual returns (bytes32) {
Expand Down
227 changes: 227 additions & 0 deletions lib/InputVerifier.sol.coprocessor
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;

import "./KMSVerifier.sol";
import "./TFHEExecutor.sol";
import "./KMSVerifierAddress.sol";
import "./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/utils/cryptography/EIP712Upgradeable.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 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
contract InputVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradeable {
struct CiphertextVerificationForCopro {
address aclAddress;
bytes32 hashOfCiphertext;
uint256[] handlesList;
address userAddress;
address contractAddress;
}

/// @notice Handle version
uint8 public constant HANDLE_VERSION = 0;

address constant coprocessorAddress = coprocessorAdd;

KMSVerifier public constant kmsVerifier = KMSVerifier(kmsVerifierAdd);
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));

/// @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 {}

function get_CIPHERTEXTVERIFICATION_COPRO_TYPE() public view virtual returns (string memory) {
return CIPHERTEXTVERIFICATION_COPRO_TYPE;
}

/// @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);
__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)
}
}

function verifyCiphertext(
TFHEExecutor.ContextUserInputs memory context,
bytes32 inputHandle,
bytes memory inputProof
) external virtual returns (uint256) {
(bool isProofCached, bytes32 cacheKey) = checkProofCache(
inputProof,
context.userAddress,
context.contractAddress,
context.aclAddress
);
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
// 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(inputProofLen == 99 + 32 * numHandles + 65 * numSignersKMS, "Error deserializing inputProof");

bytes32 hashCT;
assembly {
hashCT := mload(add(inputProof, 34))
}

// deseralize handle and check they are from 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");
listHandles[i] = element;
}

{
bytes memory signatureCoproc = new bytes(65);
for (uint256 i = 0; i < 65; i++) {
signatureCoproc[i] = inputProof[34 + 32 * numHandles + i];
}
CiphertextVerificationForCopro memory cvCopro;
cvCopro.aclAddress = context.aclAddress;
cvCopro.hashOfCiphertext = hashCT;
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");
}
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));
}
require(element == result, "Wrong inputHandle");
}
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
)
)
);
}

/// @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 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)
)
);
}
}
Loading