diff --git a/barretenberg/acir_tests/sol-test/package.json b/barretenberg/acir_tests/sol-test/package.json index a13cd369c14..f346b9e42f6 100644 --- a/barretenberg/acir_tests/sol-test/package.json +++ b/barretenberg/acir_tests/sol-test/package.json @@ -9,6 +9,6 @@ }, "dependencies": { "ethers": "^6.8.1", - "solc": "^0.8.27" + "solc": "^0.8.22" } } diff --git a/barretenberg/acir_tests/sol-test/src/index.js b/barretenberg/acir_tests/sol-test/src/index.js index 42749f88d96..25a25484ba6 100644 --- a/barretenberg/acir_tests/sol-test/src/index.js +++ b/barretenberg/acir_tests/sol-test/src/index.js @@ -5,7 +5,9 @@ import { ethers } from "ethers"; import solc from "solc"; const NUMBER_OF_FIELDS_IN_PLONK_PROOF = 93; -const NUMBER_OF_FIELDS_IN_HONK_PROOF = 447; +// TODO(https://github.com/AztecProtocol/barretenberg/issues/1093): This is the size of the proof up to Sumcheck, without public inputs, as the Honk contract does not currently have a PCS. +// This needs to be changed once Shplemini is implemented in the smart contract. +const NUMBER_OF_FIELDS_IN_HONK_PROOF = 303; // We use the solcjs compiler version in this test, although it is slower than foundry, to run the test end to end // it simplifies of parallelising the test suite diff --git a/barretenberg/acir_tests/sol-test/yarn.lock b/barretenberg/acir_tests/sol-test/yarn.lock index c0a90997654..af80282ea95 100644 --- a/barretenberg/acir_tests/sol-test/yarn.lock +++ b/barretenberg/acir_tests/sol-test/yarn.lock @@ -77,10 +77,10 @@ semver@^5.5.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -solc@^0.8.27: - version "0.8.27" - resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.27.tgz#cb8e7246cceadad8df65ceccffe640e106106bb4" - integrity sha512-BNxMol2tUAbkH7HKlXBcBqrGi2aqgv+uMHz26mJyTtlVgWmBA4ktiw0qVKHfkjf2oaHbwtbtaSeE2dhn/gTAKw== +solc@^0.8.22: + version "0.8.22" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.22.tgz#6df0bb688b9a58bbf10932730301374a6ccfb862" + integrity sha512-bA2tMZXx93R8L5LUH7TlB/f+QhkVyxrrY6LmgJnFFZlRknrhYVlBK1e3uHIdKybwoFabOFSzeaZjPeL/GIpFGQ== dependencies: command-exists "^1.2.8" commander "^8.1.0" diff --git a/barretenberg/cpp/src/barretenberg/bb/main.cpp b/barretenberg/cpp/src/barretenberg/bb/main.cpp index 052ea3774e6..07fabf7029d 100644 --- a/barretenberg/cpp/src/barretenberg/bb/main.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/main.cpp @@ -1110,6 +1110,13 @@ void prove_honk(const std::string& bytecodePath, const std::string& witnessPath, // Construct Honk proof Prover prover = compute_valid_prover(bytecodePath, witnessPath); auto proof = prover.construct_proof(); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1093): As the Smart contract doesn't verify the PCS and + // Shplemini is not constant size, we slice the proof up to sumcheck so calculation of public inputs is correct. + // This hack will be subsequently removed. + if constexpr (std::same_as) { + auto num_public_inputs = static_cast(prover.proving_key->proving_key.num_public_inputs); + proof.erase(proof.begin() + num_public_inputs + 303, proof.end()); + } if (outputPath == "-") { writeRawBytesToStdout(to_buffer(proof)); vinfo("proof written to stdout"); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/honk_contract.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/honk_contract.hpp index 95e37c1fbeb..7a451cd81f2 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/honk_contract.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_proofs/honk_contract.hpp @@ -5,8 +5,6 @@ // Source code for the Ultrahonk Solidity verifier. // It's expected that the AcirComposer will inject a library which will load the verification key into memory. const std::string HONK_CONTRACT_SOURCE = R"( -pragma solidity ^0.8.27; - type Fr is uint256; using { add as + } for Fr global; @@ -93,18 +91,6 @@ library FrLib { return numerator * invert(denominator); } - - function sqr(Fr value) internal pure returns (Fr) { - return value * value; - } - - function unwrap(Fr value) internal pure returns (uint256) { - return Fr.unwrap(value); - } - - function neg(Fr value) internal pure returns (Fr) { - return Fr.wrap(MODULUS - Fr.unwrap(value)); - } } // Free functions @@ -125,8 +111,9 @@ function sub(Fr a, Fr b) pure returns(Fr) function exp(Fr base, Fr exponent) pure returns(Fr) { - if (Fr.unwrap(exponent) == 0) return Fr.wrap(1); - + 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; } @@ -276,11 +263,6 @@ library Honk { // Sumcheck Fr[BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; - // Shplemini - Honk.G1ProofPoint[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; - Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; - Honk.G1ProofPoint shplonkQ; - Honk.G1ProofPoint kzgQuotient; } } @@ -295,19 +277,15 @@ struct Transcript { Fr[NUMBER_OF_ALPHAS] alphas; Fr[CONST_PROOF_SIZE_LOG_N] gateChallenges; Fr[CONST_PROOF_SIZE_LOG_N] sumCheckUChallenges; - // Shplemini - Fr rho; - Fr geminiR; - Fr shplonkNu; - Fr shplonkZ; // Derived Fr publicInputsDelta; + Fr lookupGrandProductDelta; } library TranscriptLib { function generateTranscript(Honk.Proof memory proof, bytes32[] calldata publicInputs, uint256 publicInputsSize) internal - pure + view returns (Transcript memory t) { Fr previousChallenge; @@ -321,14 +299,6 @@ library TranscriptLib { (t.sumCheckUChallenges, previousChallenge) = generateSumcheckChallenges(proof, previousChallenge); - (t.rho, previousChallenge) = generateRhoChallenge(proof, previousChallenge); - - (t.geminiR, previousChallenge) = generateGeminiRChallenge(proof, previousChallenge); - - (t.shplonkNu, previousChallenge) = generateShplonkNuChallenge(proof, previousChallenge); - - (t.shplonkZ, previousChallenge) = generateShplonkZChallenge(proof, previousChallenge); - return t; } @@ -342,31 +312,31 @@ library TranscriptLib { function generateEtaChallenge(Honk.Proof memory proof, bytes32[] calldata publicInputs, uint256 publicInputsSize) internal - pure + view returns (Fr eta, Fr etaTwo, Fr etaThree, Fr previousChallenge) { - bytes32[] memory round0 = new bytes32[](3 + publicInputsSize + 12); + bytes32[] memory round0 = new bytes32[](3 + NUMBER_OF_PUBLIC_INPUTS + 12); round0[0] = bytes32(proof.circuitSize); round0[1] = bytes32(proof.publicInputsSize); round0[2] = bytes32(proof.publicInputsOffset); - for (uint256 i = 0; i < publicInputsSize; i++) { + for (uint256 i = 0; i < NUMBER_OF_PUBLIC_INPUTS; i++) { round0[3 + i] = bytes32(publicInputs[i]); } // Create the first challenge // Note: w4 is added to the challenge later on - round0[3 + publicInputsSize] = bytes32(proof.w1.x_0); - round0[3 + publicInputsSize + 1] = bytes32(proof.w1.x_1); - round0[3 + publicInputsSize + 2] = bytes32(proof.w1.y_0); - round0[3 + publicInputsSize + 3] = bytes32(proof.w1.y_1); - round0[3 + publicInputsSize + 4] = bytes32(proof.w2.x_0); - round0[3 + publicInputsSize + 5] = bytes32(proof.w2.x_1); - round0[3 + publicInputsSize + 6] = bytes32(proof.w2.y_0); - round0[3 + publicInputsSize + 7] = bytes32(proof.w2.y_1); - round0[3 + publicInputsSize + 8] = bytes32(proof.w3.x_0); - round0[3 + publicInputsSize + 9] = bytes32(proof.w3.x_1); - round0[3 + publicInputsSize + 10] = bytes32(proof.w3.y_0); - round0[3 + publicInputsSize + 11] = bytes32(proof.w3.y_1); + round0[3 + NUMBER_OF_PUBLIC_INPUTS] = bytes32(proof.w1.x_0); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 1] = bytes32(proof.w1.x_1); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 2] = bytes32(proof.w1.y_0); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 3] = bytes32(proof.w1.y_1); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 4] = bytes32(proof.w2.x_0); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 5] = bytes32(proof.w2.x_1); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 6] = bytes32(proof.w2.y_0); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 7] = bytes32(proof.w2.y_1); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 8] = bytes32(proof.w3.x_0); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 9] = bytes32(proof.w3.x_1); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 10] = bytes32(proof.w3.y_0); + round0[3 + NUMBER_OF_PUBLIC_INPUTS + 11] = bytes32(proof.w3.y_1); previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(round0))); (eta, etaTwo) = splitChallenge(previousChallenge); @@ -375,10 +345,7 @@ library TranscriptLib { (etaThree, unused) = splitChallenge(previousChallenge); } - function generateBetaAndGammaChallenges(Fr previousChallenge, Honk.Proof memory proof) - internal - pure - returns (Fr beta, Fr gamma, Fr nextPreviousChallenge) + function generateBetaAndGammaChallenges(Fr previousChallenge, Honk.Proof memory proof) internal view returns (Fr beta, Fr gamma, Fr nextPreviousChallenge) { bytes32[13] memory round1; round1[0] = FrLib.toBytes32(previousChallenge); @@ -400,10 +367,7 @@ library TranscriptLib { } // Alpha challenges non-linearise the gate contributions - function generateAlphaChallenges(Fr previousChallenge, Honk.Proof memory proof) - internal - pure - returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge) + function generateAlphaChallenges(Fr previousChallenge, Honk.Proof memory proof) internal view returns (Fr[NUMBER_OF_ALPHAS] memory alphas, Fr nextPreviousChallenge) { // Generate the original sumcheck alpha 0 by hashing zPerm and zLookup uint256[9] memory alpha0; @@ -431,10 +395,7 @@ library TranscriptLib { } } - function generateGateChallenges(Fr previousChallenge) - internal - pure - returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge) + function generateGateChallenges(Fr previousChallenge) internal view returns (Fr[CONST_PROOF_SIZE_LOG_N] memory gateChallenges, Fr nextPreviousChallenge) { for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; i++) { previousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(Fr.unwrap(previousChallenge)))); @@ -444,15 +405,13 @@ library TranscriptLib { nextPreviousChallenge = previousChallenge; } - function generateSumcheckChallenges(Honk.Proof memory proof, Fr prevChallenge) - internal - pure - returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge) + function generateSumcheckChallenges(Honk.Proof memory proof, Fr prevChallenge) internal view returns (Fr[CONST_PROOF_SIZE_LOG_N] memory sumcheckChallenges, Fr nextPreviousChallenge) { for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; i++) { Fr[BATCHED_RELATION_PARTIAL_LENGTH + 1] memory univariateChal; univariateChal[0] = prevChallenge; + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1098): memcpy for (uint256 j = 0; j < BATCHED_RELATION_PARTIAL_LENGTH; j++) { univariateChal[j + 1] = proof.sumcheckUnivariates[i][j]; } @@ -462,218 +421,6 @@ library TranscriptLib { } nextPreviousChallenge = prevChallenge; } - - function generateRhoChallenge(Honk.Proof memory proof, Fr prevChallenge) - internal - pure - returns (Fr rho, Fr nextPreviousChallenge) - { - Fr[NUMBER_OF_ENTITIES + 1] memory rhoChallengeElements; - rhoChallengeElements[0] = prevChallenge; - - for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { - rhoChallengeElements[i + 1] = proof.sumcheckEvaluations[i]; - } - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(rhoChallengeElements))); - Fr unused; - (rho, unused) = splitChallenge(nextPreviousChallenge); - } - - function generateGeminiRChallenge(Honk.Proof memory proof, Fr prevChallenge) - internal - pure - returns (Fr geminiR, Fr nextPreviousChallenge) - { - uint256[(CONST_PROOF_SIZE_LOG_N - 1) * 4 + 1] memory gR; - gR[0] = Fr.unwrap(prevChallenge); - - for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N - 1; i++) { - gR[1 + i * 4] = proof.geminiFoldComms[i].x_0; - gR[2 + i * 4] = proof.geminiFoldComms[i].x_1; - gR[3 + i * 4] = proof.geminiFoldComms[i].y_0; - gR[4 + i * 4] = proof.geminiFoldComms[i].y_1; - } - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(gR))); - Fr unused; - (geminiR, unused) = splitChallenge(nextPreviousChallenge); - } - - function generateShplonkNuChallenge(Honk.Proof memory proof, Fr prevChallenge) - internal - pure - returns (Fr shplonkNu, Fr nextPreviousChallenge) - { - uint256[(CONST_PROOF_SIZE_LOG_N) + 1] memory shplonkNuChallengeElements; - shplonkNuChallengeElements[0] = Fr.unwrap(prevChallenge); - - for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; i++) { - shplonkNuChallengeElements[i + 1] = Fr.unwrap(proof.geminiAEvaluations[i]); - } - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkNuChallengeElements))); - Fr unused; - (shplonkNu, unused) = splitChallenge(nextPreviousChallenge); - } - - function generateShplonkZChallenge(Honk.Proof memory proof, Fr prevChallenge) - internal - pure - returns (Fr shplonkZ, Fr nextPreviousChallenge) - { - uint256[5] memory shplonkZChallengeElements; - shplonkZChallengeElements[0] = Fr.unwrap(prevChallenge); - - shplonkZChallengeElements[1] = proof.shplonkQ.x_0; - shplonkZChallengeElements[2] = proof.shplonkQ.x_1; - shplonkZChallengeElements[3] = proof.shplonkQ.y_0; - shplonkZChallengeElements[4] = proof.shplonkQ.y_1; - - nextPreviousChallenge = FrLib.fromBytes32(keccak256(abi.encodePacked(shplonkZChallengeElements))); - Fr unused; - (shplonkZ, unused) = splitChallenge(nextPreviousChallenge); - } - - function loadProof(bytes calldata proof) internal pure returns (Honk.Proof memory) { - Honk.Proof memory p; - - // Metadata - p.circuitSize = uint256(bytes32(proof[0x00:0x20])); - p.publicInputsSize = uint256(bytes32(proof[0x20:0x40])); - p.publicInputsOffset = uint256(bytes32(proof[0x40:0x60])); - - // Commitments - p.w1 = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[0x60:0x80])), - x_1: uint256(bytes32(proof[0x80:0xa0])), - y_0: uint256(bytes32(proof[0xa0:0xc0])), - y_1: uint256(bytes32(proof[0xc0:0xe0])) - }); - - p.w2 = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[0xe0:0x100])), - x_1: uint256(bytes32(proof[0x100:0x120])), - y_0: uint256(bytes32(proof[0x120:0x140])), - y_1: uint256(bytes32(proof[0x140:0x160])) - }); - p.w3 = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[0x160:0x180])), - x_1: uint256(bytes32(proof[0x180:0x1a0])), - y_0: uint256(bytes32(proof[0x1a0:0x1c0])), - y_1: uint256(bytes32(proof[0x1c0:0x1e0])) - }); - - // Lookup / Permutation Helper Commitments - p.lookupReadCounts = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[0x1e0:0x200])), - x_1: uint256(bytes32(proof[0x200:0x220])), - y_0: uint256(bytes32(proof[0x220:0x240])), - y_1: uint256(bytes32(proof[0x240:0x260])) - }); - p.lookupReadTags = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[0x260:0x280])), - x_1: uint256(bytes32(proof[0x280:0x2a0])), - y_0: uint256(bytes32(proof[0x2a0:0x2c0])), - y_1: uint256(bytes32(proof[0x2c0:0x2e0])) - }); - p.w4 = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[0x2e0:0x300])), - x_1: uint256(bytes32(proof[0x300:0x320])), - y_0: uint256(bytes32(proof[0x320:0x340])), - y_1: uint256(bytes32(proof[0x340:0x360])) - }); - p.lookupInverses = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[0x360:0x380])), - x_1: uint256(bytes32(proof[0x380:0x3a0])), - y_0: uint256(bytes32(proof[0x3a0:0x3c0])), - y_1: uint256(bytes32(proof[0x3c0:0x3e0])) - }); - p.zPerm = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[0x3e0:0x400])), - x_1: uint256(bytes32(proof[0x400:0x420])), - y_0: uint256(bytes32(proof[0x420:0x440])), - y_1: uint256(bytes32(proof[0x440:0x460])) - }); - - // Boundary represents a pointer to the head of the unread part of the proof - uint256 boundary = 0x460; - - // Sumcheck univariates - for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; i++) { - // The loop boundary of i, this will shift forward on each evaluation - uint256 loop_boundary = boundary + (i * 0x20 * BATCHED_RELATION_PARTIAL_LENGTH); - - for (uint256 j = 0; j < BATCHED_RELATION_PARTIAL_LENGTH; j++) { - uint256 start = loop_boundary + (j * 0x20); - uint256 end = start + 0x20; - p.sumcheckUnivariates[i][j] = FrLib.fromBytes32(bytes32(proof[start:end])); - } - } - - boundary = boundary + (CONST_PROOF_SIZE_LOG_N * BATCHED_RELATION_PARTIAL_LENGTH * 0x20); - // Sumcheck evaluations - for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { - uint256 start = boundary + (i * 0x20); - uint256 end = start + 0x20; - p.sumcheckEvaluations[i] = FrLib.fromBytes32(bytes32(proof[start:end])); - } - - boundary = boundary + (NUMBER_OF_ENTITIES * 0x20); - - // Gemini - // Read gemini fold univariates - for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N - 1; i++) { - uint256 xStart = boundary + (i * 0x80); - uint256 xEnd = xStart + 0x20; - - uint256 x1Start = xEnd; - uint256 x1End = x1Start + 0x20; - - uint256 yStart = x1End; - uint256 yEnd = yStart + 0x20; - - uint256 y1Start = yEnd; - uint256 y1End = y1Start + 0x20; - p.geminiFoldComms[i] = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[xStart:xEnd])), - x_1: uint256(bytes32(proof[x1Start:x1End])), - y_0: uint256(bytes32(proof[yStart:yEnd])), - y_1: uint256(bytes32(proof[y1Start:y1End])) - }); - } - - boundary = boundary + ((CONST_PROOF_SIZE_LOG_N - 1) * 0x80); - - // Read gemini a evaluations - for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; i++) { - uint256 start = boundary + (i * 0x20); - uint256 end = start + 0x20; - p.geminiAEvaluations[i] = FrLib.fromBytes32(bytes32(proof[start:end])); - } - - boundary = boundary + (CONST_PROOF_SIZE_LOG_N * 0x20); - - // Shplonk - p.shplonkQ = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[boundary:boundary + 0x20])), - x_1: uint256(bytes32(proof[boundary + 0x20:boundary + 0x40])), - y_0: uint256(bytes32(proof[boundary + 0x40:boundary + 0x60])), - y_1: uint256(bytes32(proof[boundary + 0x60:boundary + 0x80])) - }); - - boundary = boundary + 0x80; - - // KZG - p.kzgQuotient = Honk.G1ProofPoint({ - x_0: uint256(bytes32(proof[boundary:boundary + 0x20])), - x_1: uint256(bytes32(proof[boundary + 0x20:boundary + 0x40])), - y_0: uint256(bytes32(proof[boundary + 0x40:boundary + 0x60])), - y_1: uint256(bytes32(proof[boundary + 0x60:boundary + 0x80])) - }); - - return p; - } } // EC Point utilities @@ -721,7 +468,7 @@ library RelationsLib { function accumulateRelationEvaluations(Honk.Proof memory proof, Transcript memory tp, Fr powPartialEval) internal - pure + view returns (Fr accumulator) { Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations = proof.sumcheckEvaluations; @@ -734,22 +481,24 @@ library RelationsLib { accumulateDeltaRangeRelation(purportedEvaluations, evaluations, powPartialEval); accumulateEllipticRelation(purportedEvaluations, evaluations, powPartialEval); accumulateAuxillaryRelation(purportedEvaluations, tp, evaluations, powPartialEval); - accumulatePoseidonExternalRelation(purportedEvaluations, evaluations, powPartialEval); - accumulatePoseidonInternalRelation(purportedEvaluations, evaluations, powPartialEval); + accumulatePoseidonExternalRelation(purportedEvaluations, tp, evaluations, powPartialEval); + accumulatePoseidonInternalRelation(purportedEvaluations, tp, evaluations, powPartialEval); // batch the subrelations with the alpha challenges to obtain the full honk relation accumulator = scaleAndBatchSubrelations(evaluations, tp.alphas); } /** - * Aesthetic helper function that is used to index by enum into proof.sumcheckEvaluations, it avoids + * WIRE + * + * Wire is an 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) { + 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 * @@ -758,15 +507,16 @@ library RelationsLib { Fr[NUMBER_OF_ENTITIES] memory p, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep - ) internal pure { + ) internal view { // Relation 0 Fr q_arith = wire(p, WIRE.Q_ARITH); { - Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P); + Fr neg_half = Fr.wrap(0) - (FrLib.invert(Fr.wrap(2))); 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 + (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 - Fr.wrap(1)) * wire(p, WIRE.W_4_SHIFT); accum = accum * q_arith; accum = accum * domainSep; @@ -831,11 +581,9 @@ library RelationsLib { } function accumulateLogDerivativeLookupRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Transcript memory tp, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { + Fr[NUMBER_OF_ENTITIES] memory p, Transcript memory tp, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep) + internal view + { Fr write_term; Fr read_term; @@ -876,7 +624,7 @@ library RelationsLib { Fr[NUMBER_OF_ENTITIES] memory p, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep - ) internal pure { + ) internal view { Fr minus_one = Fr.wrap(0) - Fr.wrap(1); Fr minus_two = Fr.wrap(0) - Fr.wrap(2); Fr minus_three = Fr.wrap(0) - Fr.wrap(3); @@ -944,11 +692,10 @@ library RelationsLib { Fr x_double_identity; } - function accumulateEllipticRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { + function + accumulateEllipticRelation(Fr[NUMBER_OF_ENTITIES] memory p, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep) + internal view + { EllipticParams memory ep; ep.x_1 = wire(p, WIRE.W_R); ep.y_1 = wire(p, WIRE.W_O); @@ -1043,12 +790,11 @@ library RelationsLib { Fr auxiliary_identity; } - function accumulateAuxillaryRelation( - Fr[NUMBER_OF_ENTITIES] memory p, - Transcript memory tp, - Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep - ) internal pure { + function + accumulateAuxillaryRelation( + Fr[NUMBER_OF_ENTITIES] memory p, Transcript memory tp, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep) + internal pure + { AuxParams memory ap; /** @@ -1184,14 +930,16 @@ library RelationsLib { ap.index_is_monotonically_increasing = ap.index_delta * ap.index_delta - ap.index_delta; // deg 2 - ap.adjacent_values_match_if_adjacent_indices_match = (ap.index_delta * MINUS_ONE + Fr.wrap(1)) * ap.record_delta; // deg 2 + ap.adjacent_values_match_if_adjacent_indices_match = + (ap.index_delta * MINUS_ONE + Fr.wrap(1)) * ap.record_delta; // deg 2 - evals[13] = ap.adjacent_values_match_if_adjacent_indices_match * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) - * (wire(p, WIRE.Q_AUX) * domainSep); // deg 5 - evals[14] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) - * (wire(p, WIRE.Q_AUX) * domainSep); // deg 5 + evals[13] = ap.adjacent_values_match_if_adjacent_indices_match * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * + (wire(p, WIRE.Q_AUX) * domainSep); // deg 5 + evals[14] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_L) * wire(p, WIRE.Q_R)) * + (wire(p, WIRE.Q_AUX) * 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 + 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 @@ -1213,17 +961,18 @@ library RelationsLib { * 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 - access_type; // check value is 0 or 1; deg 2 or 8 + ap.access_check = access_type * access_type - access_type; // check value is 0 or 1; deg 2 or 8 + // deg 1 or 4 ap.next_gate_access_type = wire(p, WIRE.W_O_SHIFT) * tp.etaThree; ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_R_SHIFT) * tp.etaTwo); ap.next_gate_access_type = ap.next_gate_access_type + (wire(p, WIRE.W_L_SHIFT) * tp.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 + Fr.wrap(1) - ) * value_delta * (ap.next_gate_access_type * MINUS_ONE + Fr.wrap(1)); // deg 3 or 6 + ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation = + (ap.index_delta * MINUS_ONE + Fr.wrap(1)) * value_delta * + (ap.next_gate_access_type * MINUS_ONE + Fr.wrap(1)); // 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 @@ -1234,10 +983,12 @@ library RelationsLib { ap.next_gate_access_type * ap.next_gate_access_type - ap.next_gate_access_type; // Putting it all together... - evals[15] = ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation - * (wire(p, WIRE.Q_ARITH)) * (wire(p, WIRE.Q_AUX) * domainSep); // deg 5 or 8 - evals[16] = ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_ARITH)) * (wire(p, WIRE.Q_AUX) * domainSep); // deg 4 - evals[17] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_ARITH)) * (wire(p, WIRE.Q_AUX) * domainSep); // deg 4 or 6 + evals[15] = ap.adjacent_values_match_if_adjacent_indices_match_and_next_access_is_a_read_operation * + (wire(p, WIRE.Q_ARITH)) * (wire(p, WIRE.Q_AUX) * domainSep); // deg 5 or 8 + evals[16] = + ap.index_is_monotonically_increasing * (wire(p, WIRE.Q_ARITH)) * (wire(p, WIRE.Q_AUX) * domainSep); // deg 4 + evals[17] = ap.next_gate_access_type_is_boolean * (wire(p, WIRE.Q_ARITH)) * + (wire(p, WIRE.Q_AUX) * domainSep); // deg 4 or 6 ap.RAM_consistency_check_identity = ap.access_check * (wire(p, WIRE.Q_ARITH)); // deg 3 or 9 @@ -1264,8 +1015,9 @@ library RelationsLib { 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 + 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.auxiliary_identity = ap.memory_identity + non_native_field_identity + limb_accumulator_identity; @@ -1295,8 +1047,9 @@ library RelationsLib { function accumulatePoseidonExternalRelation( Fr[NUMBER_OF_ENTITIES] memory p, + Transcript memory tp, // I think this is not needed Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep + Fr domainSep // i guess this is the scaling factor? ) internal pure { PoseidonExternalParams memory ep; @@ -1351,11 +1104,11 @@ library RelationsLib { function accumulatePoseidonInternalRelation( Fr[NUMBER_OF_ENTITIES] memory p, + Transcript memory tp, // I think this is not needed Fr[NUMBER_OF_SUBRELATIONS] memory evals, - Fr domainSep + Fr domainSep // i guess this is the scaling factor? ) internal pure { PoseidonInternalParams memory ip; - Fr[4] memory INTERNAL_MATRIX_DIAGONAL = [ FrLib.from(0x10dc6e9c006ea38b04b1e03b4bd9490c0d03f98929ca1d7fb56821fd19d3b6e7), FrLib.from(0x0c28145b6a44df3e0149b3d0a30b3bb599df9756d4dd9b84a86b38cfb45a740b), @@ -1405,7 +1158,6 @@ library RelationsLib { // Errors error PublicInputsLengthWrong(); error SumcheckFailed(); -error ShpleminiFailed(); interface IVerifier { function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool); @@ -1414,11 +1166,10 @@ interface IVerifier { // Smart contract verifier of honk proofs contract HonkVerifier is IVerifier { - using FrLib for Fr; function verify(bytes calldata proof, bytes32[] calldata publicInputs) public view override returns (bool) { Honk.VerificationKey memory vk = loadVerificationKey(); - Honk.Proof memory p = TranscriptLib.loadProof(proof); + Honk.Proof memory p = loadProof(proof); if (publicInputs.length != vk.publicInputsSize) { revert PublicInputsLengthWrong(); @@ -1430,21 +1181,110 @@ contract HonkVerifier is IVerifier // Compute the public input delta t.publicInputsDelta = computePublicInputDelta(publicInputs, t.beta, t.gamma, vk.circuitSize, p.publicInputsOffset); - // Sumcheck bool sumcheckVerified = verifySumcheck(p, t); if (!sumcheckVerified) revert SumcheckFailed(); - bool shpleminiVerified = verifyShplemini(p, vk, t); - if (!shpleminiVerified) revert ShpleminiFailed(); - - return sumcheckVerified && shpleminiVerified; // Boolean condition not required - nice for vanity :) + return sumcheckVerified; // Boolean condition not required - nice for vanity :) } - function loadVerificationKey() internal pure returns (Honk.VerificationKey memory) { + function loadVerificationKey() internal view returns (Honk.VerificationKey memory) { return HonkVerificationKey.loadVerificationKey(); } + // TODO: mod q proof points + // TODO: Preprocess all of the memory locations + // TODO: Adjust proof point serde away from poseidon forced field elements + function loadProof(bytes calldata proof) internal view returns (Honk.Proof memory) { + Honk.Proof memory p; + + // Metadata + p.circuitSize = uint256(bytes32(proof[0x00:0x20])); + p.publicInputsSize = uint256(bytes32(proof[0x20:0x40])); + p.publicInputsOffset = uint256(bytes32(proof[0x40:0x60])); + + // Commitments + p.w1 = Honk.G1ProofPoint({ + x_0: uint256(bytes32(proof[0x60:0x80])), + x_1: uint256(bytes32(proof[0x80:0xa0])), + y_0: uint256(bytes32(proof[0xa0:0xc0])), + y_1: uint256(bytes32(proof[0xc0:0xe0])) + }); + + p.w2 = Honk.G1ProofPoint({ + x_0: uint256(bytes32(proof[0xe0:0x100])), + x_1: uint256(bytes32(proof[0x100:0x120])), + y_0: uint256(bytes32(proof[0x120:0x140])), + y_1: uint256(bytes32(proof[0x140:0x160])) + }); + p.w3 = Honk.G1ProofPoint({ + x_0: uint256(bytes32(proof[0x160:0x180])), + x_1: uint256(bytes32(proof[0x180:0x1a0])), + y_0: uint256(bytes32(proof[0x1a0:0x1c0])), + y_1: uint256(bytes32(proof[0x1c0:0x1e0])) + }); + + // Lookup / Permutation Helper Commitments + p.lookupReadCounts = Honk.G1ProofPoint({ + x_0: uint256(bytes32(proof[0x1e0:0x200])), + x_1: uint256(bytes32(proof[0x200:0x220])), + y_0: uint256(bytes32(proof[0x220:0x240])), + y_1: uint256(bytes32(proof[0x240:0x260])) + }); + p.lookupReadTags = Honk.G1ProofPoint({ + x_0: uint256(bytes32(proof[0x260:0x280])), + x_1: uint256(bytes32(proof[0x280:0x2a0])), + y_0: uint256(bytes32(proof[0x2a0:0x2c0])), + y_1: uint256(bytes32(proof[0x2c0:0x2e0])) + }); + p.w4 = Honk.G1ProofPoint({ + x_0: uint256(bytes32(proof[0x2e0:0x300])), + x_1: uint256(bytes32(proof[0x300:0x320])), + y_0: uint256(bytes32(proof[0x320:0x340])), + y_1: uint256(bytes32(proof[0x340:0x360])) + }); + p.lookupInverses = Honk.G1ProofPoint({ + x_0: uint256(bytes32(proof[0x360:0x380])), + x_1: uint256(bytes32(proof[0x380:0x3a0])), + y_0: uint256(bytes32(proof[0x3a0:0x3c0])), + y_1: uint256(bytes32(proof[0x3c0:0x3e0])) + }); + p.zPerm = Honk.G1ProofPoint({ + x_0: uint256(bytes32(proof[0x3e0:0x400])), + x_1: uint256(bytes32(proof[0x400:0x420])), + y_0: uint256(bytes32(proof[0x420:0x440])), + y_1: uint256(bytes32(proof[0x440:0x460])) + }); + + // TEMP the boundary of what has already been read + uint256 boundary = 0x460; + + // Sumcheck univariates + // TODO: in this case we know what log_n is - so we hard code it, we would want this to be included in + // a cpp template for different circuit sizes + for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; i++) { + // The loop boundary of i, this will shift forward on each evaluation + uint256 loop_boundary = boundary + (i * 0x20 * BATCHED_RELATION_PARTIAL_LENGTH); + + for (uint256 j = 0; j < BATCHED_RELATION_PARTIAL_LENGTH; j++) { + uint256 start = loop_boundary + (j * 0x20); + uint256 end = start + 0x20; + p.sumcheckUnivariates[i][j] = FrLib.fromBytes32(bytes32(proof[start:end])); + } + } + + boundary = boundary + (CONST_PROOF_SIZE_LOG_N * BATCHED_RELATION_PARTIAL_LENGTH * 0x20); + // Sumcheck evaluations + for (uint256 i = 0; i < NUMBER_OF_ENTITIES; i++) { + uint256 start = boundary + (i * 0x20); + uint256 end = start + 0x20; + p.sumcheckEvaluations[i] = FrLib.fromBytes32(bytes32(proof[start:end])); + } + + boundary = boundary + (NUMBER_OF_ENTITIES * 0x20); + return p; + } + function computePublicInputDelta( bytes32[] memory publicInputs, Fr beta, @@ -1485,9 +1325,7 @@ contract HonkVerifier is IVerifier Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round]; bool valid = checkSum(roundUnivariate, roundTarget); if (!valid) revert SumcheckFailed(); - Fr roundChallenge = tp.sumCheckUChallenges[round]; - // Update the round target for the next rounf roundTarget = computeNextTargetSum(roundUnivariate, roundChallenge); powPartialEvaluation = partiallyEvaluatePOW(tp, powPartialEvaluation, roundChallenge, round); @@ -1500,7 +1338,7 @@ contract HonkVerifier is IVerifier function checkSum(Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate, Fr roundTarget) internal - pure + view returns (bool checked) { Fr totalSum = roundUnivariate[0] + roundUnivariate[1]; @@ -1513,6 +1351,7 @@ contract HonkVerifier is IVerifier view returns (Fr targetSum) { + // TODO: inline Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [ Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000002d0), @@ -1536,6 +1375,7 @@ contract HonkVerifier is IVerifier ]; // To compute the next target sum, we evaluate the given univariate at a point u (challenge). + // TODO: opt: use same array mem for each iteratioon // Performing Barycentric evaluations // Compute B(x) Fr numeratorValue = Fr.wrap(1); @@ -1543,7 +1383,7 @@ contract HonkVerifier is IVerifier numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i)); } - // Calculate domain size N of inverses + // Calculate domain size N of inverses -- TODO: montgomery's trick Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses; for (uint256 i; i < BATCHED_RELATION_PARTIAL_LENGTH; ++i) { Fr inv = BARYCENTRIC_LAGRANGE_DENOMINATORS[i]; @@ -1571,315 +1411,6 @@ contract HonkVerifier is IVerifier Fr univariateEval = Fr.wrap(1) + (roundChallenge * (tp.gateChallenges[round] - Fr.wrap(1))); newEvaluation = currentEvaluation * univariateEval; } - - // Avoid stack too deep - struct ShpleminiIntermediates { - // i-th unshifted commitment is multiplied by −ρⁱ and the unshifted_scalar ( 1/(z−r) + ν/(z+r) ) - Fr unshiftedScalar; - // i-th shifted commitment is multiplied by −ρⁱ⁺ᵏ and the shifted_scalar r⁻¹ ⋅ (1/(z−r) − ν/(z+r)) - Fr shiftedScalar; - // 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; - } - - function verifyShplemini(Honk.Proof memory proof, Honk.VerificationKey memory vk, Transcript memory tp) - internal - view - returns (bool verified) - { - ShpleminiIntermediates memory mem; // stack - - // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size, I think this should be CONST_PROOF_SIZE - Fr[CONST_PROOF_SIZE_LOG_N] memory powers_of_evaluation_challenge = computeSquares(tp.geminiR); - - // Arrays hold values that will be linearly combined for the gemini and shplonk batch openings - Fr[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 2] memory scalars; - Honk.G1Point[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 2] memory commitments; - - Fr[CONST_PROOF_SIZE_LOG_N + 1] memory inverse_vanishing_evals = - computeInvertedGeminiDenominators(tp, powers_of_evaluation_challenge); - - mem.unshiftedScalar = inverse_vanishing_evals[0] + (tp.shplonkNu * inverse_vanishing_evals[1]); - mem.shiftedScalar = - tp.geminiR.invert() * (inverse_vanishing_evals[0] - (tp.shplonkNu * inverse_vanishing_evals[1])); - - scalars[0] = Fr.wrap(1); - commitments[0] = convertProofPoint(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.batchingChallenge = Fr.wrap(1); - mem.batchedEvaluation = Fr.wrap(0); - - for (uint256 i = 1; i <= NUMBER_UNSHIFTED; ++i) { - scalars[i] = mem.unshiftedScalar.neg() * mem.batchingChallenge; - mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i - 1] * mem.batchingChallenge); - mem.batchingChallenge = mem.batchingChallenge * tp.rho; - } - // g commitments are accumulated at r - for (uint256 i = NUMBER_UNSHIFTED + 1; i <= NUMBER_OF_ENTITIES; ++i) { - scalars[i] = mem.shiftedScalar.neg() * mem.batchingChallenge; - mem.batchedEvaluation = mem.batchedEvaluation + (proof.sumcheckEvaluations[i - 1] * mem.batchingChallenge); - mem.batchingChallenge = mem.batchingChallenge * tp.rho; - } - - commitments[1] = vk.qm; - commitments[2] = vk.qc; - commitments[3] = vk.ql; - commitments[4] = vk.qr; - commitments[5] = vk.qo; - commitments[6] = vk.q4; - commitments[7] = vk.qArith; - commitments[8] = vk.qDeltaRange; - commitments[9] = vk.qElliptic; - commitments[10] = vk.qAux; - commitments[11] = vk.qLookup; - commitments[12] = vk.qPoseidon2External; - commitments[13] = vk.qPoseidon2Internal; - commitments[14] = vk.s1; - commitments[15] = vk.s2; - commitments[16] = vk.s3; - commitments[17] = vk.s4; - commitments[18] = vk.id1; - commitments[19] = vk.id2; - commitments[20] = vk.id3; - commitments[21] = vk.id4; - commitments[22] = vk.t1; - commitments[23] = vk.t2; - commitments[24] = vk.t3; - commitments[25] = vk.t4; - commitments[26] = vk.lagrangeFirst; - commitments[27] = vk.lagrangeLast; - - // Accumulate proof points - commitments[28] = convertProofPoint(proof.w1); - commitments[29] = convertProofPoint(proof.w2); - commitments[30] = convertProofPoint(proof.w3); - commitments[31] = convertProofPoint(proof.w4); - commitments[32] = convertProofPoint(proof.zPerm); - commitments[33] = convertProofPoint(proof.lookupInverses); - commitments[34] = convertProofPoint(proof.lookupReadCounts); - commitments[35] = convertProofPoint(proof.lookupReadTags); - - // to be Shifted - commitments[36] = vk.t1; - commitments[37] = vk.t2; - commitments[38] = vk.t3; - commitments[39] = vk.t4; - commitments[40] = convertProofPoint(proof.w1); - commitments[41] = convertProofPoint(proof.w2); - commitments[42] = convertProofPoint(proof.w3); - commitments[43] = convertProofPoint(proof.w4); - commitments[44] = convertProofPoint(proof.zPerm); - - /* 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'. - */ - mem.constantTermAccumulator = Fr.wrap(0); - mem.batchingChallenge = tp.shplonkNu.sqr(); - - for (uint256 i; i < CONST_PROOF_SIZE_LOG_N - 1; ++i) { - bool dummy_round = i >= (LOG_N - 1); - - Fr scalingFactor = Fr.wrap(0); - if (!dummy_round) { - scalingFactor = mem.batchingChallenge * inverse_vanishing_evals[i + 2]; - scalars[NUMBER_OF_ENTITIES + 1 + i] = scalingFactor.neg(); - } - - mem.constantTermAccumulator = - mem.constantTermAccumulator + (scalingFactor * proof.geminiAEvaluations[i + 1]); - mem.batchingChallenge = mem.batchingChallenge * tp.shplonkNu; - - commitments[NUMBER_OF_ENTITIES + 1 + i] = convertProofPoint(proof.geminiFoldComms[i]); - } - - // Add contributions from A₀(r) and A₀(-r) to constant_term_accumulator: - // Compute evaluation A₀(r) - Fr a_0_pos = computeGeminiBatchedUnivariateEvaluation( - tp, mem.batchedEvaluation, proof.geminiAEvaluations, powers_of_evaluation_challenge - ); - - mem.constantTermAccumulator = mem.constantTermAccumulator + (a_0_pos * inverse_vanishing_evals[0]); - mem.constantTermAccumulator = - mem.constantTermAccumulator + (proof.geminiAEvaluations[0] * tp.shplonkNu * inverse_vanishing_evals[1]); - - // Finalise the batch opening claim - commitments[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N] = Honk.G1Point({x: 1, y: 2}); - scalars[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N] = mem.constantTermAccumulator; - - Honk.G1Point memory quotient_commitment = convertProofPoint(proof.kzgQuotient); - - commitments[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 1] = quotient_commitment; - scalars[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 1] = tp.shplonkZ; // evaluation challenge - - Honk.G1Point memory P_0 = batchMul(commitments, scalars); - Honk.G1Point memory P_1 = negateInplace(quotient_commitment); - - return pairing(P_0, P_1); - } - - function computeSquares(Fr r) internal pure returns (Fr[CONST_PROOF_SIZE_LOG_N] memory squares) { - squares[0] = r; - for (uint256 i = 1; i < CONST_PROOF_SIZE_LOG_N; ++i) { - squares[i] = squares[i - 1].sqr(); - } - } - - function computeInvertedGeminiDenominators( - Transcript memory tp, - Fr[CONST_PROOF_SIZE_LOG_N] memory eval_challenge_powers - ) internal view returns (Fr[CONST_PROOF_SIZE_LOG_N + 1] memory inverse_vanishing_evals) { - Fr eval_challenge = tp.shplonkZ; - inverse_vanishing_evals[0] = (eval_challenge - eval_challenge_powers[0]).invert(); - - for (uint256 i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { - Fr round_inverted_denominator = Fr.wrap(0); - if (i <= LOG_N + 1) { - round_inverted_denominator = (eval_challenge + eval_challenge_powers[i]).invert(); - } - inverse_vanishing_evals[i + 1] = round_inverted_denominator; - } - } - - function computeGeminiBatchedUnivariateEvaluation( - Transcript memory tp, - Fr batchedEvalAccumulator, - Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvaluations, - Fr[CONST_PROOF_SIZE_LOG_N] memory geminiEvalChallengePowers - ) internal view returns (Fr a_0_pos) { - for (uint256 i = CONST_PROOF_SIZE_LOG_N; i > 0; --i) { - Fr challengePower = geminiEvalChallengePowers[i - 1]; - Fr u = tp.sumCheckUChallenges[i - 1]; - Fr evalNeg = geminiEvaluations[i - 1]; - - Fr batchedEvalRoundAcc = ( - (challengePower * batchedEvalAccumulator * Fr.wrap(2)) - - evalNeg * (challengePower * (Fr.wrap(1) - u) - u) - ); - // Divide by the denominator - batchedEvalRoundAcc = batchedEvalRoundAcc * (challengePower * (Fr.wrap(1) - u) + u).invert(); - - bool is_dummy_round = (i > LOG_N); - if (!is_dummy_round) { - batchedEvalAccumulator = batchedEvalRoundAcc; - } - } - - a_0_pos = batchedEvalAccumulator; - } - - // This implementation is the same as above with different constants - function batchMul( - Honk.G1Point[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 2] memory base, - Fr[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 2] memory scalars - ) internal view returns (Honk.G1Point memory result) { - uint256 limit = NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 2; - assembly { - let success := 0x01 - let free := mload(0x40) - - // Write the original into the accumulator - // Load into memory for ecMUL, leave offset for eccAdd result - // base is an array of pointers, so we have to dereference them - mstore(add(free, 0x40), mload(mload(base))) - mstore(add(free, 0x60), mload(add(0x20, mload(base)))) - // Add scalar - mstore(add(free, 0x80), mload(scalars)) - success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, free, 0x40)) - - let count := 0x01 - for {} lt(count, limit) { 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 - i hate this - mstore(result, mload(free)) - mstore(add(result, 0x20), mload(add(free, 0x20))) - } - } - - function pairing(Honk.G1Point memory rhs, Honk.G1Point memory lhs) internal view returns (bool) { - bytes memory input = abi.encodePacked( - rhs.x, - rhs.y, - // Fixed G1 point - uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2), - uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed), - uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b), - uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa), - lhs.x, - lhs.y, - // G1 point from VK - uint256(0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1), - uint256(0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0), - uint256(0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4), - uint256(0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) - ); - - (bool success, bytes memory result) = address(0x08).staticcall(input); - bool decodedResult = abi.decode(result, (bool)); - return success && decodedResult; - } } // Conversion util - Duplicated as we cannot template LOG_N @@ -1891,7 +1422,6 @@ function convertPoints(Honk.G1ProofPoint[LOG_N + 1] memory commitments) converted[i] = convertProofPoint(commitments[i]); } } - )"; inline std::string get_honk_solidity_verifier(auto const& verification_key) diff --git a/barretenberg/sol/src/honk/HonkTypes.sol b/barretenberg/sol/src/honk/HonkTypes.sol index 55176738d07..3f5e5f76cb1 100644 --- a/barretenberg/sol/src/honk/HonkTypes.sol +++ b/barretenberg/sol/src/honk/HonkTypes.sol @@ -136,7 +136,7 @@ library Honk { // Sumcheck Fr[BATCHED_RELATION_PARTIAL_LENGTH][CONST_PROOF_SIZE_LOG_N] sumcheckUnivariates; Fr[NUMBER_OF_ENTITIES] sumcheckEvaluations; - // Shplemini + // Gemini Honk.G1ProofPoint[CONST_PROOF_SIZE_LOG_N - 1] geminiFoldComms; Fr[CONST_PROOF_SIZE_LOG_N] geminiAEvaluations; Honk.G1ProofPoint shplonkQ; diff --git a/barretenberg/sol/src/honk/HonkVerifier.sol b/barretenberg/sol/src/honk/HonkVerifier.sol new file mode 100644 index 00000000000..635d4188e71 --- /dev/null +++ b/barretenberg/sol/src/honk/HonkVerifier.sol @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs +pragma solidity >=0.8.21; + +import {IVerifier} from "../interfaces/IVerifier.sol"; +import {Add2HonkVerificationKey as VK, N, LOG_N} from "./keys/Add2HonkVerificationKey.sol"; + +import { + Honk, + WIRE, + NUMBER_OF_ENTITIES, + NUMBER_OF_SUBRELATIONS, + NUMBER_OF_ALPHAS, + NUMBER_UNSHIFTED, + BATCHED_RELATION_PARTIAL_LENGTH, + CONST_PROOF_SIZE_LOG_N +} from "./HonkTypes.sol"; + +import {ecMul, ecAdd, ecSub, negateInplace, convertProofPoint} from "./utils.sol"; + +// Field arithmetic libraries - prevent littering the code with modmul / addmul +import {MODULUS as P, MINUS_ONE, Fr, FrLib} from "./Fr.sol"; +// Transcript library to generate fiat shamir challenges +import {Transcript, TranscriptLib} from "./Transcript.sol"; + +import {RelationsLib} from "./Relations.sol"; + +error PublicInputsLengthWrong(); +error SumcheckFailed(); + +/// Smart contract verifier of honk proofs +abstract contract BaseHonkVerifier is IVerifier { + Fr internal constant GRUMPKIN_CURVE_B_PARAMETER_NEGATED = Fr.wrap(17); // -(-17) + + function verify(bytes calldata proof, bytes32[] calldata publicInputs) public view override returns (bool) { + Honk.VerificationKey memory vk = loadVerificationKey(); + Honk.Proof memory p = TranscriptLib.loadProof(proof); + + if (publicInputs.length != vk.publicInputsSize) { + revert PublicInputsLengthWrong(); + } + + // Generate the fiat shamir challenges for the whole protocol + Transcript memory t = TranscriptLib.generateTranscript(p, publicInputs, vk.publicInputsSize); + + // Compute the public input delta + t.publicInputsDelta = + computePublicInputDelta(publicInputs, t.beta, t.gamma, vk.circuitSize, p.publicInputsOffset); + + // Sumcheck + bool sumcheckVerified = verifySumcheck(p, t); + if (!sumcheckVerified) revert SumcheckFailed(); + + return sumcheckVerified; // Boolean condition not required - nice for vanity :) + } + + function loadVerificationKey() internal view returns (Honk.VerificationKey memory) { + return VK.loadVerificationKey(); + } + + function computePublicInputDelta( + bytes32[] memory publicInputs, + Fr beta, + Fr gamma, + uint256 domainSize, + uint256 offset + ) internal view returns (Fr publicInputDelta) { + Fr numerator = Fr.wrap(1); + Fr denominator = Fr.wrap(1); + + Fr numeratorAcc = gamma + (beta * FrLib.from(domainSize + offset)); + Fr denominatorAcc = gamma - (beta * FrLib.from(offset + 1)); + + { + for (uint256 i = 0; i < publicInputs.length; i++) { + Fr pubInput = FrLib.fromBytes32(publicInputs[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); + } + + uint256 constant ROUND_TARGET = 0; + + function verifySumcheck(Honk.Proof memory proof, Transcript memory tp) internal view returns (bool verified) { + Fr roundTarget; + Fr powPartialEvaluation = Fr.wrap(1); + + // We perform sumcheck reductions over log n rounds ( the multivariate degree ) + for (uint256 round; round < LOG_N; ++round) { + Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate = proof.sumcheckUnivariates[round]; + bool valid = checkSum(roundUnivariate, roundTarget); + if (!valid) revert SumcheckFailed(); + + Fr roundChallenge = tp.sumCheckUChallenges[round]; + + // Update the round target for the next rounf + roundTarget = computeNextTargetSum(roundUnivariate, roundChallenge); + powPartialEvaluation = partiallyEvaluatePOW(tp, powPartialEvaluation, roundChallenge, round); + } + + // Last round + Fr grandHonkRelationSum = RelationsLib.accumulateRelationEvaluations(proof, tp, powPartialEvaluation); + verified = (grandHonkRelationSum == roundTarget); + } + + function checkSum(Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariate, Fr roundTarget) + internal + view + returns (bool checked) + { + Fr totalSum = roundUnivariate[0] + roundUnivariate[1]; + checked = totalSum == roundTarget; + } + + // Return the new target sum for the next sumcheck round + function computeNextTargetSum(Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory roundUnivariates, Fr roundChallenge) + internal + view + returns (Fr targetSum) + { + // TODO: inline + Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_LAGRANGE_DENOMINATORS = [ + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51), + Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000002d0), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffff11), + Fr.wrap(0x0000000000000000000000000000000000000000000000000000000000000090), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffff71), + Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000000f0), + Fr.wrap(0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31), + Fr.wrap(0x00000000000000000000000000000000000000000000000000000000000013b0) + ]; + + Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory BARYCENTRIC_DOMAIN = [ + Fr.wrap(0x00), + Fr.wrap(0x01), + Fr.wrap(0x02), + Fr.wrap(0x03), + Fr.wrap(0x04), + Fr.wrap(0x05), + Fr.wrap(0x06), + Fr.wrap(0x07) + ]; + // To compute the next target sum, we evaluate the given univariate at a point u (challenge). + + // TODO: opt: use same array mem for each iteratioon + // Performing Barycentric evaluations + // Compute B(x) + Fr numeratorValue = Fr.wrap(1); + for (uint256 i; i < BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + numeratorValue = numeratorValue * (roundChallenge - Fr.wrap(i)); + } + + // Calculate domain size N of inverses -- TODO: montgomery's trick + Fr[BATCHED_RELATION_PARTIAL_LENGTH] memory denominatorInverses; + for (uint256 i; i < BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + Fr inv = BARYCENTRIC_LAGRANGE_DENOMINATORS[i]; + inv = inv * (roundChallenge - BARYCENTRIC_DOMAIN[i]); + inv = FrLib.invert(inv); + denominatorInverses[i] = inv; + } + + for (uint256 i; i < BATCHED_RELATION_PARTIAL_LENGTH; ++i) { + Fr term = roundUnivariates[i]; + term = term * denominatorInverses[i]; + targetSum = targetSum + term; + } + + // Scale the sum by the value of B(x) + targetSum = targetSum * numeratorValue; + } + + // Univariate evaluation of the monomial ((1-X_l) + X_l.B_l) at the challenge point X_l=u_l + function partiallyEvaluatePOW(Transcript memory tp, Fr currentEvaluation, Fr roundChallenge, uint256 round) + internal + pure + returns (Fr newEvaluation) + { + Fr univariateEval = Fr.wrap(1) + (roundChallenge * (tp.gateChallenges[round] - Fr.wrap(1))); + newEvaluation = currentEvaluation * univariateEval; + } + + // TODO: Implement Shplemini, functions above are left here in case they are useful + + // TODO: TODO: TODO: optimize + // Scalar Mul and acumulate into total + function batchMul(Honk.G1Point[LOG_N + 1] memory base, Fr[LOG_N + 1] memory scalars) + internal + view + returns (Honk.G1Point memory result) + { + uint256 limit = LOG_N + 1; + assembly { + let success := 0x01 + let free := mload(0x40) + + // Write the original into the accumulator + // Load into memory for ecMUL, leave offset for eccAdd result + // base is an array of pointers, so we have to dereference them + mstore(add(free, 0x40), mload(mload(base))) + mstore(add(free, 0x60), mload(add(0x20, mload(base)))) + // Add scalar + mstore(add(free, 0x80), mload(scalars)) + success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, free, 0x40)) + + let count := 0x01 + + for {} lt(count, limit) { 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)) + success := and(success, staticcall(gas(), 6, free, 0x80, free, 0x40)) + } + + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + } + } + + // This implementation is the same as above with different constants + function batchMul2( + Honk.G1Point[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 1] memory base, + Fr[NUMBER_OF_ENTITIES + CONST_PROOF_SIZE_LOG_N + 1] memory scalars + ) internal view returns (Honk.G1Point memory result) { + uint256 limit = NUMBER_OF_ENTITIES + LOG_N + 1; + assembly { + let success := 0x01 + let free := mload(0x40) + + // Write the original into the accumulator + // Load into memory for ecMUL, leave offset for eccAdd result + // base is an array of pointers, so we have to dereference them + mstore(add(free, 0x40), mload(mload(base))) + mstore(add(free, 0x60), mload(add(0x20, mload(base)))) + // Add scalar + mstore(add(free, 0x80), mload(scalars)) + success := and(success, staticcall(gas(), 7, add(free, 0x40), 0x60, free, 0x40)) + + let count := 0x01 + for {} lt(count, limit) { 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 - i hate this + mstore(result, mload(free)) + mstore(add(result, 0x20), mload(add(free, 0x20))) + } + } + + // function kzgReduceVerify( + // Honk.Proof memory proof, + // Transcript memory tp, + // Fr evaluation, + // Honk.G1Point memory commitment + // ) internal view returns (bool) { + // Honk.G1Point memory quotient_commitment = convertProofPoint(proof.zmPi); + // Honk.G1Point memory ONE = Honk.G1Point({x: 1, y: 2}); + + // Honk.G1Point memory P0 = commitment; + // P0 = ecAdd(P0, ecMul(quotient_commitment, tp.zmX)); + + // Honk.G1Point memory evalAsPoint = ecMul(ONE, evaluation); + // P0 = ecSub(P0, evalAsPoint); + + // Honk.G1Point memory P1 = negateInplace(quotient_commitment); + + // // Perform pairing check + // return pairing(P0, P1); + // } + + function pairing(Honk.G1Point memory rhs, Honk.G1Point memory lhs) internal view returns (bool) { + bytes memory input = abi.encodePacked( + rhs.x, + rhs.y, + // Fixed G1 point + uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2), + uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed), + uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b), + uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa), + lhs.x, + lhs.y, + // G1 point from VK + uint256(0x260e01b251f6f1c7e7ff4e580791dee8ea51d87a358e038b4efe30fac09383c1), + uint256(0x0118c4d5b837bcc2bc89b5b398b5974e9f5944073b32078b7e231fec938883b0), + uint256(0x04fc6369f7110fe3d25156c1bb9a72859cf2a04641f99ba4ee413c80da6a5fe4), + uint256(0x22febda3c0c0632a56475b4214e5615e11e6dd3f96e6cea2854a87d4dacc5e55) + ); + + (bool success, bytes memory result) = address(0x08).staticcall(input); + return abi.decode(result, (bool)); + } +} + +// Conversion util - Duplicated as we cannot template LOG_N +function convertPoints(Honk.G1ProofPoint[LOG_N + 1] memory commitments) + pure + returns (Honk.G1Point[LOG_N + 1] memory converted) +{ + for (uint256 i; i < LOG_N + 1; ++i) { + converted[i] = convertProofPoint(commitments[i]); + } +} diff --git a/barretenberg/sol/src/honk/Relations.sol b/barretenberg/sol/src/honk/Relations.sol index 9ca4868cc44..685797de47f 100644 --- a/barretenberg/sol/src/honk/Relations.sol +++ b/barretenberg/sol/src/honk/Relations.sol @@ -24,7 +24,7 @@ library RelationsLib { function accumulateRelationEvaluations(Honk.Proof memory proof, Transcript memory tp, Fr powPartialEval) internal - pure + view returns (Fr accumulator) { Fr[NUMBER_OF_ENTITIES] memory purportedEvaluations = proof.sumcheckEvaluations; @@ -37,7 +37,7 @@ library RelationsLib { accumulateDeltaRangeRelation(purportedEvaluations, evaluations, powPartialEval); accumulateEllipticRelation(purportedEvaluations, evaluations, powPartialEval); accumulateAuxillaryRelation(purportedEvaluations, tp, evaluations, powPartialEval); - accumulatePoseidonExternalRelation(purportedEvaluations, evaluations, powPartialEval); + accumulatePoseidonExternalRelation(purportedEvaluations, tp, evaluations, powPartialEval); accumulatePoseidonInternalRelation(purportedEvaluations, evaluations, powPartialEval); // batch the subrelations with the alpha challenges to obtain the full honk relation accumulator = scaleAndBatchSubrelations(evaluations, tp.alphas); @@ -52,7 +52,6 @@ library RelationsLib { return p[uint256(_wire)]; } - uint256 internal constant NEG_HALF_MODULO_P = 0x183227397098d014dc2822db40c0ac2e9419f4243cdcb848a1f0fac9f8000000; /** * Ultra Arithmetic Relation * @@ -61,11 +60,11 @@ library RelationsLib { Fr[NUMBER_OF_ENTITIES] memory p, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep - ) internal pure { + ) internal view { // Relation 0 Fr q_arith = wire(p, WIRE.Q_ARITH); { - Fr neg_half = Fr.wrap(NEG_HALF_MODULO_P); + Fr neg_half = Fr.wrap(0) - (FrLib.invert(Fr.wrap(2))); 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)) @@ -179,7 +178,7 @@ library RelationsLib { Fr[NUMBER_OF_ENTITIES] memory p, Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep - ) internal pure { + ) internal view { Fr minus_one = Fr.wrap(0) - Fr.wrap(1); Fr minus_two = Fr.wrap(0) - Fr.wrap(2); Fr minus_three = Fr.wrap(0) - Fr.wrap(3); @@ -602,6 +601,7 @@ library RelationsLib { function accumulatePoseidonExternalRelation( Fr[NUMBER_OF_ENTITIES] memory p, + Transcript memory tp, // I think this is not needed Fr[NUMBER_OF_SUBRELATIONS] memory evals, Fr domainSep // i guess this is the scaling factor? ) internal pure { diff --git a/barretenberg/sol/src/honk/Transcript.sol b/barretenberg/sol/src/honk/Transcript.sol index ca4e1449716..8576b180c99 100644 --- a/barretenberg/sol/src/honk/Transcript.sol +++ b/barretenberg/sol/src/honk/Transcript.sol @@ -31,7 +31,7 @@ struct Transcript { library TranscriptLib { function generateTranscript(Honk.Proof memory proof, bytes32[] calldata publicInputs, uint256 publicInputsSize) internal - pure + view returns (Transcript memory t) { Fr previousChallenge; diff --git a/barretenberg/sol/src/honk/instance/Add2Honk.sol b/barretenberg/sol/src/honk/instance/Add2Honk.sol index 00e26a8ca1b..a56a3b256a0 100644 --- a/barretenberg/sol/src/honk/instance/Add2Honk.sol +++ b/barretenberg/sol/src/honk/instance/Add2Honk.sol @@ -198,9 +198,9 @@ contract Add2HonkVerifier is IVerifier { Fr shiftedScalar; // 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 batchingChallenge; + // Accumulator for powers of rho Fr batchedEvaluation; } diff --git a/barretenberg/sol/src/honk/instance/BlakeHonk.sol b/barretenberg/sol/src/honk/instance/BlakeHonk.sol index 6e617946e06..d5f739f0315 100644 --- a/barretenberg/sol/src/honk/instance/BlakeHonk.sol +++ b/barretenberg/sol/src/honk/instance/BlakeHonk.sol @@ -199,9 +199,9 @@ contract BlakeHonkVerifier is IVerifier { Fr shiftedScalar; // 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 batchingChallenge; + // Accumulator for powers of rho Fr batchedEvaluation; } diff --git a/barretenberg/sol/src/honk/instance/EcdsaHonk.sol b/barretenberg/sol/src/honk/instance/EcdsaHonk.sol index 6d8a4a39c02..5e3c064defc 100644 --- a/barretenberg/sol/src/honk/instance/EcdsaHonk.sol +++ b/barretenberg/sol/src/honk/instance/EcdsaHonk.sol @@ -198,9 +198,9 @@ contract EcdsaHonkVerifier is IVerifier { Fr shiftedScalar; // 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 batchingChallenge; + // Accumulator for powers of rho Fr batchedEvaluation; } diff --git a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr index 642dcb4a0e0..00bb2f14ab1 100644 --- a/noir-projects/aztec-nr/aztec/src/oracle/notes.nr +++ b/noir-projects/aztec-nr/aztec/src/oracle/notes.nr @@ -1,6 +1,10 @@ use crate::note::{note_header::NoteHeader, note_interface::NoteInterface}; -use dep::protocol_types::{address::AztecAddress, utils::arr_copy_slice}; +use dep::protocol_types::{ + address::AztecAddress, + indexed_tagging_secret::{INDEXED_TAGGING_SECRET_LENGTH, IndexedTaggingSecret}, + utils::arr_copy_slice, +}; /// Notifies the simulator that a note has been created, so that it can be returned in future read requests in the same /// transaction. This note should only be added to the non-volatile database if found in an actual block. @@ -200,17 +204,42 @@ pub unconstrained fn check_nullifier_exists(inner_nullifier: Field) -> bool { unconstrained fn check_nullifier_exists_oracle(_inner_nullifier: Field) -> Field {} /// Returns the tagging secret for a given sender and recipient pair, siloed for the current contract address. +/// Includes the last known index used for tagging with this secret. /// For this to work, PXE must know the ivpsk_m of the sender. /// For the recipient's side, only the address is needed. pub unconstrained fn get_app_tagging_secret( sender: AztecAddress, recipient: AztecAddress, -) -> Field { - get_app_tagging_secret_oracle(sender, recipient) +) -> IndexedTaggingSecret { + let result = get_app_tagging_secret_oracle(sender, recipient); + IndexedTaggingSecret::deserialize(result) } #[oracle(getAppTaggingSecret)] unconstrained fn get_app_tagging_secret_oracle( _sender: AztecAddress, _recipient: AztecAddress, -) -> Field {} +) -> [Field; INDEXED_TAGGING_SECRET_LENGTH] {} + +/// Returns the tagging secrets for a given recipient and all the senders in PXE's address book, +// siloed for the current contract address. +/// Includes the last known index used for tagging with this secret. +/// For this to work, PXE must know the ivsk_m of the recipient. +pub unconstrained fn get_app_tagging_secrets_for_senders( + recipient: AztecAddress, +) -> [IndexedTaggingSecret] { + let results = get_app_tagging_secrets_for_senders_oracle(recipient); + let mut indexed_tagging_secrets = &[]; + for i in 0..results.len() { + if i % 2 != 0 { + continue; + } + indexed_tagging_secrets = indexed_tagging_secrets.push_back( + IndexedTaggingSecret::deserialize([results[i], results[i + 1]]), + ); + } + indexed_tagging_secrets +} + +#[oracle(getAppTaggingSecretsForSenders)] +unconstrained fn get_app_tagging_secrets_for_senders_oracle(_recipient: AztecAddress) -> [Field] {} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr b/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr new file mode 100644 index 00000000000..1685fdf38e8 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/indexed_tagging_secret.nr @@ -0,0 +1,10 @@ +use crate::traits::Deserialize; +use std::meta::derive; + +pub global INDEXED_TAGGING_SECRET_LENGTH: u32 = 2; + +#[derive(Deserialize)] +pub struct IndexedTaggingSecret { + secret: Field, + index: u32, +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr index 75345c071af..785d31683c0 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/lib.nr @@ -30,6 +30,7 @@ mod data; mod storage; mod validate; mod meta; +mod indexed_tagging_secret; pub use abis::kernel_circuit_public_inputs::{ KernelCircuitPublicInputs, PrivateKernelCircuitPublicInputs, PublicKernelCircuitPublicInputs, diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index b8f7c80634c..e7c79fda0ce 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -15,7 +15,14 @@ import type { AztecAddress } from '@aztec/foundation/aztec-address'; import type { Fr } from '@aztec/foundation/fields'; import type { L2Block } from '../l2_block.js'; -import type { FromLogType, GetUnencryptedLogsResponse, L2BlockL2Logs, LogFilter, LogType } from '../logs/index.js'; +import type { + EncryptedL2NoteLog, + FromLogType, + GetUnencryptedLogsResponse, + L2BlockL2Logs, + LogFilter, + LogType, +} from '../logs/index.js'; import type { MerkleTreeId } from '../merkle_tree_id.js'; import type { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import type { PublicDataWitness } from '../public_data_witness.js'; @@ -247,6 +254,14 @@ export interface AztecNode extends ProverCoordination { */ getUnencryptedLogs(filter: LogFilter): Promise; + /** + * Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag). + * @param tags - The tags to filter the logs by. + * @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match + * that tag. + */ + getLogsByTags(tags: Fr[]): Promise; + /** * Method to submit a transaction to the p2p pool. * @param tx - The transaction to be submitted. diff --git a/yarn-project/circuits.js/src/keys/derivation.ts b/yarn-project/circuits.js/src/keys/derivation.ts index ae298c208b4..1fa9276e3f3 100644 --- a/yarn-project/circuits.js/src/keys/derivation.ts +++ b/yarn-project/circuits.js/src/keys/derivation.ts @@ -127,15 +127,12 @@ export function deriveKeys(secretKey: Fr) { }; } -export function computeTaggingSecret(senderCompleteAddress: CompleteAddress, senderIvsk: Fq, recipient: AztecAddress) { - const senderPreaddress = computePreaddress( - senderCompleteAddress.publicKeys.hash(), - senderCompleteAddress.partialAddress, - ); +export function computeTaggingSecret(knownAddress: CompleteAddress, ivsk: Fq, externalAddress: AztecAddress) { + const knownPreaddress = computePreaddress(knownAddress.publicKeys.hash(), knownAddress.partialAddress); // TODO: #8970 - Computation of address point from x coordinate might fail - const recipientAddressPoint = computePoint(recipient); + const externalAddressPoint = computePoint(externalAddress); const curve = new Grumpkin(); - // Given A (sender) -> B (recipient) and h == preaddress + // Given A (known complete address) -> B (external address) and h == preaddress // Compute shared secret as S = (h_A + ivsk_A) * Addr_Point_B - return curve.mul(recipientAddressPoint, senderIvsk.add(new Fq(senderPreaddress.toBigInt()))); + return curve.mul(externalAddressPoint, ivsk.add(new Fq(knownPreaddress.toBigInt()))); } diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 022fa88bc44..4e047dfc520 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -13,6 +13,7 @@ export * from './gas_fees.js'; export * from './gas_settings.js'; export * from './global_variables.js'; export * from './header.js'; +export * from './indexed_tagging_secret.js'; export * from './kernel/combined_accumulated_data.js'; export * from './kernel/combined_constant_data.js'; export * from './kernel/enqueued_call_data.js'; diff --git a/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts b/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts new file mode 100644 index 00000000000..ab218b5b7ee --- /dev/null +++ b/yarn-project/circuits.js/src/structs/indexed_tagging_secret.ts @@ -0,0 +1,9 @@ +import { Fr } from '@aztec/foundation/fields'; + +export class IndexedTaggingSecret { + constructor(public secret: Fr, public index: number) {} + + toFields(): Fr[] { + return [this.secret, new Fr(this.index)]; + } +} diff --git a/yarn-project/cli/package.json b/yarn-project/cli/package.json index c70eeffc78b..b6a9891babd 100644 --- a/yarn-project/cli/package.json +++ b/yarn-project/cli/package.json @@ -78,7 +78,7 @@ "lodash.chunk": "^4.2.0", "lodash.groupby": "^4.6.0", "semver": "^7.5.4", - "solc": "^0.8.27", + "solc": "^0.8.26", "source-map-support": "^0.5.21", "tslib": "^2.4.0", "viem": "^2.7.15" diff --git a/yarn-project/end-to-end/package.json b/yarn-project/end-to-end/package.json index d2819649d11..bf630f4e047 100644 --- a/yarn-project/end-to-end/package.json +++ b/yarn-project/end-to-end/package.json @@ -81,7 +81,7 @@ "process": "^0.11.10", "puppeteer": "^22.2", "resolve-typescript-plugin": "^2.0.1", - "solc": "^0.8.27", + "solc": "^0.8.25", "stream-browserify": "^3.0.0", "string-argv": "^0.3.2", "ts-loader": "^9.4.4", diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 3bc0f440a6b..7cb33ec0b13 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -567,7 +567,7 @@ export function compileContract( enabled: true, runs: 200, }, - evmVersion: 'cancun', + evmVersion: 'paris', outputSelection: { '*': { '*': ['evm.bytecode.object', 'abi'], diff --git a/yarn-project/pxe/src/database/kv_pxe_database.ts b/yarn-project/pxe/src/database/kv_pxe_database.ts index b0061e587ce..3d297c5ca51 100644 --- a/yarn-project/pxe/src/database/kv_pxe_database.ts +++ b/yarn-project/pxe/src/database/kv_pxe_database.ts @@ -66,6 +66,8 @@ export class KVPxeDatabase implements PxeDatabase { #notesByTxHashAndScope: Map>; #notesByIvpkMAndScope: Map>; + #taggingSecretIndexes: AztecMap; + constructor(private db: AztecKVStore) { this.#db = db; @@ -111,6 +113,8 @@ export class KVPxeDatabase implements PxeDatabase { this.#notesByTxHashAndScope.set(scope, db.openMultiMap(`${scope}:notes_by_tx_hash`)); this.#notesByIvpkMAndScope.set(scope, db.openMultiMap(`${scope}:notes_by_ivpk_m`)); } + + this.#taggingSecretIndexes = db.openMap('tagging_secret_indices'); } public async getContract( @@ -572,4 +576,20 @@ export class KVPxeDatabase implements PxeDatabase { return incomingNotesSize + outgoingNotesSize + treeRootsSize + authWitsSize + addressesSize; } + + async incrementTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise { + const indexes = await this.getTaggingSecretsIndexes(appTaggingSecrets); + await this.db.transaction(() => { + indexes.forEach(index => { + const nextIndex = index ? index + 1 : 1; + void this.#taggingSecretIndexes.set(appTaggingSecrets.toString(), nextIndex); + }); + }); + } + + getTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise { + return this.db.transaction(() => + appTaggingSecrets.map(secret => this.#taggingSecretIndexes.get(secret.toString()) ?? 0), + ); + } } diff --git a/yarn-project/pxe/src/database/pxe_database.ts b/yarn-project/pxe/src/database/pxe_database.ts index 860b3d742a0..db845d06828 100644 --- a/yarn-project/pxe/src/database/pxe_database.ts +++ b/yarn-project/pxe/src/database/pxe_database.ts @@ -185,4 +185,8 @@ export interface PxeDatabase extends ContractArtifactDatabase, ContractInstanceD * @returns The estimated size in bytes of this db. */ estimateSize(): Promise; + + getTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise; + + incrementTaggingSecretsIndexes(appTaggingSecrets: Fr[]): Promise; } diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 1b18b32c318..50570db1ae5 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -14,6 +14,7 @@ import { type Fr, type FunctionSelector, type Header, + IndexedTaggingSecret, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, computeTaggingSecret, @@ -231,20 +232,49 @@ export class SimulatorOracle implements DBOracle { /** * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known. + * Includes the last known index used for tagging with this secret. * @param contractAddress - The contract address to silo the secret for * @param sender - The address sending the note * @param recipient - The address receiving the note - * @returns A tagging secret that can be used to tag notes. + * @returns A siloed tagging secret that can be used to tag notes. */ public async getAppTaggingSecret( contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress, - ): Promise { + ): Promise { const senderCompleteAddress = await this.getCompleteAddress(sender); const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient); // Silo the secret to the app so it can't be used to track other app's notes - return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); + const secret = poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); + const [index] = await this.db.getTaggingSecretsIndexes([secret]); + return new IndexedTaggingSecret(secret, index); + } + + /** + * Returns the siloed tagging secrets for a given recipient and all the senders in the address book + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of siloed tagging secrets + */ + public async getAppTaggingSecretsForSenders( + contractAddress: AztecAddress, + recipient: AztecAddress, + ): Promise { + const recipientCompleteAddress = await this.getCompleteAddress(recipient); + const completeAddresses = await this.db.getCompleteAddresses(); + // Filter out the addresses corresponding to accounts + const accounts = await this.keyStore.getAccounts(); + const senders = completeAddresses.filter( + completeAddress => !accounts.find(account => account.equals(completeAddress.address)), + ); + const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); + const secrets = senders.map(({ address: sender }) => { + const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, sender); + return poseidon2Hash([sharedSecret.x, sharedSecret.y, contractAddress]); + }); + const indexes = await this.db.getTaggingSecretsIndexes(secrets); + return secrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i])); } } diff --git a/yarn-project/simulator/src/acvm/oracle/oracle.ts b/yarn-project/simulator/src/acvm/oracle/oracle.ts index e3c315f0708..c8fdb5a18b2 100644 --- a/yarn-project/simulator/src/acvm/oracle/oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/oracle.ts @@ -409,11 +409,16 @@ export class Oracle { this.typedOracle.notifySetMinRevertibleSideEffectCounter(frToNumber(fromACVMField(minRevertibleSideEffectCounter))); } - async getAppTaggingSecret([sender]: ACVMField[], [recipient]: ACVMField[]): Promise { + async getAppTaggingSecret([sender]: ACVMField[], [recipient]: ACVMField[]): Promise { const taggingSecret = await this.typedOracle.getAppTaggingSecret( AztecAddress.fromString(sender), AztecAddress.fromString(recipient), ); - return toACVMField(taggingSecret); + return taggingSecret.toFields().map(toACVMField); + } + + async getAppTaggingSecretsForSenders([recipient]: ACVMField[]): Promise { + const taggingSecrets = await this.typedOracle.getAppTaggingSecretsForSenders(AztecAddress.fromString(recipient)); + return taggingSecrets.flatMap(taggingSecret => taggingSecret.toFields().map(toACVMField)); } } diff --git a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts index 1e66f0f150c..284b08f5e0f 100644 --- a/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts +++ b/yarn-project/simulator/src/acvm/oracle/typed_oracle.ts @@ -11,6 +11,7 @@ import { import { type ContractInstance, type Header, + type IndexedTaggingSecret, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, } from '@aztec/circuits.js'; @@ -253,7 +254,11 @@ export abstract class TypedOracle { throw new OracleMethodNotAvailableError('debugLog'); } - getAppTaggingSecret(_sender: AztecAddress, _recipient: AztecAddress): Promise { + getAppTaggingSecret(_sender: AztecAddress, _recipient: AztecAddress): Promise { throw new OracleMethodNotAvailableError('getAppTaggingSecret'); } + + getAppTaggingSecretsForSenders(_recipient: AztecAddress): Promise { + throw new OracleMethodNotAvailableError('getAppTaggingSecretsForSenders'); + } } diff --git a/yarn-project/simulator/src/client/db_oracle.ts b/yarn-project/simulator/src/client/db_oracle.ts index c80d4fed5df..c19a0b1636a 100644 --- a/yarn-project/simulator/src/client/db_oracle.ts +++ b/yarn-project/simulator/src/client/db_oracle.ts @@ -9,6 +9,7 @@ import { type CompleteAddress, type ContractInstance, type Header, + type IndexedTaggingSecret, type KeyValidationRequest, } from '@aztec/circuits.js'; import { type FunctionArtifact, type FunctionSelector } from '@aztec/foundation/abi'; @@ -196,10 +197,26 @@ export interface DBOracle extends CommitmentsDB { /** * Returns the tagging secret for a given sender and recipient pair. For this to work, the ivpsk_m of the sender must be known. + * Includes the last known index used for tagging with this secret. * @param contractAddress - The contract address to silo the secret for * @param sender - The address sending the note * @param recipient - The address receiving the note * @returns A tagging secret that can be used to tag notes. */ - getAppTaggingSecret(contractAddress: AztecAddress, sender: AztecAddress, recipient: AztecAddress): Promise; + getAppTaggingSecret( + contractAddress: AztecAddress, + sender: AztecAddress, + recipient: AztecAddress, + ): Promise; + + /** + * Returns the siloed tagging secrets for a given recipient and all the senders in the address book + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of siloed tagging secrets + */ + getAppTaggingSecretsForSenders( + contractAddress: AztecAddress, + recipient: AztecAddress, + ): Promise; } diff --git a/yarn-project/simulator/src/client/view_data_oracle.ts b/yarn-project/simulator/src/client/view_data_oracle.ts index 644c4b8b6bd..b8ca0266fa4 100644 --- a/yarn-project/simulator/src/client/view_data_oracle.ts +++ b/yarn-project/simulator/src/client/view_data_oracle.ts @@ -7,7 +7,12 @@ import { type NullifierMembershipWitness, type PublicDataWitness, } from '@aztec/circuit-types'; -import { type ContractInstance, type Header, type KeyValidationRequest } from '@aztec/circuits.js'; +import { + type ContractInstance, + type Header, + type IndexedTaggingSecret, + type KeyValidationRequest, +} from '@aztec/circuits.js'; import { siloNullifier } from '@aztec/circuits.js/hash'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; @@ -291,12 +296,26 @@ export class ViewDataOracle extends TypedOracle { /** * Returns the tagging secret for a given sender and recipient pair, siloed to the current contract address. + * Includes the last known index used for tagging with this secret. * For this to work, the ivpsk_m of the sender must be known. * @param sender - The address sending the note * @param recipient - The address receiving the note * @returns A tagging secret that can be used to tag notes. */ - public override async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise { + public override async getAppTaggingSecret( + sender: AztecAddress, + recipient: AztecAddress, + ): Promise { return await this.db.getAppTaggingSecret(this.contractAddress, sender, recipient); } + + /** + * Returns the siloed tagging secrets for a given recipient and all the senders in the address book + * @param contractAddress - The contract address to silo the secret for + * @param recipient - The address receiving the notes + * @returns A list of siloed tagging secrets + */ + public override async getAppTaggingSecretsForSenders(recipient: AztecAddress): Promise { + return await this.db.getAppTaggingSecretsForSenders(this.contractAddress, recipient); + } } diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 3823d6d940d..b16be91beb1 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -18,6 +18,7 @@ import { type ContractInstanceWithAddress, Gas, Header, + IndexedTaggingSecret, type KeyValidationRequest, NULLIFIER_SUBTREE_HEIGHT, type NULLIFIER_TREE_HEIGHT, @@ -749,12 +750,31 @@ export class TXE implements TypedOracle { return; } - async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise { + async getAppTaggingSecret(sender: AztecAddress, recipient: AztecAddress): Promise { const senderCompleteAddress = await this.getCompleteAddress(sender); const senderIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(sender); const sharedSecret = computeTaggingSecret(senderCompleteAddress, senderIvsk, recipient); // Silo the secret to the app so it can't be used to track other app's notes - return poseidon2Hash([sharedSecret.x, sharedSecret.y, this.contractAddress]); + const secret = poseidon2Hash([sharedSecret.x, sharedSecret.y, this.contractAddress]); + const [index] = await this.txeDatabase.getTaggingSecretsIndexes([secret]); + return new IndexedTaggingSecret(secret, index); + } + + async getAppTaggingSecretsForSenders(recipient: AztecAddress): Promise { + const recipientCompleteAddress = await this.getCompleteAddress(recipient); + const completeAddresses = await this.txeDatabase.getCompleteAddresses(); + // Filter out the addresses corresponding to accounts + const accounts = await this.keyStore.getAccounts(); + const senders = completeAddresses.filter( + completeAddress => !accounts.find(account => account.equals(completeAddress.address)), + ); + const recipientIvsk = await this.keyStore.getMasterIncomingViewingSecretKey(recipient); + const secrets = senders.map(({ address: sender }) => { + const sharedSecret = computeTaggingSecret(recipientCompleteAddress, recipientIvsk, sender); + return poseidon2Hash([sharedSecret.x, sharedSecret.y, this.contractAddress]); + }); + const indexes = await this.txeDatabase.getTaggingSecretsIndexes(secrets); + return secrets.map((secret, i) => new IndexedTaggingSecret(secret, indexes[i])); } // AVM oracles diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index f0f41fa232c..7731f55b23f 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -604,7 +604,14 @@ export class TXEService { AztecAddress.fromField(fromSingle(sender)), AztecAddress.fromField(fromSingle(recipient)), ); - return toForeignCallResult([toSingle(secret)]); + return toForeignCallResult([toArray(secret.toFields())]); + } + + async getAppTaggingSecretsForSenders(recipient: ForeignCallSingle) { + const secrets = await this.typedOracle.getAppTaggingSecretsForSenders( + AztecAddress.fromField(fromSingle(recipient)), + ); + return toForeignCallResult([toArray(secrets.flatMap(secret => secret.toFields()))]); } // AVM opcodes diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index dd7db7e86c5..3a409f0ab9c 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -480,7 +480,7 @@ __metadata: lodash.chunk: ^4.2.0 lodash.groupby: ^4.6.0 semver: ^7.5.4 - solc: ^0.8.27 + solc: ^0.8.26 source-map-support: ^0.5.21 ts-jest: ^29.1.0 ts-node: ^10.9.1 @@ -571,7 +571,7 @@ __metadata: process: ^0.11.10 puppeteer: ^22.2 resolve-typescript-plugin: ^2.0.1 - solc: ^0.8.27 + solc: ^0.8.25 stream-browserify: ^3.0.0 string-argv: ^0.3.2 ts-loader: ^9.4.4 @@ -14498,9 +14498,9 @@ __metadata: languageName: node linkType: hard -"solc@npm:^0.8.27": - version: 0.8.27 - resolution: "solc@npm:0.8.27" +"solc@npm:^0.8.25": + version: 0.8.25 + resolution: "solc@npm:0.8.25" dependencies: command-exists: ^1.2.8 commander: ^8.1.0 @@ -14511,7 +14511,24 @@ __metadata: tmp: 0.0.33 bin: solcjs: solc.js - checksum: 33a81256445b7999672db8b03f94a7a767c28b9be88f60030fbc1b43b5ef9c3fa44591193abe8dda166df333c5fa4370bfcfb67acf01f8ca828ad7fb2f8ce54e + checksum: dce5d31c24be940c2486949dd1eb6ee12f3ebcaa11e60fb51a07b3bfaf74818d503f4eb1aef83e72302b3203a00edf42b91609364d5daa94c35dde20953326ae + languageName: node + linkType: hard + +"solc@npm:^0.8.26": + version: 0.8.26 + resolution: "solc@npm:0.8.26" + dependencies: + command-exists: ^1.2.8 + commander: ^8.1.0 + follow-redirects: ^1.12.1 + js-sha3: 0.8.0 + memorystream: ^0.3.1 + semver: ^5.5.0 + tmp: 0.0.33 + bin: + solcjs: solc.js + checksum: e3eaeac76e60676377b357af8f3919d4c8c6a74b74112b49279fe8c74a3dfa1de8afe4788689fc307453bde336edc8572988d2cf9e909f84d870420eb640400c languageName: node linkType: hard