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: added kms signature in mocked mode #501

Merged
merged 3 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ export MNEMONIC="adapt mosquito move limb mobile illegal tree voyage juice mosqu
export PRIVATE_KEY_GATEWAY_DEPLOYER="717fd99986df414889fd8b51069d4f90a50af72e542c58ee065f5883779099c6"
export PRIVATE_KEY_GATEWAY_OWNER="717fd99986df414889fd8b51069d4f90a50af72e542c58ee065f5883779099c6"
export PRIVATE_KEY_GATEWAY_RELAYER="7ec931411ad75a7c201469a385d6f18a325d4923f9f213bd882bbea87e160b67"
export NUM_KMS_SIGNERS="1"
export PRIVATE_KEY_KMS_SIGNER_0="26698d458a21b843aa1ddbd5c5b098821ddf4218bb52498c4aad3a84849275bb"
export PRIVATE_KEY_KMS_SIGNER_1="e5b998ce1e664718772fa35e8d02b2b6a267a03a8ecadab15de5b125da7fa82b"
export PRIVATE_KEY_KMS_SIGNER_2="dca817bfe824b12c92d61e56056b956617da156bcd730379cb9203c822c9ba8e"
export PRIVATE_KEY_KMS_SIGNER_3="7ac1a2886ca07b3b7393ea5ff3613bb94d72129e2c7cbedc807eb55ff971394c"

