From 270bb0537f5a351d9e1ccc50dd3d9eb05dbde092 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 11 Feb 2026 01:55:59 +0500 Subject: [PATCH 1/6] chore: solidity verifier for noir circuits --- package.json | 1 + .../contracts/verifier/DkgPkVerifier.sol | 3140 +++++++++++++++++ .../enclave-contracts/deployed_contracts.json | 108 +- packages/enclave-contracts/hardhat.config.ts | 2 +- .../ignition/modules/dkgPkVerifier.ts | 12 + packages/enclave-contracts/package.json | 1 + .../scripts/deployAndSave/verifiers.ts | 163 + .../scripts/deployEnclave.ts | 13 + .../scripts/deployVerifiers.ts | 39 + packages/enclave-contracts/scripts/index.ts | 1 + scripts/generate-verifiers.ts | 383 ++ 11 files changed, 3824 insertions(+), 39 deletions(-) create mode 100644 packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol create mode 100644 packages/enclave-contracts/ignition/modules/dkgPkVerifier.ts create mode 100644 packages/enclave-contracts/scripts/deployAndSave/verifiers.ts create mode 100644 packages/enclave-contracts/scripts/deployVerifiers.ts create mode 100644 scripts/generate-verifiers.ts diff --git a/package.json b/package.json index 0e02836f9b..0fbb7404a6 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "scripts": { "bump:versions": "tsx scripts/bump-versions.ts", "build:circuits": "tsx scripts/build-circuits.ts", + "generate:verifiers": "tsx scripts/generate-verifiers.ts", "store:circuits": "tsx scripts/circuit-artifacts.ts", "clean": "tsx scripts/clean.ts", "compile": "pnpm build:ts && pnpm rust:build", diff --git a/packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol b/packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol new file mode 100644 index 0000000000..985d4fa222 --- /dev/null +++ b/packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol @@ -0,0 +1,3140 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 Aztec +pragma solidity >=0.8.21; + +uint256 constant N = 8192; +uint256 constant LOG_N = 13; +uint256 constant NUMBER_OF_PUBLIC_INPUTS = 17; +uint256 constant VK_HASH = 0x170affbea8713b79ad326398ea7b49424c51fa4dca10a65185ad33dbce4d6001; +library HonkVerificationKey { + function loadVerificationKey() + internal + pure + returns (Honk.VerificationKey memory) + { + Honk.VerificationKey memory vk = Honk.VerificationKey({ + circuitSize: uint256(8192), + logCircuitSize: uint256(13), + publicInputsSize: uint256(17), + ql: Honk.G1Point({ + x: uint256( + 0x22a702d6d3957500783049fee5af2a27155cfdf1b6c2958942f5cde4bc63d163 + ), + y: uint256( + 0x003567b43861479b2347249040b9a4c4423f494e6dd8a7de9a4e3900741c4551 + ) + }), + qr: Honk.G1Point({ + x: uint256( + 0x1333cd6f8595ef95ab9d9744d1d00175a56c1b0bc60000ac6d4eb9ec7315c70a + ), + y: uint256( + 0x1c891650f45fe25cf375ea0b322372757b89799546eed2152766130e6b99efa0 + ) + }), + qo: Honk.G1Point({ + x: uint256( + 0x251fdc445de2b90170e8c86c41a81303bb3de75352f2bd6f095bdab77486445a + ), + y: uint256( + 0x06f20c995414f9b6bdb794492d328124e2b7676329a5a1724365076751ad2a8a + ) + }), + q4: Honk.G1Point({ + x: uint256( + 0x12600bb185b845c63e4b9d5c310db2d257aa5d1d09090ef3c8bdd7334e8991d0 + ), + y: uint256( + 0x017ccf0bdad7381ff8a3bef25259d90c296b71c037ee58bcce51cb0b7a6e97f4 + ) + }), + qm: Honk.G1Point({ + x: uint256( + 0x1a5c96bf5dac44c45e28380daccedfec1d9a8919999a0d748a8686f7f42cba76 + ), + y: uint256( + 0x28c5876a4c88720ff49870cbc3b7f41c26a131f4d57ce95cbc6127859c08931f + ) + }), + qc: Honk.G1Point({ + x: uint256( + 0x2c740230a118ad04f1482eeef2e75a14aed0f961b763120de34d0fbe63ca12b0 + ), + y: uint256( + 0x1b9aa7fd5bf93acbaadd615b6e0829e714539fe471bece34b359cd6a96d00294 + ) + }), + qLookup: Honk.G1Point({ + x: uint256( + 0x0c4032c3079594eb75a8449d3d5ce8bc3661650d53f9b24d923d8f404cb0bbc9 + ), + y: uint256( + 0x1084d709650356d40f0158fd6da81f54eb5fe796a0ca89441369b7c24301f851 + ) + }), + qArith: Honk.G1Point({ + x: uint256( + 0x1f13031b7801d2da6d5206a7da087d7661d87905cef81f24e3cbe9e7a1c51488 + ), + y: uint256( + 0x2d93aa6923ac15844842bd024199117f43ca14c883f37570030fdf385356a18a + ) + }), + qDeltaRange: Honk.G1Point({ + x: uint256( + 0x300557c1eda8ed63912d72e051bb3431bd54f7395755b820a59ff85295eb36d9 + ), + y: uint256( + 0x227e5f5f62555201e96e188514b5387fb7e026a4af0ac209ec6730c4e0e6cc76 + ) + }), + qElliptic: Honk.G1Point({ + x: uint256( + 0x20a7925feb244b2ba41d31db4651a6c6c6be672ed0e7711f2ef4c2b89f9c4a5f + ), + y: uint256( + 0x2472b531a32a7d7f80ec3c3375977ba4968f326786473e34d1fd73e6236cf661 + ) + }), + qMemory: Honk.G1Point({ + x: uint256( + 0x02095e5acd019e4edaf8d736615b4a159deafdb98a171db591ed499b0219fb4b + ), + y: uint256( + 0x0644f4dc54ac72663d956a233412f748e74b1591553ecbca703829acf75e471a + ) + }), + qNnf: Honk.G1Point({ + x: uint256( + 0x022bae55678012a184d4cb970b8f9a6d1334097d611de534bef96fc9f8d9d011 + ), + y: uint256( + 0x284ee2d499274885bcb7a0d77b0a261d9192ec413b0c0a86fb27ae88cdd5a72c + ) + }), + qPoseidon2External: Honk.G1Point({ + x: uint256( + 0x2dd8c7e5ce5f6a7053c173bb140d46830ab962c6c45d637d66171ee540bb21d5 + ), + y: uint256( + 0x033b47f5f8496af8d227ef175c47b3f7cd666ebc94f0e63562ebfcf30d73f6e2 + ) + }), + qPoseidon2Internal: Honk.G1Point({ + x: uint256( + 0x2940b2ab034af88869e264e12ee268b16c221b5f2cdd96748cce870c1a7bb24a + ), + y: uint256( + 0x04cfb9bfe08adf0e6f25c2cc9e83448d8f23d9fb3a0efba8d41768520d62d0e8 + ) + }), + s1: Honk.G1Point({ + x: uint256( + 0x1b300dbb752984d39a06a3278fc9048a525d0fdf14ef98f17ffb42a971fb9032 + ), + y: uint256( + 0x01ccd716ea4eeba47b57e3110f305af0d1e2feec11f2656816dbb234ea0b3b1b + ) + }), + s2: Honk.G1Point({ + x: uint256( + 0x0d2e187f65cf5ab43eac061f54e93bdcc292b2f518ee632b030ea3290143139c + ), + y: uint256( + 0x03ff9967978b924f43af1e71f6281ee36a96c1b647bbf8c17e3cb4626068b78b + ) + }), + s3: Honk.G1Point({ + x: uint256( + 0x2106e82e5e59b21be7c8ac07ede04efc6445f01e31b5ce7894444cb081df43d1 + ), + y: uint256( + 0x0571641ab150cfa4371421f984cc897945b69ca100d0960c2ee5ca1d17cd9670 + ) + }), + s4: Honk.G1Point({ + x: uint256( + 0x27599a711decc65fa4aad9515655e029868cc54d495fb8b53fa44e165702a5ee + ), + y: uint256( + 0x1d9db80b8419761979511454f69fa448fc50d15062e434c6994c4ccc4912f672 + ) + }), + t1: Honk.G1Point({ + x: uint256( + 0x099e3bd5a0a00ab7fe18040105b9b395b5d8b7b4a63b05df652b0d10ef146d26 + ), + y: uint256( + 0x0015b8d2515d76e2ccec99dcd194592129af3a637f5a622a32440f860d1e2a7f + ) + }), + t2: Honk.G1Point({ + x: uint256( + 0x1b917517920bad3d8bc01c9595092a222b888108dc25d1aa450e0b4bc212c37e + ), + y: uint256( + 0x305e8992b148eedb22e6e992077a84482141c7ebe42000a1d58ccb74381f6d19 + ) + }), + t3: Honk.G1Point({ + x: uint256( + 0x13567e3b915c81013ada15236ba5cfa60111b440400b2bca37e2b1085e924a77 + ), + y: uint256( + 0x0148d22589b91f0d8f4674af5744dedafd63caea904b434e748f9713de8cc3d7 + ) + }), + t4: Honk.G1Point({ + x: uint256( + 0x043d063b130adfb37342af45d0155a28edd1a7e46c840d9c943fdf45521c64ce + ), + y: uint256( + 0x261522c4089330646aff96736194949330952ae74c573d1686d9cb4a00733854 + ) + }), + id1: Honk.G1Point({ + x: uint256( + 0x1fa05f284bf1410f37f0d556ccfeb052395ec8625620cf724d146a3b15496d4d + ), + y: uint256( + 0x0c8fc17b9b81b4f93a7b9bdd1cc5f01a30ef6e808409c456f6d6d13fe7d2eb17 + ) + }), + id2: Honk.G1Point({ + x: uint256( + 0x05798d2cd96b3294d8cc2f0120fa58ffea3a662e32039784f0b0b051e34e8718 + ), + y: uint256( + 0x02d377efbb702de6d6a38e5c42c6aaf56cc11762d724a635ea4d5dc72eb9e0ca + ) + }), + id3: Honk.G1Point({ + x: uint256( + 0x110bd6ab5007b97692cd6e8ea8cab85d8b039d9e8dc2a9931fa6dcd72e0adac8 + ), + y: uint256( + 0x2e418397e17371fcf5a1a5c614f4dfe9727b79d5aa127ab98c04a7a4c06ccb9f + ) + }), + id4: Honk.G1Point({ + x: uint256( + 0x0ccf438e4b27c76e599cda77ba04ad29cbaf538ded427e979eef69a5a9884b78 + ), + y: uint256( + 0x09117b0cb34fc65ed20f92de70c6f8e073c755fb4acf2d74f402c6a97ee3d01a + ) + }), + lagrangeFirst: Honk.G1Point({ + x: uint256( + 0x0000000000000000000000000000000000000000000000000000000000000001 + ), + y: uint256( + 0x0000000000000000000000000000000000000000000000000000000000000002 + ) + }), + lagrangeLast: Honk.G1Point({ + x: uint256( + 0x22671e023cbbeb82d51b48eb911951a7a1f9c2bb913f3f42a56f272e205d4624 + ), + y: uint256( + 0x139bdfe9fe99633e5abe888702fa64a75c80d62184d0a16bd999840553954bb1 + ) + }) + }); + return vk; + } +} + +pragma solidity ^0.8.27; + +interface IVerifier { + function verify( + bytes calldata _proof, + bytes32[] calldata _publicInputs + ) external returns (bool); +} + +type Fr is uint256; + +using { add as + } for Fr global; +using { sub as - } for Fr global; +using { mul as * } for Fr global; + +using { exp as ^ } for Fr global; +using { notEqual as != } for Fr global; +using { equal as == } for Fr global; + +uint256 constant SUBGROUP_SIZE = 256; +uint256 constant MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617; // Prime field order +uint256 constant P = MODULUS; +Fr constant SUBGROUP_GENERATOR = Fr.wrap( + 0x07b0c561a6148404f086204a9f36ffb0617942546750f230c893619174a57a76 +); +Fr constant SUBGROUP_GENERATOR_INVERSE = Fr.wrap( + 0x204bd3277422fad364751ad938e2b5e6a54cf8c68712848a692c553d0329f5d6 +); +Fr constant MINUS_ONE = Fr.wrap(MODULUS - 1); +Fr constant ONE = Fr.wrap(1); +Fr constant ZERO = Fr.wrap(0); +// Instantiation + +library FrLib { + function from(uint256 value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(value % MODULUS); + } + } + + function fromBytes32(bytes32 value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(uint256(value) % MODULUS); + } + } + + function toBytes32(Fr value) internal pure returns (bytes32) { + unchecked { + return bytes32(Fr.unwrap(value)); + } + } + + function invert(Fr value) internal view returns (Fr) { + uint256 v = Fr.unwrap(value); + uint256 result; + + // Call the modexp precompile to invert in the field + assembly { + let free := mload(0x40) + mstore(free, 0x20) + mstore(add(free, 0x20), 0x20) + mstore(add(free, 0x40), 0x20) + mstore(add(free, 0x60), v) + mstore(add(free, 0x80), sub(MODULUS, 2)) + mstore(add(free, 0xa0), MODULUS) + let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) + if iszero(success) { + revert(0, 0) + } + result := mload(0x00) + mstore(0x40, add(free, 0x80)) + } + + return Fr.wrap(result); + } + + function pow(Fr base, uint256 v) internal view returns (Fr) { + uint256 b = Fr.unwrap(base); + uint256 result; + + // Call the modexp precompile to invert in the field + assembly { + let free := mload(0x40) + mstore(free, 0x20) + mstore(add(free, 0x20), 0x20) + mstore(add(free, 0x40), 0x20) + mstore(add(free, 0x60), b) + mstore(add(free, 0x80), v) + mstore(add(free, 0xa0), MODULUS) + let success := staticcall(gas(), 0x05, free, 0xc0, 0x00, 0x20) + if iszero(success) { + revert(0, 0) + } + result := mload(0x00) + mstore(0x40, add(free, 0x80)) + } + + return Fr.wrap(result); + } + + function div(Fr numerator, Fr denominator) internal view returns (Fr) { + unchecked { + return numerator * invert(denominator); + } + } + + function sqr(Fr value) internal pure returns (Fr) { + unchecked { + return value * value; + } + } + + function unwrap(Fr value) internal pure returns (uint256) { + unchecked { + return Fr.unwrap(value); + } + } + + function neg(Fr value) internal pure returns (Fr) { + unchecked { + return Fr.wrap(MODULUS - Fr.unwrap(value)); + } + } +} + +// Free functions +function add(Fr a, Fr b) pure returns (Fr) { + unchecked { + return Fr.wrap(addmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); + } +} + +function mul(Fr a, Fr b) pure returns (Fr) { + unchecked { + return Fr.wrap(mulmod(Fr.unwrap(a), Fr.unwrap(b), MODULUS)); + } +} + +function sub(Fr a, Fr b) pure returns (Fr) { + unchecked { + return Fr.wrap(addmod(Fr.unwrap(a), MODULUS - Fr.unwrap(b), MODULUS)); + } +} + +function exp(Fr base, Fr exponent) pure returns (Fr) { + if (Fr.unwrap(exponent) == 0) return Fr.wrap(1); + // Implement exponent with a loop as we will overflow otherwise + for (uint256 i = 1; i < Fr.unwrap(exponent); i += i) { + base = base * base; + } + return base; +} + +function notEqual(Fr a, Fr b) pure returns (bool) { + unchecked { + return Fr.unwrap(a) != Fr.unwrap(b); + } +} + +function equal(Fr a, Fr b) pure returns (bool) { + unchecked { + return Fr.unwrap(a) == Fr.unwrap(b); + } +} + +uint256 constant CONST_PROOF_SIZE_LOG_N = 28; + +uint256 constant NUMBER_OF_SUBRELATIONS = 28; +uint256 constant BATCHED_RELATION_PARTIAL_LENGTH = 8; +uint256 constant ZK_BATCHED_RELATION_PARTIAL_LENGTH = 9; +uint256 constant NUMBER_OF_ENTITIES = 41; +uint256 constant NUMBER_UNSHIFTED = 36; +uint256 constant NUMBER_TO_BE_SHIFTED = 5; +uint256 constant PAIRING_POINTS_SIZE = 16; + +uint256 constant FIELD_ELEMENT_SIZE = 0x20; +uint256 constant GROUP_ELEMENT_SIZE = 0x40; + +// Alphas are used as relation separators so there should be NUMBER_OF_SUBRELATIONS - 1 +uint256 constant NUMBER_OF_ALPHAS = NUMBER_OF_SUBRELATIONS - 1; + +// ENUM FOR WIRES +enum WIRE { + Q_M, + Q_C, + Q_L, + Q_R, + Q_O, + Q_4, + Q_LOOKUP, + Q_ARITH, + Q_RANGE, + Q_ELLIPTIC, + Q_MEMORY, + Q_NNF, + Q_POSEIDON2_EXTERNAL, + Q_POSEIDON2_INTERNAL, + SIGMA_1, + SIGMA_2, + SIGMA_3, + SIGMA_4, + ID_1, + ID_2, + ID_3, + ID_4, + TABLE_1, + TABLE_2, + TABLE_3, + TABLE_4, + LAGRANGE_FIRST, + LAGRANGE_LAST, + W_L, + W_R, + W_O, + W_4, + Z_PERM, + LOOKUP_INVERSES, + LOOKUP_READ_COUNTS, + LOOKUP_READ_TAGS, + W_L_SHIFT, + W_R_SHIFT, + W_O_SHIFT, + W_4_SHIFT, + Z_PERM_SHIFT +} + +library Honk { + struct G1Point { + uint256 x; + uint256 y; + } + + struct VerificationKey { + // Misc Params + uint256 circuitSize; + uint256 logCircuitSize; + uint256 publicInputsSize; + // Selectors + G1Point qm; + G1Point qc; + G1Point ql; + G1Point qr; + G1Point qo; + G1Point q4; + G1Point qLookup; // Lookup + G1Point qArith; // Arithmetic widget + G1Point qDeltaRange; // Delta Range sort + G1Point qMemory; // Memory + G1Point qNnf; // Non-native Field + G1Point qElliptic; // Auxillary + G1Point qPoseidon2External; + G1Point qPoseidon2Internal; + // Copy cnstraints + G1Point s1; + G1Point s2; + G1Point s3; + G1Point s4; + // Copy identity + G1Point id1; + G1Point id2; + G1Point id3; + G1Point id4; + // Precomputed lookup table + G1Point t1; + G1Point t2; + G1Point t3; + G1Point t4; + // Fixed first and last + G1Point lagrangeFirst; + G1Point lagrangeLast; + } + + struct RelationParameters { + // challenges + Fr eta; + Fr etaTwo; + Fr etaThree; + Fr beta; + Fr gamma; + // derived + Fr publicInputsDelta; + } + + struct Proof { + // Pairing point object + Fr[PAIRING_POINTS_SIZE] pairingPointObject; + // Free wires + G1Point w1; + G1Point w2; + G1Point w3; + G1Point w4; + // Lookup helpers - Permutations + G1Point zPerm; + // Lookup helpers - logup + G1Point lookupReadCounts; + G1Point lookupReadTags; + G1Point lookupInverses; + // Sumcheck + Fr[BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; + Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; + // Shplemini + G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; + Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; + G1Point shplonkQ; + G1Point kzgQuotient; + } + + struct ZKProof { + // Pairing point object + Fr[PAIRING_POINTS_SIZE] pairingPointObject; + // Commitments to wire polynomials + G1Point w1; + G1Point w2; + G1Point w3; + G1Point w4; + // Commitments to logup witness polynomials + G1Point lookupReadCounts; + G1Point lookupReadTags; + G1Point lookupInverses; + // Commitment to grand permutation polynomial + G1Point zPerm; + G1Point[3] libraCommitments; + // Sumcheck + Fr libraSum; + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; + Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; + Fr libraEvaluation; + // ZK + G1Point geminiMaskingPoly; + Fr geminiMaskingEval; + // Shplemini + G1Point[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; + Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; + Fr[4] libraPolyEvals; + G1Point shplonkQ; + G1Point kzgQuotient; + } +} + +// ZKTranscript library to generate fiat shamir challenges, the ZK transcript only differest +struct ZKTranscript { + // Oink + Honk.RelationParameters relationParameters; + Fr[NUMBER_OF_ALPHAS] alphas; + Fr[CONST_PROOF_SIZE_LOG_N] gateChallenges; + // Sumcheck + Fr libraChallenge; + Fr[CONST_PROOF_SIZE_LOG_N] sumCheckUChallenges; + // Shplemini + Fr rho; + Fr geminiR; + Fr shplonkNu; + Fr shplonkZ; + // Derived + Fr publicInputsDelta; +} + +library ZKTranscriptLib { + function generateTranscript( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize, + uint256 logN + ) external pure returns (ZKTranscript memory t) { + Fr previousChallenge; + ( + t.relationParameters, + previousChallenge + ) = generateRelationParametersChallenges( + proof, + publicInputs, + vkHash, + publicInputsSize, + previousChallenge + ); + + (t.alphas, previousChallenge) = generateAlphaChallenges( + previousChallenge, + proof + ); + + (t.gateChallenges, previousChallenge) = generateGateChallenges( + previousChallenge, + logN + ); + (t.libraChallenge, previousChallenge) = generateLibraChallenge( + previousChallenge, + proof + ); + (t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges( + proof, + previousChallenge, + logN + ); + + (t.rho, previousChallenge) = generateRhoChallenge( + proof, + previousChallenge + ); + + (t.geminiR, previousChallenge) = generateGeminiRChallenge( + proof, + previousChallenge, + logN + ); + + (t.shplonkNu, previousChallenge) = generateShplonkNuChallenge( + proof, + previousChallenge, + logN + ); + + (t.shplonkZ, previousChallenge) = generateShplonkZChallenge( + proof, + previousChallenge + ); + return t; + } + + function splitChallenge( + Fr challenge + ) internal pure returns (Fr first, Fr second) { + uint256 challengeU256 = uint256(Fr.unwrap(challenge)); + uint256 lo = challengeU256 & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + uint256 hi = challengeU256 >> 128; + first = FrLib.fromBytes32(bytes32(lo)); + second = FrLib.fromBytes32(bytes32(hi)); + } + + function generateRelationParametersChallenges( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize, + Fr previousChallenge + ) + internal + pure + returns (Honk.RelationParameters memory rp, Fr nextPreviousChallenge) + { + ( + rp.eta, + rp.etaTwo, + rp.etaThree, + previousChallenge + ) = generateEtaChallenge(proof, publicInputs, vkHash, publicInputsSize); + + ( + rp.beta, + rp.gamma, + nextPreviousChallenge + ) = generateBetaAndGammaChallenges(previousChallenge, proof); + } + + function generateEtaChallenge( + Honk.ZKProof memory proof, + bytes32[] calldata publicInputs, + uint256 vkHash, + uint256 publicInputsSize + ) + internal + pure + returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) + { + bytes32[] memory round0 = new bytes32[](1 + publicInputsSize + 6); + round0[0] = bytes32(vkHash); + + for (uint256 i = 0; i < publicInputsSize - PAIRING_POINTS_SIZE; i++) { + round0[1 + i] = bytes32(publicInputs[i]); + } + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + round0[1 + publicInputsSize - PAIRING_POINTS_SIZE + i] = FrLib + .toBytes32(proof.pairingPointObject[i]); + } + + // Create the first challenge + // Note: w4 is added to the challenge later on + round0[1 + publicInputsSize] = bytes32(proof.w1.x); + round0[1 + publicInputsSize + 1] = bytes32(proof.w1.y); + round0[1 + publicInputsSize + 2] = bytes32(proof.w2.x); + round0[1 + publicInputsSize + 3] = bytes32(proof.w2.y); + round0[1 + publicInputsSize + 4] = bytes32(proof.w3.x); + round0[1 + publicInputsSize + 5] = bytes32(proof.w3.y); + + previousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(round0)) + ); + (eta, etaTwo) = splitChallenge(previousChallenge); + previousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(Fr.unwrap(previousChallenge))) + ); + + (etaThree, ) = splitChallenge(previousChallenge); + } + + function generateBetaAndGammaChallenges( + Fr previousChallenge, + Honk.ZKProof memory proof + ) internal pure returns (Fr beta, Fr gamma, Fr nextPreviousChallenge) { + bytes32[7] memory round1; + round1[0] = FrLib.toBytes32(previousChallenge); + round1[1] = bytes32(proof.lookupReadCounts.x); + round1[2] = bytes32(proof.lookupReadCounts.y); + round1[3] = bytes32(proof.lookupReadTags.x); + round1[4] = bytes32(proof.lookupReadTags.y); + round1[5] = bytes32(proof.w4.x); + round1[6] = bytes32(proof.w4.y); + + nextPreviousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(round1)) + ); + (beta, gamma) = splitChallenge(nextPreviousChallenge); + } + + // Alpha challenges non-linearise the gate contributions + function generateAlphaChallenges( + Fr previousChallenge, + Honk.ZKProof memory proof + ) + internal + pure + returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge) + { + // Generate the original sumcheck alpha 0 by hashing zPerm and zLookup + uint256[5] memory alpha0; + alpha0[0] = Fr.unwrap(previousChallenge); + alpha0[1] = proof.lookupInverses.x; + alpha0[2] = proof.lookupInverses.y; + alpha0[3] = proof.zPerm.x; + alpha0[4] = proof.zPerm.y; + + nextPreviousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(alpha0)) + ); + Fr alpha; + (alpha, ) = splitChallenge(nextPreviousChallenge); + + // Compute powers of alpha for batching subrelations + alphas[0] = alpha; + for (uint256 i = 1; i < NUMBER_OF_ALPHAS; i++) { + alphas[i] = alphas[i - 1] * alpha; + } + } + + function generateGateChallenges( + Fr previousChallenge, + uint256 logN + ) + internal + pure + returns ( + Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, + Fr nextPreviousChallenge + ) + { + previousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(Fr.unwrap(previousChallenge))) + ); + (gateChallenges[0], ) = splitChallenge(previousChallenge); + for (uint256 i = 1; i < logN; i++) { + gateChallenges[i] = gateChallenges[i - 1] * gateChallenges[i - 1]; + } + nextPreviousChallenge = previousChallenge; + } + + function generateLibraChallenge( + Fr previousChallenge, + Honk.ZKProof memory proof + ) internal pure returns (Fr libraChallenge, Fr nextPreviousChallenge) { + // 2 comm, 1 sum, 1 challenge + uint256[4] memory challengeData; + challengeData[0] = Fr.unwrap(previousChallenge); + challengeData[1] = proof.libraCommitments[0].x; + challengeData[2] = proof.libraCommitments[0].y; + challengeData[3] = Fr.unwrap(proof.libraSum); + nextPreviousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(challengeData)) + ); + (libraChallenge, ) = splitChallenge(nextPreviousChallenge); + } + + function generateSumcheckChallenges( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) + internal + pure + returns ( + Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, + Fr nextPreviousChallenge + ) + { + for (uint256 i = 0; i < logN; i++) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal; + univariateChal[0] = prevChallenge; + + for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { + univariateChal[j + 1] = proof.sumcheckUnivariates[i][j]; + } + prevChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(univariateChal)) + ); + + (sumcheckChallenges[i], ) = splitChallenge(prevChallenge); + } + nextPreviousChallenge = prevChallenge; + } + + // We add Libra claimed eval + 3 comm + 1 more eval + function generateRhoChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge + ) internal pure returns (Fr rho, Fr nextPreviousChallenge) { + uint256[NUMBER_OF_ENTITIES + 9] memory rhoChallengeElements; + rhoChallengeElements[0] = Fr.unwrap(prevChallenge); + uint256 i; + for (i = 1; i <= NUMBER_OF_ENTITIES; i++) { + rhoChallengeElements[i] = Fr.unwrap( + proof.sumcheckEvaluations[i - 1] + ); + } + rhoChallengeElements[i] = Fr.unwrap(proof.libraEvaluation); + + i += 1; + rhoChallengeElements[i] = proof.libraCommitments[1].x; + rhoChallengeElements[i + 1] = proof.libraCommitments[1].y; + i += 2; + rhoChallengeElements[i] = proof.libraCommitments[2].x; + rhoChallengeElements[i + 1] = proof.libraCommitments[2].y; + i += 2; + rhoChallengeElements[i] = proof.geminiMaskingPoly.x; + rhoChallengeElements[i + 1] = proof.geminiMaskingPoly.y; + + i += 2; + rhoChallengeElements[i] = Fr.unwrap(proof.geminiMaskingEval); + + nextPreviousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(rhoChallengeElements)) + ); + (rho, ) = splitChallenge(nextPreviousChallenge); + } + + function generateGeminiRChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) internal pure returns (Fr geminiR, Fr nextPreviousChallenge) { + uint256[] memory gR = new uint256[]((logN - 1) * 2 + 1); + gR[0] = Fr.unwrap(prevChallenge); + + for (uint256 i = 0; i < logN - 1; i++) { + gR[1 + i * 2] = proof.geminiFoldComms[i].x; + gR[2 + i * 2] = proof.geminiFoldComms[i].y; + } + + nextPreviousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(gR)) + ); + + (geminiR, ) = splitChallenge(nextPreviousChallenge); + } + + function generateShplonkNuChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge, + uint256 logN + ) internal pure returns (Fr shplonkNu, Fr nextPreviousChallenge) { + uint256[] memory shplonkNuChallengeElements = new uint256[]( + logN + 1 + 4 + ); + shplonkNuChallengeElements[0] = Fr.unwrap(prevChallenge); + + for (uint256 i = 1; i <= logN; i++) { + shplonkNuChallengeElements[i] = Fr.unwrap( + proof.geminiAEvaluations[i - 1] + ); + } + + uint256 libraIdx = 0; + for (uint256 i = logN + 1; i <= logN + 4; i++) { + shplonkNuChallengeElements[i] = Fr.unwrap( + proof.libraPolyEvals[libraIdx] + ); + libraIdx++; + } + + nextPreviousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(shplonkNuChallengeElements)) + ); + (shplonkNu, ) = splitChallenge(nextPreviousChallenge); + } + + function generateShplonkZChallenge( + Honk.ZKProof memory proof, + Fr prevChallenge + ) internal pure returns (Fr shplonkZ, Fr nextPreviousChallenge) { + uint256[3] memory shplonkZChallengeElements; + shplonkZChallengeElements[0] = Fr.unwrap(prevChallenge); + + shplonkZChallengeElements[1] = proof.shplonkQ.x; + shplonkZChallengeElements[2] = proof.shplonkQ.y; + + nextPreviousChallenge = FrLib.fromBytes32( + keccak256(abi.encodePacked(shplonkZChallengeElements)) + ); + (shplonkZ, ) = splitChallenge(nextPreviousChallenge); + } + + function loadProof( + bytes calldata proof, + uint256 logN + ) internal pure returns (Honk.ZKProof memory p) { + uint256 boundary = 0x0; + + // Pairing point object + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + p.pairingPointObject[i] = bytesToFr( + proof[boundary:boundary + FIELD_ELEMENT_SIZE] + ); + boundary += FIELD_ELEMENT_SIZE; + } + // Commitments + p.w1 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.w2 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.w3 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + + // Lookup / Permutation Helper Commitments + p.lookupReadCounts = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + p.lookupReadTags = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + p.w4 = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.lookupInverses = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + p.zPerm = bytesToG1Point(proof[boundary:boundary + GROUP_ELEMENT_SIZE]); + boundary += GROUP_ELEMENT_SIZE; + p.libraCommitments[0] = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + + p.libraSum = bytesToFr(proof[boundary:boundary + FIELD_ELEMENT_SIZE]); + boundary += FIELD_ELEMENT_SIZE; + // Sumcheck univariates + for (uint256 i = 0; i < logN; i++) { + for (uint256 j = 0; j < ZK_BATCHED_RELATION_PARTIAL_LENGTH; j++) { + p.sumcheckUnivariates[i][j] = bytesToFr( + proof[boundary:boundary + FIELD_ELEMENT_SIZE] + ); + boundary += FIELD_ELEMENT_SIZE; + } + } + + // Sumcheck evaluations + for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { + p.sumcheckEvaluations[i] = bytesToFr( + proof[boundary:boundary + FIELD_ELEMENT_SIZE] + ); + boundary += FIELD_ELEMENT_SIZE; + } + + p.libraEvaluation = bytesToFr( + proof[boundary:boundary + FIELD_ELEMENT_SIZE] + ); + boundary += FIELD_ELEMENT_SIZE; + + p.libraCommitments[1] = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + p.libraCommitments[2] = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + p.geminiMaskingPoly = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + p.geminiMaskingEval = bytesToFr( + proof[boundary:boundary + FIELD_ELEMENT_SIZE] + ); + boundary += FIELD_ELEMENT_SIZE; + + // Gemini + // Read gemini fold univariates + for (uint256 i = 0; i < logN - 1; i++) { + p.geminiFoldComms[i] = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + } + + // Read gemini a evaluations + for (uint256 i = 0; i < logN; i++) { + p.geminiAEvaluations[i] = bytesToFr( + proof[boundary:boundary + FIELD_ELEMENT_SIZE] + ); + boundary += FIELD_ELEMENT_SIZE; + } + + for (uint256 i = 0; i < 4; i++) { + p.libraPolyEvals[i] = bytesToFr( + proof[boundary:boundary + FIELD_ELEMENT_SIZE] + ); + boundary += FIELD_ELEMENT_SIZE; + } + + // Shplonk + p.shplonkQ = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + boundary += GROUP_ELEMENT_SIZE; + // KZG + p.kzgQuotient = bytesToG1Point( + proof[boundary:boundary + GROUP_ELEMENT_SIZE] + ); + } +} + +// Field arithmetic libraries + +library RelationsLib { + Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17) + + function accumulateRelationEvaluations( + Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_ALPHAS] memory alphas, + Fr powPartialEval + ) internal pure returns (Fr accumulator) { + Fr[NUMBER_OF_SUBRELATIONS] memory evaluations; + + // Accumulate all relations in Ultra Honk - each with varying number of subrelations + accumulateArithmeticRelation( + purportedEvaluations, + evaluations, + powPartialEval + ); + accumulatePermutationRelation( + purportedEvaluations, + rp, + evaluations, + powPartialEval + ); + accumulateLogDerivativeLookupRelation( + purportedEvaluations, + rp, + evaluations, + powPartialEval + ); + accumulateDeltaRangeRelation( + purportedEvaluations, + evaluations, + powPartialEval + ); + accumulateEllipticRelation( + purportedEvaluations, + evaluations, + powPartialEval + ); + accumulateMemoryRelation( + purportedEvaluations, + rp, + evaluations, + powPartialEval + ); + accumulateNnfRelation( + purportedEvaluations, + evaluations, + powPartialEval + ); + accumulatePoseidonExternalRelation( + purportedEvaluations, + evaluations, + powPartialEval + ); + accumulatePoseidonInternalRelation( + purportedEvaluations, + evaluations, + powPartialEval + ); + + // batch the subrelations with the alpha challenges to obtain the full honk relation + accumulator = scaleAndBatchSubrelations(evaluations, alphas); + } + + /** + * Aesthetic helper function that is used to index by enum into proof.sumcheckEvaluations, it avoids + * the relation checking code being cluttered with uint256 type casting, which is often a different colour in code + * editors, and thus is noisy. + */ + function wire( + Fr[NUMBER_OF_ENTITIES] memory p, + WIRE _wire + ) internal pure returns (Fr) { + return p[uint256(_wire)]; + } + + uint256 internal constant NEG_HALF_MODULO_P = + 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; + /** + * Ultra Arithmetic Relation + * + */ + + function accumulateArithmeticRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + // Relation 0 + Fr q_arith = wire(p, WIRE.Q_ARITH); + { + Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P); + + Fr accum = (q_arith - Fr.wrap(3)) * + (wire(p, WIRE.Q_M) * wire(p, WIRE.W_R) * wire(p, WIRE.W_L)) * + neg_half; + accum = + accum + + (wire(p, WIRE.Q_L) * wire(p, WIRE.W_L)) + + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_R)) + + (wire(p, WIRE.Q_O) * wire(p, WIRE.W_O)) + + (wire(p, WIRE.Q_4) * wire(p, WIRE.W_4)) + + wire(p, WIRE.Q_C); + accum = accum + (q_arith - ONE) * wire(p, WIRE.W_4_SHIFT); + accum = accum * q_arith; + accum = accum * domainSep; + evals[0] = accum; + } + + // Relation 1 + { + Fr accum = wire(p, WIRE.W_L) + + wire(p, WIRE.W_4) - + wire(p, WIRE.W_L_SHIFT) + + wire(p, WIRE.Q_M); + accum = accum * (q_arith - Fr.wrap(2)); + accum = accum * (q_arith - ONE); + accum = accum * q_arith; + accum = accum * domainSep; + evals[1] = accum; + } + } + + function accumulatePermutationRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr grand_product_numerator; + Fr grand_product_denominator; + + { + Fr num = wire(p, WIRE.W_L) + + wire(p, WIRE.ID_1) * + rp.beta + + rp.gamma; + num = + num * + (wire(p, WIRE.W_R) + wire(p, WIRE.ID_2) * rp.beta + rp.gamma); + num = + num * + (wire(p, WIRE.W_O) + wire(p, WIRE.ID_3) * rp.beta + rp.gamma); + num = + num * + (wire(p, WIRE.W_4) + wire(p, WIRE.ID_4) * rp.beta + rp.gamma); + + grand_product_numerator = num; + } + { + Fr den = wire(p, WIRE.W_L) + + wire(p, WIRE.SIGMA_1) * + rp.beta + + rp.gamma; + den = + den * + (wire(p, WIRE.W_R) + + wire(p, WIRE.SIGMA_2) * + rp.beta + + rp.gamma); + den = + den * + (wire(p, WIRE.W_O) + + wire(p, WIRE.SIGMA_3) * + rp.beta + + rp.gamma); + den = + den * + (wire(p, WIRE.W_4) + + wire(p, WIRE.SIGMA_4) * + rp.beta + + rp.gamma); + + grand_product_denominator = den; + } + + // Contribution 2 + { + Fr acc = (wire(p, WIRE.Z_PERM) + wire(p, WIRE.LAGRANGE_FIRST)) * + grand_product_numerator; + + acc = + acc - + ((wire(p, WIRE.Z_PERM_SHIFT) + + (wire(p, WIRE.LAGRANGE_LAST) * rp.publicInputsDelta)) * + grand_product_denominator); + acc = acc * domainSep; + evals[2] = acc; + } + + // Contribution 3 + { + Fr acc = (wire(p, WIRE.LAGRANGE_LAST) * + wire(p, WIRE.Z_PERM_SHIFT)) * domainSep; + evals[3] = acc; + } + } + + function accumulateLogDerivativeLookupRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr write_term; + Fr read_term; + + // Calculate the write term (the table accumulation) + { + write_term = + wire(p, WIRE.TABLE_1) + + rp.gamma + + (wire(p, WIRE.TABLE_2) * rp.eta) + + (wire(p, WIRE.TABLE_3) * rp.etaTwo) + + (wire(p, WIRE.TABLE_4) * rp.etaThree); + } + + // Calculate the write term + { + Fr derived_entry_1 = wire(p, WIRE.W_L) + + rp.gamma + + (wire(p, WIRE.Q_R) * wire(p, WIRE.W_L_SHIFT)); + Fr derived_entry_2 = wire(p, WIRE.W_R) + + wire(p, WIRE.Q_M) * + wire(p, WIRE.W_R_SHIFT); + Fr derived_entry_3 = wire(p, WIRE.W_O) + + wire(p, WIRE.Q_C) * + wire(p, WIRE.W_O_SHIFT); + + read_term = + derived_entry_1 + + (derived_entry_2 * rp.eta) + + (derived_entry_3 * rp.etaTwo) + + (wire(p, WIRE.Q_O) * rp.etaThree); + } + + Fr read_inverse = wire(p, WIRE.LOOKUP_INVERSES) * write_term; + Fr write_inverse = wire(p, WIRE.LOOKUP_INVERSES) * read_term; + + Fr inverse_exists_xor = wire(p, WIRE.LOOKUP_READ_TAGS) + + wire(p, WIRE.Q_LOOKUP) - + (wire(p, WIRE.LOOKUP_READ_TAGS) * wire(p, WIRE.Q_LOOKUP)); + + // Inverse calculated correctly relation + Fr accumulatorNone = read_term * + write_term * + wire(p, WIRE.LOOKUP_INVERSES) - + inverse_exists_xor; + accumulatorNone = accumulatorNone * domainSep; + + // Inverse + Fr accumulatorOne = wire(p, WIRE.Q_LOOKUP) * + read_inverse - + wire(p, WIRE.LOOKUP_READ_COUNTS) * + write_inverse; + + Fr read_tag = wire(p, WIRE.LOOKUP_READ_TAGS); + + Fr read_tag_boolean_relation = read_tag * read_tag - read_tag; + + evals[4] = accumulatorNone; + evals[5] = accumulatorOne; + evals[6] = read_tag_boolean_relation * domainSep; + } + + function accumulateDeltaRangeRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + Fr minus_one = ZERO - ONE; + Fr minus_two = ZERO - Fr.wrap(2); + Fr minus_three = ZERO - Fr.wrap(3); + + // Compute wire differences + Fr delta_1 = wire(p, WIRE.W_R) - wire(p, WIRE.W_L); + Fr delta_2 = wire(p, WIRE.W_O) - wire(p, WIRE.W_R); + Fr delta_3 = wire(p, WIRE.W_4) - wire(p, WIRE.W_O); + Fr delta_4 = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_4); + + // Contribution 6 + { + Fr acc = delta_1; + acc = acc * (delta_1 + minus_one); + acc = acc * (delta_1 + minus_two); + acc = acc * (delta_1 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[7] = acc; + } + + // Contribution 7 + { + Fr acc = delta_2; + acc = acc * (delta_2 + minus_one); + acc = acc * (delta_2 + minus_two); + acc = acc * (delta_2 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[8] = acc; + } + + // Contribution 8 + { + Fr acc = delta_3; + acc = acc * (delta_3 + minus_one); + acc = acc * (delta_3 + minus_two); + acc = acc * (delta_3 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[9] = acc; + } + + // Contribution 9 + { + Fr acc = delta_4; + acc = acc * (delta_4 + minus_one); + acc = acc * (delta_4 + minus_two); + acc = acc * (delta_4 + minus_three); + acc = acc * wire(p, WIRE.Q_RANGE); + acc = acc * domainSep; + evals[10] = acc; + } + } + + struct EllipticParams { + // Points + Fr x_1; + Fr y_1; + Fr x_2; + Fr y_2; + Fr y_3; + Fr x_3; + // push accumulators into memory + Fr x_double_identity; + } + + function accumulateEllipticRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + EllipticParams memory ep; + ep.x_1 = wire(p, WIRE.W_R); + ep.y_1 = wire(p, WIRE.W_O); + + ep.x_2 = wire(p, WIRE.W_L_SHIFT); + ep.y_2 = wire(p, WIRE.W_4_SHIFT); + ep.y_3 = wire(p, WIRE.W_O_SHIFT); + ep.x_3 = wire(p, WIRE.W_R_SHIFT); + + Fr q_sign = wire(p, WIRE.Q_L); + Fr q_is_double = wire(p, WIRE.Q_M); + + // Contribution 10 point addition, x-coordinate check + // q_elliptic * (x3 + x2 + x1)(x2 - x1)(x2 - x1) - y2^2 - y1^2 + 2(y2y1)*q_sign = 0 + Fr x_diff = (ep.x_2 - ep.x_1); + Fr y1_sqr = (ep.y_1 * ep.y_1); + { + // Move to top + Fr partialEval = domainSep; + + Fr y2_sqr = (ep.y_2 * ep.y_2); + Fr y1y2 = ep.y_1 * ep.y_2 * q_sign; + Fr x_add_identity = (ep.x_3 + ep.x_2 + ep.x_1); + x_add_identity = x_add_identity * x_diff * x_diff; + x_add_identity = x_add_identity - y2_sqr - y1_sqr + y1y2 + y1y2; + + evals[11] = + x_add_identity * + partialEval * + wire(p, WIRE.Q_ELLIPTIC) * + (ONE - q_is_double); + } + + // Contribution 11 point addition, x-coordinate check + // q_elliptic * (q_sign * y1 + y3)(x2 - x1) + (x3 - x1)(y2 - q_sign * y1) = 0 + { + Fr y1_plus_y3 = ep.y_1 + ep.y_3; + Fr y_diff = ep.y_2 * q_sign - ep.y_1; + Fr y_add_identity = y1_plus_y3 * + x_diff + + (ep.x_3 - ep.x_1) * + y_diff; + evals[12] = + y_add_identity * + domainSep * + wire(p, WIRE.Q_ELLIPTIC) * + (ONE - q_is_double); + } + + // Contribution 10 point doubling, x-coordinate check + // (x3 + x1 + x1) (4y1*y1) - 9 * x1 * x1 * x1 * x1 = 0 + // N.B. we're using the equivalence x1*x1*x1 === y1*y1 - curve_b to reduce degree by 1 + { + Fr x_pow_4 = (y1_sqr + GRUMPKIN_CURVE_B_PARAMETER_NEGATED) * ep.x_1; + Fr y1_sqr_mul_4 = y1_sqr + y1_sqr; + y1_sqr_mul_4 = y1_sqr_mul_4 + y1_sqr_mul_4; + Fr x1_pow_4_mul_9 = x_pow_4 * Fr.wrap(9); + + // NOTE: pushed into memory (stack >:'( ) + ep.x_double_identity = + (ep.x_3 + ep.x_1 + ep.x_1) * + y1_sqr_mul_4 - + x1_pow_4_mul_9; + + Fr acc = ep.x_double_identity * + domainSep * + wire(p, WIRE.Q_ELLIPTIC) * + q_is_double; + evals[11] = evals[11] + acc; + } + + // Contribution 11 point doubling, y-coordinate check + // (y1 + y1) (2y1) - (3 * x1 * x1)(x1 - x3) = 0 + { + Fr x1_sqr_mul_3 = (ep.x_1 + ep.x_1 + ep.x_1) * ep.x_1; + Fr y_double_identity = x1_sqr_mul_3 * + (ep.x_1 - ep.x_3) - + (ep.y_1 + ep.y_1) * + (ep.y_1 + ep.y_3); + evals[12] = + evals[12] + + y_double_identity * + domainSep * + wire(p, WIRE.Q_ELLIPTIC) * + q_is_double; + } + } + + // Parameters used within the Memory Relation + // A struct is used to work around stack too deep. This relation has alot of variables + struct MemParams { + Fr memory_record_check; + Fr partial_record_check; + Fr next_gate_access_type; + Fr record_delta; + Fr index_delta; + Fr adjacent_values_match_if_adjacent_indices_match; + Fr adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation; + Fr access_check; + Fr next_gate_access_type_is_boolean; + Fr ROM_consistency_check_identity; + Fr RAM_consistency_check_identity; + Fr timestamp_delta; + Fr RAM_timestamp_check_identity; + Fr memory_identity; + Fr index_is_monotonically_increasing; + } + + function accumulateMemoryRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Honk.RelationParameters memory rp, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + MemParams memory ap; + + /** + * MEMORY + * + * A RAM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * t: `timestamp` of memory cell being accessed (used for RAM, set to 0 for ROM) + * * v: `value` of memory cell being accessed + * * a: `access` type of record. read: 0 = read, 1 = write + * * r: `record` of memory cell. record = access + index * eta + timestamp * eta_two + value * eta_three + * + * A ROM memory record contains a tuple of the following fields: + * * i: `index` of memory cell being accessed + * * v: `value1` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * v2:`value2` of memory cell being accessed (ROM tables can store up to 2 values per index) + * * r: `record` of memory cell. record = index * eta + value2 * eta_two + value1 * eta_three + * + * When performing a read/write access, the values of i, t, v, v2, a, r are stored in the following wires + + * selectors, depending on whether the gate is a RAM read/write or a ROM read + * + * | gate type | i | v2/t | v | a | r | + * | --------- | -- | ----- | -- | -- | -- | + * | ROM | w1 | w2 | w3 | -- | w4 | + * | RAM | w1 | w2 | w3 | qc | w4 | + * + * (for accesses where `index` is a circuit constant, it is assumed the circuit will apply a copy constraint on + * `w2` to fix its value) + * + * + */ + + /** + * Memory Record Check + * Partial degree: 1 + * Total degree: 4 + * + * A ROM/ROM access gate can be evaluated with the identity: + * + * qc + w1 \eta + w2 \eta_two + w3 \eta_three - w4 = 0 + * + * For ROM gates, qc = 0 + */ + ap.memory_record_check = wire(p, WIRE.W_O) * rp.etaThree; + ap.memory_record_check = + ap.memory_record_check + + (wire(p, WIRE.W_R) * rp.etaTwo); + ap.memory_record_check = + ap.memory_record_check + + (wire(p, WIRE.W_L) * rp.eta); + ap.memory_record_check = ap.memory_record_check + wire(p, WIRE.Q_C); + ap.partial_record_check = ap.memory_record_check; // used in RAM consistency check; deg 1 or 4 + ap.memory_record_check = ap.memory_record_check - wire(p, WIRE.W_4); + + /** + * Contribution 13 & 14 + * ROM Consistency Check + * Partial degree: 1 + * Total degree: 4 + * + * For every ROM read, a set equivalence check is applied between the record witnesses, and a second set of + * records that are sorted. + * + * We apply the following checks for the sorted records: + * + * 1. w1, w2, w3 correctly map to 'index', 'v1, 'v2' for a given record value at w4 + * 2. index values for adjacent records are monotonically increasing + * 3. if, at gate i, index_i == index_{i + 1}, then value1_i == value1_{i + 1} and value2_i == value2_{i + 1} + * + */ + ap.index_delta = wire(p, WIRE.W_L_SHIFT) - wire(p, WIRE.W_L); + ap.record_delta = wire(p, WIRE.W_4_SHIFT) - wire(p, WIRE.W_4); + + ap.index_is_monotonically_increasing = + ap.index_delta * + (ap.index_delta - Fr.wrap(1)); // deg 2 + + ap.adjacent_values_match_if_adjacent_indices_match = + (ap.index_delta * MINUS_ONE + ONE) * + ap.record_delta; // deg 2 + + evals[14] = + ap.adjacent_values_match_if_adjacent_indices_match * + (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 + evals[15] = + ap.index_is_monotonically_increasing * + (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 + + ap.ROM_consistency_check_identity = + ap.memory_record_check * + (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)); // deg 3 or 7 + + /** + * Contributions 15,16,17 + * RAM Consistency Check + * + * The 'access' type of the record is extracted with the expression `w_4 - ap.partial_record_check` + * (i.e. for an honest Prover `w1 * eta + w2 * eta^2 + w3 * eta^3 - w4 = access`. + * This is validated by requiring `access` to be boolean + * + * For two adjacent entries in the sorted list if _both_ + * A) index values match + * B) adjacent access value is 0 (i.e. next gate is a READ) + * then + * C) both values must match. + * The gate boolean check is + * (A && B) => C === !(A && B) || C === !A || !B || C + * + * N.B. it is the responsibility of the circuit writer to ensure that every RAM cell is initialized + * with a WRITE operation. + */ + Fr access_type = (wire(p, WIRE.W_4) - ap.partial_record_check); // will be 0 or 1 for honest Prover; deg 1 or 4 + ap.access_check = access_type * (access_type - Fr.wrap(1)); // check value is 0 or 1; deg 2 or 8 + + // reverse order we could re-use `ap.partial_record_check` 1 - ((w3' * eta + w2') * eta + w1') * eta + // deg 1 or 4 + ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * rp.etaThree; + ap.next_gate_access_type = + ap.next_gate_access_type + + (wire(p, WIRE.W_R_SHIFT) * rp.etaTwo); + ap.next_gate_access_type = + ap.next_gate_access_type + + (wire(p, WIRE.W_L_SHIFT) * rp.eta); + ap.next_gate_access_type = + wire(p, WIRE.W_4_SHIFT) - + ap.next_gate_access_type; + + Fr value_delta = wire(p, WIRE.W_O_SHIFT) - wire(p, WIRE.W_O); + ap + .adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = + (ap.index_delta * MINUS_ONE + ONE) * + value_delta * + (ap.next_gate_access_type * MINUS_ONE + ONE); // deg 3 or 6 + + // We can't apply the RAM consistency check identity on the final entry in the sorted list (the wires in the + // next gate would make the identity fail). We need to validate that its 'access type' bool is correct. Can't + // do with an arithmetic gate because of the `eta` factors. We need to check that the *next* gate's access + // type is correct, to cover this edge case + // deg 2 or 4 + ap.next_gate_access_type_is_boolean = + ap.next_gate_access_type * + ap.next_gate_access_type - + ap.next_gate_access_type; + + // Putting it all together... + evals[16] = + ap + .adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation * + (wire(p, WIRE.Q_O)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 5 or 8 + evals[17] = + ap.index_is_monotonically_increasing * + (wire(p, WIRE.Q_O)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 + evals[18] = + ap.next_gate_access_type_is_boolean * + (wire(p, WIRE.Q_O)) * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 6 + + ap.RAM_consistency_check_identity = + ap.access_check * + (wire(p, WIRE.Q_O)); // deg 3 or 9 + + /** + * RAM Timestamp Consistency Check + * + * | w1 | w2 | w3 | w4 | + * | index | timestamp | timestamp_check | -- | + * + * Let delta_index = index_{i + 1} - index_{i} + * + * Iff delta_index == 0, timestamp_check = timestamp_{i + 1} - timestamp_i + * Else timestamp_check = 0 + */ + ap.timestamp_delta = wire(p, WIRE.W_R_SHIFT) - wire(p, WIRE.W_R); + ap.RAM_timestamp_check_identity = + (ap.index_delta * MINUS_ONE + ONE) * + ap.timestamp_delta - + wire(p, WIRE.W_O); // deg 3 + + /** + * Complete Contribution 12 + * The complete RAM/ROM memory identity + * Partial degree: + */ + ap.memory_identity = ap.ROM_consistency_check_identity; // deg 3 or 6 + ap.memory_identity = + ap.memory_identity + + ap.RAM_timestamp_check_identity * + (wire(p, WIRE.Q_4) * wire(p, WIRE.Q_L)); // deg 4 + ap.memory_identity = + ap.memory_identity + + ap.memory_record_check * + (wire(p, WIRE.Q_M) * wire(p, WIRE.Q_L)); // deg 3 or 6 + ap.memory_identity = + ap.memory_identity + + ap.RAM_consistency_check_identity; // deg 3 or 9 + + // (deg 3 or 9) + (deg 4) + (deg 3) + ap.memory_identity = + ap.memory_identity * + (wire(p, WIRE.Q_MEMORY) * domainSep); // deg 4 or 10 + evals[13] = ap.memory_identity; + } + + // Constants for the Non-native Field relation + Fr constant LIMB_SIZE = Fr.wrap(uint256(1) << 68); + Fr constant SUBLIMB_SHIFT = Fr.wrap(uint256(1) << 14); + + // Parameters used within the Non-Native Field Relation + // A struct is used to work around stack too deep. This relation has alot of variables + struct NnfParams { + Fr limb_subproduct; + Fr non_native_field_gate_1; + Fr non_native_field_gate_2; + Fr non_native_field_gate_3; + Fr limb_accumulator_1; + Fr limb_accumulator_2; + Fr nnf_identity; + } + + function accumulateNnfRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + NnfParams memory ap; + + /** + * Contribution 12 + * Non native field arithmetic gate 2 + * deg 4 + * + * _ _ + * / _ _ _ 14 \ + * q_2 . q_4 | (w_1 . w_2) + (w_1 . w_2) + (w_1 . w_4 + w_2 . w_3 - w_3) . 2 - w_3 - w_4 | + * \_ _/ + * + * + */ + ap.limb_subproduct = + wire(p, WIRE.W_L) * + wire(p, WIRE.W_R_SHIFT) + + wire(p, WIRE.W_L_SHIFT) * + wire(p, WIRE.W_R); + ap.non_native_field_gate_2 = (wire(p, WIRE.W_L) * + wire(p, WIRE.W_4) + + wire(p, WIRE.W_R) * + wire(p, WIRE.W_O) - + wire(p, WIRE.W_O_SHIFT)); + ap.non_native_field_gate_2 = ap.non_native_field_gate_2 * LIMB_SIZE; + ap.non_native_field_gate_2 = + ap.non_native_field_gate_2 - + wire(p, WIRE.W_4_SHIFT); + ap.non_native_field_gate_2 = + ap.non_native_field_gate_2 + + ap.limb_subproduct; + ap.non_native_field_gate_2 = + ap.non_native_field_gate_2 * + wire(p, WIRE.Q_4); + + ap.limb_subproduct = ap.limb_subproduct * LIMB_SIZE; + ap.limb_subproduct = + ap.limb_subproduct + + (wire(p, WIRE.W_L_SHIFT) * wire(p, WIRE.W_R_SHIFT)); + ap.non_native_field_gate_1 = ap.limb_subproduct; + ap.non_native_field_gate_1 = + ap.non_native_field_gate_1 - + (wire(p, WIRE.W_O) + wire(p, WIRE.W_4)); + ap.non_native_field_gate_1 = + ap.non_native_field_gate_1 * + wire(p, WIRE.Q_O); + + ap.non_native_field_gate_3 = ap.limb_subproduct; + ap.non_native_field_gate_3 = + ap.non_native_field_gate_3 + + wire(p, WIRE.W_4); + ap.non_native_field_gate_3 = + ap.non_native_field_gate_3 - + (wire(p, WIRE.W_O_SHIFT) + wire(p, WIRE.W_4_SHIFT)); + ap.non_native_field_gate_3 = + ap.non_native_field_gate_3 * + wire(p, WIRE.Q_M); + + Fr non_native_field_identity = ap.non_native_field_gate_1 + + ap.non_native_field_gate_2 + + ap.non_native_field_gate_3; + non_native_field_identity = + non_native_field_identity * + wire(p, WIRE.Q_R); + + // ((((w2' * 2^14 + w1') * 2^14 + w3) * 2^14 + w2) * 2^14 + w1 - w4) * qm + // deg 2 + ap.limb_accumulator_1 = wire(p, WIRE.W_R_SHIFT) * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L_SHIFT); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_O); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_R); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * SUBLIMB_SHIFT; + ap.limb_accumulator_1 = ap.limb_accumulator_1 + wire(p, WIRE.W_L); + ap.limb_accumulator_1 = ap.limb_accumulator_1 - wire(p, WIRE.W_4); + ap.limb_accumulator_1 = ap.limb_accumulator_1 * wire(p, WIRE.Q_4); + + // ((((w3' * 2^14 + w2') * 2^14 + w1') * 2^14 + w4) * 2^14 + w3 - w4') * qm + // deg 2 + ap.limb_accumulator_2 = wire(p, WIRE.W_O_SHIFT) * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_R_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_L_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_4); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * SUBLIMB_SHIFT; + ap.limb_accumulator_2 = ap.limb_accumulator_2 + wire(p, WIRE.W_O); + ap.limb_accumulator_2 = ap.limb_accumulator_2 - wire(p, WIRE.W_4_SHIFT); + ap.limb_accumulator_2 = ap.limb_accumulator_2 * wire(p, WIRE.Q_M); + + Fr limb_accumulator_identity = ap.limb_accumulator_1 + + ap.limb_accumulator_2; + limb_accumulator_identity = + limb_accumulator_identity * + wire(p, WIRE.Q_O); // deg 3 + + ap.nnf_identity = non_native_field_identity + limb_accumulator_identity; + ap.nnf_identity = ap.nnf_identity * (wire(p, WIRE.Q_NNF) * domainSep); + evals[19] = ap.nnf_identity; + } + + struct PoseidonExternalParams { + Fr s1; + Fr s2; + Fr s3; + Fr s4; + Fr u1; + Fr u2; + Fr u3; + Fr u4; + Fr t0; + Fr t1; + Fr t2; + Fr t3; + Fr v1; + Fr v2; + Fr v3; + Fr v4; + Fr q_pos_by_scaling; + } + + function accumulatePoseidonExternalRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + PoseidonExternalParams memory ep; + + ep.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); + ep.s2 = wire(p, WIRE.W_R) + wire(p, WIRE.Q_R); + ep.s3 = wire(p, WIRE.W_O) + wire(p, WIRE.Q_O); + ep.s4 = wire(p, WIRE.W_4) + wire(p, WIRE.Q_4); + + ep.u1 = ep.s1 * ep.s1 * ep.s1 * ep.s1 * ep.s1; + ep.u2 = ep.s2 * ep.s2 * ep.s2 * ep.s2 * ep.s2; + ep.u3 = ep.s3 * ep.s3 * ep.s3 * ep.s3 * ep.s3; + ep.u4 = ep.s4 * ep.s4 * ep.s4 * ep.s4 * ep.s4; + // matrix mul v = M_E * u with 14 additions + ep.t0 = ep.u1 + ep.u2; // u_1 + u_2 + ep.t1 = ep.u3 + ep.u4; // u_3 + u_4 + ep.t2 = ep.u2 + ep.u2 + ep.t1; // 2u_2 + // ep.t2 += ep.t1; // 2u_2 + u_3 + u_4 + ep.t3 = ep.u4 + ep.u4 + ep.t0; // 2u_4 + // ep.t3 += ep.t0; // u_1 + u_2 + 2u_4 + ep.v4 = ep.t1 + ep.t1; + ep.v4 = ep.v4 + ep.v4 + ep.t3; + // ep.v4 += ep.t3; // u_1 + u_2 + 4u_3 + 6u_4 + ep.v2 = ep.t0 + ep.t0; + ep.v2 = ep.v2 + ep.v2 + ep.t2; + // ep.v2 += ep.t2; // 4u_1 + 6u_2 + u_3 + u_4 + ep.v1 = ep.t3 + ep.v2; // 5u_1 + 7u_2 + u_3 + 3u_4 + ep.v3 = ep.t2 + ep.v4; // u_1 + 3u_2 + 5u_3 + 7u_4 + + ep.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_EXTERNAL) * domainSep; + evals[20] = + evals[20] + + ep.q_pos_by_scaling * + (ep.v1 - wire(p, WIRE.W_L_SHIFT)); + + evals[21] = + evals[21] + + ep.q_pos_by_scaling * + (ep.v2 - wire(p, WIRE.W_R_SHIFT)); + + evals[22] = + evals[22] + + ep.q_pos_by_scaling * + (ep.v3 - wire(p, WIRE.W_O_SHIFT)); + + evals[23] = + evals[23] + + ep.q_pos_by_scaling * + (ep.v4 - wire(p, WIRE.W_4_SHIFT)); + } + + struct PoseidonInternalParams { + Fr u1; + Fr u2; + Fr u3; + Fr u4; + Fr u_sum; + Fr v1; + Fr v2; + Fr v3; + Fr v4; + Fr s1; + Fr q_pos_by_scaling; + } + + function accumulatePoseidonInternalRelation( + Fr[NUMBER_OF_ENTITIES] memory p, + Fr[NUMBER_OF_SUBRELATIONS] memory evals, + Fr domainSep + ) internal pure { + PoseidonInternalParams memory ip; + + Fr[4] memory INTERNAL_MATRIX_DIAGONAL = [ + FrLib.from( + 0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7 + ), + FrLib.from( + 0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b + ), + FrLib.from( + 0x00544b8338791518b2c7645a50392798b21f75bb60e3596170067d00141cac15 + ), + FrLib.from( + 0x222c01175718386f2e2e82eb122789e352e105a3b8fa852613bc534433ee428b + ) + ]; + + // add round constants + ip.s1 = wire(p, WIRE.W_L) + wire(p, WIRE.Q_L); + + // apply s-box round + ip.u1 = ip.s1 * ip.s1 * ip.s1 * ip.s1 * ip.s1; + ip.u2 = wire(p, WIRE.W_R); + ip.u3 = wire(p, WIRE.W_O); + ip.u4 = wire(p, WIRE.W_4); + + // matrix mul with v = M_I * u 4 muls and 7 additions + ip.u_sum = ip.u1 + ip.u2 + ip.u3 + ip.u4; + + ip.q_pos_by_scaling = wire(p, WIRE.Q_POSEIDON2_INTERNAL) * domainSep; + + ip.v1 = ip.u1 * INTERNAL_MATRIX_DIAGONAL[0] + ip.u_sum; + evals[24] = + evals[24] + + ip.q_pos_by_scaling * + (ip.v1 - wire(p, WIRE.W_L_SHIFT)); + + ip.v2 = ip.u2 * INTERNAL_MATRIX_DIAGONAL[1] + ip.u_sum; + evals[25] = + evals[25] + + ip.q_pos_by_scaling * + (ip.v2 - wire(p, WIRE.W_R_SHIFT)); + + ip.v3 = ip.u3 * INTERNAL_MATRIX_DIAGONAL[2] + ip.u_sum; + evals[26] = + evals[26] + + ip.q_pos_by_scaling * + (ip.v3 - wire(p, WIRE.W_O_SHIFT)); + + ip.v4 = ip.u4 * INTERNAL_MATRIX_DIAGONAL[3] + ip.u_sum; + evals[27] = + evals[27] + + ip.q_pos_by_scaling * + (ip.v4 - wire(p, WIRE.W_4_SHIFT)); + } + + function scaleAndBatchSubrelations( + Fr[NUMBER_OF_SUBRELATIONS] memory evaluations, + Fr[NUMBER_OF_ALPHAS] memory subrelationChallenges + ) internal pure returns (Fr accumulator) { + accumulator = evaluations[0]; + + for (uint256 i = 1; i < NUMBER_OF_SUBRELATIONS; ++i) { + accumulator = + accumulator + + evaluations[i] * + subrelationChallenges[i - 1]; + } + } +} + +// Field arithmetic libraries - prevent littering the code with modmul / addmul + +library CommitmentSchemeLib { + using FrLib for Fr; + + // Avoid stack too deep + struct ShpleminiIntermediates { + Fr unshiftedScalar; + Fr shiftedScalar; + Fr unshiftedScalarNeg; + Fr shiftedScalarNeg; + // Scalar to be multiplied by [1]₁ + Fr constantTermAccumulator; + // Accumulator for powers of rho + Fr batchingChallenge; + // Linear combination of multilinear (sumcheck) evaluations and powers of rho + Fr batchedEvaluation; + Fr[4] denominators; + Fr[4] batchingScalars; + // 1/(z - r^{2^i}) for i = 0, ..., logSize, dynamically updated + Fr posInvertedDenominator; + // 1/(z + r^{2^i}) for i = 0, ..., logSize, dynamically updated + Fr negInvertedDenominator; + // ν^{2i} * 1/(z - r^{2^i}) + Fr scalingFactorPos; + // ν^{2i+1} * 1/(z + r^{2^i}) + Fr scalingFactorNeg; + // Fold_i(r^{2^i}) reconstructed by Verifier + Fr[] foldPosEvaluations; + } + + function computeSquares( + Fr r, + uint256 logN + ) internal pure returns (Fr[] memory) { + Fr[] memory squares = new Fr[](logN); + squares[0] = r; + for (uint256 i = 1; i < logN; ++i) { + squares[i] = squares[i - 1].sqr(); + } + return squares; + } + // Compute the evaluations Aā‚—(r^{2Ė”}) for l = 0, ..., m-1 + + function computeFoldPosEvaluations( + Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckUChallenges, + Fr batchedEvalAccumulator, + Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvaluations, + Fr[] memory geminiEvalChallengePowers, + uint256 logSize + ) internal view returns (Fr[] memory) { + Fr[] memory foldPosEvaluations = new Fr[](logSize); + for (uint256 i = logSize; i > 0; --i) { + Fr challengePower = geminiEvalChallengePowers[i - 1]; + Fr u = sumcheckUChallenges[i - 1]; + + Fr batchedEvalRoundAcc = ((challengePower * + batchedEvalAccumulator * + Fr.wrap(2)) - + geminiEvaluations[i - 1] * + (challengePower * (ONE - u) - u)); + // Divide by the denominator + batchedEvalRoundAcc = + batchedEvalRoundAcc * + (challengePower * (ONE - u) + u).invert(); + + batchedEvalAccumulator = batchedEvalRoundAcc; + foldPosEvaluations[i - 1] = batchedEvalRoundAcc; + } + return foldPosEvaluations; + } +} + +uint256 constant Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; // EC group order. F_q + +function bytes32ToString(bytes32 value) pure returns (string memory result) { + bytes memory alphabet = "0123456789abcdef"; + + bytes memory str = new bytes(66); + str[0] = "0"; + str[1] = "x"; + for (uint256 i = 0; i < 32; i++) { + str[2 + i * 2] = alphabet[uint8(value[i] >> 4)]; + str[3 + i * 2] = alphabet[uint8(value[i] & 0x0f)]; + } + result = string(str); +} + +// Fr utility + +function bytesToFr(bytes calldata proofSection) pure returns (Fr scalar) { + scalar = FrLib.fromBytes32(bytes32(proofSection)); +} + +// EC Point utilities +function bytesToG1Point( + bytes calldata proofSection +) pure returns (Honk.G1Point memory point) { + point = Honk.G1Point({ + x: uint256(bytes32(proofSection[0x00:0x20])) % Q, + y: uint256(bytes32(proofSection[0x20:0x40])) % Q + }); +} + +function negateInplace( + Honk.G1Point memory point +) pure returns (Honk.G1Point memory) { + point.y = (Q - point.y) % Q; + return point; +} + +/** + * Convert the pairing points to G1 points. + * + * The pairing points are serialised as an array of 68 bit limbs representing two points + * The lhs of a pairing operation and the rhs of a pairing operation + * + * There are 4 fields for each group element, leaving 8 fields for each side of the pairing. + * + * @param pairingPoints The pairing points to convert. + * @return lhs + * @return rhs + */ +function convertPairingPointsToG1( + Fr[PAIRING_POINTS_SIZE] memory pairingPoints +) pure returns (Honk.G1Point memory lhs, Honk.G1Point memory rhs) { + uint256 lhsX = Fr.unwrap(pairingPoints[0]); + lhsX |= Fr.unwrap(pairingPoints[1]) << 68; + lhsX |= Fr.unwrap(pairingPoints[2]) << 136; + lhsX |= Fr.unwrap(pairingPoints[3]) << 204; + lhs.x = lhsX; + + uint256 lhsY = Fr.unwrap(pairingPoints[4]); + lhsY |= Fr.unwrap(pairingPoints[5]) << 68; + lhsY |= Fr.unwrap(pairingPoints[6]) << 136; + lhsY |= Fr.unwrap(pairingPoints[7]) << 204; + lhs.y = lhsY; + + uint256 rhsX = Fr.unwrap(pairingPoints[8]); + rhsX |= Fr.unwrap(pairingPoints[9]) << 68; + rhsX |= Fr.unwrap(pairingPoints[10]) << 136; + rhsX |= Fr.unwrap(pairingPoints[11]) << 204; + rhs.x = rhsX; + + uint256 rhsY = Fr.unwrap(pairingPoints[12]); + rhsY |= Fr.unwrap(pairingPoints[13]) << 68; + rhsY |= Fr.unwrap(pairingPoints[14]) << 136; + rhsY |= Fr.unwrap(pairingPoints[15]) << 204; + rhs.y = rhsY; +} + +/** + * Hash the pairing inputs from the present verification context with those extracted from the public inputs. + * + * @param proofPairingPoints Pairing points from the proof - (public inputs). + * @param accLhs Accumulator point for the left side - result of shplemini. + * @param accRhs Accumulator point for the right side - result of shplemini. + * @return recursionSeparator The recursion separator - generated from hashing the above. + */ +function generateRecursionSeparator( + Fr[PAIRING_POINTS_SIZE] memory proofPairingPoints, + Honk.G1Point memory accLhs, + Honk.G1Point memory accRhs +) pure returns (Fr recursionSeparator) { + // hash the proof aggregated X + // hash the proof aggregated Y + // hash the accum X + // hash the accum Y + + ( + Honk.G1Point memory proofLhs, + Honk.G1Point memory proofRhs + ) = convertPairingPointsToG1(proofPairingPoints); + + uint256[8] memory recursionSeparatorElements; + + // Proof points + recursionSeparatorElements[0] = proofLhs.x; + recursionSeparatorElements[1] = proofLhs.y; + recursionSeparatorElements[2] = proofRhs.x; + recursionSeparatorElements[3] = proofRhs.y; + + // Accumulator points + recursionSeparatorElements[4] = accLhs.x; + recursionSeparatorElements[5] = accLhs.y; + recursionSeparatorElements[6] = accRhs.x; + recursionSeparatorElements[7] = accRhs.y; + + recursionSeparator = FrLib.fromBytes32( + keccak256(abi.encodePacked(recursionSeparatorElements)) + ); +} + +/** + * G1 Mul with Separator + * Using the ecAdd and ecMul precompiles + * + * @param basePoint The point to multiply. + * @param other The other point to add. + * @param recursionSeperator The separator to use for the multiplication. + * @return `(recursionSeperator * basePoint) + other`. + */ +function mulWithSeperator( + Honk.G1Point memory basePoint, + Honk.G1Point memory other, + Fr recursionSeperator +) view returns (Honk.G1Point memory) { + Honk.G1Point memory result; + + result = ecMul(recursionSeperator, basePoint); + result = ecAdd(result, other); + + return result; +} + +/** + * G1 Mul + * Takes a Fr value and a G1 point and uses the ecMul precompile to return the result. + * + * @param value The value to multiply the point by. + * @param point The point to multiply. + * @return result The result of the multiplication. + */ +function ecMul( + Fr value, + Honk.G1Point memory point +) view returns (Honk.G1Point memory) { + Honk.G1Point memory result; + + assembly { + let free := mload(0x40) + // Write the point into memory (two 32 byte words) + // Memory layout: + // Address | value + // free | point.x + // free + 0x20| point.y + mstore(free, mload(point)) + mstore(add(free, 0x20), mload(add(point, 0x20))) + // Write the scalar into memory (one 32 byte word) + // Memory layout: + // Address | value + // free + 0x40| value + mstore(add(free, 0x40), value) + + // Call the ecMul precompile, it takes in the following + // [point.x, point.y, scalar], and returns the result back into the free memory location. + let success := staticcall(gas(), 0x07, free, 0x60, free, 0x40) + if iszero(success) { + revert(0, 0) + } + // Copy the result of the multiplication back into the result memory location. + // Memory layout: + // Address | value + // result | result.x + // result + 0x20| result.y + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + + mstore(0x40, add(free, 0x60)) + } + + return result; +} + +/** + * G1 Add + * Takes two G1 points and uses the ecAdd precompile to return the result. + * + * @param lhs The left hand side of the addition. + * @param rhs The right hand side of the addition. + * @return result The result of the addition. + */ +function ecAdd( + Honk.G1Point memory lhs, + Honk.G1Point memory rhs +) view returns (Honk.G1Point memory) { + Honk.G1Point memory result; + + assembly { + let free := mload(0x40) + // Write lhs into memory (two 32 byte words) + // Memory layout: + // Address | value + // free | lhs.x + // free + 0x20| lhs.y + mstore(free, mload(lhs)) + mstore(add(free, 0x20), mload(add(lhs, 0x20))) + + // Write rhs into memory (two 32 byte words) + // Memory layout: + // Address | value + // free + 0x40| rhs.x + // free + 0x60| rhs.y + mstore(add(free, 0x40), mload(rhs)) + mstore(add(free, 0x60), mload(add(rhs, 0x20))) + + // Call the ecAdd precompile, it takes in the following + // [lhs.x, lhs.y, rhs.x, rhs.y], and returns their addition back into the free memory location. + let success := staticcall(gas(), 0x06, free, 0x80, free, 0x40) + if iszero(success) { + revert(0, 0) + } + + // Copy the result of the addition back into the result memory location. + // Memory layout: + // Address | value + // result | result.x + // result + 0x20| result.y + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + + mstore(0x40, add(free, 0x80)) + } + + return result; +} + +function validateOnCurve(Honk.G1Point memory point) pure { + uint256 x = point.x; + uint256 y = point.y; + + bool success = false; + assembly { + let xx := mulmod(x, x, Q) + success := eq(mulmod(y, y, Q), addmod(mulmod(x, xx, Q), 3, Q)) + } + + require(success, "point is not on the curve"); +} + +function pairing( + Honk.G1Point memory rhs, + Honk.G1Point memory lhs +) view returns (bool decodedResult) { + bytes memory input = abi.encodePacked( + rhs.x, + rhs.y, + // Fixed G2 point + uint256( + 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2 + ), + uint256( + 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed + ), + uint256( + 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b + ), + uint256( + 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa + ), + lhs.x, + lhs.y, + // G2 point from VK + uint256( + 0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1 + ), + uint256( + 0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0 + ), + uint256( + 0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4 + ), + uint256( + 0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55 + ) + ); + + (bool success, bytes memory result) = address(0x08).staticcall(input); + decodedResult = success && abi.decode(result, (bool)); +} + +// Field arithmetic libraries - prevent littering the code with modmul / addmul + +abstract contract BaseZKHonkVerifier is IVerifier { + using FrLib for Fr; + + uint256 immutable $N; + uint256 immutable $LOG_N; + uint256 immutable $VK_HASH; + uint256 immutable $NUM_PUBLIC_INPUTS; + + constructor( + uint256 _N, + uint256 _logN, + uint256 _vkHash, + uint256 _numPublicInputs + ) { + $N = _N; + $LOG_N = _logN; + $VK_HASH = _vkHash; + $NUM_PUBLIC_INPUTS = _numPublicInputs; + } + + // Errors + error ProofLengthWrong(); + error ProofLengthWrongWithLogN( + uint256 logN, + uint256 actualLength, + uint256 expectedLength + ); + error PublicInputsLengthWrong(); + error SumcheckFailed(); + error ShpleminiFailed(); + error GeminiChallengeInSubgroup(); + error ConsistencyCheckFailed(); + + // Constants for proof length calculation (matching UltraKeccakZKFlavor) + uint256 constant NUM_WITNESS_ENTITIES = 8; + uint256 constant NUM_ELEMENTS_COMM = 2; // uint256 elements for curve points + uint256 constant NUM_ELEMENTS_FR = 1; // uint256 elements for field elements + uint256 constant NUM_LIBRA_EVALUATIONS = 4; // libra evaluations + + // Calculate proof size based on log_n (matching UltraKeccakZKFlavor formula) + function calculateProofSize(uint256 logN) internal pure returns (uint256) { + // Witness and Libra commitments + uint256 proofLength = NUM_WITNESS_ENTITIES * NUM_ELEMENTS_COMM; // witness commitments + proofLength += NUM_ELEMENTS_COMM * 4; // Libra concat, grand sum, quotient comms + Gemini masking + + // Sumcheck + proofLength += + logN * + ZK_BATCHED_RELATION_PARTIAL_LENGTH * + NUM_ELEMENTS_FR; // sumcheck univariates + proofLength += NUMBER_OF_ENTITIES * NUM_ELEMENTS_FR; // sumcheck evaluations + + // Libra and Gemini + proofLength += NUM_ELEMENTS_FR * 3; // Libra sum, claimed eval, Gemini masking eval + proofLength += logN * NUM_ELEMENTS_FR; // Gemini a evaluations + proofLength += NUM_LIBRA_EVALUATIONS * NUM_ELEMENTS_FR; // libra evaluations + + // PCS commitments + proofLength += (logN - 1) * NUM_ELEMENTS_COMM; // Gemini Fold commitments + proofLength += NUM_ELEMENTS_COMM * 2; // Shplonk Q and KZG W commitments + + // Pairing points + proofLength += PAIRING_POINTS_SIZE; // pairing inputs carried on public inputs + + return proofLength; + } + + uint256 constant SHIFTED_COMMITMENTS_START = 30; + + function loadVerificationKey() + internal + pure + virtual + returns (Honk.VerificationKey memory); + + function verify( + bytes calldata proof, + bytes32[] calldata publicInputs + ) public view override returns (bool verified) { + // Calculate expected proof size based on $LOG_N + uint256 expectedProofSize = calculateProofSize($LOG_N); + + // Check the received proof is the expected size where each field element is 32 bytes + if (proof.length != expectedProofSize * 32) { + revert ProofLengthWrongWithLogN( + $LOG_N, + proof.length, + expectedProofSize * 32 + ); + } + + Honk.VerificationKey memory vk = loadVerificationKey(); + Honk.ZKProof memory p = ZKTranscriptLib.loadProof(proof, $LOG_N); + + if (publicInputs.length != vk.publicInputsSize - PAIRING_POINTS_SIZE) { + revert PublicInputsLengthWrong(); + } + + // Generate the fiat shamir challenges for the whole protocol + ZKTranscript memory t = ZKTranscriptLib.generateTranscript( + p, + publicInputs, + $VK_HASH, + $NUM_PUBLIC_INPUTS, + $LOG_N + ); + + // Derive public input delta + t.relationParameters.publicInputsDelta = computePublicInputDelta( + publicInputs, + p.pairingPointObject, + t.relationParameters.beta, + t.relationParameters.gamma /*pubInputsOffset=*/, + 1 + ); + + // Sumcheck + if (!verifySumcheck(p, t)) revert SumcheckFailed(); + + if (!verifyShplemini(p, vk, t)) revert ShpleminiFailed(); + + verified = true; + } + + uint256 constant PERMUTATION_ARGUMENT_VALUE_SEPARATOR = 1 << 28; + + function computePublicInputDelta( + bytes32[] memory publicInputs, + Fr[PAIRING_POINTS_SIZE] memory pairingPointObject, + Fr beta, + Fr gamma, + uint256 offset + ) internal view returns (Fr publicInputDelta) { + Fr numerator = Fr.wrap(1); + Fr denominator = Fr.wrap(1); + + Fr numeratorAcc = gamma + + (beta * FrLib.from(PERMUTATION_ARGUMENT_VALUE_SEPARATOR + offset)); + Fr denominatorAcc = gamma - (beta * FrLib.from(offset + 1)); + + { + for ( + uint256 i = 0; + i < $NUM_PUBLIC_INPUTS - PAIRING_POINTS_SIZE; + i++ + ) { + Fr pubInput = FrLib.fromBytes32(publicInputs[i]); + + numerator = numerator * (numeratorAcc + pubInput); + denominator = denominator * (denominatorAcc + pubInput); + + numeratorAcc = numeratorAcc + beta; + denominatorAcc = denominatorAcc - beta; + } + + for (uint256 i = 0; i < PAIRING_POINTS_SIZE; i++) { + Fr pubInput = pairingPointObject[i]; + + numerator = numerator * (numeratorAcc + pubInput); + denominator = denominator * (denominatorAcc + pubInput); + + numeratorAcc = numeratorAcc + beta; + denominatorAcc = denominatorAcc - beta; + } + } + + // Fr delta = numerator / denominator; // TOOO: batch invert later? + publicInputDelta = FrLib.div(numerator, denominator); + } + + function verifySumcheck( + Honk.ZKProof memory proof, + ZKTranscript memory tp + ) internal view returns (bool verified) { + Fr roundTargetSum = tp.libraChallenge * proof.libraSum; // default 0 + Fr powPartialEvaluation = Fr.wrap(1); + + // We perform sumcheck reductions over log n rounds ( the multivariate degree ) + for (uint256 round; round < $LOG_N; ++round) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] + memory roundUnivariate = proof.sumcheckUnivariates[round]; + Fr totalSum = roundUnivariate[0] + roundUnivariate[1]; + if (totalSum != roundTargetSum) revert SumcheckFailed(); + + Fr roundChallenge = tp.sumCheckUChallenges[round]; + + // Update the round target for the next rounf + roundTargetSum = computeNextTargetSum( + roundUnivariate, + roundChallenge + ); + powPartialEvaluation = + powPartialEvaluation * + (Fr.wrap(1) + + roundChallenge * + (tp.gateChallenges[round] - Fr.wrap(1))); + } + + // Last round + Fr grandHonkRelationSum = RelationsLib.accumulateRelationEvaluations( + proof.sumcheckEvaluations, + tp.relationParameters, + tp.alphas, + powPartialEvaluation + ); + + Fr evaluation = Fr.wrap(1); + for (uint256 i = 2; i < $LOG_N; i++) { + evaluation = evaluation * tp.sumCheckUChallenges[i]; + } + + grandHonkRelationSum = + grandHonkRelationSum * + (Fr.wrap(1) - evaluation) + + proof.libraEvaluation * + tp.libraChallenge; + verified = (grandHonkRelationSum == roundTargetSum); + } + + // Return the new target sum for the next sumcheck round + function computeNextTargetSum( + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariates, + Fr roundChallenge + ) internal view returns (Fr targetSum) { + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] + memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [ + Fr.wrap( + 0x0000000000000000000000000000000000000000000000000000000000009d80 + ), + Fr.wrap( + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51 + ), + Fr.wrap( + 0x00000000000000000000000000000000000000000000000000000000000005a0 + ), + Fr.wrap( + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31 + ), + Fr.wrap( + 0x0000000000000000000000000000000000000000000000000000000000000240 + ), + Fr.wrap( + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31 + ), + Fr.wrap( + 0x00000000000000000000000000000000000000000000000000000000000005a0 + ), + Fr.wrap( + 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51 + ), + Fr.wrap( + 0x0000000000000000000000000000000000000000000000000000000000009d80 + ) + ]; + + // To compute the next target sum, we evaluate the given univariate at a point u (challenge). + + // Performing Barycentric evaluations + // Compute B(x) + Fr numeratorValue = Fr.wrap(1); + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i)); + } + + Fr[ZK_BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses; + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + denominatorInverses[i] = FrLib.invert( + BARYCENTRIC_LAGRANGE_DENOMINATORS[i] * + (roundChallenge - Fr.wrap(i)) + ); + } + + for (uint256 i = 0; i < ZK_BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + targetSum = + targetSum + + roundUnivariates[i] * + denominatorInverses[i]; + } + + // Scale the sum by the value of B(x) + targetSum = targetSum * numeratorValue; + } + + uint256 constant LIBRA_COMMITMENTS = 3; + uint256 constant LIBRA_EVALUATIONS = 4; + uint256 constant LIBRA_UNIVARIATES_LENGTH = 9; + + struct PairingInputs { + Honk.G1Point P_0; + Honk.G1Point P_1; + } + + function verifyShplemini( + Honk.ZKProof memory proof, + Honk.VerificationKey memory vk, + ZKTranscript memory tp + ) internal view returns (bool verified) { + CommitmentSchemeLib.ShpleminiIntermediates memory mem; // stack + + // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size + Fr[] memory powers_of_evaluation_challenge = CommitmentSchemeLib + .computeSquares(tp.geminiR, $LOG_N); + // Arrays hold values that will be linearly combined for the gemini and shplonk batch openings + Fr[] memory scalars = new Fr[]( + NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3 + ); + Honk.G1Point[] memory commitments = new Honk.G1Point[]( + NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3 + ); + + mem.posInvertedDenominator = (tp.shplonkZ - + powers_of_evaluation_challenge[0]).invert(); + mem.negInvertedDenominator = (tp.shplonkZ + + powers_of_evaluation_challenge[0]).invert(); + + mem.unshiftedScalar = + mem.posInvertedDenominator + + (tp.shplonkNu * mem.negInvertedDenominator); + mem.shiftedScalar = + tp.geminiR.invert() * + (mem.posInvertedDenominator - + (tp.shplonkNu * mem.negInvertedDenominator)); + + scalars[0] = Fr.wrap(1); + commitments[0] = proof.shplonkQ; + + /* Batch multivariate opening claims, shifted and unshifted + * The vector of scalars is populated as follows: + * \f[ + * \left( + * - \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), + * \ldots, + * - \rho^{i+k-1} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), + * - \rho^{i+k} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right), + * \ldots, + * - \rho^{k+m-1} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right) + * \right) + * \f] + * + * The following vector is concatenated to the vector of commitments: + * \f[ + * f_0, \ldots, f_{m-1}, f_{\text{shift}, 0}, \ldots, f_{\text{shift}, k-1} + * \f] + * + * Simultaneously, the evaluation of the multilinear polynomial + * \f[ + * \sum \rho^i \cdot f_i + \sum \rho^{i+k} \cdot f_{\text{shift}, i} + * \f] + * at the challenge point \f$ (u_0,\ldots, u_{n-1}) \f$ is computed. + * + * This approach minimizes the number of iterations over the commitments to multilinear polynomials + * and eliminates the need to store the powers of \f$ \rho \f$. + */ + mem.batchedEvaluation = proof.geminiMaskingEval; + mem.batchingChallenge = tp.rho; + mem.unshiftedScalarNeg = mem.unshiftedScalar.neg(); + mem.shiftedScalarNeg = mem.shiftedScalar.neg(); + + scalars[1] = mem.unshiftedScalarNeg; + for (uint256 i = 0; i < NUMBER_UNSHIFTED; ++i) { + scalars[i + 2] = mem.unshiftedScalarNeg * mem.batchingChallenge; + mem.batchedEvaluation = + mem.batchedEvaluation + + (proof.sumcheckEvaluations[i] * mem.batchingChallenge); + mem.batchingChallenge = mem.batchingChallenge * tp.rho; + } + // g commitments are accumulated at r + // For each of the to be shifted commitments perform the shift in place by + // adding to the unshifted value. + // We do so, as the values are to be used in batchMul later, and as + // `a * c + b * c = (a + b) * c` this will allow us to reduce memory and compute. + // Applied to w1, w2, w3, w4 and zPerm + for (uint256 i = 0; i < NUMBER_TO_BE_SHIFTED; ++i) { + uint256 scalarOff = i + SHIFTED_COMMITMENTS_START; + uint256 evaluationOff = i + NUMBER_UNSHIFTED; + + scalars[scalarOff] = + scalars[scalarOff] + + (mem.shiftedScalarNeg * mem.batchingChallenge); + mem.batchedEvaluation = + mem.batchedEvaluation + + (proof.sumcheckEvaluations[evaluationOff] * + mem.batchingChallenge); + mem.batchingChallenge = mem.batchingChallenge * tp.rho; + } + + commitments[1] = proof.geminiMaskingPoly; + + commitments[2] = vk.qm; + commitments[3] = vk.qc; + commitments[4] = vk.ql; + commitments[5] = vk.qr; + commitments[6] = vk.qo; + commitments[7] = vk.q4; + commitments[8] = vk.qLookup; + commitments[9] = vk.qArith; + commitments[10] = vk.qDeltaRange; + commitments[11] = vk.qElliptic; + commitments[12] = vk.qMemory; + commitments[13] = vk.qNnf; + commitments[14] = vk.qPoseidon2External; + commitments[15] = vk.qPoseidon2Internal; + commitments[16] = vk.s1; + commitments[17] = vk.s2; + commitments[18] = vk.s3; + commitments[19] = vk.s4; + commitments[20] = vk.id1; + commitments[21] = vk.id2; + commitments[22] = vk.id3; + commitments[23] = vk.id4; + commitments[24] = vk.t1; + commitments[25] = vk.t2; + commitments[26] = vk.t3; + commitments[27] = vk.t4; + commitments[28] = vk.lagrangeFirst; + commitments[29] = vk.lagrangeLast; + + // Accumulate proof points + commitments[30] = proof.w1; + commitments[31] = proof.w2; + commitments[32] = proof.w3; + commitments[33] = proof.w4; + commitments[34] = proof.zPerm; + commitments[35] = proof.lookupInverses; + commitments[36] = proof.lookupReadCounts; + commitments[37] = proof.lookupReadTags; + + /* Batch gemini claims from the prover + * place the commitments to gemini aįµ¢ to the vector of commitments, compute the contributions from + * aįµ¢(āˆ’r²ⁱ) for i=1, … , nāˆ’1 to the constant term accumulator, add corresponding scalars + * + * 1. Moves the vector + * \f[ + * \left( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{n-1}) \right) + * \f] + * to the 'commitments' vector. + * + * 2. Computes the scalars: + * \f[ + * \frac{\nu^{2}}{z + r^2}, \frac{\nu^3}{z + r^4}, \ldots, \frac{\nu^{n-1}}{z + r^{2^{n-1}}} + * \f] + * and places them into the 'scalars' vector. + * + * 3. Accumulates the summands of the constant term: + * \f[ + * \sum_{i=2}^{n-1} \frac{\nu^{i} \cdot A_i(-r^{2^i})}{z + r^{2^i}} + * \f] + * and adds them to the 'constant_term_accumulator'. + */ + + // Add contributions from Aā‚€(r) and Aā‚€(-r) to constant_term_accumulator: + // Compute the evaluations Aā‚—(r^{2Ė”}) for l = 0, ..., $LOG_N - 1 + Fr[] memory foldPosEvaluations = CommitmentSchemeLib + .computeFoldPosEvaluations( + tp.sumCheckUChallenges, + mem.batchedEvaluation, + proof.geminiAEvaluations, + powers_of_evaluation_challenge, + $LOG_N + ); + + mem.constantTermAccumulator = + foldPosEvaluations[0] * + mem.posInvertedDenominator; + mem.constantTermAccumulator = + mem.constantTermAccumulator + + (proof.geminiAEvaluations[0] * + tp.shplonkNu * + mem.negInvertedDenominator); + + mem.batchingChallenge = tp.shplonkNu.sqr(); + uint256 boundary = NUMBER_UNSHIFTED + 2; + + // Compute Shplonk constant term contributions from Aā‚—(± r^{2Ė”}) for l = 1, ..., m-1; + // Compute scalar multipliers for each fold commitment + for (uint256 i = 0; i < $LOG_N - 1; ++i) { + bool dummy_round = i >= ($LOG_N - 1); + + if (!dummy_round) { + // Update inverted denominators + mem.posInvertedDenominator = (tp.shplonkZ - + powers_of_evaluation_challenge[i + 1]).invert(); + mem.negInvertedDenominator = (tp.shplonkZ + + powers_of_evaluation_challenge[i + 1]).invert(); + + // Compute the scalar multipliers for Aā‚—(± r^{2Ė”}) and [Aā‚—] + mem.scalingFactorPos = + mem.batchingChallenge * + mem.posInvertedDenominator; + mem.scalingFactorNeg = + mem.batchingChallenge * + tp.shplonkNu * + mem.negInvertedDenominator; + scalars[boundary + i] = + mem.scalingFactorNeg.neg() + + mem.scalingFactorPos.neg(); + + // Accumulate the const term contribution given by + // v^{2l} * Aā‚—(r^{2Ė”}) /(z-r^{2^l}) + v^{2l+1} * Aā‚—(-r^{2Ė”}) /(z+ r^{2^l}) + Fr accumContribution = mem.scalingFactorNeg * + proof.geminiAEvaluations[i + 1]; + accumContribution = + accumContribution + + mem.scalingFactorPos * + foldPosEvaluations[i + 1]; + mem.constantTermAccumulator = + mem.constantTermAccumulator + + accumContribution; + } + // Update the running power of v + mem.batchingChallenge = + mem.batchingChallenge * + tp.shplonkNu * + tp.shplonkNu; + + commitments[boundary + i] = proof.geminiFoldComms[i]; + } + + boundary += $LOG_N - 1; + + // Finalize the batch opening claim + mem.denominators[0] = Fr.wrap(1).div(tp.shplonkZ - tp.geminiR); + mem.denominators[1] = Fr.wrap(1).div( + tp.shplonkZ - SUBGROUP_GENERATOR * tp.geminiR + ); + mem.denominators[2] = mem.denominators[0]; + mem.denominators[3] = mem.denominators[0]; + + mem.batchingChallenge = + mem.batchingChallenge * + tp.shplonkNu * + tp.shplonkNu; + for (uint256 i = 0; i < LIBRA_EVALUATIONS; i++) { + Fr scalingFactor = mem.denominators[i] * mem.batchingChallenge; + mem.batchingScalars[i] = scalingFactor.neg(); + mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu; + mem.constantTermAccumulator = + mem.constantTermAccumulator + + scalingFactor * + proof.libraPolyEvals[i]; + } + scalars[boundary] = mem.batchingScalars[0]; + scalars[boundary + 1] = mem.batchingScalars[1] + mem.batchingScalars[2]; + scalars[boundary + 2] = mem.batchingScalars[3]; + + for (uint256 i = 0; i < LIBRA_COMMITMENTS; i++) { + commitments[boundary++] = proof.libraCommitments[i]; + } + + commitments[boundary] = Honk.G1Point({ x: 1, y: 2 }); + scalars[boundary++] = mem.constantTermAccumulator; + + if ( + !checkEvalsConsistency( + proof.libraPolyEvals, + tp.geminiR, + tp.sumCheckUChallenges, + proof.libraEvaluation + ) + ) { + revert ConsistencyCheckFailed(); + } + + Honk.G1Point memory quotient_commitment = proof.kzgQuotient; + + commitments[boundary] = quotient_commitment; + scalars[boundary] = tp.shplonkZ; // evaluation challenge + + PairingInputs memory pair; + pair.P_0 = batchMul(commitments, scalars); + pair.P_1 = negateInplace(quotient_commitment); + + // Aggregate pairing points + Fr recursionSeparator = generateRecursionSeparator( + proof.pairingPointObject, + pair.P_0, + pair.P_1 + ); + ( + Honk.G1Point memory P_0_other, + Honk.G1Point memory P_1_other + ) = convertPairingPointsToG1(proof.pairingPointObject); + + // Validate the points from the proof are on the curve + validateOnCurve(P_0_other); + validateOnCurve(P_1_other); + + // accumulate with aggregate points in proof + pair.P_0 = mulWithSeperator(pair.P_0, P_0_other, recursionSeparator); + pair.P_1 = mulWithSeperator(pair.P_1, P_1_other, recursionSeparator); + + return pairing(pair.P_0, pair.P_1); + } + + struct SmallSubgroupIpaIntermediates { + Fr[SUBGROUP_SIZE] challengePolyLagrange; + Fr challengePolyEval; + Fr lagrangeFirst; + Fr lagrangeLast; + Fr rootPower; + Fr[SUBGROUP_SIZE] denominators; // this has to disappear + Fr diff; + } + + function checkEvalsConsistency( + Fr[LIBRA_EVALUATIONS] memory libraPolyEvals, + Fr geminiR, + Fr[CONST_PROOF_SIZE_LOG_N] memory uChallenges, + Fr libraEval + ) internal view returns (bool check) { + Fr one = Fr.wrap(1); + Fr vanishingPolyEval = geminiR.pow(SUBGROUP_SIZE) - one; + if (vanishingPolyEval == Fr.wrap(0)) { + revert GeminiChallengeInSubgroup(); + } + + SmallSubgroupIpaIntermediates memory mem; + mem.challengePolyLagrange[0] = one; + for (uint256 round = 0; round < $LOG_N; round++) { + uint256 currIdx = 1 + LIBRA_UNIVARIATES_LENGTH * round; + mem.challengePolyLagrange[currIdx] = one; + for ( + uint256 idx = currIdx + 1; + idx < currIdx + LIBRA_UNIVARIATES_LENGTH; + idx++ + ) { + mem.challengePolyLagrange[idx] = + mem.challengePolyLagrange[idx - 1] * + uChallenges[round]; + } + } + + mem.rootPower = one; + mem.challengePolyEval = Fr.wrap(0); + for (uint256 idx = 0; idx < SUBGROUP_SIZE; idx++) { + mem.denominators[idx] = mem.rootPower * geminiR - one; + mem.denominators[idx] = mem.denominators[idx].invert(); + mem.challengePolyEval = + mem.challengePolyEval + + mem.challengePolyLagrange[idx] * + mem.denominators[idx]; + mem.rootPower = mem.rootPower * SUBGROUP_GENERATOR_INVERSE; + } + + Fr numerator = vanishingPolyEval * Fr.wrap(SUBGROUP_SIZE).invert(); + mem.challengePolyEval = mem.challengePolyEval * numerator; + mem.lagrangeFirst = mem.denominators[0] * numerator; + mem.lagrangeLast = mem.denominators[SUBGROUP_SIZE - 1] * numerator; + + mem.diff = mem.lagrangeFirst * libraPolyEvals[2]; + + mem.diff = + mem.diff + + (geminiR - SUBGROUP_GENERATOR_INVERSE) * + (libraPolyEvals[1] - + libraPolyEvals[2] - + libraPolyEvals[0] * + mem.challengePolyEval); + mem.diff = + mem.diff + + mem.lagrangeLast * + (libraPolyEvals[2] - libraEval) - + vanishingPolyEval * + libraPolyEvals[3]; + + check = mem.diff == Fr.wrap(0); + } + + // This implementation is the same as above with different constants + function batchMul( + Honk.G1Point[] memory base, + Fr[] memory scalars + ) internal view returns (Honk.G1Point memory result) { + uint256 limit = NUMBER_UNSHIFTED + $LOG_N + LIBRA_COMMITMENTS + 3; + + // Validate all points are on the curve + for (uint256 i = 0; i < limit; ++i) { + validateOnCurve(base[i]); + } + + bool success = true; + assembly { + let free := mload(0x40) + + let count := 0x01 + for {} lt(count, add(limit, 1)) { + count := add(count, 1) + } { + // Get loop offsets + let base_base := add(base, mul(count, 0x20)) + let scalar_base := add(scalars, mul(count, 0x20)) + + mstore(add(free, 0x40), mload(mload(base_base))) + mstore(add(free, 0x60), mload(add(0x20, mload(base_base)))) + // Add scalar + mstore(add(free, 0x80), mload(scalar_base)) + + success := and( + success, + staticcall( + gas(), + 7, + add(free, 0x40), + 0x60, + add(free, 0x40), + 0x40 + ) + ) + // accumulator = accumulator + accumulator_2 + success := and( + success, + staticcall(gas(), 6, free, 0x80, free, 0x40) + ) + } + + // Return the result + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + } + + require(success, ShpleminiFailed()); + } +} + +contract DkgPkVerifier is + BaseZKHonkVerifier(N, LOG_N, VK_HASH, NUMBER_OF_PUBLIC_INPUTS) +{ + function loadVerificationKey() + internal + pure + override + returns (Honk.VerificationKey memory) + { + return HonkVerificationKey.loadVerificationKey(); + } +} diff --git a/packages/enclave-contracts/deployed_contracts.json b/packages/enclave-contracts/deployed_contracts.json index 9429c1f1b5..45307b7918 100644 --- a/packages/enclave-contracts/deployed_contracts.json +++ b/packages/enclave-contracts/deployed_contracts.json @@ -107,47 +107,53 @@ "address": "0xC39b101f2FB4ea677c1EA18f92C15CDD54Af40c2" } }, - "localhost": { + "undefined": { "PoseidonT3": { "blockNumber": 3, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" + } + }, + "localhost": { + "PoseidonT3": { + "blockNumber": 338, + "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 4, - "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" + "blockNumber": 339, + "address": "0x1D13fF25b10C9a6741DFdce229073bed652197c7" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 5, - "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" + "blockNumber": 340, + "address": "0x24d41dbc3d60d0784f8a937c59FBDe51440D5140" }, "EnclaveTicketToken": { "constructorArgs": { - "baseToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "baseToken": "0x1D13fF25b10C9a6741DFdce229073bed652197c7", "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 7, - "address": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "blockNumber": 342, + "address": "0x618fB9dbd2BD6eb968B4c1af36af6CB0b45310Ec" }, "SlashingManager": { "constructorArgs": { "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "bondingRegistry": "0x0000000000000000000000000000000000000001" }, - "blockNumber": 8, - "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + "blockNumber": 343, + "address": "0xa779C1D17bC5230c07afdC51376CAC1cb3Dd5314" }, "BondingRegistry": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketToken": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", - "licenseToken": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", + "ticketToken": "0x618fB9dbd2BD6eb968B4c1af36af6CB0b45310Ec", + "licenseToken": "0x24d41dbc3d60d0784f8a937c59FBDe51440D5140", "registry": "0x0000000000000000000000000000000000000001", "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "ticketPrice": "10000000", @@ -156,14 +162,14 @@ "exitDelay": "604800" }, "proxyRecords": { - "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000005fc8d32690cc91d4c39d9d3abcbd16989f875707000000000000000000000000cf7ed3acca5a467e9e704c703e8d87f634fb0fc90000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", + "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000618fb9dbd2bd6eb968b4c1af36af6cb0b45310ec00000000000000000000000024d41dbc3d60d0784f8a937c59fbde51440d51400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", - "proxyAdminAddress": "0x94099942864EA81cCF197E9D71ac53310b1468D8", - "implementationAddress": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" + "proxyAddress": "0xA3307BF348ACC4bEDdd67CCA2f7F0c4349d347Db", + "proxyAdminAddress": "0xA7eDcd3F5d0985be01f8930229BFd3fDBA2F5034", + "implementationAddress": "0x76cec9299B6Fa418dc71416FF353737AB7933A7D" }, - "blockNumber": 8, - "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + "blockNumber": 343, + "address": "0xA3307BF348ACC4bEDdd67CCA2f7F0c4349d347Db" }, "CiphernodeRegistryOwnable": { "constructorArgs": { @@ -174,45 +180,71 @@ "proxyRecords": { "initData": "0x1794bb3c000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "proxyAdminAddress": "0x6F1216D1BFe15c98520CA1434FC1d9D57AC95321", - "implementationAddress": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" + "proxyAddress": "0xc0Bb1650A8eA5dDF81998f17B5319afD656f4c11", + "proxyAdminAddress": "0xdeB02111e4e5Fa8C7c05e983a3446893a36D12ad", + "implementationAddress": "0x313F922BE1649cEc058EC0f076664500c78bdc0b" }, - "blockNumber": 11, - "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + "blockNumber": 346, + "address": "0xc0Bb1650A8eA5dDF81998f17B5319afD656f4c11" }, "Enclave": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "registry": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "bondingRegistry": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", - "feeToken": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + "registry": "0xc0Bb1650A8eA5dDF81998f17B5319afD656f4c11", + "bondingRegistry": "0xA3307BF348ACC4bEDdd67CCA2f7F0c4349d347Db", + "e3RefundManager": "0x0000000000000000000000000000000000000001", + "feeToken": "0x1D13fF25b10C9a6741DFdce229073bed652197c7", "maxDuration": "2592000", + "timeoutConfig": "{\"committeeFormationWindow\":3600,\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600,\"gracePeriod\":600}", "params": [ "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000" ] }, "proxyRecords": { - "initData": "0xefe0308b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000610178da211fef7d417bc0e6fed39f05609ad7880000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe60000000000000000000000009fe46736679d2d9a65f0992f2272de9f3c7fa6e00000000000000000000000000000000000000000000000000000000000278d0000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000", + "initData": "0x69c5b347000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000c0bb1650a8ea5ddf81998f17b5319afd656f4c11000000000000000000000000a3307bf348acc4beddd67cca2f7f0c4349d347db00000000000000000000000000000000000000000000000000000000000000010000000000000000000000001d13ff25b10c9a6741dfdce229073bed652197c70000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e100000000000000000000000000000000000000000000000000000000000000258000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "proxyAdminAddress": "0x1F708C24a0D3A740cD47cC0444E9480899f3dA7D", - "implementationAddress": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + "proxyAddress": "0x90c84237fDdf091b1E63f369AF122EB46000bc70", + "proxyAdminAddress": "0xd119D184628e094322007cEa4F2535Ec3A06E6b1", + "implementationAddress": "0x5322471a7E37Ac2B8902cFcba84d266b37D811A0" }, - "blockNumber": 13, - "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + "blockNumber": 348, + "address": "0x90c84237fDdf091b1E63f369AF122EB46000bc70" + }, + "E3RefundManager": { + "constructorArgs": { + "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "enclave": "0x90c84237fDdf091b1E63f369AF122EB46000bc70", + "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + }, + "proxyRecords": { + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000090c84237fddf091b1e63f369af122eb46000bc70000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "proxyAddress": "0x103A3b128991781EE2c8db0454cA99d67b257923", + "proxyAdminAddress": "0xD8Fdf2Adc2F6502755af003661664a695ECC7d12", + "implementationAddress": "0x3D63c50AD04DD5aE394CAB562b7691DD5de7CF6f" + }, + "blockNumber": 350, + "address": "0x103A3b128991781EE2c8db0454cA99d67b257923" }, "MockComputeProvider": { - "blockNumber": 23, - "address": "0x59b670e9fA9D0A427751Af201D676719a970857b" + "blockNumber": 361, + "address": "0x20d7B364E8Ed1F4260b5B90C41c2deC3C1F6D367" }, "MockDecryptionVerifier": { - "blockNumber": 24, - "address": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1" + "blockNumber": 362, + "address": "0xf5C3953Ae4639806fcbCC3196f71dd81B0da4348" }, "MockE3Program": { - "blockNumber": 25, - "address": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + "blockNumber": 363, + "address": "0x90b97E83e22AFa2e6A96b3549A0E495D5Bae61aF" + }, + "DkgPkVerifier_ZKTranscriptLib": { + "blockNumber": 366, + "address": "0xccA9728291bC98ff4F97EF57Be3466227b0eb06C" + }, + "DkgPkVerifier": { + "blockNumber": 367, + "address": "0xc6B407503dE64956Ad3cF5Ab112cA4f56AA13517" } } } \ No newline at end of file diff --git a/packages/enclave-contracts/hardhat.config.ts b/packages/enclave-contracts/hardhat.config.ts index f375888ed1..73e047e74a 100644 --- a/packages/enclave-contracts/hardhat.config.ts +++ b/packages/enclave-contracts/hardhat.config.ts @@ -179,7 +179,7 @@ const config: HardhatUserConfig = { settings: { optimizer: { enabled: true, - runs: 800, + runs: 100, }, metadata: { bytecodeHash: "none", diff --git a/packages/enclave-contracts/ignition/modules/dkgPkVerifier.ts b/packages/enclave-contracts/ignition/modules/dkgPkVerifier.ts new file mode 100644 index 0000000000..504280e6b7 --- /dev/null +++ b/packages/enclave-contracts/ignition/modules/dkgPkVerifier.ts @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("DkgPkVerifier", (m) => { + const dkgPkVerifier = m.contract("DkgPkVerifier"); + + return { dkgPkVerifier }; +}) as any; diff --git a/packages/enclave-contracts/package.json b/packages/enclave-contracts/package.json index 26c3dc2209..876589f549 100644 --- a/packages/enclave-contracts/package.json +++ b/packages/enclave-contracts/package.json @@ -159,6 +159,7 @@ "coverage": "pnpm test --coverage", "deploy": "hardhat run scripts/run.ts", "deploy:mocks": "export DEPLOY_MOCKS=true && pnpm clean:deployments && pnpm run deploy", + "deploy:verifiers": "hardhat run scripts/deployVerifiers.ts", "upgrade:enclave": "hardhat run scripts/upgrade/enclave.ts", "upgrade:bondingRegistry": "hardhat run scripts/upgrade/bondingRegistry.ts", "upgrade:ciphernodeRegistryOwnable": "hardhat run scripts/upgrade/ciphernodeRegistryOwnable.ts", diff --git a/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts b/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts new file mode 100644 index 0000000000..7e573c7603 --- /dev/null +++ b/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +import fs from "fs"; +import type { HardhatRuntimeEnvironment } from "hardhat/types/hre"; +import path from "path"; + +import { readDeploymentArgs, storeDeploymentArgs } from "../utils"; + +/** + * Discovers all Solidity verifier contracts in contracts/verifier/ directory. + * Returns an array of contract names (without .sol extension). + */ +export const discoverVerifierContracts = (): string[] => { + const verifierDir = path.join(process.cwd(), "contracts/verifier"); + if (!fs.existsSync(verifierDir)) { + return []; + } + + return fs + .readdirSync(verifierDir) + .filter((f) => f.endsWith(".sol")) + .map((f) => f.replace(".sol", "")); +}; + +/** + * Reads a compiled artifact to extract unlinked library references. + * Returns a map of fully-qualified library name → library name. + */ +const getRequiredLibraries = async ( + contractName: string, + hre: HardhatRuntimeEnvironment, +): Promise => { + const artifact = await hre.artifacts.readArtifact(contractName); + const linkRefs = artifact.bytecode.match(/__\$[a-f0-9]{34}\$__/g); + if (!linkRefs || linkRefs.length === 0) return []; + + // Extract library names from linkReferences in the artifact + const libraries: string[] = []; + const linkReferences = (artifact as any).linkReferences ?? {}; + for (const source of Object.keys(linkReferences)) { + for (const libName of Object.keys(linkReferences[source])) { + libraries.push(`${source}:${libName}`); + } + } + return libraries; +}; + +/** + * Deploys a single verifier contract and saves the deployment record. + * Automatically detects and deploys any libraries the verifier depends on. + * Skips deployment if the contract is already deployed on the target chain. + */ +export const deployAndSaveVerifier = async ( + contractName: string, + hre: HardhatRuntimeEnvironment, +): Promise<{ address: string }> => { + const { ethers } = await hre.network.connect(); + const chain = hre.globalOptions.network; + + // Check if already deployed + const existing = readDeploymentArgs(contractName, chain); + if (existing?.address) { + console.log(` ${contractName} already deployed at ${existing.address}`); + return { address: existing.address }; + } + + // Detect and deploy required libraries + const requiredLibs = await getRequiredLibraries(contractName, hre); + const libraries: Record = {}; + + for (const fqn of requiredLibs) { + const libName = fqn.split(":").pop()!; + const libStorageKey = `${contractName}_${libName}`; + + // Check if library is already deployed + const existingLib = readDeploymentArgs(libStorageKey, chain); + if (existingLib?.address) { + console.log( + ` ${libName} library already deployed at ${existingLib.address}`, + ); + libraries[fqn] = existingLib.address; + continue; + } + + // Deploy the library + console.log(` Deploying library ${libName}...`); + const libFactory = await ethers.getContractFactory(libName); + const libContract = await libFactory.deploy(); + await libContract.waitForDeployment(); + const libAddress = await libContract.getAddress(); + const libBlockNumber = await ethers.provider.getBlockNumber(); + + storeDeploymentArgs( + { blockNumber: libBlockNumber, address: libAddress }, + libStorageKey, + chain, + ); + + console.log(` ${libName} library deployed to: ${libAddress}`); + libraries[fqn] = libAddress; + } + + // Deploy the verifier contract with linked libraries + const factory = + Object.keys(libraries).length > 0 + ? await ethers.getContractFactory(contractName, { libraries }) + : await ethers.getContractFactory(contractName); + + const contract = await factory.deploy(); + await contract.waitForDeployment(); + + const address = await contract.getAddress(); + const blockNumber = await ethers.provider.getBlockNumber(); + + storeDeploymentArgs( + { + blockNumber, + address, + }, + contractName, + chain, + ); + + console.log(` ${contractName} deployed to: ${address}`); + return { address }; +}; + +export interface VerifierDeployments { + [contractName: string]: string; // contract name → deployed address +} + +/** + * Deploys all verifier contracts found in contracts/verifier/. + * Skips any that are already deployed on the target chain. + * + * @returns A mapping of contract names to their deployed addresses. + */ +export const deployAndSaveAllVerifiers = async ( + hre: HardhatRuntimeEnvironment, +): Promise => { + const contractNames = discoverVerifierContracts(); + + if (contractNames.length === 0) { + console.log( + " No verifier contracts found in contracts/verifier/. Skipping.", + ); + return {}; + } + + console.log(` Found ${contractNames.length} verifier contract(s)`); + + const deployments: VerifierDeployments = {}; + + for (const name of contractNames) { + const { address } = await deployAndSaveVerifier(name, hre); + deployments[name] = address; + } + + return deployments; +}; diff --git a/packages/enclave-contracts/scripts/deployEnclave.ts b/packages/enclave-contracts/scripts/deployEnclave.ts index 8ab8141586..6dfb6c5240 100644 --- a/packages/enclave-contracts/scripts/deployEnclave.ts +++ b/packages/enclave-contracts/scripts/deployEnclave.ts @@ -15,6 +15,7 @@ import { deployAndSaveEnclaveToken } from "./deployAndSave/enclaveToken"; import { deployAndSaveMockStableToken } from "./deployAndSave/mockStableToken"; import { deployAndSavePoseidonT3 } from "./deployAndSave/poseidonT3"; import { deployAndSaveSlashingManager } from "./deployAndSave/slashingManager"; +import { deployAndSaveAllVerifiers } from "./deployAndSave/verifiers"; import { deployMocks } from "./deployMocks"; /** @@ -225,6 +226,16 @@ export const deployEnclave = async (withMocks?: boolean) => { console.log(`Successfully enabled E3 Program in Enclave contract`); } + // Deploy circuit verifiers (if any exist in contracts/verifier/) + console.log("Deploying circuit verifiers..."); + const verifierDeployments = await deployAndSaveAllVerifiers(hre); + const verifierEntries = Object.entries(verifierDeployments); + + const verifierLines = + verifierEntries.length > 0 + ? verifierEntries.map(([name, addr]) => ` ${name}: ${addr}`).join("\n") + : " (none)"; + console.log(` ============================================ Deployment Complete! @@ -237,6 +248,8 @@ export const deployEnclave = async (withMocks?: boolean) => { CiphernodeRegistry: ${ciphernodeRegistryAddress} E3RefundManager: ${e3RefundManagerAddress} Enclave: ${enclaveAddress} + Circuit Verifiers: +${verifierLines} ============================================ `); }; diff --git a/packages/enclave-contracts/scripts/deployVerifiers.ts b/packages/enclave-contracts/scripts/deployVerifiers.ts new file mode 100644 index 0000000000..b79210533b --- /dev/null +++ b/packages/enclave-contracts/scripts/deployVerifiers.ts @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +import hre from "hardhat"; + +import { deployAndSaveAllVerifiers } from "./deployAndSave/verifiers"; + +/** + * Standalone script to deploy only the circuit verifier contracts. + * Usage: hardhat run scripts/deployVerifiers.ts --network + */ +const main = async () => { + console.log("Deploying circuit verifier contracts...\n"); + + const verifierDeployments = await deployAndSaveAllVerifiers(hre); + const entries = Object.entries(verifierDeployments); + + if (entries.length === 0) { + console.log("No verifier contracts found in contracts/verifier/."); + return; + } + + console.log(` + ============================================ + Verifier Deployment Complete! + ============================================`); + for (const [name, address] of entries) { + console.log(` ${name}: ${address}`); + } + console.log(` ============================================ + `); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/packages/enclave-contracts/scripts/index.ts b/packages/enclave-contracts/scripts/index.ts index 6ac68aed5d..e829982f50 100644 --- a/packages/enclave-contracts/scripts/index.ts +++ b/packages/enclave-contracts/scripts/index.ts @@ -18,4 +18,5 @@ export * from "./deployAndSave/slashingManager"; export * from "./deployAndSave/mockComputeProvider"; export * from "./deployAndSave/mockDecryptionVerifier"; export * from "./deployAndSave/mockProgram"; +export * from "./deployAndSave/verifiers"; export * from "./verify"; diff --git a/scripts/generate-verifiers.ts b/scripts/generate-verifiers.ts new file mode 100644 index 0000000000..d637be413d --- /dev/null +++ b/scripts/generate-verifiers.ts @@ -0,0 +1,383 @@ +#!/usr/bin/env tsx +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +/** + * Generate Solidity verifier contracts from compiled Noir circuits. + * + * Prerequisites: + * - `nargo` and `bb` (Barretenberg CLI) must be installed and in PATH + * - Circuits should be compiled first (`pnpm build:circuits`) or this script + * will compile them automatically. + * + * Usage: + * pnpm generate:verifiers # Generate all verifiers + * pnpm generate:verifiers --group dkg # Only DKG circuits + * pnpm generate:verifiers --group threshold # Only Threshold circuits + * pnpm generate:verifiers --circuit pk # Only a specific circuit + * pnpm generate:verifiers --clean # Remove existing verifiers first + * pnpm generate:verifiers --dry-run # Show what would be generated + */ + +import { execSync } from 'child_process' +import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs' +import { basename, join, resolve } from 'path' + +// --------------------------------------------------------------------------- +// Types & constants +// --------------------------------------------------------------------------- + +const CIRCUIT_GROUPS = { + DKG: 'dkg', + THRESHOLD: 'threshold', + AGGREGATION: 'recursive_aggregation', +} as const + +type CircuitGroup = (typeof CIRCUIT_GROUPS)[keyof typeof CIRCUIT_GROUPS] +const ALL_GROUPS: CircuitGroup[] = [CIRCUIT_GROUPS.DKG, CIRCUIT_GROUPS.THRESHOLD, CIRCUIT_GROUPS.AGGREGATION] + +const LICENSE_HEADER = `// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +` + +interface CircuitInfo { + name: string + group: CircuitGroup + path: string + packageName: string +} + +interface GenerateOptions { + groups?: CircuitGroup[] + circuits?: string[] + clean?: boolean + dryRun?: boolean + compile?: boolean // compile circuits before generating verifiers + oracleHash?: string // oracle hash scheme for bb write_vk (default: keccak) +} + +// --------------------------------------------------------------------------- +// Main class +// --------------------------------------------------------------------------- + +class VerifierGenerator { + private rootDir: string + private circuitsDir: string + private verifierDir: string + private options: GenerateOptions + + constructor(rootDir?: string, options: GenerateOptions = {}) { + this.rootDir = rootDir ?? resolve(__dirname, '..') + this.circuitsDir = join(this.rootDir, 'circuits', 'bin') + this.verifierDir = join(this.rootDir, 'packages', 'enclave-contracts', 'contracts', 'verifier') + this.options = { + groups: ALL_GROUPS, + clean: false, + compile: true, + oracleHash: 'keccak', + ...options, + } + } + + async generate(): Promise { + console.log('šŸ”® Generating Solidity verifiers from Noir circuits...\n') + + this.checkTool('nargo --version', 'nargo') + this.checkTool('bb --version', 'bb') + + const circuits = this.discoverCircuits() + if (circuits.length === 0) { + console.log(' āš ļø No circuits found') + return + } + + console.log(` Found ${circuits.length} circuit(s)\n`) + + if (this.options.dryRun) { + console.log(' Would generate verifiers for:', circuits.map((c) => `${c.group}/${c.name}`).join(', ')) + console.log(` Output directory: ${this.verifierDir}`) + return + } + + // Prepare output directory + if (this.options.clean && existsSync(this.verifierDir)) { + rmSync(this.verifierDir, { recursive: true }) + console.log(' šŸ—‘ļø Cleaned existing verifier directory') + } + mkdirSync(this.verifierDir, { recursive: true }) + + const generated: string[] = [] + const errors: string[] = [] + + for (const circuit of circuits) { + try { + const solFile = this.generateVerifier(circuit) + generated.push(solFile) + } catch (error: any) { + errors.push(`${circuit.group}/${circuit.name}: ${error.message}`) + console.error(` āœ— ${circuit.group}/${circuit.name}: ${error.message}`) + } + } + + console.log(`\nāœ… Generated ${generated.length} Solidity verifier(s) in:`) + console.log(` ${this.verifierDir}\n`) + + for (const f of generated) { + console.log(` • ${basename(f)}`) + } + + if (errors.length > 0) { + console.error(`\nāŒ ${errors.length} error(s):`) + for (const e of errors) console.error(` • ${e}`) + process.exit(1) + } + } + + // ------------------------------------------------------------------------- + // Discovery + // ------------------------------------------------------------------------- + + private discoverCircuits(): CircuitInfo[] { + const circuits: CircuitInfo[] = [] + if (!existsSync(this.circuitsDir)) return circuits + + for (const group of this.options.groups ?? ALL_GROUPS) { + const groupDir = join(this.circuitsDir, group) + if (!existsSync(groupDir)) continue + + for (const entry of readdirSync(groupDir)) { + const circuitPath = join(groupDir, entry) + if (statSync(circuitPath).isDirectory() && existsSync(join(circuitPath, 'Nargo.toml'))) { + if (!this.options.circuits || this.options.circuits.includes(entry)) { + const packageName = this.getPackageName(circuitPath) + circuits.push({ name: entry, group, path: circuitPath, packageName }) + } + } + } + } + return circuits + } + + private getPackageName(circuitPath: string): string { + try { + const content = readFileSync(join(circuitPath, 'Nargo.toml'), 'utf-8') + const match = content.match(/^name\s*=\s*"([^"]+)"/m) + if (match) return match[1] + } catch { + // fall through + } + return basename(circuitPath) + } + + // ------------------------------------------------------------------------- + // Generation pipeline (compile → write_vk → write_solidity_verifier) + // ------------------------------------------------------------------------- + + private generateVerifier(circuit: CircuitInfo): string { + const { name, group, path: circuitPath, packageName } = circuit + + // 1. Compile if needed + const jsonFile = this.ensureCompiled(circuit) + + // 2. Generate VK + const targetDir = this.findTargetDir(circuit, packageName) + const vkPath = this.ensureVk(jsonFile, targetDir, packageName) + + // 3. Generate Solidity verifier + const rawSolPath = join(targetDir, `${packageName}_verifier.sol`) + execSync(`bb write_solidity_verifier -k "${vkPath}" -o "${rawSolPath}"`, { stdio: 'pipe' }) + + if (!existsSync(rawSolPath)) { + throw new Error('bb write_solidity_verifier did not produce output') + } + + // 4. Post-process: rename contract, add license header, copy to output + const contractName = this.toContractName(group, name) + const outputFileName = `${contractName}.sol` + const outputPath = join(this.verifierDir, outputFileName) + + let solidity = readFileSync(rawSolPath, 'utf-8') + + // Replace the default contract name (HonkVerifier) with our descriptive name + solidity = solidity.replace(/contract\s+HonkVerifier/g, `contract ${contractName}`) + + // Replace license header – bb produces Apache-2.0 by default + solidity = solidity.replace(/\/\/\s*SPDX-License-Identifier:[^\n]*\n(\/\/[^\n]*\n)*/, LICENSE_HEADER) + + writeFileSync(outputPath, solidity) + + // Clean up intermediate file + rmSync(rawSolPath, { force: true }) + + console.log(` āœ“ ${group}/${name} → ${outputFileName}`) + return outputPath + } + + /** + * Ensure the circuit is compiled and return the path to the JSON artifact. + */ + private ensureCompiled(circuit: CircuitInfo): string { + const targetDirs = this.candidateTargetDirs(circuit) + + // Check if already compiled + for (const dir of targetDirs) { + const candidate = join(dir, `${circuit.packageName}.json`) + if (existsSync(candidate)) return candidate + } + + // Compile + if (!this.options.compile) { + throw new Error(`Compiled artifact ${circuit.packageName}.json not found. Run 'pnpm build:circuits' first or remove --no-compile.`) + } + + console.log(` ā³ Compiling ${circuit.group}/${circuit.name}...`) + execSync('nargo compile', { cwd: circuit.path, stdio: 'pipe' }) + + // Search again + for (const dir of targetDirs) { + const candidate = join(dir, `${circuit.packageName}.json`) + if (existsSync(candidate)) return candidate + } + + throw new Error(`Compiled artifact ${circuit.packageName}.json not found after compilation`) + } + + /** + * Ensure VK exists and return its path. + */ + private ensureVk(jsonFile: string, targetDir: string, packageName: string): string { + const vkFile = join(targetDir, `${packageName}.vk`) + + // Check if VK already exists + if (existsSync(vkFile)) return vkFile + + // Also check for a bare 'vk' file + const defaultVk = join(targetDir, 'vk') + if (existsSync(defaultVk)) { + copyFileSync(defaultVk, vkFile) + return vkFile + } + + // Generate VK + const oracleHashFlag = this.options.oracleHash ? ` --oracle_hash ${this.options.oracleHash}` : '' + execSync(`bb write_vk -b "${jsonFile}" -o "${targetDir}"${oracleHashFlag}`, { stdio: 'pipe' }) + + // bb writes to 'vk' by default, rename to .vk + if (existsSync(defaultVk) && !existsSync(vkFile)) { + copyFileSync(defaultVk, vkFile) + rmSync(defaultVk, { force: true }) + } + + if (!existsSync(vkFile)) { + throw new Error(`Failed to generate verification key for ${packageName}`) + } + + return vkFile + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private candidateTargetDirs(circuit: CircuitInfo): string[] { + const groupDir = join(this.circuitsDir, circuit.group) + return [join(groupDir, 'target'), join(this.circuitsDir, 'target'), join(circuit.path, 'target')] + } + + private findTargetDir(circuit: CircuitInfo, packageName: string): string { + for (const dir of this.candidateTargetDirs(circuit)) { + const candidate = join(dir, `${packageName}.json`) + if (existsSync(candidate)) return dir + } + throw new Error(`Target directory not found for ${circuit.group}/${circuit.name}`) + } + + /** + * Convert group/name to a PascalCase Solidity contract name. + * e.g. (dkg, pk) → DkgPkVerifier + * (threshold, pk_generation) → ThresholdPkGenerationVerifier + * (recursive_aggregation, fold) → RecursiveAggregationFoldVerifier + */ + private toContractName(group: CircuitGroup, name: string): string { + const pascal = (s: string) => + s + .split(/[_\-]+/) + .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()) + .join('') + return `${pascal(group)}${pascal(name)}Verifier` + } + + private checkTool(cmd: string, name: string): void { + try { + execSync(cmd, { stdio: ['pipe', 'pipe', 'pipe'] }) + } catch { + throw new Error(`${name} is not installed or not in PATH`) + } + } +} + +// --------------------------------------------------------------------------- +// CLI +// --------------------------------------------------------------------------- + +async function main() { + const args = process.argv.slice(2) + const options: GenerateOptions = {} + + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg === '-h' || arg === '--help') { + showHelp() + process.exit(0) + } else if (arg === '--dry-run') { + options.dryRun = true + } else if (arg === '--clean') { + options.clean = true + } else if (arg === '--no-compile') { + options.compile = false + } else if (arg === '--group') { + options.groups = args[++i]?.split(',') as CircuitGroup[] + } else if (arg === '--circuit') { + ;(options.circuits ??= []).push(args[++i]) + } else if (arg === '--oracle-hash') { + options.oracleHash = args[++i] + } + } + + const generator = new VerifierGenerator(undefined, options) + await generator.generate() +} + +function showHelp() { + console.log(` +Usage: generate-verifiers [options] + +Generates Solidity verifier contracts from compiled Noir circuits +and places them in packages/enclave-contracts/contracts/verifier/. + +Options: + --group Circuit groups (comma-separated: dkg,threshold,recursive_aggregation) + --circuit Generate verifier for specific circuit(s) (repeatable) + --clean Remove existing verifier directory before generating + --no-compile Don't compile circuits automatically (fail if not already compiled) + --oracle-hash Oracle hash scheme for VK generation (default: keccak) + --dry-run Show what would be generated without doing anything + -h, --help Show this help message + +Examples: + pnpm generate:verifiers # All circuits + pnpm generate:verifiers --group dkg # Only DKG circuits + pnpm generate:verifiers --group threshold --clean # Threshold only, clean first + pnpm generate:verifiers --circuit pk --circuit fold # Specific circuits +`) +} + +if (require.main === module) main() + +export { VerifierGenerator, GenerateOptions, CircuitGroup, CIRCUIT_GROUPS } From 97f008498a64fc46ffff0f5fd7668b107697344a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 11 Feb 2026 01:57:08 +0500 Subject: [PATCH 2/6] chore: lint --- scripts/generate-verifiers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/generate-verifiers.ts b/scripts/generate-verifiers.ts index d637be413d..25337168f1 100644 --- a/scripts/generate-verifiers.ts +++ b/scripts/generate-verifiers.ts @@ -180,7 +180,7 @@ class VerifierGenerator { // ------------------------------------------------------------------------- private generateVerifier(circuit: CircuitInfo): string { - const { name, group, path: circuitPath, packageName } = circuit + const { name, group, packageName } = circuit // 1. Compile if needed const jsonFile = this.ensureCompiled(circuit) @@ -307,7 +307,7 @@ class VerifierGenerator { private toContractName(group: CircuitGroup, name: string): string { const pascal = (s: string) => s - .split(/[_\-]+/) + .split(/[_-]+/) .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()) .join('') return `${pascal(group)}${pascal(name)}Verifier` From 0bce20eb89972b4fa4d0244bdf8d65589e611bae Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 11 Feb 2026 02:00:52 +0500 Subject: [PATCH 3/6] chore: lint --- packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol b/packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol index 985d4fa222..5b7da618f6 100644 --- a/packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol +++ b/packages/enclave-contracts/contracts/verifier/DkgPkVerifier.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2022 Aztec -pragma solidity >=0.8.21; +pragma solidity >=0.8.27; uint256 constant N = 8192; uint256 constant LOG_N = 13; From d3e74f878f6a565d98972911e63ab0b6b8d1d9f7 Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 11 Feb 2026 02:02:54 +0500 Subject: [PATCH 4/6] chore: lint --- packages/enclave-contracts/.solhintignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/enclave-contracts/.solhintignore b/packages/enclave-contracts/.solhintignore index 16dc0c0d7b..79b6675271 100644 --- a/packages/enclave-contracts/.solhintignore +++ b/packages/enclave-contracts/.solhintignore @@ -1,3 +1,6 @@ # directories **/artifacts **/node_modules + +# auto-generated verifier contracts (BB/Barretenberg) +contracts/verifier/**/*.sol From 5d8558e19f22ac2d032b21438c618b5da885303a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 11 Feb 2026 02:16:08 +0500 Subject: [PATCH 5/6] fix: review comments --- .../enclave-contracts/deployed_contracts.json | 6 ------ .../scripts/deployAndSave/verifiers.ts | 7 ++++++- scripts/generate-verifiers.ts | 21 ++++++++++++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/enclave-contracts/deployed_contracts.json b/packages/enclave-contracts/deployed_contracts.json index 45307b7918..98345114c4 100644 --- a/packages/enclave-contracts/deployed_contracts.json +++ b/packages/enclave-contracts/deployed_contracts.json @@ -107,12 +107,6 @@ "address": "0xC39b101f2FB4ea677c1EA18f92C15CDD54Af40c2" } }, - "undefined": { - "PoseidonT3": { - "blockNumber": 3, - "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" - } - }, "localhost": { "PoseidonT3": { "blockNumber": 338, diff --git a/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts b/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts index 7e573c7603..1f6efadb50 100644 --- a/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts +++ b/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts @@ -58,7 +58,8 @@ export const deployAndSaveVerifier = async ( hre: HardhatRuntimeEnvironment, ): Promise<{ address: string }> => { const { ethers } = await hre.network.connect(); - const chain = hre.globalOptions.network; + const [signer] = await ethers.getSigners(); + const chain = (await signer.provider?.getNetwork())?.name ?? "localhost"; // Check if already deployed const existing = readDeploymentArgs(contractName, chain); @@ -142,6 +143,10 @@ export const deployAndSaveAllVerifiers = async ( hre: HardhatRuntimeEnvironment, ): Promise => { const contractNames = discoverVerifierContracts(); + const { ethers } = await hre.network.connect(); + const [signer] = await ethers.getSigners(); + const chain = (await signer.provider?.getNetwork())?.name ?? "localhost"; + console.log(` Deploying to network: ${chain}`); if (contractNames.length === 0) { console.log( diff --git a/scripts/generate-verifiers.ts b/scripts/generate-verifiers.ts index 25337168f1..4fb2b81451 100644 --- a/scripts/generate-verifiers.ts +++ b/scripts/generate-verifiers.ts @@ -342,11 +342,26 @@ async function main() { } else if (arg === '--no-compile') { options.compile = false } else if (arg === '--group') { - options.groups = args[++i]?.split(',') as CircuitGroup[] + const value = args[++i] + if (!value || value.startsWith('--')) { + console.error('Error: --group requires a value') + process.exit(1) + } + options.groups = value.split(',') as CircuitGroup[] } else if (arg === '--circuit') { - ;(options.circuits ??= []).push(args[++i]) + const value = args[++i] + if (!value || value.startsWith('--')) { + console.error('Error: --circuit requires a value') + process.exit(1) + } + ;(options.circuits ??= []).push(value) } else if (arg === '--oracle-hash') { - options.oracleHash = args[++i] + const value = args[++i] + if (!value || value.startsWith('--')) { + console.error('Error: --oracle-hash requires a value') + process.exit(1) + } + options.oracleHash = value } } From f2661a68123bd5effedb16fbeed58ce9d4368c2a Mon Sep 17 00:00:00 2001 From: Hamza Khalid Date: Wed, 11 Feb 2026 05:27:36 +0500 Subject: [PATCH 6/6] fix: review comments --- .../enclave-contracts/deployed_contracts.json | 100 ++++++++-------- .../scripts/deployAndSave/verifiers.ts | 108 ++++++++---------- scripts/README.md | 91 +++++++++++++++ scripts/build-circuits.ts | 6 +- scripts/circuit-constants.ts | 19 +++ scripts/generate-verifiers.ts | 10 +- 6 files changed, 210 insertions(+), 124 deletions(-) create mode 100644 scripts/circuit-constants.ts diff --git a/packages/enclave-contracts/deployed_contracts.json b/packages/enclave-contracts/deployed_contracts.json index 98345114c4..8a6ac9cbca 100644 --- a/packages/enclave-contracts/deployed_contracts.json +++ b/packages/enclave-contracts/deployed_contracts.json @@ -109,45 +109,45 @@ }, "localhost": { "PoseidonT3": { - "blockNumber": 338, + "blockNumber": 29, "address": "0x3333333C0A88F9BE4fd23ed0536F9B6c427e3B93" }, "MockUSDC": { "constructorArgs": { "initialSupply": "1000000" }, - "blockNumber": 339, - "address": "0x1D13fF25b10C9a6741DFdce229073bed652197c7" + "blockNumber": 30, + "address": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933" }, "EnclaveToken": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 340, - "address": "0x24d41dbc3d60d0784f8a937c59FBDe51440D5140" + "blockNumber": 31, + "address": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E" }, "EnclaveTicketToken": { "constructorArgs": { - "baseToken": "0x1D13fF25b10C9a6741DFdce229073bed652197c7", + "baseToken": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933", "registry": "0x0000000000000000000000000000000000000001", "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, - "blockNumber": 342, - "address": "0x618fB9dbd2BD6eb968B4c1af36af6CB0b45310Ec" + "blockNumber": 33, + "address": "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB" }, "SlashingManager": { "constructorArgs": { "admin": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "bondingRegistry": "0x0000000000000000000000000000000000000001" }, - "blockNumber": 343, - "address": "0xa779C1D17bC5230c07afdC51376CAC1cb3Dd5314" + "blockNumber": 34, + "address": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042" }, "BondingRegistry": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ticketToken": "0x618fB9dbd2BD6eb968B4c1af36af6CB0b45310Ec", - "licenseToken": "0x24d41dbc3d60d0784f8a937c59FBDe51440D5140", + "ticketToken": "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB", + "licenseToken": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E", "registry": "0x0000000000000000000000000000000000000001", "slashedFundsTreasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "ticketPrice": "10000000", @@ -156,14 +156,14 @@ "exitDelay": "604800" }, "proxyRecords": { - "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000618fb9dbd2bd6eb968b4c1af36af6cb0b45310ec00000000000000000000000024d41dbc3d60d0784f8a937c59fbde51440d51400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", + "initData": "0x7333fa82000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000084ea74d481ee0a5332c457a4d796187f6ba67feb000000000000000000000000e6e340d132b5f46d1e472debcd681b2abc16e57e0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000009896800000000000000000000000000000000000000000000000056bc75e2d6310000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000093a80", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xA3307BF348ACC4bEDdd67CCA2f7F0c4349d347Db", - "proxyAdminAddress": "0xA7eDcd3F5d0985be01f8930229BFd3fDBA2F5034", - "implementationAddress": "0x76cec9299B6Fa418dc71416FF353737AB7933A7D" + "proxyAddress": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8", + "proxyAdminAddress": "0x9467A509DA43CB50EB332187602534991Be1fEa4", + "implementationAddress": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9" }, - "blockNumber": 343, - "address": "0xA3307BF348ACC4bEDdd67CCA2f7F0c4349d347Db" + "blockNumber": 34, + "address": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8" }, "CiphernodeRegistryOwnable": { "constructorArgs": { @@ -174,20 +174,20 @@ "proxyRecords": { "initData": "0x1794bb3c000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0xc0Bb1650A8eA5dDF81998f17B5319afD656f4c11", - "proxyAdminAddress": "0xdeB02111e4e5Fa8C7c05e983a3446893a36D12ad", - "implementationAddress": "0x313F922BE1649cEc058EC0f076664500c78bdc0b" + "proxyAddress": "0xf5059a5D33d5853360D16C683c16e67980206f36", + "proxyAdminAddress": "0x55652FF92Dc17a21AD6810Cce2F4703fa2339CAE", + "implementationAddress": "0x851356ae760d987E095750cCeb3bC6014560891C" }, - "blockNumber": 346, - "address": "0xc0Bb1650A8eA5dDF81998f17B5319afD656f4c11" + "blockNumber": 37, + "address": "0xf5059a5D33d5853360D16C683c16e67980206f36" }, "Enclave": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "registry": "0xc0Bb1650A8eA5dDF81998f17B5319afD656f4c11", - "bondingRegistry": "0xA3307BF348ACC4bEDdd67CCA2f7F0c4349d347Db", + "registry": "0xf5059a5D33d5853360D16C683c16e67980206f36", + "bondingRegistry": "0x1613beB3B2C4f22Ee086B2b38C1476A3cE7f78E8", "e3RefundManager": "0x0000000000000000000000000000000000000001", - "feeToken": "0x1D13fF25b10C9a6741DFdce229073bed652197c7", + "feeToken": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933", "maxDuration": "2592000", "timeoutConfig": "{\"committeeFormationWindow\":3600,\"dkgWindow\":7200,\"computeWindow\":86400,\"decryptionWindow\":3600,\"gracePeriod\":600}", "params": [ @@ -195,50 +195,50 @@ ] }, "proxyRecords": { - "initData": "0x69c5b347000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000c0bb1650a8ea5ddf81998f17b5319afd656f4c11000000000000000000000000a3307bf348acc4beddd67cca2f7f0c4349d347db00000000000000000000000000000000000000000000000000000000000000010000000000000000000000001d13ff25b10c9a6741dfdce229073bed652197c70000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e100000000000000000000000000000000000000000000000000000000000000258000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000", + "initData": "0x69c5b347000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000f5059a5d33d5853360d16c683c16e67980206f360000000000000000000000001613beb3b2c4f22ee086b2b38c1476a3ce7f78e8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000067d269191c92caf3cd7723f116c85e6e9bf559330000000000000000000000000000000000000000000000000000000000278d000000000000000000000000000000000000000000000000000000000000001c2000000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e100000000000000000000000000000000000000000000000000000000000000258000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000ffffee0010000000000000000000000000000000000000000000000000000000ffffc400100000000000000000000000000000000000000000000000000000000000000013300000000000000000000000000000000000000000000000000000000000000", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x90c84237fDdf091b1E63f369AF122EB46000bc70", - "proxyAdminAddress": "0xd119D184628e094322007cEa4F2535Ec3A06E6b1", - "implementationAddress": "0x5322471a7E37Ac2B8902cFcba84d266b37D811A0" + "proxyAddress": "0x998abeb3E57409262aE5b751f60747921B33613E", + "proxyAdminAddress": "0xb4dC171C0edEc8C0032cd0f2d30921c09FA35e34", + "implementationAddress": "0x95401dc811bb5740090279Ba06cfA8fcF6113778" }, - "blockNumber": 348, - "address": "0x90c84237fDdf091b1E63f369AF122EB46000bc70" + "blockNumber": 39, + "address": "0x998abeb3E57409262aE5b751f60747921B33613E" }, "E3RefundManager": { "constructorArgs": { "owner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "enclave": "0x90c84237fDdf091b1E63f369AF122EB46000bc70", + "enclave": "0x998abeb3E57409262aE5b751f60747921B33613E", "treasury": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, "proxyRecords": { - "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000090c84237fddf091b1e63f369af122eb46000bc70000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "initData": "0xc0c53b8b000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000998abeb3e57409262ae5b751f60747921b33613e000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", "initialOwner": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "proxyAddress": "0x103A3b128991781EE2c8db0454cA99d67b257923", - "proxyAdminAddress": "0xD8Fdf2Adc2F6502755af003661664a695ECC7d12", - "implementationAddress": "0x3D63c50AD04DD5aE394CAB562b7691DD5de7CF6f" + "proxyAddress": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528", + "proxyAdminAddress": "0xca4211da53d1bbab819B03138302a21d6F6B7647", + "implementationAddress": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49" }, - "blockNumber": 350, - "address": "0x103A3b128991781EE2c8db0454cA99d67b257923" + "blockNumber": 41, + "address": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528" }, "MockComputeProvider": { - "blockNumber": 361, - "address": "0x20d7B364E8Ed1F4260b5B90C41c2deC3C1F6D367" + "blockNumber": 52, + "address": "0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154" }, "MockDecryptionVerifier": { - "blockNumber": 362, - "address": "0xf5C3953Ae4639806fcbCC3196f71dd81B0da4348" + "blockNumber": 53, + "address": "0xb7278A61aa25c888815aFC32Ad3cC52fF24fE575" }, "MockE3Program": { - "blockNumber": 363, - "address": "0x90b97E83e22AFa2e6A96b3549A0E495D5Bae61aF" + "blockNumber": 54, + "address": "0xCD8a1C3ba11CF5ECfa6267617243239504a98d90" }, - "DkgPkVerifier_ZKTranscriptLib": { - "blockNumber": 366, - "address": "0xccA9728291bC98ff4F97EF57Be3466227b0eb06C" + "ZKTranscriptLib": { + "blockNumber": 57, + "address": "0x7969c5eD335650692Bc04293B07F5BF2e7A673C0" }, "DkgPkVerifier": { - "blockNumber": 367, - "address": "0xc6B407503dE64956Ad3cF5Ab112cA4f56AA13517" + "blockNumber": 58, + "address": "0x7bc06c482DEAd17c0e297aFbC32f6e63d3846650" } } } \ No newline at end of file diff --git a/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts b/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts index 1f6efadb50..97ebf320e6 100644 --- a/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts +++ b/packages/enclave-contracts/scripts/deployAndSave/verifiers.ts @@ -26,36 +26,51 @@ export const discoverVerifierContracts = (): string[] => { }; /** - * Reads a compiled artifact to extract unlinked library references. - * Returns a map of fully-qualified library name → library name. + * Deploys ZKTranscriptLib library required by BB-generated verifiers. + * Reuses existing deployment if already deployed on the chain. */ -const getRequiredLibraries = async ( - contractName: string, +const deployZKTranscriptLib = async ( hre: HardhatRuntimeEnvironment, -): Promise => { - const artifact = await hre.artifacts.readArtifact(contractName); - const linkRefs = artifact.bytecode.match(/__\$[a-f0-9]{34}\$__/g); - if (!linkRefs || linkRefs.length === 0) return []; - - // Extract library names from linkReferences in the artifact - const libraries: string[] = []; - const linkReferences = (artifact as any).linkReferences ?? {}; - for (const source of Object.keys(linkReferences)) { - for (const libName of Object.keys(linkReferences[source])) { - libraries.push(`${source}:${libName}`); - } + chain: string, +): Promise => { + const libName = "ZKTranscriptLib"; + + // Check if library is already deployed + const existing = readDeploymentArgs(libName, chain); + if (existing?.address) { + console.log(` ${libName} already deployed at ${existing.address}`); + return existing.address; } - return libraries; + + // Deploy the library + console.log(` Deploying ${libName}...`); + const { ethers } = await hre.network.connect(); + const factory = await ethers.getContractFactory(libName); + const contract = await factory.deploy(); + await contract.waitForDeployment(); + + const address = await contract.getAddress(); + const blockNumber = await ethers.provider.getBlockNumber(); + + storeDeploymentArgs({ blockNumber, address }, libName, chain); + + console.log(` ${libName} deployed to: ${address}`); + return address; }; /** * Deploys a single verifier contract and saves the deployment record. - * Automatically detects and deploys any libraries the verifier depends on. + * BB-generated verifiers require ZKTranscriptLib to be linked. * Skips deployment if the contract is already deployed on the target chain. + * + * Note: The library FQN (fully-qualified name) uses the pattern: + * "contracts/verifier/.sol:ZKTranscriptLib" + * If you get linking errors, check the contract's compiled artifact for the exact FQN. */ export const deployAndSaveVerifier = async ( contractName: string, hre: HardhatRuntimeEnvironment, + zkTranscriptLibAddress: string, ): Promise<{ address: string }> => { const { ethers } = await hre.network.connect(); const [signer] = await ethers.getSigners(); @@ -68,48 +83,14 @@ export const deployAndSaveVerifier = async ( return { address: existing.address }; } - // Detect and deploy required libraries - const requiredLibs = await getRequiredLibraries(contractName, hre); - const libraries: Record = {}; - - for (const fqn of requiredLibs) { - const libName = fqn.split(":").pop()!; - const libStorageKey = `${contractName}_${libName}`; - - // Check if library is already deployed - const existingLib = readDeploymentArgs(libStorageKey, chain); - if (existingLib?.address) { - console.log( - ` ${libName} library already deployed at ${existingLib.address}`, - ); - libraries[fqn] = existingLib.address; - continue; - } - - // Deploy the library - console.log(` Deploying library ${libName}...`); - const libFactory = await ethers.getContractFactory(libName); - const libContract = await libFactory.deploy(); - await libContract.waitForDeployment(); - const libAddress = await libContract.getAddress(); - const libBlockNumber = await ethers.provider.getBlockNumber(); - - storeDeploymentArgs( - { blockNumber: libBlockNumber, address: libAddress }, - libStorageKey, - chain, - ); - - console.log(` ${libName} library deployed to: ${libAddress}`); - libraries[fqn] = libAddress; - } - - // Deploy the verifier contract with linked libraries - const factory = - Object.keys(libraries).length > 0 - ? await ethers.getContractFactory(contractName, { libraries }) - : await ethers.getContractFactory(contractName); + // Link ZKTranscriptLib - FQN pattern: "contracts/verifier/.sol:ZKTranscriptLib" + const libraryFQN = `project/contracts/verifier/${contractName}.sol:ZKTranscriptLib`; + const libraries = { + [libraryFQN]: zkTranscriptLibAddress, + }; + // Deploy the verifier contract with linked library + const factory = await ethers.getContractFactory(contractName, { libraries }); const contract = await factory.deploy(); await contract.waitForDeployment(); @@ -157,10 +138,17 @@ export const deployAndSaveAllVerifiers = async ( console.log(` Found ${contractNames.length} verifier contract(s)`); + // Deploy ZKTranscriptLib once, reused by all verifiers + const zkTranscriptLibAddress = await deployZKTranscriptLib(hre, chain); + const deployments: VerifierDeployments = {}; for (const name of contractNames) { - const { address } = await deployAndSaveVerifier(name, hre); + const { address } = await deployAndSaveVerifier( + name, + hre, + zkTranscriptLibAddress, + ); deployments[name] = address; } diff --git a/scripts/README.md b/scripts/README.md index 00d9c34aac..5eceb047b8 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -240,3 +240,94 @@ Circuits are built locally and stored in a git branch: 2. **CI**: Pulls from branch during release, attaches to GitHub release 3. **After release**: Circuits live permanently in release assets + +## Verifier Generator + +`generate-verifiers.ts` - Generates Solidity verifier contracts from compiled Noir circuits. + +### Usage + +```bash +# Generate verifiers for all circuits +pnpm generate:verifiers + +# Generate only for specific group +pnpm generate:verifiers --group dkg +pnpm generate:verifiers --group threshold + +# Generate for specific circuit(s) +pnpm generate:verifiers --circuit pk +pnpm generate:verifiers --circuit pk --circuit fold + +# Clean existing verifiers before generating +pnpm generate:verifiers --clean + +# Preview what would be generated +pnpm generate:verifiers --dry-run + +# Skip auto-compilation (requires pre-built circuits) +pnpm generate:verifiers --no-compile + +# Specify oracle hash scheme for VK generation +pnpm generate:verifiers --oracle-hash keccak +``` + +### What it does + +Automates the full pipeline from Noir circuits to on-chain Solidity verifiers: + +1. **Discovers circuits** in `circuits/bin/{dkg,threshold,recursive_aggregation}/` +2. **Compiles circuits** with `nargo compile` (if not already compiled) +3. **Generates verification keys** using `bb write_vk --oracle_hash keccak` +4. **Generates Solidity verifiers** using `bb write_solidity_verifier` +5. **Post-processes** the generated Solidity: + - Renames contract from `HonkVerifier` to descriptive name (e.g., `DkgPkVerifier`, + `ThresholdPkGenerationVerifier`) + - Replaces Apache-2.0 license header with LGPL-3.0-only +6. **Outputs** to `packages/enclave-contracts/contracts/verifier/` + +### Options + +- `--group ` - Circuit groups (comma-separated: dkg,threshold,recursive_aggregation) +- `--circuit ` - Generate verifier for specific circuit(s) (repeatable) +- `--clean` - Remove existing verifier directory before generating +- `--no-compile` - Don't compile circuits automatically (fail if not already compiled) +- `--oracle-hash ` - Oracle hash scheme for VK generation (default: keccak) +- `--dry-run` - Show what would be generated without doing anything +- `-h, --help` - Show help message + +### Prerequisites + +- `nargo` - Noir compiler ([install](https://noir-lang.org/docs/getting_started/installation/)) +- `bb` - Barretenberg CLI for proof system operations + +### Output Example + +``` +šŸ”® Generating Solidity verifiers from Noir circuits... + + Found 13 circuit(s) + + āœ“ dkg/pk → DkgPkVerifier.sol + āœ“ dkg/sk_share_computation → DkgSkShareComputationVerifier.sol + āœ“ threshold/pk_generation → ThresholdPkGenerationVerifier.sol + ... + +āœ… Generated 13 Solidity verifier(s) in: + packages/enclave-contracts/contracts/verifier/ +``` + +### Integration + +Generated verifiers are automatically: + +- Compiled with aggressive size optimization (`runs: 1` in Hardhat config) +- Deployed via `pnpm deploy` (integrated into main deployment flow) +- Saved to `deployed_contracts.json` +- Verified on block explorers via `pnpm verify:contracts` + +### Notes + +- Verifier contracts are large (~24KB) due to pairing cryptography +- Library linking (e.g., `ZKTranscriptLib`) is handled automatically during deployment +- Generated files are excluded from linting (`.solhintignore`) diff --git a/scripts/build-circuits.ts b/scripts/build-circuits.ts index 21c460e34c..41f64a3150 100644 --- a/scripts/build-circuits.ts +++ b/scripts/build-circuits.ts @@ -9,11 +9,7 @@ import { execSync } from 'child_process' import { createHash } from 'crypto' import { appendFileSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs' import { basename, join, resolve } from 'path' - -// Types & Constants -const CIRCUIT_GROUPS = { DKG: 'dkg', THRESHOLD: 'threshold', AGGREGATION: 'recursive_aggregation' } as const -type CircuitGroup = (typeof CIRCUIT_GROUPS)[keyof typeof CIRCUIT_GROUPS] -const ALL_GROUPS: CircuitGroup[] = [CIRCUIT_GROUPS.DKG, CIRCUIT_GROUPS.THRESHOLD, CIRCUIT_GROUPS.AGGREGATION] +import { ALL_GROUPS, CIRCUIT_GROUPS, type CircuitGroup } from './circuit-constants' interface CircuitInfo { name: string diff --git a/scripts/circuit-constants.ts b/scripts/circuit-constants.ts new file mode 100644 index 0000000000..a0fd798130 --- /dev/null +++ b/scripts/circuit-constants.ts @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +/** + * Shared constants for circuit groups used across build and verifier generation scripts. + */ + +export const CIRCUIT_GROUPS = { + DKG: 'dkg', + THRESHOLD: 'threshold', + AGGREGATION: 'recursive_aggregation', +} as const + +export type CircuitGroup = (typeof CIRCUIT_GROUPS)[keyof typeof CIRCUIT_GROUPS] + +export const ALL_GROUPS: CircuitGroup[] = [CIRCUIT_GROUPS.DKG, CIRCUIT_GROUPS.THRESHOLD, CIRCUIT_GROUPS.AGGREGATION] diff --git a/scripts/generate-verifiers.ts b/scripts/generate-verifiers.ts index 4fb2b81451..a9ac95e07f 100644 --- a/scripts/generate-verifiers.ts +++ b/scripts/generate-verifiers.ts @@ -25,20 +25,12 @@ import { execSync } from 'child_process' import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'fs' import { basename, join, resolve } from 'path' +import { ALL_GROUPS, CIRCUIT_GROUPS, type CircuitGroup } from './circuit-constants' // --------------------------------------------------------------------------- // Types & constants // --------------------------------------------------------------------------- -const CIRCUIT_GROUPS = { - DKG: 'dkg', - THRESHOLD: 'threshold', - AGGREGATION: 'recursive_aggregation', -} as const - -type CircuitGroup = (typeof CIRCUIT_GROUPS)[keyof typeof CIRCUIT_GROUPS] -const ALL_GROUPS: CircuitGroup[] = [CIRCUIT_GROUPS.DKG, CIRCUIT_GROUPS.THRESHOLD, CIRCUIT_GROUPS.AGGREGATION] - const LICENSE_HEADER = `// SPDX-License-Identifier: LGPL-3.0-only // // This file is provided WITHOUT ANY WARRANTY;