diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp b/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp index ef66005f7687..38cc184773af 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp @@ -311,8 +311,7 @@ void Chonk::complete_kernel_circuit_logic(ClientCircuit& circuit) } // Determine kernel type from queue contents - bool is_init_kernel = - stdlib_verification_queue.size() == 1 && (stdlib_verification_queue.front().type == QUEUE_TYPE::OINK); + bool is_init_kernel = stdlib_verification_queue.front().type == QUEUE_TYPE::OINK; bool is_hiding_kernel = stdlib_verification_queue.size() == 1 && (stdlib_verification_queue.front().type == QUEUE_TYPE::HN_FINAL); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.cpp index a516266e17dc..8e70a6b34d8c 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/hypernova_recursion_constraint.cpp @@ -31,7 +31,7 @@ using namespace bb; */ std::shared_ptr create_mock_chonk_from_constraints(const std::vector& constraints) { - auto ivc = std::make_shared(std::max(constraints.size(), static_cast(4))); + auto ivc = std::make_shared(std::max(constraints.size(), static_cast(MAX_APPS_PER_KERNEL + 1))); // Check constraint proof type. Throws if proof_type is not a valid HyperNova type auto constraint_has_type = [](const RecursionConstraint& c, Chonk::QUEUE_TYPE expected) { return proof_type_to_chonk_queue_type(c.proof_type) == expected; diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp index 14a65afa33ff..50026a759c1b 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp @@ -16,6 +16,7 @@ #include "barretenberg/transcript/transcript.hpp" #include "barretenberg/ultra_honk/prover_instance.hpp" #include "sumcheck_round.hpp" +#include namespace bb { @@ -874,7 +875,11 @@ template class SumcheckVerifier { // For other flavors, we perform the sumcheck univariate consistency check bool verified = true; - ClaimedEvaluations purported_evaluations; + // Heap-allocate ClaimedEvaluations (AllValues) to keep the sumcheck-verify stack frame small. + // For recursive flavors with many columns (e.g. AVM), holding this inline on the stack can exceed the 8 MB + // stack limit once nested inside the inner-Mega AVM recursive verifier chain + auto purported_evaluations_storage = std::make_unique(); + ClaimedEvaluations& purported_evaluations = *purported_evaluations_storage; for (size_t round_idx = 0; round_idx < virtual_log_n; round_idx++) { round.process_round(transcript, multivariate_challenge, gate_separators, round_idx); verified = verified && !round.round_failed; @@ -895,15 +900,16 @@ template class SumcheckVerifier { if constexpr (IsTranslatorFlavor) { // Translator path: receive full-circuit evaluations, set them, and complete // (computable precomputed selectors + L_0 scaling of minicircuit wires already placed above) - auto get_full_circuit_evaluations = + auto get_full_circuit_evaluations = std::make_unique>( transcript->template receive_from_prover>( - "Sumcheck:evaluations"); + "Sumcheck:evaluations")); Flavor::complete_full_circuit_evaluations( - purported_evaluations, get_full_circuit_evaluations, std::span(multivariate_challenge)); + purported_evaluations, *get_full_circuit_evaluations, std::span(multivariate_challenge)); } else { - auto transcript_evaluations = - transcript->template receive_from_prover>("Sumcheck:evaluations"); - for (auto [eval, transcript_eval] : zip_view(purported_evaluations.get_all(), transcript_evaluations)) { + // Heap-allocate transcript_evaluations for the same reason as purported_evaluations above. + auto transcript_evaluations = std::make_unique>( + transcript->template receive_from_prover>("Sumcheck:evaluations")); + for (auto [eval, transcript_eval] : zip_view(purported_evaluations.get_all(), *transcript_evaluations)) { eval = transcript_eval; } } @@ -932,7 +938,7 @@ template class SumcheckVerifier { // For ZK Flavors: the evaluations of Libra univariates are included in the Sumcheck Output return SumcheckOutput{ .challenge = multivariate_challenge, - .claimed_evaluations = purported_evaluations, + .claimed_evaluations = std::move(purported_evaluations), .verified = verified, .claimed_libra_evaluation = zk_correction_handler.get_libra_evaluation(), .round_univariate_commitments = round.get_round_univariate_commitments(), diff --git a/barretenberg/cpp/src/barretenberg/transcript/README.md b/barretenberg/cpp/src/barretenberg/transcript/README.md index b707f9e65e10..d69d6eff24bb 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/README.md +++ b/barretenberg/cpp/src/barretenberg/transcript/README.md @@ -280,9 +280,9 @@ Origin tags prevent these violations by **tainting** each value with metadata tr ```cpp struct OriginTag { - size_t transcript_index; // Which transcript instance created this value - numeric::uint256_t round_provenance; // Which protocol rounds contributed to this value - bool instant_death; // Poison flag - abort on any arithmetic + size_t transcript_index; // Which transcript instance created this value + numeric::uint512_t round_provenance; // Which protocol rounds contributed to this value + bool instant_death; // Poison flag - abort on any arithmetic }; ``` @@ -303,42 +303,42 @@ transcript_index = unique_transcript_index.fetch_add(1); #### round_provenance - Round Tracking Bitmask -A 256-bit value split into two 128-bit halves: +A 512-bit value split into two 256-bit halves: ``` -[Upper 128 bits: Challenges] [Lower 128 bits: Submitted Values] +[Upper 256 bits: Challenges] [Lower 256 bits: Submitted Values] ``` -- **Bit `i` in lower 128 bits**: This value uses data submitted in round `i` -- **Bit `i` in upper 128 bits**: This value uses a challenge generated in round `i` +- **Bit `i` in lower 256 bits**: This value uses data submitted in round `i` +- **Bit `i` in upper 256 bits**: This value uses a challenge generated in round `i` **Construction**: ```cpp // SINGLE BIT EXAMPLES - Values from one round only // Submitted value in round 3 -round_provenance = (1 << 3); // 0x0000...0008 (bit 3 in lower 128 bits) +round_provenance = (1 << 3); // 0x0000...0008 (bit 3 in lower 256 bits) // Challenge from round 5 -round_provenance = (1 << (5 + 128)); // 0x0020...0000 (bit 5 in upper 128 bits) +round_provenance = (1 << (5 + 256)); // 0x0020...0000 (bit 5 in upper 256 bits) // MULTIPLE BITS EXAMPLES - Values combined from multiple rounds // Value depending on submitted data from BOTH round 0 AND round 2 -round_provenance = (1 << 0) | (1 << 2); // 0x0000...0005 (bits 0 and 2 in lower 128) +round_provenance = (1 << 0) | (1 << 2); // 0x0000...0005 (bits 0 and 2 in lower 256) // Meaning: "This value incorporates data submitted in rounds 0 AND 2" // Value depending on challenges from BOTH round 1 AND round 3 -round_provenance = (1 << (1 + 128)) | (1 << (3 + 128)); // 0x000A...0000 (bits 1 and 3 in upper 128) +round_provenance = (1 << (1 + 256)) | (1 << (3 + 256)); // 0x000A...0000 (bits 1 and 3 in upper 256) // Meaning: "This value incorporates challenges from rounds 1 AND 3" // Value depending on submitted data (round 0) AND challenge (round 0) -round_provenance = (1 << 0) | (1 << (0 + 128)); // 0x0001...0001 (bit 0 in both halves) +round_provenance = (1 << 0) | (1 << (0 + 256)); // 0x0001...0001 (bit 0 in both halves) // Meaning: "This value uses both the data submitted in round 0 AND the challenge from round 0" // Complex example: submitted data from rounds 0,1 and challenges from rounds 0,2 -round_provenance = (1 << 0) | (1 << 1) | (1 << (0 + 128)) | (1 << (2 + 128)); +round_provenance = (1 << 0) | (1 << 1) | (1 << (0 + 256)) | (1 << (2 + 256)); // 0x0005...0003 // Meaning: "This value's computation involved: // - Submitted values from rounds 0 and 1 @@ -397,7 +397,7 @@ OriginTag::OriginTag(const OriginTag& tag_a, const OriginTag& tag_b) } // 5. Check cross-round contamination (the critical security check) - check_round_provenances(tag_a.round_provenance, tag_b.round_provenance); + check_round_provenance(tag_a.round_provenance, tag_b.round_provenance); // 6. Merge the tags transcript_index = tag_a.transcript_index; @@ -422,23 +422,23 @@ In recursive verification (in-circuit mode), values receive origin tags when the // Recursive verifier receives wire commitment from proof (round 0) auto comm = transcript->receive_from_prover("wire_comm"); // comm.get_origin_tag() = OriginTag(transcript_id, round=0, is_submitted=true) -// → round_provenance = 0x0000...0001 (bit 0 set in lower 128 bits) +// → round_provenance = 0x0000...0001 (bit 0 set in lower 256 bits) // Verifier generates challenge after round 0 data auto beta = transcript->get_challenge("beta"); // beta.get_origin_tag() = OriginTag(transcript_id, round=0, is_submitted=false) -// → round_provenance = 0x0001...0000 (bit 0 set in upper 128 bits) +// → round_provenance = 0x0001...0000 (bit 0 set in upper 256 bits) ``` **Tag construction** (in `origin_tag.hpp:OriginTag` constructor): ```cpp OriginTag(size_t parent_index, size_t child_index, bool is_submitted = true) : transcript_index(parent_index) - , round_provenance((static_cast(1) << (child_index + (is_submitted ? 0 : 128)))) + , round_provenance((static_cast(1) << (child_index + (is_submitted ? 0 : 256)))) ``` -- Submitted values: bit shifted by `child_index` (lower 128 bits) -- Challenges: bit shifted by `child_index + 128` (upper 128 bits) +- Submitted values: bit shifted by `child_index` (lower 256 bits) +- Challenges: bit shifted by `child_index + 256` (upper 256 bits) #### 2. Tag Merging Example @@ -458,21 +458,26 @@ This merged tag now carries the full provenance: it depends on data submitted in #### 3. The Cross-Round Check -The critical security check in `check_round_provenances()`: +The critical security check in `check_round_provenance()`: ```cpp -void check_round_provenances(const uint256_t& tag_a, const uint256_t& tag_b) +void check_round_provenance(const uint512_t& provenance_a, const uint512_t& provenance_b) { - const uint128_t* challenges_a = (const uint128_t*)(&tag_a.data[2]); // Upper 128 bits - const uint128_t* submitted_a = (const uint128_t*)(&tag_a.data[0]); // Lower 128 bits + // Lower 256 bits = submitted rounds, Upper 256 bits = challenge rounds + const uint256_t& submitted_a = provenance_a.lo; + const uint256_t& submitted_b = provenance_b.lo; - // Similar for tag_b... + // Nothing to check if either has no submitted data or both are from the same round(s) + if (submitted_a == 0 || submitted_b == 0 || submitted_a == submitted_b) { + return; + } - // VIOLATION: Two submitted values from different rounds mixing without challenges - if (*challenges_a == 0 && *challenges_b == 0 && // Neither has challenge bits set - *submitted_a != 0 && *submitted_b != 0 && // Both have submitted bits set - *submitted_a != *submitted_b) { // From different rounds - throw_or_abort("Submitted values from 2 different rounds are mixing without challenges"); + // VIOLATION: max challenge round must be >= max submitted round, otherwise the submitted + // values from different rounds were combined without a challenge that binds the later round. + const int max_challenge_round = highest_set_bit_256(provenance_a.hi | provenance_b.hi); + const int max_submitted_round = highest_set_bit_256(submitted_a | submitted_b); + if (max_challenge_round < max_submitted_round) { + throw_or_abort("Round provenance check failed: max challenge round < max submitted round"); } } ``` @@ -587,27 +592,29 @@ ROUND 2 - LOG DERIVATIVE INVERSE (challenge_generation_phase=true → false, rou │ receive_from_prover("lookup_inverses") │──► Origin tag: OriginTag(42, 2, true) └──────────────────────────────────────────┘ round_provenance = 0x0000...0004 (bit 2 lower) +┌──────────────────────────────────┐ +│ get_challenge("delta") │──► Tag: OriginTag(42, 2, false) +└──────────────────────────────────┘ round_provenance = 0x0004...0000 (bit 2 upper) + ** Example: Tag merging with cross-round values ** - combined = eta * lookup_inverses + combined = delta * lookup_inverses │ │ Tag Merging: - │ eta.tag = OriginTag(42, 0, false) → 0x0001...0000 - │ lookup_inv.tag = OriginTag(42, 2, true) → 0x0000...0004 - │ combined.tag = OriginTag(42, parent) → 0x0001...0004 (OR'd) + │ delta.tag = OriginTag(42, 2, false) → 0x0004...0000 + │ lookup_inv.tag = OriginTag(42, 2, true) → 0x0000...0004 + │ combined.tag = OriginTag(42, parent) → 0x0004...0004 (OR'd) │ ▼ ┌─────────────────────────────────────────────┐ - │ check_round_provenances(eta.tag, lookup_inv.tag) │ + │ check_round_provenance(delta.tag, lookup_inv.tag) │ └─────────────────────────────────────────────┘ │ - │ CHECK: Submitted values from different rounds? - │ challenges_eta = 0x0001...0000 (non-zero) ✓ - │ submitted_eta = 0x0000...0000 (zero) - │ challenges_inv = 0x0000...0000 (zero) - │ submitted_inv = 0x0000...0004 (non-zero) + │ CHECK: max challenge round >= max submitted round? + │ max challenge round = 2 (delta is round-2 challenge) + │ max submitted round = 2 (lookup_inverses submitted in round 2) │ - │ ✓ PASS: eta has challenge bits, prevents cross-round violation + │ ✓ PASS: 2 >= 2. Round-2 challenge correctly binds round-2 submitted data. │ ▼ combined (FF witness with merged tag) @@ -618,35 +625,34 @@ ORIGIN TAG VIOLATION EXAMPLE (Cross-Round Contamination) ┌──────────────────────────────────┐ │ Round 0: w_l (wire commitment) │ tag = OriginTag(42, 0, true) - │ round_provenance = 0x...0001 │ (bit 0 in lower 128 bits) + │ round_provenance = 0x...0001 │ (bit 0 in lower 256 bits) └──────────────────────────────────┘ │ - │ (eta challenges generated) + │ (eta challenges generated → tag has bit 0 in upper 256 bits) │ ┌──────────────────────────────────┐ │ Round 1: w_4 (4th wire) │ tag = OriginTag(42, 1, true) - │ round_provenance = 0x...0002 │ (bit 1 in lower 128 bits) + │ round_provenance = 0x...0002 │ (bit 1 in lower 256 bits) └──────────────────────────────────┘ │ ▼ - ❌ VIOLATION: Direct mixing without challenges - result = w_l + w_4 + ❌ VIOLATION: round-0 challenge cannot bind round-1 submission + result = eta * w_l + w_4 │ - check_round_provenances() detects: - challenges_w_l = 0 (no challenges involved) - challenges_w_4 = 0 (no challenges involved) - submitted_w_l = 0x...0001 (from round 0) - submitted_w_4 = 0x...0002 (from round 1) - submitted_w_l != submitted_w_4 ← DIFFERENT ROUNDS! + check_round_provenance() detects: + max challenge round = 0 (only eta involved, from round 0) + max submitted round = 1 (w_4 was submitted in round 1) + 0 < 1 ← challenge does not reach the latest submitted round! - → throw_or_abort("Submitted values from 2 different - rounds are mixing without challenges") + → throw_or_abort("Round provenance check failed: + max challenge round < max submitted round") - ✅ CORRECT: Must use challenge to combine cross-round values - result = eta * w_l + w_4 + ✅ CORRECT: Use a challenge from round >= max submitted round + auto beta = transcript->get_challenge("beta"); // round-1 challenge + result = beta * w_l + w_4 │ - │ eta.tag has challenge bits from round 0 - │ → Properly binds the two rounds together + │ beta.tag has challenge bits from round 1 + │ max challenge round = 1, max submitted round = 1 → PASS ▼ OK (merged tag tracks both rounds AND challenge) diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp index e9d60e3a82fe..d8cc514ef3e1 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.cpp @@ -7,7 +7,6 @@ #include "barretenberg/transcript/origin_tag.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" -#include #include namespace bb { @@ -16,32 +15,21 @@ using namespace numeric; namespace { /** - * @brief Find the position of the highest set bit in a uint128_t - * @return -1 if no bits are set, otherwise the bit position (0-127) + * @brief Find the position of the highest set bit in a uint256_t + * @return -1 if no bits are set, otherwise the bit position (0-255) */ -inline int highest_set_bit_128(uint128_t value) +inline int highest_set_bit_256(uint256_t value) { if (value == 0) { return -1; } - // Check high 64 bits first - auto high = static_cast(value >> 64); - if (high != 0) { - return 127 - __builtin_clzll(high); + for (int idx = 0; idx < 4; idx++) { + auto chunk = static_cast(value >> (64 * (3 - idx))); + if (chunk != 0) { + return 255 - (idx * 64) - __builtin_clzll(chunk); + } } - // Check low 64 bits - auto low = static_cast(value); - return 63 - __builtin_clzll(low); -} - -/** - * @brief Safely extract uint128_t from uint256_t data array using memcpy to avoid strict aliasing issues - */ -inline uint128_t extract_uint128(const uint64_t* data) -{ - uint128_t result = 0; - std::memcpy(&result, data, sizeof(uint128_t)); - return result; + return -1; } } // namespace @@ -60,11 +48,11 @@ inline uint128_t extract_uint128(const uint64_t* data) * @param provenance_a Round provenance of first element * @param provenance_b Round provenance of second element */ -void check_round_provenance(const uint256_t& provenance_a, const uint256_t& provenance_b) +void check_round_provenance(const uint512_t& provenance_a, const uint512_t& provenance_b) { - // Lower 128 bits = submitted rounds, Upper 128 bits = challenge rounds - const auto submitted_a = extract_uint128(&provenance_a.data[0]); - const auto submitted_b = extract_uint128(&provenance_b.data[0]); + // Lower 256 bits = submitted rounds, Upper 256 bits = challenge rounds + const uint256_t& submitted_a = provenance_a.lo; + const uint256_t& submitted_b = provenance_b.lo; // Nothing to check if either has no submitted data or both are from the same round(s) if (submitted_a == 0 || submitted_b == 0 || submitted_a == submitted_b) { @@ -72,10 +60,8 @@ void check_round_provenance(const uint256_t& provenance_a, const uint256_t& prov } // Ensure that values from different rounds are not mixing without max challenge round >= max submitted round - const auto challenges_a = extract_uint128(&provenance_a.data[2]); - const auto challenges_b = extract_uint128(&provenance_b.data[2]); - const int max_challenge_round = highest_set_bit_128(challenges_a | challenges_b); - const int max_submitted_round = highest_set_bit_128(submitted_a | submitted_b); + const int max_challenge_round = highest_set_bit_256(provenance_a.hi | provenance_b.hi); + const int max_submitted_round = highest_set_bit_256(submitted_a | submitted_b); if (max_challenge_round < max_submitted_round) { throw_or_abort("Round provenance check failed: max challenge round (" + std::to_string(max_challenge_round) + diff --git a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp index 634dc8c3ce02..44873fecf2a8 100644 --- a/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp +++ b/barretenberg/cpp/src/barretenberg/transcript/origin_tag.hpp @@ -16,7 +16,7 @@ #include "barretenberg/common/assert.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" -#include +#include "barretenberg/numeric/uintx/uintx.hpp" #include #include #include @@ -65,7 +65,7 @@ template constexpr bool is_iterable_v = is_iterable::value; namespace bb { -void check_round_provenance(const uint256_t& provenance_a, const uint256_t& provenance_b); +void check_round_provenance(const uint512_t& provenance_a, const uint512_t& provenance_b); #ifndef AZTEC_NO_ORIGIN_TAGS struct OriginTag { @@ -87,10 +87,10 @@ struct OriginTag { size_t transcript_index = FREE_WITNESS; // round_provenance specifies which submitted values and challenges have been used to generate this element - // The lower 128 bits represent using a submitted value from a corresponding round (the shift represents the - // round) The higher 128 bits represent using a challenge value from an corresponding round (the shift + // The lower 256 bits represent using a submitted value from a corresponding round (the shift represents the + // round) The higher 256 bits represent using a challenge value from an corresponding round (the shift // represents the round) - numeric::uint256_t round_provenance = numeric::uint256_t(0); + numeric::uint512_t round_provenance = numeric::uint512_t(0); // Instant death is used for poisoning values we should never use in arithmetic bool instant_death = false; @@ -117,9 +117,9 @@ struct OriginTag { */ OriginTag(size_t transcript_idx, size_t round_number, bool is_submitted = true) : transcript_index(transcript_idx) - , round_provenance((static_cast(1) << (round_number + (is_submitted ? 0 : 128)))) + , round_provenance((static_cast(1) << (round_number + (is_submitted ? 0 : 256)))) { - BB_ASSERT_LT(round_number, 128U); + BB_ASSERT_LT(round_number, 256U); } /** @@ -166,19 +166,19 @@ struct OriginTag { void set_free_witness() { transcript_index = FREE_WITNESS; - round_provenance = 0; + round_provenance = numeric::uint512_t(0); } void unset_free_witness() { transcript_index = CONSTANT; - round_provenance = numeric::uint256_t(0); + round_provenance = numeric::uint512_t(0); } bool is_constant() const { return transcript_index == CONSTANT && !instant_death; } void set_constant() { transcript_index = CONSTANT; - round_provenance = numeric::uint256_t(0); + round_provenance = numeric::uint512_t(0); } // Static factory methods for cleaner syntax @@ -206,7 +206,7 @@ struct OriginTag { /** * @brief Clear the round_provenance to address round provenance false positives. */ - void clear_round_provenance() { round_provenance = numeric::uint256_t(0); } + void clear_round_provenance() { round_provenance = numeric::uint512_t(0); } }; inline std::ostream& operator<<(std::ostream& os, OriginTag const& v) { diff --git a/noir-projects/noir-protocol-circuits/Nargo.template.toml b/noir-projects/noir-protocol-circuits/Nargo.template.toml index 7b8c5179424a..7cde97b4f0ff 100644 --- a/noir-projects/noir-protocol-circuits/Nargo.template.toml +++ b/noir-projects/noir-protocol-circuits/Nargo.template.toml @@ -12,8 +12,10 @@ members = [ "crates/parity-root", "crates/private-kernel-lib", "crates/private-kernel-init", + "crates/private-kernel-init-3", "crates/private-kernel-init-simulated", "crates/private-kernel-inner", + "crates/private-kernel-inner-3", "crates/private-kernel-inner-simulated", "crates/private-kernel-reset", "crates/private-kernel-reset-simulated", diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-init-3/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/private-kernel-init-3/Nargo.toml new file mode 100644 index 000000000000..a9d7d87ee1ad --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-init-3/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "private_kernel_init_3" +type = "bin" +authors = [""] +compiler_version = ">=0.18.0" + +[dependencies] +private_kernel_lib = { path = "../private-kernel-lib" } +types = { path = "../types" } diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-init-3/src/main.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-init-3/src/main.nr new file mode 100644 index 000000000000..fd224678e8e3 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-init-3/src/main.nr @@ -0,0 +1,39 @@ +use private_kernel_lib::{private_kernel_init_n, PrivateKernelInitNCircuitPrivateInputs}; +use types::{ + abis::{ + private_circuit_public_inputs::PrivateCircuitPublicInputs, + private_kernel::private_call_data::PrivateCallDataWithoutPublicInputs, + protocol_contracts::ProtocolContracts, transaction::tx_request::TxRequest, + }, + PrivateKernelCircuitPublicInputs, +}; + +fn main( + tx_request: TxRequest, + vk_tree_root: Field, + protocol_contracts: ProtocolContracts, + private_call_0: PrivateCallDataWithoutPublicInputs, + private_call_1: PrivateCallDataWithoutPublicInputs, + private_call_2: PrivateCallDataWithoutPublicInputs, + is_private_only: bool, + first_nullifier_hint: Field, + revertible_counter_hint: u32, + app_public_inputs_0: call_data(1) PrivateCircuitPublicInputs, + app_public_inputs_1: call_data(2) PrivateCircuitPublicInputs, + app_public_inputs_2: call_data(3) PrivateCircuitPublicInputs, +) -> return_data PrivateKernelCircuitPublicInputs { + let private_inputs = PrivateKernelInitNCircuitPrivateInputs { + tx_request, + vk_tree_root, + protocol_contracts, + private_calls: [ + private_call_0.to_private_call_data(app_public_inputs_0), + private_call_1.to_private_call_data(app_public_inputs_1), + private_call_2.to_private_call_data(app_public_inputs_2), + ], + is_private_only, + first_nullifier_hint, + revertible_counter_hint, + }; + private_kernel_init_n::execute::<3>(private_inputs) +} diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-inner-3/Nargo.toml b/noir-projects/noir-protocol-circuits/crates/private-kernel-inner-3/Nargo.toml new file mode 100644 index 000000000000..7f70ba73336b --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-inner-3/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "private_kernel_inner_3" +type = "bin" +authors = [""] +compiler_version = ">=0.18.0" + +[dependencies] +private_kernel_lib = { path = "../private-kernel-lib" } +types = { path = "../types" } diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-inner-3/src/main.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-inner-3/src/main.nr new file mode 100644 index 000000000000..02c088d17c89 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-inner-3/src/main.nr @@ -0,0 +1,30 @@ +use private_kernel_lib::{private_kernel_inner_n, PrivateKernelInnerNCircuitPrivateInputs}; +use types::{ + abis::{ + private_circuit_public_inputs::PrivateCircuitPublicInputs, + private_kernel::private_call_data::PrivateCallDataWithoutPublicInputs, + private_kernel_data::PrivateKernelDataWithoutPublicInputs, + }, + PrivateKernelCircuitPublicInputs, +}; + +fn main( + previous_kernel: PrivateKernelDataWithoutPublicInputs, + previous_kernel_public_inputs: call_data(0) PrivateKernelCircuitPublicInputs, + private_call_0: PrivateCallDataWithoutPublicInputs, + private_call_1: PrivateCallDataWithoutPublicInputs, + private_call_2: PrivateCallDataWithoutPublicInputs, + app_public_inputs_0: call_data(1) PrivateCircuitPublicInputs, + app_public_inputs_1: call_data(2) PrivateCircuitPublicInputs, + app_public_inputs_2: call_data(3) PrivateCircuitPublicInputs, +) -> return_data PrivateKernelCircuitPublicInputs { + let private_inputs = PrivateKernelInnerNCircuitPrivateInputs { + previous_kernel: previous_kernel.to_private_kernel_data(previous_kernel_public_inputs), + private_calls: [ + private_call_0.to_private_call_data(app_public_inputs_0), + private_call_1.to_private_call_data(app_public_inputs_1), + private_call_2.to_private_call_data(app_public_inputs_2), + ], + }; + private_kernel_inner_n::execute::<3>(private_inputs) +} diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/lib.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/lib.nr index 9eb56b0661b6..4ca2af21cfe8 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/lib.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/lib.nr @@ -3,7 +3,10 @@ mod abis; mod accumulated_data; mod components; pub mod private_kernel_init; +pub mod private_kernel_init_n; +pub(crate) mod private_kernel_batch; pub mod private_kernel_inner; +pub mod private_kernel_inner_n; pub mod private_kernel_tail; pub mod private_kernel_tail_to_public; pub mod private_kernel_reset; @@ -15,7 +18,9 @@ pub use abis::{PaddedSideEffectAmounts, PaddedSideEffects, PrivateKernelResetHin pub use hiding_kernel_to_public::HidingKernelToPublicPrivateInputs; pub use hiding_kernel_to_rollup::HidingKernelToRollupPrivateInputs; pub use private_kernel_init::PrivateKernelInitCircuitPrivateInputs; +pub use private_kernel_init_n::PrivateKernelInitNCircuitPrivateInputs; pub use private_kernel_inner::PrivateKernelInnerCircuitPrivateInputs; +pub use private_kernel_inner_n::PrivateKernelInnerNCircuitPrivateInputs; pub use private_kernel_reset::PrivateKernelResetCircuitPrivateInputs; pub use private_kernel_tail::PrivateKernelTailCircuitPrivateInputs; pub use private_kernel_tail_to_public::PrivateKernelTailToPublicCircuitPrivateInputs; diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_batch.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_batch.nr new file mode 100644 index 000000000000..7ecd05baf5da --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_batch.nr @@ -0,0 +1,39 @@ +use crate::components::{ + private_call_data_validator::PrivateCallDataValidator, + private_kernel_circuit_output_composer::PrivateKernelCircuitOutputComposer, + private_kernel_circuit_output_validator::PrivateKernelCircuitOutputValidator, +}; +use types::abis::{ + kernel_circuit_public_inputs::PrivateKernelCircuitPublicInputs, + private_kernel::private_call_data::PrivateCallData, +}; + +unconstrained fn generate_next_output( + previous_kernel_public_inputs: PrivateKernelCircuitPublicInputs, + private_call: PrivateCallData, +) -> PrivateKernelCircuitPublicInputs { + PrivateKernelCircuitOutputComposer::new_from_previous_kernel(previous_kernel_public_inputs) + .pop_top_call_request() + .with_private_call(private_call) + .finish() +} + +pub fn execute_next_private_call( + previous_kernel_public_inputs: PrivateKernelCircuitPublicInputs, + private_call: PrivateCallData, +) -> PrivateKernelCircuitPublicInputs { + PrivateCallDataValidator::new(private_call).validate_as_inner_call( + previous_kernel_public_inputs, + ); + + // Safety: the constrained validator below proves the unconstrained composer output matches this transition. + let output = unsafe { generate_next_output(previous_kernel_public_inputs, private_call) }; + + if ::types::validate::should_validate_output() { + PrivateKernelCircuitOutputValidator::new(output).validate_as_inner_call( + previous_kernel_public_inputs, + private_call, + ); + } + output +} diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_init_n.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_init_n.nr new file mode 100644 index 000000000000..e80ac964bb6c --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_init_n.nr @@ -0,0 +1,41 @@ +use crate::{ + private_kernel_batch::execute_next_private_call, + private_kernel_init::{self, PrivateKernelInitCircuitPrivateInputs}, +}; +use types::abis::{ + kernel_circuit_public_inputs::PrivateKernelCircuitPublicInputs, + private_kernel::private_call_data::PrivateCallData, protocol_contracts::ProtocolContracts, + transaction::tx_request::TxRequest, +}; + +pub struct PrivateKernelInitNCircuitPrivateInputs { + pub tx_request: TxRequest, + pub private_calls: [PrivateCallData; N], + pub vk_tree_root: Field, + pub protocol_contracts: ProtocolContracts, + pub is_private_only: bool, + pub first_nullifier_hint: Field, + pub revertible_counter_hint: u32, +} + +pub fn execute( + inputs: PrivateKernelInitNCircuitPrivateInputs, +) -> PrivateKernelCircuitPublicInputs { + let mut output = private_kernel_init::execute( + PrivateKernelInitCircuitPrivateInputs { + tx_request: inputs.tx_request, + private_call: inputs.private_calls[0], + vk_tree_root: inputs.vk_tree_root, + protocol_contracts: inputs.protocol_contracts, + is_private_only: inputs.is_private_only, + first_nullifier_hint: inputs.first_nullifier_hint, + revertible_counter_hint: inputs.revertible_counter_hint, + }, + ); + + for i in 1..N { + output = execute_next_private_call(output, inputs.private_calls[i]); + } + + output +} diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_inner_n.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_inner_n.nr new file mode 100644 index 000000000000..4a73da36614b --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/private_kernel_inner_n.nr @@ -0,0 +1,29 @@ +use crate::{ + private_kernel_batch::execute_next_private_call, + private_kernel_inner::ALLOWED_PREVIOUS_CIRCUITS, +}; +use types::abis::{ + kernel_circuit_public_inputs::PrivateKernelCircuitPublicInputs, + private_kernel::private_call_data::PrivateCallData, private_kernel_data::PrivateKernelData, +}; + +pub struct PrivateKernelInnerNCircuitPrivateInputs { + pub previous_kernel: PrivateKernelData, + pub private_calls: [PrivateCallData; N], +} + +pub fn execute( + inputs: PrivateKernelInnerNCircuitPrivateInputs, +) -> PrivateKernelCircuitPublicInputs { + if !::std::runtime::is_unconstrained() { + inputs.previous_kernel.verify(false); + } + inputs.previous_kernel.validate_vk_in_vk_tree(ALLOWED_PREVIOUS_CIRCUITS); + + let mut output = inputs.previous_kernel.public_inputs; + for i in 0..N { + output = execute_next_private_call(output, inputs.private_calls[i]); + } + + output +} diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/mod.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/mod.nr index d44179715831..5c1cf4b90278 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/mod.nr @@ -1,4 +1,5 @@ mod private_kernel_circuit_output_validator_builder; +mod private_kernel_batch; mod private_kernel_init; mod private_kernel_inner; mod private_kernel_reset; diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/private_kernel_batch/mod.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/private_kernel_batch/mod.nr new file mode 100644 index 000000000000..371bf1107694 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/private_kernel_batch/mod.nr @@ -0,0 +1,742 @@ +use crate::{ + private_kernel_init::{self, PrivateKernelInitCircuitPrivateInputs}, + private_kernel_init_n::{self, PrivateKernelInitNCircuitPrivateInputs}, + private_kernel_inner::{self, PrivateKernelInnerCircuitPrivateInputs}, + private_kernel_inner_n::{self, PrivateKernelInnerNCircuitPrivateInputs}, +}; +use protocol_test_utils::FixtureBuilder; +use types::{ + abis::{ + kernel_circuit_public_inputs::PrivateKernelCircuitPublicInputs, + private_kernel_data::PrivateKernelData, transaction::tx_request::TxRequest, + }, + constants::{ + MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, PRIVATE_KERNEL_INIT_VK_INDEX, + PRIVATE_KERNEL_INNER_VK_INDEX, + }, +}; + +fn private_kernel_data_with_vk_index( + public_inputs: PrivateKernelCircuitPublicInputs, + vk_index: u32, +) -> PrivateKernelData { + let kernel_data = FixtureBuilder::new().in_vk_tree(vk_index).to_private_kernel_data(); + PrivateKernelData { public_inputs, vk_data: kernel_data.vk_data } +} + +fn assert_private_kernel_public_inputs_eq( + actual: PrivateKernelCircuitPublicInputs, + expected: PrivateKernelCircuitPublicInputs, +) { + assert_eq(actual.constants, expected.constants); + assert_eq( + actual.min_revertible_side_effect_counter, + expected.min_revertible_side_effect_counter, + ); + assert_eq(actual.end, expected.end); + assert_eq(actual.validation_requests, expected.validation_requests); + assert_eq(actual.public_teardown_call_request, expected.public_teardown_call_request); + assert_eq(actual.fee_payer, expected.fee_payer); + assert_eq(actual.expiration_timestamp, expected.expiration_timestamp); + assert_eq(actual.is_private_only, expected.is_private_only); + assert_eq(actual.claimed_first_nullifier, expected.claimed_first_nullifier); + assert_eq(actual.claimed_revertible_counter, expected.claimed_revertible_counter); +} + +fn execute_init_n_sequential( + inputs: PrivateKernelInitNCircuitPrivateInputs, +) -> PrivateKernelCircuitPublicInputs { + let mut output = private_kernel_init::execute( + PrivateKernelInitCircuitPrivateInputs { + tx_request: inputs.tx_request, + private_call: inputs.private_calls[0], + vk_tree_root: inputs.vk_tree_root, + protocol_contracts: inputs.protocol_contracts, + is_private_only: inputs.is_private_only, + first_nullifier_hint: inputs.first_nullifier_hint, + revertible_counter_hint: inputs.revertible_counter_hint, + }, + ); + + for i in 1..N { + let vk_index = if i == 1 { + PRIVATE_KERNEL_INIT_VK_INDEX + } else { + PRIVATE_KERNEL_INNER_VK_INDEX + }; + output = private_kernel_inner::execute( + PrivateKernelInnerCircuitPrivateInputs { + previous_kernel: private_kernel_data_with_vk_index(output, vk_index), + private_call: inputs.private_calls[i], + }, + ); + } + + output +} + +fn execute_inner_n_sequential( + inputs: PrivateKernelInnerNCircuitPrivateInputs, +) -> PrivateKernelCircuitPublicInputs { + let mut output = private_kernel_inner::execute( + PrivateKernelInnerCircuitPrivateInputs { + previous_kernel: inputs.previous_kernel, + private_call: inputs.private_calls[0], + }, + ); + + for i in 1..N { + output = private_kernel_inner::execute( + PrivateKernelInnerCircuitPrivateInputs { + previous_kernel: private_kernel_data_with_vk_index( + output, + PRIVATE_KERNEL_INNER_VK_INDEX, + ), + private_call: inputs.private_calls[i], + }, + ); + } + + output +} + +struct BatchNTestBuilder { + tx_request: TxRequest, + private_calls: [FixtureBuilder; N], + is_private_only: bool, + first_nullifier_hint: Field, + revertible_counter_hint: u32, +} + +impl BatchNTestBuilder { + fn new() -> Self { + let mut private_calls = [FixtureBuilder::new(); N]; + + private_calls[0] = FixtureBuilder::new_from_counter(2).is_first_call(); + let mut first_private_call = private_calls[0]; + first_private_call.compute_update_tree_and_hints(); + private_calls[0] = first_private_call; + + for i in 1..N { + let mut private_call = FixtureBuilder::new_from_counter(i * 1000); + private_call.anchor_block_header = private_calls[0].anchor_block_header; + private_call.tx_context = private_calls[0].tx_context; + private_call.compute_update_tree_and_hints(); + private_calls[i] = private_call; + } + + let tx_request = private_calls[0].to_tx_request(); + + BatchNTestBuilder { + tx_request, + private_calls, + is_private_only: false, + first_nullifier_hint: 123, + revertible_counter_hint: 0, + } + } + + fn execute_linear_chain(self) -> PrivateKernelCircuitPublicInputs { + self.execute_with_shape(false, true) + } + + fn execute_linear_chain_without_second_to_third_sync(self) -> PrivateKernelCircuitPublicInputs { + self.execute_with_shape(false, false) + } + + fn execute_linear_chain_without_first_to_second_sync(self) -> PrivateKernelCircuitPublicInputs { + let mut inputs = self.inputs_with_shape(false, true); + inputs.private_calls[0].public_inputs.private_call_requests.array[0].args_hash += 1; + + private_kernel_init_n::execute::(inputs) + } + + fn execute_depth_first_with_sibling(self) -> PrivateKernelCircuitPublicInputs { + self.execute_with_shape(true, true) + } + + fn execute_with_shape( + self, + include_sibling: bool, + sync_second_to_third: bool, + ) -> PrivateKernelCircuitPublicInputs { + private_kernel_init_n::execute::(self.inputs_with_shape( + include_sibling, + sync_second_to_third, + )) + } + + fn execute_sequential_with_shape( + self, + include_sibling: bool, + sync_second_to_third: bool, + ) -> PrivateKernelCircuitPublicInputs { + execute_init_n_sequential( + self.inputs_with_shape(include_sibling, sync_second_to_third), + ) + } + + fn inputs_with_shape( + self, + include_sibling: bool, + sync_second_to_third: bool, + ) -> PrivateKernelInitNCircuitPrivateInputs { + let mut private_calls = self.private_calls; + + if include_sibling { + let mut first_private_call = private_calls[0]; + first_private_call.append_private_call_requests(2); + private_calls[0] = first_private_call; + } else { + let mut first_private_call = private_calls[0]; + first_private_call.add_private_call_request(); + private_calls[0] = first_private_call; + } + + for i in 1..N { + let previous_request = private_calls[i - 1].private_call_requests.get(0); + private_calls[i].msg_sender = private_calls[i - 1].contract_address; + private_calls[i].counter_start = previous_request.start_side_effect_counter; + if private_calls[i].counter <= previous_request.start_side_effect_counter { + private_calls[i].counter = previous_request.start_side_effect_counter + 1; + } + + if i < N - 1 { + let mut private_call = private_calls[i]; + private_call.add_private_call_request(); + private_calls[i] = private_call; + } + } + + for i in 1..N { + let reverse_index = N - i - 1; + let next_call_request = private_calls[reverse_index + 1].to_private_call_request(); + let next_counter = private_calls[reverse_index + 1].counter; + let mut current_call = private_calls[reverse_index]; + if (reverse_index < N - 2) | sync_second_to_third { + current_call.private_call_requests.set(0, next_call_request); + } + if current_call.counter <= next_counter { + current_call.counter = next_counter + 1; + } + private_calls[reverse_index] = current_call; + } + + if include_sibling { + let mut first_private_call = private_calls[0]; + let mut sibling_request = private_calls[0].private_call_requests.get(1); + sibling_request.start_side_effect_counter = private_calls[1].counter + 1; + sibling_request.end_side_effect_counter = sibling_request.start_side_effect_counter + 1; + first_private_call.private_call_requests.set(1, sibling_request); + private_calls[0] = first_private_call; + } + + if include_sibling { + let sibling_request = private_calls[0].private_call_requests.get(1); + if private_calls[0].counter <= sibling_request.end_side_effect_counter { + private_calls[0].counter = sibling_request.end_side_effect_counter + 1; + } + } + + let mut private_call_data = [private_calls[0].to_private_call_data(); N]; + for i in 1..N { + private_call_data[i] = private_calls[i].to_private_call_data(); + } + + PrivateKernelInitNCircuitPrivateInputs { + tx_request: self.tx_request, + private_calls: private_call_data, + vk_tree_root: private_calls[0].vk_tree_root, + protocol_contracts: private_calls[0].protocol_contracts, + is_private_only: self.is_private_only, + first_nullifier_hint: self.first_nullifier_hint, + revertible_counter_hint: self.revertible_counter_hint, + } + } + + fn append_note_hashes(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_note_hashes(amount); + self.private_calls[index] = private_call; + } + + fn append_nullifiers(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_nullifiers(amount); + self.private_calls[index] = private_call; + } + + fn append_l2_to_l1_msgs(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_l2_to_l1_msgs(amount); + self.private_calls[index] = private_call; + } + + fn append_private_logs(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_private_logs(amount); + self.private_calls[index] = private_call; + } + + fn append_contract_class_logs(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_contract_class_logs(amount); + self.private_calls[index] = private_call; + } + + fn append_public_call_requests(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_public_call_requests(amount); + self.private_calls[index] = private_call; + } + + fn append_note_hash_read_requests(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_note_hash_read_requests(amount); + self.private_calls[index] = private_call; + } + + fn append_nullifier_read_requests(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_nullifier_read_requests(amount); + self.private_calls[index] = private_call; + } + + fn append_key_validation_requests(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_key_validation_requests(amount); + self.private_calls[index] = private_call; + } + + fn set_fee_payer(&mut self, index: u32) { + let mut private_call = self.private_calls[index]; + private_call.is_fee_payer = true; + self.private_calls[index] = private_call; + } + + fn set_public_teardown_call_request(&mut self, index: u32) { + let mut private_call = self.private_calls[index]; + private_call.set_public_teardown_call_request(); + self.private_calls[index] = private_call; + } + + fn end_setup(&mut self, index: u32) { + let mut private_call = self.private_calls[index]; + private_call.end_setup(); + self.private_calls[index] = private_call; + } + + fn set_static_call(&mut self, index: u32) { + let mut private_call = self.private_calls[index]; + let _ = private_call.is_static_call(); + self.private_calls[index] = private_call; + } + + fn increment_anchor_block_header_version(&mut self, index: u32) { + let mut private_call = self.private_calls[index]; + private_call.anchor_block_header.global_variables.version += 1; + self.private_calls[index] = private_call; + } +} + +struct InnerNTestBuilder { + previous_kernel: FixtureBuilder, + private_calls: [FixtureBuilder; N], +} + +impl InnerNTestBuilder { + fn new() -> Self { + let mut previous_kernel = FixtureBuilder::new_from_counter(135) + .in_vk_tree(PRIVATE_KERNEL_INIT_VK_INDEX) + .as_parent_contract(); + previous_kernel.compute_update_tree_and_hints(); + + let mut private_calls = [FixtureBuilder::new(); N]; + for i in 0..N { + let mut private_call = FixtureBuilder::new_from_counter((i + 1) * 1000); + private_call.anchor_block_header = previous_kernel.anchor_block_header; + private_call.tx_context = previous_kernel.tx_context; + private_call.compute_update_tree_and_hints(); + private_calls[i] = private_call; + } + + InnerNTestBuilder { previous_kernel, private_calls } + } + + fn execute_linear_chain(self) -> PrivateKernelCircuitPublicInputs { + private_kernel_inner_n::execute::(self.linear_chain_inputs()) + } + + fn execute_linear_chain_sequential(self) -> PrivateKernelCircuitPublicInputs { + execute_inner_n_sequential(self.linear_chain_inputs()) + } + + fn linear_chain_inputs(self) -> PrivateKernelInnerNCircuitPrivateInputs { + let mut previous_kernel = self.previous_kernel; + let mut private_calls = self.private_calls; + + for i in 0..N - 1 { + let mut private_call = private_calls[i]; + private_call.add_private_call_request(); + private_calls[i] = private_call; + let next_request = private_calls[i].private_call_requests.get(0); + private_calls[i + 1].msg_sender = private_calls[i].contract_address; + private_calls[i + 1].counter_start = next_request.start_side_effect_counter; + if private_calls[i + 1].counter <= next_request.start_side_effect_counter { + private_calls[i + 1].counter = next_request.start_side_effect_counter + 1; + } + } + + for i in 1..N { + let reverse_index = N - i - 1; + let next_call_request = private_calls[reverse_index + 1].to_private_call_request(); + let next_counter = private_calls[reverse_index + 1].counter; + let mut current_call = private_calls[reverse_index]; + current_call.private_call_requests.set(0, next_call_request); + if current_call.counter <= next_counter { + current_call.counter = next_counter + 1; + } + private_calls[reverse_index] = current_call; + } + + previous_kernel.private_call_requests.push(private_calls[0].to_private_call_request()); + + let mut private_call_data = [private_calls[0].to_private_call_data(); N]; + for i in 1..N { + private_call_data[i] = private_calls[i].to_private_call_data(); + } + + PrivateKernelInnerNCircuitPrivateInputs { + previous_kernel: previous_kernel.to_private_kernel_data(), + private_calls: private_call_data, + } + } + + fn append_note_hashes(&mut self, index: u32, amount: u32) { + let mut private_call = self.private_calls[index]; + private_call.append_note_hashes(amount); + self.private_calls[index] = private_call; + } +} + +fn add_one_of_each_per_call_with_tx_bounded_logs(builder: &mut BatchNTestBuilder) { + for i in 0..N { + builder.append_note_hashes(i, 1); + builder.append_nullifiers(i, 1); + builder.append_l2_to_l1_msgs(i, 1); + builder.append_private_logs(i, 1); + builder.append_public_call_requests(i, 1); + builder.append_note_hash_read_requests(i, 1); + builder.append_nullifier_read_requests(i, 1); + builder.append_key_validation_requests(i, 1); + } + + builder.append_contract_class_logs(0, 1); +} + +fn assert_batch_n_linear_chain_consumes_all_calls() { + let builder = BatchNTestBuilder::::new(); + + let output = builder.execute_linear_chain(); + + assert_eq(output.end.private_call_stack.length, 0); +} + +fn assert_batch_n_linear_chain_matches_sequential_kernels() { + let builder = BatchNTestBuilder::::new(); + + let batched = builder.execute_linear_chain(); + let sequential = builder.execute_sequential_with_shape(false, true); + + assert_private_kernel_public_inputs_eq(batched, sequential); +} + +fn assert_batch_n_with_everything_non_empty_matches_sequential_kernels() { + let mut builder = BatchNTestBuilder::::new(); + add_one_of_each_per_call_with_tx_bounded_logs(&mut builder); + + let batched = builder.execute_linear_chain(); + let sequential = builder.execute_sequential_with_shape(false, true); + + assert_private_kernel_public_inputs_eq(batched, sequential); +} + +fn assert_batch_n_accumulates_side_effects_across_slots() { + let mut builder = BatchNTestBuilder::::new(); + + for i in 0..N { + builder.append_note_hashes(i, 1); + builder.append_l2_to_l1_msgs(i, 1); + } + + let note_hashes = builder.private_calls.map(|call| call.note_hashes.storage()[0]); + let l2_to_l1_msgs = builder.private_calls.map(|call| call.l2_to_l1_msgs.storage()[0]); + let contract_addresses = builder.private_calls.map(|call| call.contract_address); + + let output = builder.execute_linear_chain(); + + assert_eq(output.end.note_hashes.length, N, "incorrect note hash length"); + assert_eq(output.end.l2_to_l1_msgs.length, N, "incorrect l2 to l1 message length"); + for i in 0..N { + assert_eq(output.end.note_hashes.array[i], note_hashes[i]); + assert_eq(output.end.note_hashes.array[i].contract_address, contract_addresses[i]); + assert_eq(output.end.l2_to_l1_msgs.array[i], l2_to_l1_msgs[i]); + } +} + +fn assert_batch_n_depth_first_child_keeps_sibling_on_stack() { + let builder = BatchNTestBuilder::::new(); + + let output = builder.execute_depth_first_with_sibling(); + + assert_eq(output.end.private_call_stack.length, 1); +} + +fn assert_batch_n_depth_first_with_sibling_matches_sequential_kernels() { + let builder = BatchNTestBuilder::::new(); + + let batched = builder.execute_depth_first_with_sibling(); + let sequential = builder.execute_sequential_with_shape(true, true); + + assert_private_kernel_public_inputs_eq(batched, sequential); +} + +fn assert_batch_n_last_call_must_match_previous_call_stack_fails() { + let builder = BatchNTestBuilder::::new(); + + let _ = builder.execute_linear_chain_without_second_to_third_sync(); +} + +fn assert_batch_n_second_call_must_match_first_call_stack_fails() { + let builder = BatchNTestBuilder::::new(); + + let _ = builder.execute_linear_chain_without_first_to_second_sync(); +} + +fn assert_batch_n_second_call_claimed_lengths_are_validated() { + let mut builder = BatchNTestBuilder::::new(); + + builder.append_note_hash_read_requests(1, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL + 1); + + let _ = builder.execute_linear_chain(); +} + +fn assert_batch_n_last_call_side_effect_counters_are_validated() { + let mut builder = BatchNTestBuilder::::new(); + let last_index = N - 1; + + builder.append_note_hashes(last_index, 1); + let mut inputs = builder.inputs_with_shape(false, true); + let mut note_hash = inputs.private_calls[last_index].public_inputs.note_hashes.array[0]; + note_hash.counter = inputs.private_calls[last_index].public_inputs.start_side_effect_counter; + inputs.private_calls[last_index].public_inputs.note_hashes.array[0] = note_hash; + + let _ = private_kernel_init_n::execute::(inputs); +} + +fn assert_batch_n_second_call_must_match_previous_kernel_header() { + let mut builder = BatchNTestBuilder::::new(); + + builder.increment_anchor_block_header_version(1); + + let _ = builder.execute_linear_chain(); +} + +fn assert_batch_n_fee_payer_conflict_fails() { + let mut builder = BatchNTestBuilder::::new(); + + builder.set_fee_payer(0); + builder.set_fee_payer(1); + + let _ = builder.execute_linear_chain(); +} + +fn assert_batch_n_public_teardown_conflict_fails() { + let mut builder = BatchNTestBuilder::::new(); + + builder.set_public_teardown_call_request(0); + builder.set_public_teardown_call_request(1); + + let _ = builder.execute_linear_chain(); +} + +fn assert_batch_n_min_revertible_side_effect_counter_conflict_fails() { + let mut builder = BatchNTestBuilder::::new(); + + builder.end_setup(0); + builder.end_setup(1); + builder.revertible_counter_hint = builder.private_calls[0].min_revertible_side_effect_counter; + + let _ = builder.execute_linear_chain(); +} + +fn assert_batch_n_static_call_requires_static_nested_private_call_fails() { + let mut builder = BatchNTestBuilder::::new(); + + builder.set_static_call(1); + + let _ = builder.execute_linear_chain(); +} + +fn assert_batch_n_static_call_restrictions_apply_to_next_slot_fails() { + let mut builder = BatchNTestBuilder::::new(); + let last_index = N - 1; + + for i in 1..N { + builder.set_static_call(i); + } + builder.append_note_hashes(last_index, 1); + + let _ = builder.execute_linear_chain(); +} + +fn assert_inner_n_linear_chain_consumes_all_calls() { + let builder = InnerNTestBuilder::::new(); + + let output = builder.execute_linear_chain(); + + assert_eq(output.end.private_call_stack.length, 0); +} + +fn assert_inner_n_linear_chain_matches_sequential_kernels() { + let builder = InnerNTestBuilder::::new(); + + let batched = builder.execute_linear_chain(); + let sequential = builder.execute_linear_chain_sequential(); + + assert_private_kernel_public_inputs_eq(batched, sequential); +} + +fn assert_inner_n_accumulates_side_effects_after_previous_kernel() { + let mut builder = InnerNTestBuilder::::new(); + + builder.previous_kernel.append_note_hashes(1); + for i in 0..N { + builder.append_note_hashes(i, 1); + } + + let previous_note_hash = builder.previous_kernel.note_hashes.storage()[0]; + let note_hashes = builder.private_calls.map(|call| call.note_hashes.storage()[0]); + + let output = builder.execute_linear_chain(); + + assert_eq(output.end.note_hashes.length, N + 1, "incorrect note hash length"); + assert_eq(output.end.note_hashes.array[0], previous_note_hash); + for i in 0..N { + assert_eq(output.end.note_hashes.array[i + 1], note_hashes[i]); + } +} + +fn assert_inner_n_with_previous_side_effects_matches_sequential_kernels() { + let mut builder = InnerNTestBuilder::::new(); + + builder.previous_kernel.append_note_hashes(1); + for i in 0..N { + builder.append_note_hashes(i, 1); + } + + let batched = builder.execute_linear_chain(); + let sequential = builder.execute_linear_chain_sequential(); + + assert_private_kernel_public_inputs_eq(batched, sequential); +} + +#[test] +fn batch_3_accumulates_side_effects_across_slots() { + assert_batch_n_accumulates_side_effects_across_slots::<3>(); +} + +#[test] +fn batch_3_linear_chain_consumes_all_calls() { + assert_batch_n_linear_chain_consumes_all_calls::<3>(); +} + +#[test] +fn batch_3_linear_chain_matches_sequential_kernels() { + assert_batch_n_linear_chain_matches_sequential_kernels::<3>(); +} + +#[test] +fn batch_3_depth_first_child_keeps_sibling_on_stack() { + assert_batch_n_depth_first_child_keeps_sibling_on_stack::<3>(); +} + +#[test] +fn batch_3_depth_first_with_sibling_matches_sequential_kernels() { + assert_batch_n_depth_first_with_sibling_matches_sequential_kernels::<3>(); +} + +#[test] +fn batch_3_with_everything_non_empty_matches_sequential_kernels() { + assert_batch_n_with_everything_non_empty_matches_sequential_kernels::<3>(); +} + +#[test(should_fail_with = "call_context does not match call request")] +fn batch_3_third_call_must_match_second_call_stack_fails() { + assert_batch_n_last_call_must_match_previous_call_stack_fails::<3>(); +} + +#[test(should_fail_with = "args_hash does not match call request")] +fn batch_3_second_call_must_match_first_call_stack_fails() { + assert_batch_n_second_call_must_match_first_call_stack_fails::<3>(); +} + +#[test(should_fail_with = "note_hash_read_requests length out of bounds")] +fn batch_3_second_call_claimed_lengths_are_validated() { + assert_batch_n_second_call_claimed_lengths_are_validated::<3>(); +} + +#[test(should_fail_with = "Counter must be greater than previous counter")] +fn batch_3_third_call_side_effect_counters_are_validated() { + assert_batch_n_last_call_side_effect_counters_are_validated::<3>(); +} + +#[test(should_fail_with = "anchor block header mismatch")] +fn batch_3_second_call_must_match_previous_kernel_header() { + assert_batch_n_second_call_must_match_previous_kernel_header::<3>(); +} + +#[test(should_fail_with = "Cannot overwrite non-empty fee_payer")] +fn batch_3_fee_payer_conflict_fails() { + assert_batch_n_fee_payer_conflict_fails::<3>(); +} + +#[test(should_fail_with = "Public teardown call request already set")] +fn batch_3_public_teardown_conflict_fails() { + assert_batch_n_public_teardown_conflict_fails::<3>(); +} + +#[test(should_fail_with = "cannot overwrite non-zero min_revertible_side_effect_counter")] +fn batch_3_min_revertible_side_effect_counter_conflict_fails() { + assert_batch_n_min_revertible_side_effect_counter_conflict_fails::<3>(); +} + +#[test(should_fail_with = "nested private call of a static call must be static")] +fn batch_3_static_call_requires_static_nested_private_call_fails() { + assert_batch_n_static_call_requires_static_nested_private_call_fails::<3>(); +} + +#[test(should_fail_with = "note_hashes must be empty for static calls")] +fn batch_3_static_call_restrictions_apply_to_next_slot_fails() { + assert_batch_n_static_call_restrictions_apply_to_next_slot_fails::<3>(); +} + +#[test] +fn inner_3_accumulates_side_effects_after_previous_kernel() { + assert_inner_n_accumulates_side_effects_after_previous_kernel::<3>(); +} + +#[test] +fn inner_3_with_previous_side_effects_matches_sequential_kernels() { + assert_inner_n_with_previous_side_effects_matches_sequential_kernels::<3>(); +} + +#[test] +fn inner_3_linear_chain_consumes_all_calls() { + assert_inner_n_linear_chain_consumes_all_calls::<3>(); +} + +#[test] +fn inner_3_linear_chain_matches_sequential_kernels() { + assert_inner_n_linear_chain_matches_sequential_kernels::<3>(); +} diff --git a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/private_kernel_inner/output_composition_tests.nr b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/private_kernel_inner/output_composition_tests.nr index d35a85356126..672ad79dbd3e 100644 --- a/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/private_kernel_inner/output_composition_tests.nr +++ b/noir-projects/noir-protocol-circuits/crates/private-kernel-lib/src/tests/private_kernel_inner/output_composition_tests.nr @@ -3,7 +3,7 @@ use protocol_test_utils::assert_claimed_length_array_eq; use types::{ address::{AztecAddress, EthAddress}, constants::{ - MAX_CONTRACT_CLASS_LOGS_PER_CALL, MAX_CONTRACT_CLASS_LOGS_PER_TX, + DEFAULT_UPDATE_DELAY, MAX_CONTRACT_CLASS_LOGS_PER_CALL, MAX_CONTRACT_CLASS_LOGS_PER_TX, MAX_ENQUEUED_CALLS_PER_CALL, MAX_ENQUEUED_CALLS_PER_TX, MAX_KEY_VALIDATION_REQUESTS_PER_CALL, MAX_KEY_VALIDATION_REQUESTS_PER_TX, MAX_L2_TO_L1_MSGS_PER_CALL, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_CALL, @@ -273,6 +273,26 @@ fn expiration_timestamp_pick_private_call() { assert_eq(pi.expiration_timestamp, 78); } +#[test] +fn expiration_timestamp_pick_contract_update_horizon() { + let mut builder = TestBuilder::new(); + + let contract_update_horizon = builder + .private_call + .anchor_block_header + .global_variables + .timestamp + + DEFAULT_UPDATE_DELAY + - 1; + + builder.previous_kernel.expiration_timestamp = contract_update_horizon + 1000; + builder.private_call.expiration_timestamp = contract_update_horizon + 1000; + + let pi = builder.execute(); + + assert_eq(pi.expiration_timestamp, contract_update_horizon); +} + #[test] fn with_everything_non_empty() { let mut builder = TestBuilder::new();