# Block explorer API keys
export ARBISCAN_API_KEY="zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
Expand Down
9 changes: 4 additions & 5 deletions gateway/GatewayContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,11 @@ contract GatewayContract is UUPSUpgradeable, Ownable2StepUpgradeable {
bytes memory decryptedCts,
bytes[] memory signatures
) external payable virtual onlyRelayer {
// TODO: this should be un-commented once KMS will have the signatures implemented
//require(
// kmsVerifier.verifySignatures(decryptionRequests[requestID].cts, decryptedCts, signatures),
// "KMS signature verification failed"
//);
GatewayContractStorage storage $ = _getGatewayContractStorage();
require(
kmsVerifier.verifySignatures($.decryptionRequests[requestID].cts, decryptedCts, signatures),
"KMS signature verification failed"
);
require(!$.isFulfilled[requestID], "Request is already fulfilled");
DecryptionRequest memory decryptionReq = $.decryptionRequests[requestID];
require(block.timestamp <= decryptionReq.maxTimestamp, "Too late");
Expand Down
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ task('test', async (taskArgs, hre, runSuper) => {
await hre.run('task:deployTFHEExecutor');
await hre.run('task:deployKMSVerifier');
await hre.run('task:deployFHEPayment');
await hre.run('task:addSigners', { numSigners: +process.env.NUM_KMS_SIGNERS! });
await hre.run('task:launchFhevm', { skipGetCoin: false });
}
await hre.run('compile:specific', { contract: 'examples' });
Expand Down
90 changes: 45 additions & 45 deletions lib/KMSVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

/// @title KMS Verifier for signature verification and verifier management
/// @title KMS Verifier for signature verification and signers management
/// @author The developer
/// @notice This contract allows for the management of verifiers and provides methods to verify signatures
/// @dev The contract uses OpenZeppelin's SignatureChecker for cryptographic operations
/// @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
contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradeable {
struct DecryptionResult {
uint256[] handlesList;
Expand All @@ -31,9 +31,9 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea

/// @custom:storage-location erc7201:fhevm.storage.KMSVerifier
struct KMSVerifierStorage {
mapping(address => bool) isVerifier; /// @notice Mapping to keep track of addresses that are verifiers
address[] verifiers; /// @notice Array to keep track of all verifiers
uint256 threshold; /// @notice The threshold for the number of verifiers required for a signature to be valid
mapping(address => bool) isSigner; /// @notice Mapping to keep track of addresses that are signers
address[] signers; /// @notice Array to keep track of all signers
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))
Expand All @@ -48,14 +48,14 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea

function _authorizeUpgrade(address _newImplementation) internal virtual override onlyOwner {}

function isVerifier(address account) public virtual returns (bool) {
function isSigner(address account) public virtual returns (bool) {
KMSVerifierStorage storage $ = _getKMSVerifierStorage();
return $.isVerifier[account];
return $.isSigner[account];
}

function getVerifiers() public view virtual returns (address[] memory) {
function getSigners() public view virtual returns (address[] memory) {
KMSVerifierStorage storage $ = _getKMSVerifierStorage();
return $.verifiers;
return $.signers;
}

function getThreshold() public view virtual returns (uint256) {
Expand All @@ -67,13 +67,13 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea
return DECRYPTIONRESULT_TYPE;
}

/// @notice Emitted when a verifier is added
/// @param verifier The address of the verifier that was added
event VerifierAdded(address indexed verifier);
/// @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 verifier is removed
/// @param verifier The address of the verifier that was removed
event VerifierRemoved(address indexed verifier);
/// @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() {
Expand All @@ -86,23 +86,23 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea
__EIP712_init(CONTRACT_NAME, "1");
}

/// @notice Sets the threshold for the number of verifiers required for a signature to be valid
/// @notice Sets the threshold for the number of signers required for a signature to be valid
function applyThreshold() internal virtual {
KMSVerifierStorage storage $ = _getKMSVerifierStorage();
$.threshold = ($.verifiers.length - 1) / 3 + 1;
$.threshold = ($.signers.length - 1) / 3 + 1;
}

/// @notice Adds a new verifier
/// @dev Only the owner can add a verifier
/// @param verifier The address to be added as a verifier
function addVerifier(address verifier) public virtual onlyOwner {
require(verifier != address(0), "KMSVerifier: Address is null");
/// @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 {
require(signer != address(0), "KMSVerifier: Address is null");
KMSVerifierStorage storage $ = _getKMSVerifierStorage();
require($.isVerifier[verifier], "KMSVerifier: Address is already a verifier");
$.isVerifier[verifier] = true;
$.verifiers.push(verifier);
require(!$.isSigner[signer], "KMSVerifier: Address is already a signer");
$.isSigner[signer] = true;
$.signers.push(signer);
applyThreshold();
emit VerifierAdded(verifier);
emit SignerAdded(signer);
}

function hashDecryptionResult(DecryptionResult memory decRes) internal view virtual returns (bytes32) {
Expand All @@ -118,29 +118,29 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea
);
}

/// @notice Removes an existing verifier
/// @dev Only the owner can remove a verifier
/// @param verifier The address to be removed from verifiers
function removeVerifier(address verifier) public virtual onlyOwner {
/// @notice Removes an existing signer
/// @dev Only the owner can remove a signer
/// @param signer The address to be removed from signers
function removeSigner(address signer) public virtual onlyOwner {
KMSVerifierStorage storage $ = _getKMSVerifierStorage();
require($.isVerifier[verifier], "KMSVerifier: Address is not a verifier");
require($.isSigner[signer], "KMSVerifier: Address is not a signer");

// Remove verifier from the mapping
$.isVerifier[verifier] = false;
// Remove signer from the mapping
$.isSigner[signer] = false;

// Find the index of the verifier and remove it from the array
for (uint i = 0; i < $.verifiers.length; i++) {
if ($.verifiers[i] == verifier) {
$.verifiers[i] = $.verifiers[$.verifiers.length - 1]; // Move the last element into the place to delete
$.verifiers.pop(); // Remove the last element
// 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();
emit VerifierRemoved(verifier);
emit SignerRemoved(signer);
return;
}
}
}

/// @notice recovers the verifier's address from a `signature` and a `message` digest
/// @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
Expand Down Expand Up @@ -178,21 +178,21 @@ contract KMSVerifier is UUPSUpgradeable, Ownable2StepUpgradeable, EIP712Upgradea
require(numSignatures > 0, "KmsVerifier: no signatures provided");
KMSVerifierStorage storage $ = _getKMSVerifierStorage();
require(numSignatures >= $.threshold, "KmsVerifier: at least threshold number of signatures required");
address[] memory recoveredVerifiers = new address[](numSignatures);
address[] memory recoveredSigners = new address[](numSignatures);
uint256 uniqueValidCount;
for (uint256 i = 0; i < numSignatures; i++) {
address signerRecovered = recoverSigner(message, signatures[i]);
if ($.isVerifier[signerRecovered]) {
if ($.isSigner[signerRecovered]) {
if (!tload(signerRecovered)) {
recoveredVerifiers[uniqueValidCount] = signerRecovered;
recoveredSigners[uniqueValidCount] = signerRecovered;
uniqueValidCount++;
tstore(signerRecovered, 1);
}
}
if (uniqueValidCount >= $.threshold) {
for (uint256 j = 0; i < uniqueValidCount; i++) {
/// @note : clearing transient storage for composability
tstore(recoveredVerifiers[j], 0);
tstore(recoveredSigners[j], 0);
}
return true;
}
Expand Down
15 changes: 15 additions & 0 deletions tasks/taskDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,18 @@ task('task:deployFHEPayment').setAction(async function (taskArguments: TaskArgum
}
console.log('FHEPayment was deployed at address:', address);
});

task('task:addSigners').setAction(async function (taskArguments: TaskArguments, { ethers }) {
const deployer = (await ethers.getSigners())[9];
const factory = await ethers.getContractFactory('KMSVerifier', deployer);
const kmsAdd = dotenv.parse(fs.readFileSync('lib/.env.kmsverifier')).KMS_VERIFIER_CONTRACT_ADDRESS;
const kmsVerifier = await factory.attach(kmsAdd);

for (let idx = 0; idx < taskArguments.numSigners; idx++) {
const privKeySigner = process.env[`PRIVATE_KEY_KMS_SIGNER_${idx}`];
const kmsSigner = new ethers.Wallet(privKeySigner).connect(ethers.provider);
const tx = await kmsVerifier.addSigner(kmsSigner.address);
await tx.wait();
console.log(`KMS signer no${idx} was added to KMSVerifier contract`);
}
});
67 changes: 66 additions & 1 deletion test/asyncDecrypt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import dotenv from 'dotenv';
import { Wallet } from 'ethers';
import fs from 'fs';
import { ethers, network } from 'hardhat';

Expand Down Expand Up @@ -142,7 +143,12 @@ const fulfillAllPastRequestsIds = async (mocked: boolean) => {
const encodedData = abiCoder.encode(['uint256', ...types], [31, ...valuesFormatted2]); // 31 is just a dummy uint256 requestID to get correct abi encoding for the remaining arguments (i.e everything except the requestID)
const calldata = '0x' + encodedData.slice(66); // we just pop the dummy requestID to get the correct value to pass for `decryptedCts`

const tx = await gateway.connect(relayer).fulfillRequest(requestID, calldata, [], { value: msgValue });
const numSigners = +process.env.NUM_KMS_SIGNERS!;
const decryptResultsEIP712signatures = await computeDecryptSignatures(handles, calldata, numSigners);

const tx = await gateway
.connect(relayer)
.fulfillRequest(requestID, calldata, decryptResultsEIP712signatures, { value: msgValue });
await tx.wait();
} else {
// in fhEVM mode we must wait until the gateway service relayer submits the decryption fulfillment tx
Expand All @@ -152,3 +158,62 @@ const fulfillAllPastRequestsIds = async (mocked: boolean) => {
}
}
};

async function computeDecryptSignatures(
handlesList: bigint[],
decryptedResult: string,
numSigners: number,
): Promise<string[]> {
const signatures: string[] = [];

for (let idx = 0; idx < numSigners; idx++) {
const privKeySigner = process.env[`PRIVATE_KEY_KMS_SIGNER_${idx}`];
if (privKeySigner) {
const kmsSigner = new ethers.Wallet(privKeySigner).connect(ethers.provider);
const signature = await kmsSign(handlesList, decryptedResult, kmsSigner);
signatures.push(signature);
} else {
throw new Error(`Private key for signer ${idx} not found in environment variables`);
}
}
return signatures;
}

async function kmsSign(handlesList: bigint[], decryptedResult: string, kmsSigner: Wallet) {
const kmsAdd = dotenv.parse(fs.readFileSync('lib/.env.kmsverifier')).KMS_VERIFIER_CONTRACT_ADDRESS;
const chainId = (await ethers.provider.getNetwork()).chainId;

const domain = {
name: 'KMSVerifier',
version: '1',
chainId: chainId,
verifyingContract: kmsAdd,
};

const types = {
DecryptionResult: [
{
name: 'handlesList',
type: 'uint256[]',
},
{
name: 'decryptedResult',
type: 'bytes',
},
],
};

const message = {
handlesList: handlesList,
decryptedResult: decryptedResult,
};

const signature = await kmsSigner.signTypedData(domain, types, message);
const sigRSV = ethers.Signature.from(signature);
const v = 27 + sigRSV.yParity;
const r = sigRSV.r;
const s = sigRSV.s;

const result = r + s.substring(2) + v.toString(16);
return result;
}
Loading