From 34be2c3800c2d99c11fe3448e01c77abf60c726d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Rodr=C3=ADguez?= Date: Fri, 10 Jan 2025 13:51:37 +0100 Subject: [PATCH 1/3] feat: Use tail public inputs as transaction hash (#11100) Implements https://github.com/AztecProtocol/aztec-packages/issues/9269 Separates the role of the first nullifier and the transaction hash. The transaction hash is now the hash of the tail public inputs. The first nullifier is still used for note uniqueness and replayability protection --- .../src/barretenberg/vm/avm/trace/trace.cpp | 7 +-- .../src/barretenberg/vm/avm/trace/trace.hpp | 2 +- .../easy_private_voting_contract/src/main.nr | 2 + .../crates/rollup-lib/src/abis/tx_effect.nr | 2 + .../src/base/private_base_rollup.nr | 45 ++++++++----------- .../rollup-lib/src/base/public_base_rollup.nr | 29 +++++++----- .../block_root/block_root_rollup_inputs.nr | 14 +++--- .../crates/rollup-lib/src/components.nr | 12 ++++- ..._to_public_kernel_circuit_public_inputs.nr | 13 +++++- ..._to_rollup_kernel_circuit_public_inputs.nr | 13 +++++- .../crates/types/src/constants.nr | 21 ++------- .../aztec-node/src/aztec-node/server.test.ts | 31 ++++++------- .../aztec-node/src/aztec-node/server.ts | 2 +- .../circuit-types/src/tx/processed_tx.ts | 6 ++- yarn-project/circuit-types/src/tx/tx.ts | 41 ++++++++++------- yarn-project/circuit-types/src/tx_effect.ts | 21 ++++++--- yarn-project/circuits.js/src/constants.gen.ts | 2 + yarn-project/circuits.js/src/structs/index.ts | 2 +- ...ivate_kernel_tail_circuit_public_inputs.ts | 6 +-- .../kernel/private_to_avm_accumulated_data.ts | 25 ++++++++++- .../private_to_public_accumulated_data.ts | 19 +++++++- ..._to_public_kernel_circuit_public_inputs.ts | 32 ++++++++++++- .../private_to_rollup_accumulated_data.ts | 13 +++++- ...to_rollup_kernel_circuit_public_inputs.ts} | 38 ++++++++++++---- .../src/structs/kernel/tx_constant_data.ts | 13 +++++- .../src/structs/l2_to_l1_message.ts | 19 +++++++- .../src/structs/rollup/private_tube_data.ts | 2 +- .../src/structs/rollup_validation_requests.ts | 18 +++++++- .../circuits.js/src/tests/factories.ts | 4 +- .../src/conversion/server.ts | 2 +- .../src/mem_pools/tx_pool/aztec_kv_tx_pool.ts | 13 +++++- .../mem_pools/tx_pool/tx_pool_test_suite.ts | 10 +++-- .../p2p/src/services/reqresp/reqresp.test.ts | 2 + .../block_building_helpers.test.ts | 20 +++++---- .../src/orchestrator/tx-proving-state.ts | 2 +- .../prover-node/src/prover-node.test.ts | 4 +- .../brute_force_note_info.ts | 2 +- .../produce_note_daos.ts | 2 + .../produce_note_daos_for_key.ts | 2 + .../pxe/src/pxe_service/pxe_service.ts | 4 +- .../pxe/src/simulator_oracle/index.ts | 1 + .../simulator_oracle/simulator_oracle.test.ts | 3 +- .../simulator/src/avm/avm_simulator.test.ts | 2 +- .../simulator/src/avm/fixtures/index.ts | 6 +-- .../simulator/src/avm/journal/journal.test.ts | 2 +- .../simulator/src/avm/journal/journal.ts | 13 +++--- .../src/avm/opcodes/accrued_substate.test.ts | 2 +- .../simulator/src/public/public_processor.ts | 2 +- .../simulator/src/public/public_tx_context.ts | 25 +++-------- .../src/public/public_tx_simulator.ts | 6 +-- 50 files changed, 387 insertions(+), 192 deletions(-) rename yarn-project/circuits.js/src/structs/kernel/{kernel_circuit_public_inputs.ts => private_to_rollup_kernel_circuit_public_inputs.ts} (68%) diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp index cd7e26908bd..346ac3b059d 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.cpp @@ -288,7 +288,8 @@ void AvmTraceBuilder::insert_private_revertible_state(const std::vector& sil for (size_t i = 0; i < siloed_note_hashes.size(); i++) { size_t note_index_in_tx = i + get_inserted_note_hashes_count(); - FF nonce = AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_tx_hash(), note_index_in_tx); + FF nonce = + AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_first_nullifier(), note_index_in_tx); unique_note_hashes.push_back( AvmMerkleTreeTraceBuilder::unconstrained_compute_unique_note_hash(nonce, siloed_note_hashes.at(i))); } @@ -3101,8 +3102,8 @@ AvmError AvmTraceBuilder::op_emit_note_hash(uint8_t indirect, uint32_t note_hash AppendTreeHint note_hash_write_hint = execution_hints.note_hash_write_hints.at(note_hash_write_counter++); FF siloed_note_hash = AvmMerkleTreeTraceBuilder::unconstrained_silo_note_hash( current_public_call_request.contract_address, row.main_ia); - FF nonce = - AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_tx_hash(), inserted_note_hashes_count); + FF nonce = AvmMerkleTreeTraceBuilder::unconstrained_compute_note_hash_nonce(get_first_nullifier(), + inserted_note_hashes_count); FF unique_note_hash = AvmMerkleTreeTraceBuilder::unconstrained_compute_unique_note_hash(nonce, siloed_note_hash); ASSERT(unique_note_hash == note_hash_write_hint.leaf_value); diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp index 0ae4e06d904..a2c750d633e 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/trace.hpp @@ -395,7 +395,7 @@ class AvmTraceBuilder { uint32_t get_inserted_note_hashes_count(); uint32_t get_inserted_nullifiers_count(); uint32_t get_public_data_writes_count(); - FF get_tx_hash() const { return public_inputs.previous_non_revertible_accumulated_data.nullifiers[0]; } + FF get_first_nullifier() const { return public_inputs.previous_non_revertible_accumulated_data.nullifiers[0]; } // TODO: remove these once everything is constrained. AvmMemoryTag unconstrained_get_memory_tag(AddressWithMode addr); diff --git a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr index c7f51654c76..d293a2c0171 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr @@ -1,8 +1,10 @@ +// docs:start:declaration mod test; use dep::aztec::macros::aztec; #[aztec] contract EasyPrivateVoting { + // docs:end:declaration // docs:start:imports use dep::aztec::{ keys::getters::get_public_keys, diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/tx_effect.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/tx_effect.nr index 54fcf722f76..fe5f3cab437 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/tx_effect.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/tx_effect.nr @@ -9,6 +9,7 @@ use types::{ }; pub(crate) struct TxEffect { + pub(crate) tx_hash: Field, pub(crate) revert_code: u8, pub(crate) transaction_fee: Field, pub(crate) note_hashes: [Field; MAX_NOTE_HASHES_PER_TX], @@ -25,6 +26,7 @@ pub(crate) struct TxEffect { impl Empty for TxEffect { fn empty() -> Self { TxEffect { + tx_hash: 0, revert_code: 0, transaction_fee: 0, note_hashes: [0; MAX_NOTE_HASHES_PER_TX], diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr index 903b6d37e79..eabcb3a5902 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/private_base_rollup.nr @@ -111,6 +111,7 @@ impl PrivateBaseRollupInputs { let out_hash = compute_kernel_out_hash(siloed_l2_to_l1_msgs); let tx_effect = TxEffect { + tx_hash: self.tube_data.public_inputs.hash(), revert_code: 0, transaction_fee, note_hashes: self.tube_data.public_inputs.end.note_hashes, @@ -241,7 +242,7 @@ mod tests { NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, NULLIFIERS_PREFIX, PRIVATE_LOG_SIZE_IN_FIELDS, PRIVATE_LOGS_PREFIX, PUBLIC_DATA_TREE_HEIGHT, REVERT_CODE_PREFIX, TUBE_VK_INDEX, TX_FEE_PREFIX, - TX_START_PREFIX, UNENCRYPTED_LOGS_PREFIX, + TX_START_PREFIX, }, data::{public_data_hint::PublicDataHint, PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, hash::silo_l2_to_l1_message, @@ -816,12 +817,13 @@ mod tests { unconstrained fn non_empty_tx_effects_sponge() { let mut builder = PrivateBaseRollupInputsBuilder::new(); builder.tube_data.append_note_hashes(50); - let outputs = builder.execute(); - let mut tx_effects = [0; 53]; - // TODO(#8954): This test uses 50 notes and 3 extra absorbed fields + let inputs = builder.build_inputs(); + let outputs = inputs.execute(); + let mut tx_effects = [0; 54]; + // TODO(#8954): This test uses 50 notes and 4 extra absorbed fields // This may change when logs are deconstructed // Initial field = TX_START_PREFIX | 0 | txlen[0] txlen[1] | 0 | REVERT_CODE_PREFIX | 0 | revert_code - // The first 3 are: i=0 init field, i=1: tx fee, i=2: note prefix + // The first 4 are: i=0 init field, i=1: tx hash, i=2: tx fee, i=3: note prefix tx_effects[0] = field_from_bytes( array_concat( TX_START_PREFIX.to_be_bytes::<8>(), @@ -829,13 +831,15 @@ mod tests { ), true, ); - tx_effects[1] = field_from_bytes( + // TX hash + tx_effects[1] = inputs.tube_data.public_inputs.hash(); + tx_effects[2] = field_from_bytes( array_concat([TX_FEE_PREFIX, 0], (0).to_be_bytes::<29>()), true, ); - tx_effects[2] = encode_blob_prefix(NOTES_PREFIX, 50); + tx_effects[3] = encode_blob_prefix(NOTES_PREFIX, 50); for i in 0..50 { - tx_effects[i + 3] = builder.tube_data.note_hashes.storage()[i].value(); + tx_effects[i + 4] = builder.tube_data.note_hashes.storage()[i].value(); } let mut expected_sponge = outputs.start_sponge_blob; @@ -849,21 +853,8 @@ mod tests { let NUM_NULLIFIERS = 3; let NUM_MSGS = 5; let NUM_PRIV_EVENT_LOGS = 4; - let NUM_UNENC_LOGS = 6; let NUM_CC_LOGS = 1; - let TOTAL_BLOB_FIELDS = 2 // revert code and tx fee - + NUM_NOTES - + 1 // notes and prefix - + NUM_NULLIFIERS - + 1 // nullifiers and prefix - + NUM_MSGS - + 1 // L2 to L1 msgs and prefix - + NUM_UNENC_LOGS - + 1 // unenc. logs and prefix - + NUM_CC_LOGS - + 1 // contract class logs and prefix - + (NUM_NOTES + NUM_PRIV_EVENT_LOGS) * PRIVATE_LOG_SIZE_IN_FIELDS - + 1; // private logs and prefix + let mut builder = PrivateBaseRollupInputsBuilder::new(); builder.tube_data.set_gas_used(100, 200); builder.constants.global_variables.gas_fees.fee_per_da_gas = 1; @@ -887,17 +878,19 @@ mod tests { builder.tube_data.append_private_logs(NUM_PRIV_EVENT_LOGS); // Below will only work with NUM_CC_LOGS=1 builder.tube_data.add_contract_class_log_hash(1, 2); - let outputs = builder.execute(); + let inputs = builder.build_inputs(); + let outputs = inputs.execute(); let mut reconstructed_tx_effects = [0; TX_EFFECTS_BLOB_HASH_INPUT_FIELDS]; - + // tx hash + reconstructed_tx_effects[1] = inputs.tube_data.public_inputs.hash(); // tx fee - reconstructed_tx_effects[1] = field_from_bytes( + reconstructed_tx_effects[2] = field_from_bytes( array_concat([TX_FEE_PREFIX, 0], tx_fee.to_be_bytes::<29>()), true, ); // notes - let mut offset = 2; + let mut offset = 3; let notes_prefix = encode_blob_prefix(NOTES_PREFIX, NUM_NOTES); reconstructed_tx_effects[offset] = notes_prefix; offset += 1; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr index ae48ee65245..ef0263b5a9e 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/public_base_rollup.nr @@ -87,6 +87,7 @@ impl PublicBaseRollupInputs { .fold(0, |len, l: ScopedLogHash| len + l.log_hash.length); TxEffect { + tx_hash: self.tube_data.public_inputs.hash(), revert_code, transaction_fee: from_public.transaction_fee, note_hashes: from_public.accumulated_data.note_hashes, @@ -916,12 +917,13 @@ mod tests { unconstrained fn non_empty_tx_effects_sponge() { let mut builder = PublicBaseRollupInputsBuilder::new(); builder.avm_data.append_note_hashes(50); - let outputs = builder.execute(); - let mut tx_effects = [0; 53]; - // TODO(#8954): This test uses 50 notes and 3 extra absorbed fields + let inputs = builder.build_inputs(); + let outputs = inputs.execute(); + let mut tx_effects = [0; 54]; + // TODO(#8954): This test uses 50 notes and 4 extra absorbed fields // This may change when logs are deconstructed // Initial field = TX_START_PREFIX | 0 | txlen[0] txlen[1] | 0 | REVERT_CODE_PREFIX | 0 | revert_code - // The first 3 are: i=0 init field, i=1: tx fee, i=2: note prefix + // The first 4 are: i=0 init field, i=1: tx hash, i=2: tx fee, i=3: note prefix tx_effects[0] = field_from_bytes( array_concat( TX_START_PREFIX.to_be_bytes::<8>(), @@ -929,13 +931,15 @@ mod tests { ), true, ); - tx_effects[1] = field_from_bytes( + // TX hash + tx_effects[1] = inputs.tube_data.public_inputs.hash(); + tx_effects[2] = field_from_bytes( array_concat([TX_FEE_PREFIX, 0], (0).to_be_bytes::<29>()), true, ); - tx_effects[2] = encode_blob_prefix(NOTES_PREFIX, 50); + tx_effects[3] = encode_blob_prefix(NOTES_PREFIX, 50); for i in 0..50 { - tx_effects[i + 3] = builder.avm_data.note_hashes.storage()[i].value(); + tx_effects[i + 4] = builder.avm_data.note_hashes.storage()[i].value(); } let mut expected_sponge = outputs.start_sponge_blob; @@ -953,7 +957,7 @@ mod tests { let NUM_CC_LOGS = 1; let PUB_DATA_SLOT = 25; let PUB_DATA_VALUE = 60; - let TOTAL_BLOB_FIELDS = 2 // revert code and tx fee + let TOTAL_BLOB_FIELDS = 3 // revert code, tx hash and tx fee + NUM_NOTES + 1 // notes and prefix + NUM_NULLIFIERS @@ -993,7 +997,8 @@ mod tests { builder.avm_data.append_unencrypted_log_hashes(NUM_UNENC_LOGS); // Below will only work with NUM_CC_LOGS=1 builder.tube_data.add_contract_class_log_hash(1, 2); - let outputs = builder.execute(); + let inputs = builder.build_inputs(); + let outputs = inputs.execute(); let mut reconstructed_tx_effects = [0; TX_EFFECTS_BLOB_HASH_INPUT_FIELDS]; // Initial field = TX_START_PREFIX | 0 | txlen[0] txlen[1] | 0 | REVERT_CODE_PREFIX | 0 | revert_code @@ -1014,13 +1019,15 @@ mod tests { ), true, ); + // tx hash + reconstructed_tx_effects[1] = inputs.tube_data.public_inputs.hash(); // tx fee - reconstructed_tx_effects[1] = field_from_bytes( + reconstructed_tx_effects[2] = field_from_bytes( array_concat([TX_FEE_PREFIX, 0], tx_fee.to_be_bytes::<29>()), true, ); // notes - let mut offset = 2; + let mut offset = 3; let notes_prefix = encode_blob_prefix(NOTES_PREFIX, NUM_NOTES); reconstructed_tx_effects[offset] = notes_prefix; offset += 1; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/block_root_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/block_root_rollup_inputs.nr index ebc342151a8..45bf221e1ef 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/block_root_rollup_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/block_root/block_root_rollup_inputs.nr @@ -202,13 +202,15 @@ pub(crate) mod tests { // The below values are generated from block_building_helper.test.ts. let blob_fields_0 = [ - 0x000000000000000000000000000000000074785f737461727400000400010000, + 0x000000000000000000000000000000000074785f737461727400000500010000, + 0x000000000000000000000000000000000000000000000000000000000000002a, 0x0002000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000004000001, 0x0000000000000000000000000000000000000000000000000000000000000123, ]; let blob_fields_1 = [ - 0x000000000000000000000000000000000074785f737461727400000600010000, + 0x000000000000000000000000000000000074785f737461727400000700010000, + 0x000000000000000000000000000000000000000000000000000000000000002b, 0x0002000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000003000001, 0x0000000000000000000000000000000000000000000000000000000000006789, @@ -216,12 +218,12 @@ pub(crate) mod tests { 0x0000000000000000000000000000000000000000000000000000000000000045, ]; let expected_blob_commitment = [ - 0x00ad8be66e7276942652627bb00fe1e65dc1c3c6701ab27cc05eff662950071d, - 0x00000000000000000000000000000071baf7a9af9757f1d3878b37b438797213, + 0x008c32fe581c8fdba12c0d7597911dead2d937d68525bae655508412bb53bb98, + 0x0000000000000000000000000000006aaa0680f21270e7d8de4e19da5164f95c, ]; let expected_blobs_hash = - 0x00dc577f5c94c82b847693b76ee69cd33d4e5eee3adb6f37d8d7ab662c84725d; - let expected_z = 0x1582b354f32263abde313d597582ebceafe17d4e2a68dd47533383e85b4cb780; + 0x00a965619c8668b834755678b32d023b9c5e8588ce449f44f7fa9335455b5cc5; + let expected_z = 0x1f92b871671f27a378d23f1cef10fbd8f0d90dd7172da9e3c3fc1aa745a072c3; let mut builder = TestBuilder::new_with_blobs_fields(blob_fields_0, blob_fields_1); builder.data.blob_commitments[0].inner = expected_blob_commitment; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr index 46c9eebfaff..42a2f485bfe 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr @@ -140,6 +140,7 @@ pub fn encode_blob_prefix(input_type: u8, array_len: u32) -> Field { // Tx effects consist of // 1 field for revert code +// 1 field for tx hash // 1 field for transaction fee // MAX_NOTE_HASHES_PER_TX fields for note hashes // MAX_NULLIFIERS_PER_TX fields for nullifiers @@ -151,6 +152,7 @@ pub fn encode_blob_prefix(input_type: u8, array_len: u32) -> Field { // MAX_CONTRACT_CLASS_LOGS_PER_TX fields for contract class logs // 7 fields for prefixes for each of the above categories pub(crate) global TX_EFFECTS_BLOB_HASH_INPUT_FIELDS: u32 = 1 + + 1 + 1 + MAX_NOTE_HASHES_PER_TX + MAX_NULLIFIERS_PER_TX @@ -172,8 +174,8 @@ pub(crate) fn append_tx_effects_for_blob( let mut out_sponge = start_sponge_blob; // If we have an empty tx (usually a padding tx), we don't want to absorb anything - // An empty tx will only have 2 effects - revert code and fee - hence offset = 2 - if offset != 2 { + // An empty tx will only have 3 effects - revert code, tx hash and fee - hence offset = 3 + if offset != 3 { out_sponge.absorb(tx_effects_hash_input, offset); } @@ -208,6 +210,9 @@ fn get_tx_effects_hash_input( // We only know the value once the appending is complete, hence we overwrite input[0] below offset += 1; + assert_eq(tx_effects_hash_input[offset], tx_effect.tx_hash); + offset += 1; + // TX FEE // Using 29 bytes to encompass all reasonable fee lengths assert_eq( @@ -400,6 +405,9 @@ unconstrained fn get_tx_effects_hash_input_helper( tx_effects_hash_input[offset] = 0; offset += 1; + tx_effects_hash_input[offset] = tx_effect.tx_hash; + offset += 1; + // TX FEE // Using 29 bytes to encompass all reasonable fee lengths tx_effects_hash_input[offset] = field_from_bytes( diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_public_kernel_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_public_kernel_circuit_public_inputs.nr index 517f6870590..265fc2e1ba2 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_public_kernel_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_public_kernel_circuit_public_inputs.nr @@ -5,8 +5,11 @@ use crate::{ validation_requests::RollupValidationRequests, }, address::AztecAddress, - constants::PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH, - traits::{Deserialize, Empty, Serialize}, + constants::{ + GENERATOR_INDEX__PUBLIC_TX_HASH, PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH, + }, + hash::poseidon2_hash_with_separator, + traits::{Deserialize, Empty, Hash, Serialize}, utils::reader::Reader, }; @@ -65,6 +68,12 @@ impl Serialize for Privat } } +impl Hash for PrivateToPublicKernelCircuitPublicInputs { + fn hash(self) -> Field { + poseidon2_hash_with_separator(self.serialize(), GENERATOR_INDEX__PUBLIC_TX_HASH) + } +} + impl Deserialize for PrivateToPublicKernelCircuitPublicInputs { fn deserialize( fields: [Field; PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH], diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_rollup_kernel_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_rollup_kernel_circuit_public_inputs.nr index 9f00f0320e5..887be0af24e 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_rollup_kernel_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/kernel_circuit_public_inputs/private_to_rollup_kernel_circuit_public_inputs.nr @@ -4,8 +4,11 @@ use crate::{ tx_constant_data::TxConstantData, validation_requests::RollupValidationRequests, }, address::AztecAddress, - constants::PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH, - traits::{Deserialize, Empty, Serialize}, + constants::{ + GENERATOR_INDEX__PRIVATE_TX_HASH, PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH, + }, + hash::poseidon2_hash_with_separator, + traits::{Deserialize, Empty, Hash, Serialize}, utils::reader::Reader, }; use std::meta::derive; @@ -48,6 +51,12 @@ impl Serialize for Privat } } +impl Hash for PrivateToRollupKernelCircuitPublicInputs { + fn hash(self) -> Field { + poseidon2_hash_with_separator(self.serialize(), GENERATOR_INDEX__PRIVATE_TX_HASH) + } +} + impl Deserialize for PrivateToRollupKernelCircuitPublicInputs { fn deserialize( fields: [Field; PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH], diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index 4f00c01f79f..921a1fb6405 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -489,20 +489,7 @@ pub global AVM_PROOF_LENGTH_IN_FIELDS: u32 = 4155; pub global AVM_PUBLIC_COLUMN_MAX_SIZE: u32 = 1024; pub global AVM_PUBLIC_INPUTS_FLATTENED_SIZE: u32 = 2 * AVM_PUBLIC_COLUMN_MAX_SIZE + PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH; -/** - * Enumerate the hash_indices which are used for pedersen hashing. - * We start from 1 to avoid the default generators. The generator indices are listed - * based on the number of elements each index hashes. The following conditions must be met: - * - * +-----------+-------------------------------+----------------------+ - * | Hash size | Number of elements hashed (n) | Condition to use | - * |-----------+-------------------------------+----------------------| - * | LOW | n <= 8 | 0 < hash_index <= 32 | - * | MID | 8 < n <= 16 | 32 < hash_index <= 40 | - * | HIGH | 16 < n <= 48 | 40 < hash_index <= 48 | - * +-----------+-------------------------------+----------------------+ - */ -// Indices with size <= 8 + pub global GENERATOR_INDEX__NOTE_HASH: u32 = 1; pub global GENERATOR_INDEX__NOTE_HASH_NONCE: u32 = 2; pub global GENERATOR_INDEX__UNIQUE_NOTE_HASH: u32 = 3; @@ -535,14 +522,11 @@ pub global GENERATOR_INDEX__SIDE_EFFECT: u32 = 29; pub global GENERATOR_INDEX__FEE_PAYLOAD: u32 = 30; pub global GENERATOR_INDEX__COMBINED_PAYLOAD: u32 = 31; pub global GENERATOR_INDEX__TX_NULLIFIER: u32 = 32; -// Indices with size <= 16 pub global GENERATOR_INDEX__TX_REQUEST: u32 = 33; pub global GENERATOR_INDEX__SIGNATURE_PAYLOAD: u32 = 34; -// Indices with size <= 44 pub global GENERATOR_INDEX__VK: u32 = 41; pub global GENERATOR_INDEX__PRIVATE_CIRCUIT_PUBLIC_INPUTS: u32 = 42; pub global GENERATOR_INDEX__PUBLIC_CIRCUIT_PUBLIC_INPUTS: u32 = 43; -// TODO: Function args generator index is being used to hash 64 items pub global GENERATOR_INDEX__FUNCTION_ARGS: u32 = 44; pub global GENERATOR_INDEX__AUTHWIT_INNER: u32 = 45; pub global GENERATOR_INDEX__AUTHWIT_OUTER: u32 = 46; @@ -557,6 +541,9 @@ pub global GENERATOR_INDEX__NOTE_NULLIFIER: u32 = 53; pub global GENERATOR_INDEX__NOTE_HIDING_POINT: u32 = 54; pub global GENERATOR_INDEX__SYMMETRIC_KEY: u8 = 55; +pub global GENERATOR_INDEX__PUBLIC_TX_HASH: u32 = 56; +pub global GENERATOR_INDEX__PRIVATE_TX_HASH: u32 = 57; + // AVM memory tags pub global MEM_TAG_FF: Field = 0; pub global MEM_TAG_U1: Field = 1; diff --git a/yarn-project/aztec-node/src/aztec-node/server.test.ts b/yarn-project/aztec-node/src/aztec-node/server.test.ts index 7ba1860158b..c2a290c8d50 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.test.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.test.ts @@ -10,7 +10,14 @@ import { type WorldStateSynchronizer, mockTxForRollup, } from '@aztec/circuit-types'; -import { type ContractDataSource, EthAddress, Fr, GasFees, MaxBlockNumber } from '@aztec/circuits.js'; +import { + type ContractDataSource, + EthAddress, + Fr, + GasFees, + MaxBlockNumber, + RollupValidationRequests, +} from '@aztec/circuits.js'; import { type P2P } from '@aztec/p2p'; import { type GlobalVariableBuilder } from '@aztec/sequencer-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; @@ -107,7 +114,7 @@ describe('aztec node', () => { expect(await node.isValidTx(doubleSpendTx)).toEqual({ result: 'valid' }); // We push a duplicate nullifier that was created in the same transaction - doubleSpendTx.data.forRollup!.end.nullifiers.push(doubleSpendTx.data.forRollup!.end.nullifiers[0]); + doubleSpendTx.data.forRollup!.end.nullifiers[1] = doubleSpendTx.data.forRollup!.end.nullifiers[0]; expect(await node.isValidTx(doubleSpendTx)).toEqual({ result: 'invalid', reason: ['Duplicate nullifier in tx'] }); @@ -154,19 +161,13 @@ describe('aztec node', () => { const invalidMaxBlockNumberMetadata = txs[1]; const validMaxBlockNumberMetadata = txs[2]; - invalidMaxBlockNumberMetadata.data.rollupValidationRequests = { - maxBlockNumber: new MaxBlockNumber(true, new Fr(1)), - getSize: () => 1, - toBuffer: () => Fr.ZERO.toBuffer(), - toString: () => Fr.ZERO.toString(), - }; - - validMaxBlockNumberMetadata.data.rollupValidationRequests = { - maxBlockNumber: new MaxBlockNumber(true, new Fr(5)), - getSize: () => 1, - toBuffer: () => Fr.ZERO.toBuffer(), - toString: () => Fr.ZERO.toString(), - }; + invalidMaxBlockNumberMetadata.data.rollupValidationRequests = new RollupValidationRequests( + new MaxBlockNumber(true, new Fr(1)), + ); + + validMaxBlockNumberMetadata.data.rollupValidationRequests = new RollupValidationRequests( + new MaxBlockNumber(true, new Fr(5)), + ); lastBlockNumber = 3; diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 969f618b58c..67e96b8e77c 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -834,7 +834,7 @@ export class AztecNodeService implements AztecNode, Traceable { * @param tx - The transaction to simulate. **/ @trackSpan('AztecNodeService.simulatePublicCalls', (tx: Tx) => ({ - [Attributes.TX_HASH]: tx.tryGetTxHash()?.toString(), + [Attributes.TX_HASH]: tx.getTxHash().toString(), })) public async simulatePublicCalls(tx: Tx, enforceFeePayment = true): Promise { const txHash = tx.getTxHash(); diff --git a/yarn-project/circuit-types/src/tx/processed_tx.ts b/yarn-project/circuit-types/src/tx/processed_tx.ts index 877cca9ecff..feacb5de6e1 100644 --- a/yarn-project/circuit-types/src/tx/processed_tx.ts +++ b/yarn-project/circuit-types/src/tx/processed_tx.ts @@ -93,6 +93,7 @@ export function makeProcessedTxFromPrivateOnlyTx( const data = tx.data.forRollup!; const txEffect = new TxEffect( RevertCode.OK, + tx.getTxHash(), transactionFee, data.end.noteHashes.filter(h => !h.isZero()), data.end.nullifiers.filter(h => !h.isZero()), @@ -114,7 +115,7 @@ export function makeProcessedTxFromPrivateOnlyTx( } satisfies GasUsed; return { - hash: tx.getTxHash(), + hash: txEffect.txHash, data: tx.data, clientIvcProof: tx.clientIvcProof, avmProvingRequest: undefined, @@ -156,6 +157,7 @@ export function makeProcessedTxFromTxWithPublicCalls( const txEffect = new TxEffect( revertCode, + tx.getTxHash(), avmOutput.transactionFee, avmOutput.accumulatedData.noteHashes.filter(h => !h.isZero()), avmOutput.accumulatedData.nullifiers.filter(h => !h.isZero()), @@ -171,7 +173,7 @@ export function makeProcessedTxFromTxWithPublicCalls( ); return { - hash: tx.getTxHash(), + hash: txEffect.txHash, data: tx.data, clientIvcProof: tx.clientIvcProof, avmProvingRequest, diff --git a/yarn-project/circuit-types/src/tx/tx.ts b/yarn-project/circuit-types/src/tx/tx.ts index 969a069e4f7..87768204e1a 100644 --- a/yarn-project/circuit-types/src/tx/tx.ts +++ b/yarn-project/circuit-types/src/tx/tx.ts @@ -28,6 +28,8 @@ import { TxHash } from './tx_hash.js'; */ export class Tx extends Gossipable { static override p2pTopic: string; + // For memoization + private txHash: TxHash | undefined; constructor( /** @@ -174,25 +176,27 @@ export class Tx extends Gossipable { } /** - * Construct & return transaction hash. - * @returns The transaction's hash. + * Computes (if necessary) & return transaction hash. + * @returns The hash of the public inputs of the private kernel tail circuit. */ - getTxHash(): TxHash { - // Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier - const firstNullifier = this.data.getNonEmptyNullifiers()[0]; - if (!firstNullifier || firstNullifier.isZero()) { - throw new Error(`Cannot get tx hash since first nullifier is missing`); + getTxHash(forceRecompute = false): TxHash { + if (!this.txHash || forceRecompute) { + const hash = this.data.forPublic + ? this.data.toPrivateToPublicKernelCircuitPublicInputs().hash() + : this.data.toPrivateToRollupKernelCircuitPublicInputs().hash(); + this.txHash = new TxHash(hash); } - return new TxHash(firstNullifier); + return this.txHash!; } - /** Returns the tx hash, or undefined if none is set. */ - tryGetTxHash(): TxHash | undefined { - try { - return this.getTxHash(); - } catch { - return undefined; - } + /** + * Allows setting the hash of the Tx. + * Use this when you want to skip computing it from the original data. + * Don't set a Tx hash received from an untrusted source. + * @param hash - The hash to set. + */ + setTxHash(hash: TxHash) { + this.txHash = hash; } /** Returns stats about this tx. */ @@ -283,7 +287,7 @@ export class Tx extends Gossipable { PublicExecutionRequest.fromBuffer(x.toBuffer()), ); const publicTeardownFunctionCall = PublicExecutionRequest.fromBuffer(tx.publicTeardownFunctionCall.toBuffer()); - return new Tx( + const clonedTx = new Tx( publicInputs, clientIvcProof, unencryptedLogs, @@ -291,6 +295,11 @@ export class Tx extends Gossipable { enqueuedPublicFunctionCalls, publicTeardownFunctionCall, ); + if (tx.txHash) { + clonedTx.setTxHash(TxHash.fromBuffer(tx.txHash.toBuffer())); + } + + return clonedTx; } static random() { diff --git a/yarn-project/circuit-types/src/tx_effect.ts b/yarn-project/circuit-types/src/tx_effect.ts index 507827c4494..a5995915736 100644 --- a/yarn-project/circuit-types/src/tx_effect.ts +++ b/yarn-project/circuit-types/src/tx_effect.ts @@ -53,6 +53,10 @@ export class TxEffect { * Whether the transaction reverted during public app logic. */ public revertCode: RevertCode, + /** + * The identifier of the transaction. + */ + public txHash: TxHash, /** * The transaction fee, denominated in FPA. */ @@ -139,6 +143,7 @@ export class TxEffect { toBuffer(): Buffer { return serializeToBuffer([ this.revertCode, + this.txHash, this.transactionFee, serializeArrayOfBufferableToVector(this.noteHashes, 1), serializeArrayOfBufferableToVector(this.nullifiers, 1), @@ -167,6 +172,7 @@ export class TxEffect { return new TxEffect( RevertCode.fromBuffer(reader), + TxHash.fromBuffer(reader), Fr.fromBuffer(reader), reader.readVectorUint8Prefix(Fr), reader.readVectorUint8Prefix(Fr), @@ -213,6 +219,7 @@ export class TxEffect { const contractClassLogs = ContractClassTxL2Logs.random(1, 1); return new TxEffect( RevertCode.random(), + TxHash.random(), new Fr(Math.floor(Math.random() * 100_000)), makeTuple(MAX_NOTE_HASHES_PER_TX, Fr.random), makeTuple(MAX_NULLIFIERS_PER_TX, Fr.random), @@ -229,6 +236,7 @@ export class TxEffect { static empty(): TxEffect { return new TxEffect( RevertCode.OK, + TxHash.zero(), Fr.ZERO, [], [], @@ -339,6 +347,8 @@ export class TxEffect { const flattened: Fr[] = []; // We reassign the first field when we know the length of all effects - see below flattened.push(Fr.ZERO); + + flattened.push(this.txHash.hash); // TODO: how long should tx fee be? For now, not using toPrefix() flattened.push( new Fr( @@ -409,6 +419,8 @@ export class TxEffect { } const { length: _, revertCode } = this.decodeFirstField(firstField); effect.revertCode = RevertCode.fromField(new Fr(revertCode)); + + effect.txHash = new TxHash(reader.readField()); // TODO: how long should tx fee be? For now, not using fromPrefix() const prefixedFee = reader.readField(); // NB: Fr.fromBuffer hangs here if you provide a buffer less than 32 in len @@ -495,9 +507,10 @@ export class TxEffect { }); } - static from(fields: Omit, 'txHash'>) { + static from(fields: FieldsOf) { return new TxEffect( fields.revertCode, + fields.txHash, fields.transactionFee, fields.noteHashes, fields.nullifiers, @@ -515,6 +528,7 @@ export class TxEffect { return z .object({ revertCode: RevertCode.schema, + txHash: TxHash.schema, transactionFee: schemas.Fr, noteHashes: z.array(schemas.Fr), nullifiers: z.array(schemas.Fr), @@ -532,6 +546,7 @@ export class TxEffect { [inspect.custom]() { return `TxEffect { revertCode: ${this.revertCode}, + txHash: ${this.txHash}, transactionFee: ${this.transactionFee}, note hashes: [${this.noteHashes.map(h => h.toString()).join(', ')}], nullifiers: [${this.nullifiers.map(h => h.toString()).join(', ')}], @@ -553,8 +568,4 @@ export class TxEffect { static fromString(str: string) { return TxEffect.fromBuffer(hexToBuffer(str)); } - - get txHash(): TxHash { - return new TxHash(this.nullifiers[0]); - } } diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index afa72979d9e..e6e00cd1551 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -395,4 +395,6 @@ export enum GeneratorIndex { NOTE_NULLIFIER = 53, NOTE_HIDING_POINT = 54, SYMMETRIC_KEY = 55, + PUBLIC_TX_HASH = 56, + PRIVATE_TX_HASH = 57, } diff --git a/yarn-project/circuits.js/src/structs/index.ts b/yarn-project/circuits.js/src/structs/index.ts index 0059acf4213..8da2960cd5e 100644 --- a/yarn-project/circuits.js/src/structs/index.ts +++ b/yarn-project/circuits.js/src/structs/index.ts @@ -18,7 +18,7 @@ export * from './block_header.js'; export * from './indexed_tagging_secret.js'; export * from './kernel/private_to_rollup_accumulated_data.js'; export * from './kernel/combined_constant_data.js'; -export * from './kernel/kernel_circuit_public_inputs.js'; +export * from './kernel/private_to_rollup_kernel_circuit_public_inputs.js'; export * from './kernel/private_accumulated_data.js'; export * from './kernel/private_call_data.js'; export * from './kernel/private_kernel_circuit_public_inputs.js'; diff --git a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts index 14d20e21a11..9b389874bde 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_kernel_tail_circuit_public_inputs.ts @@ -7,10 +7,10 @@ import { countAccumulatedItems, mergeAccumulatedData } from '../../utils/index.j import { Gas } from '../gas.js'; import { PublicCallRequest } from '../public_call_request.js'; import { RollupValidationRequests } from '../rollup_validation_requests.js'; -import { PrivateToRollupKernelCircuitPublicInputs } from './kernel_circuit_public_inputs.js'; import { PrivateToPublicAccumulatedData } from './private_to_public_accumulated_data.js'; import { PrivateToPublicKernelCircuitPublicInputs } from './private_to_public_kernel_circuit_public_inputs.js'; import { PrivateToRollupAccumulatedData } from './private_to_rollup_accumulated_data.js'; +import { PrivateToRollupKernelCircuitPublicInputs } from './private_to_rollup_kernel_circuit_public_inputs.js'; import { TxConstantData } from './tx_constant_data.js'; export class PartialPrivateTailPublicInputsForPublic { @@ -144,7 +144,7 @@ export class PrivateKernelTailCircuitPublicInputs { ); } - toPublicKernelCircuitPublicInputs() { + toPrivateToPublicKernelCircuitPublicInputs() { if (!this.forPublic) { throw new Error('Private tail public inputs is not for public circuit.'); } @@ -170,9 +170,9 @@ export class PrivateKernelTailCircuitPublicInputs { this.constants.protocolContractTreeRoot, ); return new PrivateToRollupKernelCircuitPublicInputs( + constants, this.rollupValidationRequests, this.forRollup.end, - constants, this.gasUsed, this.feePayer, ); diff --git a/yarn-project/circuits.js/src/structs/kernel/private_to_avm_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_avm_accumulated_data.ts index 75026cd5371..66eaee844ea 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_to_avm_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_avm_accumulated_data.ts @@ -1,11 +1,22 @@ import { type FieldsOf, makeTuple } from '@aztec/foundation/array'; import { arraySerializedSizeOfNonEmpty } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { + BufferReader, + FieldReader, + type Tuple, + serializeToBuffer, + serializeToFields, +} from '@aztec/foundation/serialize'; import { inspect } from 'util'; -import { MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX } from '../../constants.gen.js'; +import { + MAX_L2_TO_L1_MSGS_PER_TX, + MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, + PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH, +} from '../../constants.gen.js'; import { ScopedL2ToL1Message } from '../l2_to_l1_message.js'; import { type UInt32 } from '../shared.js'; @@ -37,6 +48,16 @@ export class PrivateToAvmAccumulatedData { ); } + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToAvmAccumulatedData.getFields(this)); + if (fields.length !== PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToAvmAccumulatedData. Expected ${PRIVATE_TO_AVM_ACCUMULATED_DATA_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + static from(fields: FieldsOf) { return new PrivateToAvmAccumulatedData(...PrivateToAvmAccumulatedData.getFields(fields)); } diff --git a/yarn-project/circuits.js/src/structs/kernel/private_to_public_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_public_accumulated_data.ts index 99a3c48fa22..b9df967a756 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_to_public_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_public_accumulated_data.ts @@ -1,7 +1,13 @@ import { type FieldsOf, makeTuple } from '@aztec/foundation/array'; import { arraySerializedSizeOfNonEmpty } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { + BufferReader, + FieldReader, + type Tuple, + serializeToBuffer, + serializeToFields, +} from '@aztec/foundation/serialize'; import { inspect } from 'util'; @@ -12,6 +18,7 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, + PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH, } from '../../constants.gen.js'; import { ScopedL2ToL1Message } from '../l2_to_l1_message.js'; import { ScopedLogHash } from '../log_hash.js'; @@ -82,6 +89,16 @@ export class PrivateToPublicAccumulatedData { return serializeToBuffer(...PrivateToPublicAccumulatedData.getFields(this)); } + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToPublicAccumulatedData.getFields(this)); + if (fields.length !== PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToPublicAccumulatedData. Expected ${PRIVATE_TO_PUBLIC_ACCUMULATED_DATA_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + static empty() { return new PrivateToPublicAccumulatedData( makeTuple(MAX_NOTE_HASHES_PER_TX, Fr.zero), diff --git a/yarn-project/circuits.js/src/structs/kernel/private_to_public_kernel_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_public_kernel_circuit_public_inputs.ts index ed837be8084..0c5a5247e5d 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_to_public_kernel_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_public_kernel_circuit_public_inputs.ts @@ -1,7 +1,11 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; +import { type Fr } from '@aztec/foundation/fields'; +import { BufferReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; +import { type FieldsOf } from '@aztec/foundation/types'; +import { GeneratorIndex, PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH } from '../../constants.gen.js'; import { Gas } from '../gas.js'; import { PublicCallRequest } from '../public_call_request.js'; import { RollupValidationRequests } from '../rollup_validation_requests.js'; @@ -31,6 +35,18 @@ export class PrivateToPublicKernelCircuitPublicInputs { ); } + static getFields(fields: FieldsOf) { + return [ + fields.constants, + fields.rollupValidationRequests, + fields.nonRevertibleAccumulatedData, + fields.revertibleAccumulatedData, + fields.publicTeardownCallRequest, + fields.gasUsed, + fields.feePayer, + ] as const; + } + static fromBuffer(buffer: Buffer | BufferReader) { const reader = BufferReader.asReader(buffer); return new PrivateToPublicKernelCircuitPublicInputs( @@ -63,4 +79,18 @@ export class PrivateToPublicKernelCircuitPublicInputs { toString() { return bufferToHex(this.toBuffer()); } + + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToPublicKernelCircuitPublicInputs.getFields(this)); + if (fields.length !== PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToPublicKernelCircuitPublicInputs. Expected ${PRIVATE_TO_PUBLIC_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + + hash() { + return poseidon2HashWithSeparator(this.toFields(), GeneratorIndex.PUBLIC_TX_HASH); + } } diff --git a/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_accumulated_data.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_accumulated_data.ts index fc3fcc0b9cb..7d511c61ca9 100644 --- a/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_accumulated_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_accumulated_data.ts @@ -2,7 +2,7 @@ import { type FieldsOf, makeTuple } from '@aztec/foundation/array'; import { arraySerializedSizeOfNonEmpty } from '@aztec/foundation/collection'; import { Fr } from '@aztec/foundation/fields'; import { bufferSchemaFor } from '@aztec/foundation/schemas'; -import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, type Tuple, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; import { inspect } from 'util'; @@ -13,6 +13,7 @@ import { MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, MAX_PRIVATE_LOGS_PER_TX, + PRIVATE_TO_ROLLUP_ACCUMULATED_DATA_LENGTH, } from '../../constants.gen.js'; import { ScopedL2ToL1Message } from '../l2_to_l1_message.js'; import { ScopedLogHash } from '../log_hash.js'; @@ -129,6 +130,16 @@ export class PrivateToRollupAccumulatedData { ); } + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToRollupAccumulatedData.getFields(this)); + if (fields.length !== PRIVATE_TO_ROLLUP_ACCUMULATED_DATA_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToRollupAccumulatedData. Expected ${PRIVATE_TO_ROLLUP_ACCUMULATED_DATA_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + [inspect.custom]() { return `PrivateToRollupAccumulatedData { noteHashes: [${this.noteHashes diff --git a/yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_kernel_circuit_public_inputs.ts similarity index 68% rename from yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts rename to yarn-project/circuits.js/src/structs/kernel/private_to_rollup_kernel_circuit_public_inputs.ts index c0fecd8b999..f822a51ac7e 100644 --- a/yarn-project/circuits.js/src/structs/kernel/kernel_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/kernel/private_to_rollup_kernel_circuit_public_inputs.ts @@ -1,8 +1,12 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { poseidon2HashWithSeparator } from '@aztec/foundation/crypto'; +import { type Fr } from '@aztec/foundation/fields'; import { bufferSchemaFor } from '@aztec/foundation/schemas'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; +import { type FieldsOf } from '@aztec/foundation/types'; +import { GeneratorIndex, PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH } from '../../constants.gen.js'; import { Gas } from '../gas.js'; import { RollupValidationRequests } from '../rollup_validation_requests.js'; import { PrivateToRollupAccumulatedData } from './private_to_rollup_accumulated_data.js'; @@ -14,6 +18,10 @@ import { TxConstantData } from './tx_constant_data.js'; */ export class PrivateToRollupKernelCircuitPublicInputs { constructor( + /** + * Data which is not modified by the circuits. + */ + public constants: TxConstantData, /** * Validation requests accumulated from private and public execution to be completed by the rollup. */ @@ -22,10 +30,6 @@ export class PrivateToRollupKernelCircuitPublicInputs { * Data accumulated from both public and private circuits. */ public end: PrivateToRollupAccumulatedData, - /** - * Data which is not modified by the circuits. - */ - public constants: TxConstantData, /** * Gas used during this transaction */ @@ -41,7 +45,7 @@ export class PrivateToRollupKernelCircuitPublicInputs { } toBuffer() { - return serializeToBuffer(this.rollupValidationRequests, this.end, this.constants, this.gasUsed, this.feePayer); + return serializeToBuffer(this.constants, this.rollupValidationRequests, this.end, this.gasUsed, this.feePayer); } /** @@ -52,9 +56,9 @@ export class PrivateToRollupKernelCircuitPublicInputs { static fromBuffer(buffer: Buffer | BufferReader): PrivateToRollupKernelCircuitPublicInputs { const reader = BufferReader.asReader(buffer); return new PrivateToRollupKernelCircuitPublicInputs( + reader.readObject(TxConstantData), reader.readObject(RollupValidationRequests), reader.readObject(PrivateToRollupAccumulatedData), - reader.readObject(TxConstantData), reader.readObject(Gas), reader.readObject(AztecAddress), ); @@ -62,9 +66,9 @@ export class PrivateToRollupKernelCircuitPublicInputs { static empty() { return new PrivateToRollupKernelCircuitPublicInputs( + TxConstantData.empty(), RollupValidationRequests.empty(), PrivateToRollupAccumulatedData.empty(), - TxConstantData.empty(), Gas.empty(), AztecAddress.ZERO, ); @@ -83,8 +87,26 @@ export class PrivateToRollupKernelCircuitPublicInputs { return this.toBuffer(); } + static getFields(fields: FieldsOf) { + return [fields.constants, fields.rollupValidationRequests, fields.end, fields.gasUsed, fields.feePayer] as const; + } + /** Creates an instance from a hex string. */ static get schema() { return bufferSchemaFor(PrivateToRollupKernelCircuitPublicInputs); } + + toFields(): Fr[] { + const fields = serializeToFields(...PrivateToRollupKernelCircuitPublicInputs.getFields(this)); + if (fields.length !== PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH) { + throw new Error( + `Invalid number of fields for PrivateToRollupKernelCircuitPublicInputs. Expected ${PRIVATE_TO_ROLLUP_KERNEL_CIRCUIT_PUBLIC_INPUTS_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + + hash() { + return poseidon2HashWithSeparator(this.toFields(), GeneratorIndex.PRIVATE_TX_HASH); + } } diff --git a/yarn-project/circuits.js/src/structs/kernel/tx_constant_data.ts b/yarn-project/circuits.js/src/structs/kernel/tx_constant_data.ts index 67549544e4a..361852982ad 100644 --- a/yarn-project/circuits.js/src/structs/kernel/tx_constant_data.ts +++ b/yarn-project/circuits.js/src/structs/kernel/tx_constant_data.ts @@ -1,7 +1,8 @@ import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; +import { TX_CONSTANT_DATA_LENGTH } from '../../constants.gen.js'; import { BlockHeader } from '../block_header.js'; import { TxContext } from '../tx_context.js'; @@ -49,6 +50,16 @@ export class TxConstantData { ); } + toFields(): Fr[] { + const fields = serializeToFields(...TxConstantData.getFields(this)); + if (fields.length !== TX_CONSTANT_DATA_LENGTH) { + throw new Error( + `Invalid number of fields for TxConstantData. Expected ${TX_CONSTANT_DATA_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + static fromBuffer(buffer: Buffer | BufferReader): TxConstantData { const reader = BufferReader.asReader(buffer); return new TxConstantData( diff --git a/yarn-project/circuits.js/src/structs/l2_to_l1_message.ts b/yarn-project/circuits.js/src/structs/l2_to_l1_message.ts index 37d1ae3c811..d6954188600 100644 --- a/yarn-project/circuits.js/src/structs/l2_to_l1_message.ts +++ b/yarn-project/circuits.js/src/structs/l2_to_l1_message.ts @@ -1,9 +1,10 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; +import { type FieldsOf } from '@aztec/foundation/types'; -import { L2_TO_L1_MESSAGE_LENGTH } from '../constants.gen.js'; +import { L2_TO_L1_MESSAGE_LENGTH, SCOPED_L2_TO_L1_MESSAGE_LENGTH } from '../constants.gen.js'; export class L2ToL1Message { constructor(public recipient: EthAddress, public content: Fr, public counter: number) {} @@ -85,6 +86,10 @@ export class L2ToL1Message { export class ScopedL2ToL1Message { constructor(public message: L2ToL1Message, public contractAddress: AztecAddress) {} + static getFields(fields: FieldsOf) { + return [fields.message, fields.contractAddress] as const; + } + static empty() { return new ScopedL2ToL1Message(L2ToL1Message.empty(), AztecAddress.ZERO); } @@ -107,6 +112,16 @@ export class ScopedL2ToL1Message { return new ScopedL2ToL1Message(reader.readObject(L2ToL1Message), reader.readObject(AztecAddress)); } + toFields(): Fr[] { + const fields = serializeToFields(...ScopedL2ToL1Message.getFields(this)); + if (fields.length !== SCOPED_L2_TO_L1_MESSAGE_LENGTH) { + throw new Error( + `Invalid number of fields for ScopedL2ToL1Message. Expected ${SCOPED_L2_TO_L1_MESSAGE_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + isEmpty(): boolean { return this.message.isEmpty() && this.contractAddress.isZero(); } diff --git a/yarn-project/circuits.js/src/structs/rollup/private_tube_data.ts b/yarn-project/circuits.js/src/structs/rollup/private_tube_data.ts index b574dbc49b2..a6eac678493 100644 --- a/yarn-project/circuits.js/src/structs/rollup/private_tube_data.ts +++ b/yarn-project/circuits.js/src/structs/rollup/private_tube_data.ts @@ -1,7 +1,7 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { NESTED_RECURSIVE_ROLLUP_HONK_PROOF_LENGTH } from '../../constants.gen.js'; -import { PrivateToRollupKernelCircuitPublicInputs } from '../kernel/kernel_circuit_public_inputs.js'; +import { PrivateToRollupKernelCircuitPublicInputs } from '../kernel/private_to_rollup_kernel_circuit_public_inputs.js'; import { RecursiveProof, makeEmptyRecursiveProof } from '../recursive_proof.js'; import { VkWitnessData } from '../vk_witness_data.js'; diff --git a/yarn-project/circuits.js/src/structs/rollup_validation_requests.ts b/yarn-project/circuits.js/src/structs/rollup_validation_requests.ts index 2d2279b8c88..c415c981782 100644 --- a/yarn-project/circuits.js/src/structs/rollup_validation_requests.ts +++ b/yarn-project/circuits.js/src/structs/rollup_validation_requests.ts @@ -1,7 +1,9 @@ import { type Fr } from '@aztec/foundation/fields'; -import { BufferReader, FieldReader, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, FieldReader, serializeToBuffer, serializeToFields } from '@aztec/foundation/serialize'; import { bufferToHex, hexToBuffer } from '@aztec/foundation/string'; +import { type FieldsOf } from '@aztec/foundation/types'; +import { ROLLUP_VALIDATION_REQUESTS_LENGTH } from '../constants.gen.js'; import { MaxBlockNumber } from './max_block_number.js'; /** @@ -27,11 +29,25 @@ export class RollupValidationRequests { return bufferToHex(this.toBuffer()); } + static getFields(fields: FieldsOf) { + return [fields.maxBlockNumber] as const; + } + static fromFields(fields: Fr[] | FieldReader) { const reader = FieldReader.asReader(fields); return new RollupValidationRequests(MaxBlockNumber.fromFields(reader)); } + toFields(): Fr[] { + const fields = serializeToFields(...RollupValidationRequests.getFields(this)); + if (fields.length !== ROLLUP_VALIDATION_REQUESTS_LENGTH) { + throw new Error( + `Invalid number of fields for RollupValidationRequests. Expected ${ROLLUP_VALIDATION_REQUESTS_LENGTH}, got ${fields.length}`, + ); + } + return fields; + } + /** * Deserializes from a buffer or reader, corresponding to a write in cpp. * @param buffer - Buffer or reader to read from. diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 91b4a613eef..0c99e91f8c0 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -139,7 +139,7 @@ import { TxConstantData, VkWitnessData, } from '../structs/index.js'; -import { PrivateToRollupKernelCircuitPublicInputs } from '../structs/kernel/kernel_circuit_public_inputs.js'; +import { PrivateToRollupKernelCircuitPublicInputs } from '../structs/kernel/private_to_rollup_kernel_circuit_public_inputs.js'; import { AvmProofData } from '../structs/rollup/avm_proof_data.js'; import { BaseOrMergeRollupPublicInputs } from '../structs/rollup/base_or_merge_rollup_public_inputs.js'; import { PrivateBaseRollupHints, PublicBaseRollupHints } from '../structs/rollup/base_rollup_hints.js'; @@ -422,9 +422,9 @@ export function makePrivateToRollupKernelCircuitPublicInputs( fullAccumulatedData = true, ): PrivateToRollupKernelCircuitPublicInputs { return new PrivateToRollupKernelCircuitPublicInputs( + makeTxConstantData(seed + 0x100), makeRollupValidationRequests(seed), makePrivateToRollupAccumulatedData(seed, fullAccumulatedData), - makeTxConstantData(seed + 0x100), makeGas(seed + 0x600), makeAztecAddress(seed + 0x700), ); diff --git a/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts b/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts index 3e4175b7536..b0ae555c1e8 100644 --- a/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts +++ b/yarn-project/noir-protocol-circuits-types/src/conversion/server.ts @@ -943,9 +943,9 @@ export function mapPrivateToRollupKernelCircuitPublicInputsFromNoir( inputs: PrivateToRollupKernelCircuitPublicInputsNoir, ) { return new PrivateToRollupKernelCircuitPublicInputs( + mapTxConstantDataFromNoir(inputs.constants), mapRollupValidationRequestsFromNoir(inputs.rollup_validation_requests), mapPrivateToRollupAccumulatedDataFromNoir(inputs.end), - mapTxConstantDataFromNoir(inputs.constants), mapGasFromNoir(inputs.gas_used), mapAztecAddressFromNoir(inputs.fee_payer), ); diff --git a/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts b/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts index 3247c88355c..58a92439957 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts @@ -117,7 +117,12 @@ export class AztecKVTxPool implements TxPool { */ public getTxByHash(txHash: TxHash): Tx | undefined { const buffer = this.#txs.get(txHash.toString()); - return buffer ? Tx.fromBuffer(buffer) : undefined; + if (buffer) { + const tx = Tx.fromBuffer(buffer); + tx.setTxHash(txHash); + return tx; + } + return undefined; } /** @@ -190,7 +195,11 @@ export class AztecKVTxPool implements TxPool { * @returns Array of tx objects in the order they were added to the pool. */ public getAllTxs(): Tx[] { - return Array.from(this.#txs.values()).map(buffer => Tx.fromBuffer(buffer)); + return Array.from(this.#txs.entries()).map(([hash, buffer]) => { + const tx = Tx.fromBuffer(buffer); + tx.setTxHash(TxHash.fromString(hash)); + return tx; + }); } /** diff --git a/yarn-project/p2p/src/mem_pools/tx_pool/tx_pool_test_suite.ts b/yarn-project/p2p/src/mem_pools/tx_pool/tx_pool_test_suite.ts index f3c92e688b8..056d12d6e4a 100644 --- a/yarn-project/p2p/src/mem_pools/tx_pool/tx_pool_test_suite.ts +++ b/yarn-project/p2p/src/mem_pools/tx_pool/tx_pool_test_suite.ts @@ -69,10 +69,12 @@ export function describeTxPool(getTxPool: () => TxPool) { await pool.addTxs([tx1]); // this peer knows that tx2 was mined, but it does not have the tx object await pool.markAsMined([tx1.getTxHash(), someTxHashThatThisPeerDidNotSee], 1); - expect(pool.getMinedTxHashes()).toEqual([ - [tx1.getTxHash(), 1], - [someTxHashThatThisPeerDidNotSee, 1], - ]); + expect(new Set(pool.getMinedTxHashes())).toEqual( + new Set([ + [tx1.getTxHash(), 1], + [someTxHashThatThisPeerDidNotSee, 1], + ]), + ); // reorg: both txs should now become available again await pool.markMinedAsPending([tx1.getTxHash(), someTxHashThatThisPeerDidNotSee]); diff --git a/yarn-project/p2p/src/services/reqresp/reqresp.test.ts b/yarn-project/p2p/src/services/reqresp/reqresp.test.ts index 11a951d9e33..e99e2f4cb55 100644 --- a/yarn-project/p2p/src/services/reqresp/reqresp.test.ts +++ b/yarn-project/p2p/src/services/reqresp/reqresp.test.ts @@ -153,6 +153,8 @@ describe('ReqResp', () => { await sleep(500); const res = await nodes[0].req.sendRequest(TX_REQ_PROTOCOL, txHash); + // Set tx hash since expect will compare private properties + res.getTxHash(); expect(res).toEqual(tx); }); diff --git a/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts b/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts index 20043b613ca..97f67d24ce0 100644 --- a/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts +++ b/yarn-project/prover-client/src/orchestrator/block_building_helpers.test.ts @@ -1,4 +1,4 @@ -import { TxEffect } from '@aztec/circuit-types'; +import { TxEffect, TxHash } from '@aztec/circuit-types'; import { Fr } from '@aztec/circuits.js'; import { BlobPublicInputs } from '@aztec/circuits.js/blobs'; import { updateInlineTestData } from '@aztec/foundation/testing/files'; @@ -49,29 +49,33 @@ describe('buildBlobHints', () => { it('correctly builds hints for non-empty blob fields', () => { const txEffect0 = TxEffect.empty(); + txEffect0.txHash = new TxHash(new Fr(42)); txEffect0.nullifiers[0] = new Fr(0x123); const txEffect1 = TxEffect.empty(); + txEffect1.txHash = new TxHash(new Fr(43)); txEffect1.noteHashes[0] = new Fr(0x6789); txEffect1.nullifiers[0] = new Fr(0x45); const { blobFields, blobCommitments, blobsHash, blobs } = buildBlobHints([txEffect0, txEffect1]); - const blobFields0Str = fieldArrToStr(blobFields.slice(0, 4)); - const blobFields1Str = fieldArrToStr(blobFields.slice(4)); - expect(blobFields.length).toBe(4 + 6); + const blobFields0Str = fieldArrToStr(blobFields.slice(0, 5)); + const blobFields1Str = fieldArrToStr(blobFields.slice(5)); + expect(blobFields.length).toBe(5 + 7); expect(blobCommitments.length).toBe(1); const blobCommitmentStr = fieldArrToStr(blobCommitments[0]); expect(blobCommitmentStr).toMatchInlineSnapshot( - `"[0x00ad8be66e7276942652627bb00fe1e65dc1c3c6701ab27cc05eff662950071d, 0x00000000000000000000000000000071baf7a9af9757f1d3878b37b438797213]"`, + `"[0x008c32fe581c8fdba12c0d7597911dead2d937d68525bae655508412bb53bb98, 0x0000000000000000000000000000006aaa0680f21270e7d8de4e19da5164f95c]"`, ); const blobsHashStr = blobsHash.toString(); - expect(blobsHashStr).toMatchInlineSnapshot(`"0x00dc577f5c94c82b847693b76ee69cd33d4e5eee3adb6f37d8d7ab662c84725d"`); + expect(blobsHashStr).toMatchInlineSnapshot(`"0x00a965619c8668b834755678b32d023b9c5e8588ce449f44f7fa9335455b5cc5"`); const publicInputs = BlobPublicInputs.fromBlob(blobs[0]); - expect(publicInputs.y).toBe(11463660905914812112228400842008710735611240877901286242511876802170210355245n); + expect(publicInputs.y).toMatchInlineSnapshot( + `17179655213294173540446545222866729565951946174336496855332549059993428157821n`, + ); const zStr = publicInputs.z.toString(); - expect(zStr).toMatchInlineSnapshot(`"0x1582b354f32263abde313d597582ebceafe17d4e2a68dd47533383e85b4cb780"`); + expect(zStr).toMatchInlineSnapshot(`"0x1f92b871671f27a378d23f1cef10fbd8f0d90dd7172da9e3c3fc1aa745a072c3"`); // Run with AZTEC_GENERATE_TEST_DATA=1 to update noir test data. updateInlineTestData( diff --git a/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts b/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts index 71d3a8017fc..457a6b3e4b6 100644 --- a/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts @@ -104,7 +104,7 @@ export class TxProvingState { } const tubeData = new PublicTubeData( - this.processedTx.data.toPublicKernelCircuitPublicInputs(), + this.processedTx.data.toPrivateToPublicKernelCircuitPublicInputs(), this.tube.proof, this.#getTubeVkData(), ); diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index 8d3359149b2..d784fbea92d 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -154,9 +154,7 @@ describe('prover-node', () => { l2BlockSource.getL1Constants.mockResolvedValue(EmptyL1RollupConstants); // Coordination plays along and returns a tx whenever requested - mockCoordination.getTxByHash.mockImplementation(hash => - Promise.resolve(mock({ getTxHash: () => hash, tryGetTxHash: () => hash })), - ); + mockCoordination.getTxByHash.mockImplementation(hash => Promise.resolve(mock({ getTxHash: () => hash }))); // A sample claim claim = { epochToProve: 10n, bondProvider: address } as EpochProofClaim; diff --git a/yarn-project/pxe/src/note_decryption_utils/brute_force_note_info.ts b/yarn-project/pxe/src/note_decryption_utils/brute_force_note_info.ts index 7ce0ade4c4a..abbae919f82 100644 --- a/yarn-project/pxe/src/note_decryption_utils/brute_force_note_info.ts +++ b/yarn-project/pxe/src/note_decryption_utils/brute_force_note_info.ts @@ -34,6 +34,7 @@ export async function bruteForceNoteInfo( simulator: AcirSimulator, uniqueNoteHashes: Fr[], txHash: TxHash, + firstNullifier: Fr, contractAddress: AztecAddress, storageSlot: Fr, noteTypeId: NoteSelector, @@ -46,7 +47,6 @@ export async function bruteForceNoteInfo( let noteHash: Fr | undefined; let uniqueNoteHash: Fr | undefined; let innerNullifier: Fr | undefined; - const firstNullifier = Fr.fromBuffer(txHash.toBuffer()); for (; noteHashIndex < uniqueNoteHashes.length; ++noteHashIndex) { if (excludedIndices.has(noteHashIndex)) { diff --git a/yarn-project/pxe/src/note_decryption_utils/produce_note_daos.ts b/yarn-project/pxe/src/note_decryption_utils/produce_note_daos.ts index 7e1f94abe03..242b34b39ff 100644 --- a/yarn-project/pxe/src/note_decryption_utils/produce_note_daos.ts +++ b/yarn-project/pxe/src/note_decryption_utils/produce_note_daos.ts @@ -31,6 +31,7 @@ export async function produceNoteDaos( addressPoint: PublicKey | undefined, payload: L1NotePayload, txHash: TxHash, + firstNullifier: Fr, l2BlockNumber: number, l2BlockHash: string, noteHashes: Fr[], @@ -51,6 +52,7 @@ export async function produceNoteDaos( addressPoint, payload, txHash, + firstNullifier, l2BlockNumber, l2BlockHash, noteHashes, diff --git a/yarn-project/pxe/src/note_decryption_utils/produce_note_daos_for_key.ts b/yarn-project/pxe/src/note_decryption_utils/produce_note_daos_for_key.ts index 291d9efd80d..ef78cf7a76b 100644 --- a/yarn-project/pxe/src/note_decryption_utils/produce_note_daos_for_key.ts +++ b/yarn-project/pxe/src/note_decryption_utils/produce_note_daos_for_key.ts @@ -13,6 +13,7 @@ export async function produceNoteDaosForKey( pkM: PublicKey, payload: L1NotePayload, txHash: TxHash, + firstNullifier: Fr, l2BlockNumber: number, l2BlockHash: string, noteHashes: Fr[], @@ -39,6 +40,7 @@ export async function produceNoteDaosForKey( simulator, noteHashes, txHash, + firstNullifier, payload.contractAddress, payload.storageSlot, payload.noteTypeId, diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 6082a3cc1eb..b6b4eba6cd6 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -517,8 +517,8 @@ export class PXEService implements PXE { } } - this.log.info(`Simulation completed for ${simulatedTx.tryGetTxHash()} in ${timer.ms()}ms`, { - txHash: simulatedTx.tryGetTxHash(), + this.log.info(`Simulation completed for ${simulatedTx.getTxHash()} in ${timer.ms()}ms`, { + txHash: simulatedTx.getTxHash(), ...txInfo, ...(profileResult ? { gateCounts: profileResult.gateCounts } : {}), ...(publicOutput diff --git a/yarn-project/pxe/src/simulator_oracle/index.ts b/yarn-project/pxe/src/simulator_oracle/index.ts index 7b3d332738e..019ca15c7fc 100644 --- a/yarn-project/pxe/src/simulator_oracle/index.ts +++ b/yarn-project/pxe/src/simulator_oracle/index.ts @@ -610,6 +610,7 @@ export class SimulatorOracle implements DBOracle { notePayload ? recipient.toAddressPoint() : undefined, payload!, txEffect.data.txHash, + txEffect.data.nullifiers[0], txEffect.l2BlockNumber, txEffect.l2BlockHash, txEffect.data.noteHashes, diff --git a/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts b/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts index e4e95f0ada5..11e9a6aaaf4 100644 --- a/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts +++ b/yarn-project/pxe/src/simulator_oracle/simulator_oracle.test.ts @@ -490,7 +490,7 @@ describe('Simulator oracle', () => { }); function mockTaggedLogs(requests: MockNoteRequest[], nullifiers: number = 0) { - const txEffectsMap: { [k: string]: { noteHashes: Fr[]; txHash: TxHash } } = {}; + const txEffectsMap: { [k: string]: { noteHashes: Fr[]; txHash: TxHash; nullifiers: Fr[] } } = {}; const taggedLogs: TxScopedL2Log[] = []; const groupedByTx = requests.reduce<{ [i: number]: { [j: number]: MockNoteRequest[] } }>((acc, request) => { if (!acc[request.blockNumber]) { @@ -513,6 +513,7 @@ describe('Simulator oracle', () => { if (!txEffectsMap[txHash.toString()]) { txEffectsMap[txHash.toString()] = { txHash, + nullifiers: [new Fr(txHash.hash.toBigInt() + 27n)], noteHashes: Array(maxNoteIndex + 1) .fill(0) .map(() => Fr.random()), diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 7ee18457dbf..d6fff38670f 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -642,7 +642,7 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(trace.traceNewNoteHash).toHaveBeenCalledTimes(1); const siloedNotehash = siloNoteHash(address, value0); - const nonce = computeNoteHashNonce(Fr.fromBuffer(context.persistableState.txHash.toBuffer()), 0); + const nonce = computeNoteHashNonce(context.persistableState.firstNullifier, 0); const uniqueNoteHash = computeUniqueNoteHash(nonce, siloedNotehash); expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash); }); diff --git a/yarn-project/simulator/src/avm/fixtures/index.ts b/yarn-project/simulator/src/avm/fixtures/index.ts index 7990a0b2c3b..494a500f412 100644 --- a/yarn-project/simulator/src/avm/fixtures/index.ts +++ b/yarn-project/simulator/src/avm/fixtures/index.ts @@ -1,4 +1,4 @@ -import { TxHash, isNoirCallStackUnresolved } from '@aztec/circuit-types'; +import { isNoirCallStackUnresolved } from '@aztec/circuit-types'; import { GasFees, GlobalVariables, MAX_L2_GAS_PER_TX_PUBLIC_PORTION } from '@aztec/circuits.js'; import { type FunctionArtifact, FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; @@ -45,7 +45,7 @@ export function initPersistableStateManager(overrides?: { nullifiers?: NullifierManager; doMerkleOperations?: boolean; merkleTrees?: AvmEphemeralForest; - txHash?: TxHash; + firstNullifier?: Fr; }): AvmPersistableStateManager { const worldStateDB = overrides?.worldStateDB || mock(); return new AvmPersistableStateManager( @@ -55,7 +55,7 @@ export function initPersistableStateManager(overrides?: { overrides?.nullifiers || new NullifierManager(worldStateDB), overrides?.doMerkleOperations || false, overrides?.merkleTrees || mock(), - overrides?.txHash || new TxHash(new Fr(27)), + overrides?.firstNullifier || new Fr(27), ); } diff --git a/yarn-project/simulator/src/avm/journal/journal.test.ts b/yarn-project/simulator/src/avm/journal/journal.test.ts index 9aae7f88889..afd936960e4 100644 --- a/yarn-project/simulator/src/avm/journal/journal.test.ts +++ b/yarn-project/simulator/src/avm/journal/journal.test.ts @@ -85,7 +85,7 @@ describe('journal', () => { persistableState.writeNoteHash(address, utxo); expect(trace.traceNewNoteHash).toHaveBeenCalledTimes(1); const siloedNotehash = siloNoteHash(address, utxo); - const nonce = computeNoteHashNonce(Fr.fromBuffer(persistableState.txHash.toBuffer()), 1); + const nonce = computeNoteHashNonce(persistableState.firstNullifier, 1); const uniqueNoteHash = computeUniqueNoteHash(nonce, siloedNotehash); expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash); }); diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 87f93d97b45..1c0182ad74d 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -1,4 +1,4 @@ -import { MerkleTreeId, type TxHash } from '@aztec/circuit-types'; +import { MerkleTreeId } from '@aztec/circuit-types'; import { AztecAddress, CANONICAL_AUTH_REGISTRY_ADDRESS, @@ -61,7 +61,7 @@ export class AvmPersistableStateManager { private readonly doMerkleOperations: boolean = false, /** Ephmeral forest for merkle tree operations */ public merkleTrees: AvmEphemeralForest, - public readonly txHash: TxHash, + public readonly firstNullifier: Fr, ) {} /** @@ -71,7 +71,7 @@ export class AvmPersistableStateManager { worldStateDB: WorldStateDB, trace: PublicSideEffectTraceInterface, doMerkleOperations: boolean = false, - txHash: TxHash, + firstNullifier: Fr, ) { const ephemeralForest = await AvmEphemeralForest.create(worldStateDB.getMerkleInterface()); return new AvmPersistableStateManager( @@ -81,7 +81,7 @@ export class AvmPersistableStateManager { /*nullifiers=*/ new NullifierManager(worldStateDB), /*doMerkleOperations=*/ doMerkleOperations, ephemeralForest, - txHash, + firstNullifier, ); } @@ -96,7 +96,7 @@ export class AvmPersistableStateManager { this.nullifiers.fork(), this.doMerkleOperations, this.merkleTrees.fork(), - this.txHash, + this.firstNullifier, ); } @@ -294,8 +294,7 @@ export class AvmPersistableStateManager { * @param noteHash - the non unique note hash to write */ public writeSiloedNoteHash(noteHash: Fr): void { - const txHash = Fr.fromBuffer(this.txHash.toBuffer()); - const nonce = computeNoteHashNonce(txHash, this.trace.getNoteHashCount()); + const nonce = computeNoteHashNonce(this.firstNullifier, this.trace.getNoteHashCount()); const uniqueNoteHash = computeUniqueNoteHash(nonce, noteHash); this.writeUniqueNoteHash(uniqueNoteHash); diff --git a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts index af4fd095786..2fd3d84edba 100644 --- a/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/accrued_substate.test.ts @@ -125,7 +125,7 @@ describe('Accrued Substate', () => { await new EmitNoteHash(/*indirect=*/ 0, /*offset=*/ value0Offset).execute(context); expect(trace.traceNewNoteHash).toHaveBeenCalledTimes(1); const siloedNotehash = siloNoteHash(address, value0); - const nonce = computeNoteHashNonce(Fr.fromBuffer(context.persistableState.txHash.toBuffer()), 0); + const nonce = computeNoteHashNonce(persistableState.firstNullifier, 0); const uniqueNoteHash = computeUniqueNoteHash(nonce, siloedNotehash); expect(trace.traceNewNoteHash).toHaveBeenCalledWith(uniqueNoteHash); }); diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index eb3f743d963..b67d187cdaa 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -290,7 +290,7 @@ export class PublicProcessor implements Traceable { return [result, failed, returns]; } - @trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.tryGetTxHash()?.toString() })) + @trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.getTxHash().toString() })) private async processTx(tx: Tx, deadline?: Date): Promise<[ProcessedTx, NestedProcessReturnValues[]]> { const [time, [processedTx, returnValues]] = await elapsed(() => this.processTxWithinDeadline(tx, deadline)); diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index 0c376934a20..194838dd018 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -7,7 +7,7 @@ import { type SimulationError, type Tx, TxExecutionPhase, - TxHash, + type TxHash, } from '@aztec/circuit-types'; import { AvmCircuitInputs, @@ -61,6 +61,7 @@ export class PublicTxContext { public avmProvingRequest: AvmProvingRequest | undefined; // FIXME(dbanks12): remove constructor( + public readonly txHash: TxHash, public readonly state: PhaseStateManager, private readonly globalVariables: GlobalVariables, private readonly startStateReference: StateReference, @@ -103,12 +104,14 @@ export class PublicTxContext { previousAccumulatedDataArrayLengths, ); + const firstNullifier = nonRevertibleAccumulatedDataFromPrivate.nullifiers[0]; + // Transaction level state manager that will be forked for revertible phases. const txStateManager = await AvmPersistableStateManager.create( worldStateDB, enqueuedCallTrace, doMerkleOperations, - fetchTxHash(nonRevertibleAccumulatedDataFromPrivate), + firstNullifier, ); const gasSettings = tx.data.constants.txContext.gasSettings; @@ -117,6 +120,7 @@ export class PublicTxContext { const gasAllocatedToPublic = applyMaxToAvailableGas(gasSettings.gasLimits.sub(gasUsedByPrivate)); return new PublicTxContext( + tx.getTxHash(), new PhaseStateManager(txStateManager), globalVariables, await db.getStateReference(), @@ -188,14 +192,6 @@ export class PublicTxContext { return this.revertCode; } - /** - * Construct & return transaction hash. - * @returns The transaction's hash. - */ - getTxHash(): TxHash { - return fetchTxHash(this.nonRevertibleAccumulatedDataFromPrivate); - } - /** * Are there any call requests for the speciiied phase? */ @@ -452,12 +448,3 @@ function applyMaxToAvailableGas(availableGas: Gas) { /*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_TX_PUBLIC_PORTION), ); } - -function fetchTxHash(nonRevertibleAccumulatedData: PrivateToPublicAccumulatedData): TxHash { - // Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier - const firstNullifier = nonRevertibleAccumulatedData.nullifiers[0]; - if (!firstNullifier || firstNullifier.isZero()) { - throw new Error(`Cannot get tx hash since first nullifier is missing`); - } - return new TxHash(firstNullifier); -} diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index b4c91484e27..e973d28f654 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -219,8 +219,8 @@ export class PublicTxSimulator { const callRequests = context.getCallRequestsForPhase(phase); const executionRequests = context.getExecutionRequestsForPhase(phase); - this.log.debug(`Processing phase ${TxExecutionPhase[phase]} for tx ${context.getTxHash()}`, { - txHash: context.getTxHash().toString(), + this.log.debug(`Processing phase ${TxExecutionPhase[phase]} for tx ${context.txHash}`, { + txHash: context.txHash.toString(), phase: TxExecutionPhase[phase], callRequests: callRequests.length, executionRequests: executionRequests.length, @@ -266,7 +266,7 @@ export class PublicTxSimulator { * @returns The result of execution. */ @trackSpan('PublicTxSimulator.simulateEnqueuedCall', (phase, context, _callRequest, executionRequest) => ({ - [Attributes.TX_HASH]: context.getTxHash().toString(), + [Attributes.TX_HASH]: context.txHash.toString(), [Attributes.TARGET_ADDRESS]: executionRequest.callContext.contractAddress.toString(), [Attributes.SENDER_ADDRESS]: executionRequest.callContext.msgSender.toString(), [Attributes.SIMULATOR_PHASE]: TxExecutionPhase[phase].toString(), From 1775e53025f9946ba26b8b624a0f15f4ccdabd2f Mon Sep 17 00:00:00 2001 From: Facundo Date: Fri, 10 Jan 2025 12:52:32 +0000 Subject: [PATCH 2/3] chore(avm): fix mac build (#11147) Use bb's format. --- barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp index 7855c991513..a06cbfd8fb4 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/trace/execution.cpp @@ -1,5 +1,6 @@ #include "barretenberg/vm/avm/trace/execution.hpp" #include "barretenberg/bb/log.hpp" +#include "barretenberg/common/log.hpp" #include "barretenberg/common/serialize.hpp" #include "barretenberg/common/thread.hpp" #include "barretenberg/common/throw_or_abort.hpp" @@ -31,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -195,7 +197,7 @@ void show_trace_info(const auto& trace) std::string fullnesses; for (size_t j = i; j < i + 10 && j < column_stats.size(); j++) { const auto& stat = column_stats.at(j); - fullnesses += std::format("{:3}: {:3}% ", stat.column_number, stat.fullness); + fullnesses += format(std::setw(3), stat.column_number, ": ", std::setw(3), stat.fullness, "% "); } vinfo(fullnesses); } From f034e2af6f372e393b63ff19ca6d118d03506e1f Mon Sep 17 00:00:00 2001 From: iakovenkos <105737703+iakovenkos@users.noreply.github.com> Date: Fri, 10 Jan 2025 16:02:56 +0100 Subject: [PATCH 3/3] chore: SmallSubgroupIPA tests (#11106) This PR is a follow-up to https://github.com/AztecProtocol/aztec-packages/pull/10773 --- .../commitment_key.test.hpp | 32 +- .../commitment_schemes/gemini/gemini_impl.hpp | 9 + .../shplonk/shplemini.test.cpp | 306 ++++++++++++------ .../small_subgroup_ipa/small_subgroup_ipa.hpp | 40 ++- .../small_subgroup_ipa.test.cpp | 248 ++++++++++++++ .../utils/instance_witness_generator.hpp | 70 ++++ .../utils/test_settings.hpp | 29 ++ 7 files changed, 628 insertions(+), 106 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.test.cpp create mode 100644 barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp create mode 100644 barretenberg/cpp/src/barretenberg/commitment_schemes/utils/test_settings.hpp diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.test.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.test.hpp index adafef90c7c..de417753927 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.test.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.test.hpp @@ -14,44 +14,54 @@ namespace bb { constexpr size_t COMMITMENT_TEST_NUM_BN254_POINTS = 4096; constexpr size_t COMMITMENT_TEST_NUM_GRUMPKIN_POINTS = 1 << CONST_ECCVM_LOG_N; -template inline std::shared_ptr CreateCommitmentKey(); +template inline std::shared_ptr create_commitment_key(const size_t num_points = 0); -template <> inline std::shared_ptr> CreateCommitmentKey>() +template <> +inline std::shared_ptr> create_commitment_key>( + const size_t num_points) { srs::init_crs_factory(bb::srs::get_ignition_crs_path()); + if (num_points != 0) { + return std::make_shared>(num_points); + }; return std::make_shared>(COMMITMENT_TEST_NUM_BN254_POINTS); } // For IPA -template <> inline std::shared_ptr> CreateCommitmentKey>() +template <> +inline std::shared_ptr> create_commitment_key>( + const size_t num_points) { srs::init_grumpkin_crs_factory(bb::srs::get_grumpkin_crs_path()); + if (num_points != 0) { + return std::make_shared>(num_points); + } return std::make_shared>(COMMITMENT_TEST_NUM_GRUMPKIN_POINTS); } -template inline std::shared_ptr CreateCommitmentKey() +template inline std::shared_ptr create_commitment_key(size_t num_points) // requires std::default_initializable { - return std::make_shared(); + return std::make_shared(num_points); } -template inline std::shared_ptr CreateVerifierCommitmentKey(); +template inline std::shared_ptr create_verifier_commitment_key(); template <> -inline std::shared_ptr> CreateVerifierCommitmentKey< +inline std::shared_ptr> create_verifier_commitment_key< VerifierCommitmentKey>() { return std::make_shared>(); } // For IPA template <> -inline std::shared_ptr> CreateVerifierCommitmentKey< +inline std::shared_ptr> create_verifier_commitment_key< VerifierCommitmentKey>() { auto crs_factory = std::make_shared>( bb::srs::get_grumpkin_crs_path(), COMMITMENT_TEST_NUM_GRUMPKIN_POINTS); return std::make_shared>(COMMITMENT_TEST_NUM_GRUMPKIN_POINTS, crs_factory); } -template inline std::shared_ptr CreateVerifierCommitmentKey() +template inline std::shared_ptr create_verifier_commitment_key() // requires std::default_initializable { return std::make_shared(); @@ -149,10 +159,10 @@ template class CommitmentTest : public ::testing::Test { { // Avoid reallocating static objects if called in subclasses of FooTest. if (commitment_key == nullptr) { - commitment_key = CreateCommitmentKey(); + commitment_key = create_commitment_key(); } if (verification_key == nullptr) { - verification_key = CreateVerifierCommitmentKey(); + verification_key = create_verifier_commitment_key(); } } diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp index a66387474d1..3828a009b6b 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp @@ -124,6 +124,15 @@ std::vector::Claim> GeminiProver_::prove( } const Fr r_challenge = transcript->template get_challenge("Gemini:r"); + const bool gemini_challenge_in_small_subgroup = (has_zk) && (r_challenge.pow(Curve::SUBGROUP_SIZE) == Fr(1)); + + // If Gemini evaluation challenge lands in the multiplicative subgroup used by SmallSubgroupIPA protocol, the + // evaluations of prover polynomials at this challenge would leak witness data. + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1194). Handle edge cases in PCS + if (gemini_challenge_in_small_subgroup) { + throw_or_abort("Gemini evaluation challenge is in the SmallSubgroup."); + } + std::vector claims = compute_fold_polynomial_evaluations(log_n, std::move(fold_polynomials), r_challenge, std::move(batched_group)); diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp index bfb9fd93169..2bcc54538b2 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp @@ -5,8 +5,10 @@ #include "../kzg/kzg.hpp" #include "../shplonk/shplonk.hpp" #include "../utils/batch_mul_native.hpp" -#include "barretenberg/commitment_schemes/claim.hpp" #include "barretenberg/commitment_schemes/ipa/ipa.hpp" +#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp" +#include "barretenberg/commitment_schemes/utils/instance_witness_generator.hpp" +#include "barretenberg/commitment_schemes/utils/test_settings.hpp" #include "barretenberg/ecc/curves/bn254/g1.hpp" #include @@ -14,29 +16,29 @@ namespace bb { -template class ShpleminiTest : public CommitmentTest { +template class ShpleminiTest : public CommitmentTest { public: - using Fr = typename Curve::ScalarField; - using Commitment = typename Curve::AffineElement; - using GroupElement = typename Curve::Element; - using Polynomial = bb::Polynomial; + static constexpr size_t n = 32; + static constexpr size_t log_n = 5; + static constexpr size_t num_polynomials = 5; + static constexpr size_t num_shiftable = 2; }; -using CurveTypes = ::testing::Types; +using TestSettings = ::testing::Types; -TYPED_TEST_SUITE(ShpleminiTest, CurveTypes); +TYPED_TEST_SUITE(ShpleminiTest, TestSettings); // This test checks that batch_multivariate_opening_claims method operates correctly TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching) { - using ShpleminiVerifier = ShpleminiVerifier_; - using Fr = typename TypeParam::ScalarField; - using GroupElement = typename TypeParam::Element; - using Commitment = typename TypeParam::AffineElement; - using Polynomial = typename bb::Polynomial; + using Curve = typename TypeParam::Curve; + using ShpleminiVerifier = ShpleminiVerifier_; + using Fr = typename Curve::ScalarField; + using GroupElement = typename Curve::Element; + using Commitment = typename Curve::AffineElement; + using CK = typename TypeParam::CommitmentKey; - const size_t n = 16; - const size_t log_n = 4; + std::shared_ptr ck = create_commitment_key(this->n); // Generate mock challenges Fr rho = Fr::random_element(); @@ -45,37 +47,40 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching) Fr shplonk_eval_challenge = Fr::random_element(); // Generate multilinear polynomials and compute their commitments - auto mle_opening_point = this->random_evaluation_point(log_n); - auto poly1 = Polynomial::random(n); - auto poly2 = Polynomial::random(n, /*shiftable*/ 1); - Polynomial poly3(n); - - Commitment commitment1 = this->commit(poly1); - Commitment commitment2 = this->commit(poly2); - Commitment commitment3 = this->commit(poly3); - EXPECT_TRUE(commitment3.is_point_at_infinity()); - - std::vector unshifted_commitments = { commitment1, commitment2, commitment3 }; - std::vector shifted_commitments = { commitment2, commitment3 }; - - // Evaluate the polynomials at the multivariate challenge, poly3 is not evaluated, because it is 0. - auto eval1 = poly1.evaluate_mle(mle_opening_point); - auto eval2 = poly2.evaluate_mle(mle_opening_point); - Fr eval3{ 0 }; - Fr eval3_shift{ 0 }; - auto eval2_shift = poly2.evaluate_mle(mle_opening_point, true); + auto mle_opening_point = this->random_evaluation_point(this->log_n); + + auto pcs_instance_witness = + InstanceWitnessGenerator(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck); // Collect multilinear evaluations - std::vector multilinear_evaluations = { eval1, eval2, eval3, eval2_shift, eval3_shift }; - std::vector rhos = gemini::powers_of_rho(rho, multilinear_evaluations.size()); + std::vector rhos = gemini::powers_of_rho(rho, this->num_polynomials + this->num_shiftable); // Compute batched multivariate evaluation - Fr batched_evaluation = - std::inner_product(multilinear_evaluations.begin(), multilinear_evaluations.end(), rhos.begin(), Fr::zero()); + Fr batched_evaluation = Fr(0); + size_t idx = 0; + for (auto& eval : pcs_instance_witness.unshifted_evals) { + batched_evaluation += eval * rhos[idx]; + idx++; + } + + for (auto& eval : pcs_instance_witness.shifted_evals) { + batched_evaluation += eval * rhos[idx]; + idx++; + } // Compute batched commitments manually - GroupElement batched_commitment_unshifted = commitment1 * rhos[0] + commitment2 * rhos[1] + commitment3 * rhos[2]; - GroupElement batched_commitment_to_be_shifted = commitment2 * rhos[3] + commitment3 * rhos[4]; + idx = 0; + GroupElement batched_commitment_unshifted = GroupElement::zero(); + for (auto& comm : pcs_instance_witness.unshifted_commitments) { + batched_commitment_unshifted += comm * rhos[idx]; + idx++; + } + + GroupElement batched_commitment_to_be_shifted = GroupElement::zero(); + for (auto& comm : pcs_instance_witness.to_be_shifted_commitments) { + batched_commitment_to_be_shifted += comm * rhos[idx]; + idx++; + } // Compute expected result manually GroupElement commitment_to_univariate = @@ -93,17 +98,17 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching) std::vector scalars; Fr verifier_batched_evaluation{ 0 }; - Fr unshifted_scalar = (shplonk_eval_challenge - gemini_eval_challenge).invert() + - shplonk_batching_challenge * (shplonk_eval_challenge + gemini_eval_challenge).invert(); + const Fr unshifted_scalar = (shplonk_eval_challenge - gemini_eval_challenge).invert() + + shplonk_batching_challenge * (shplonk_eval_challenge + gemini_eval_challenge).invert(); - Fr shifted_scalar = gemini_eval_challenge.invert() * - ((shplonk_eval_challenge - gemini_eval_challenge).invert() - - shplonk_batching_challenge * (shplonk_eval_challenge + gemini_eval_challenge).invert()); + const Fr shifted_scalar = gemini_eval_challenge.invert() * + ((shplonk_eval_challenge - gemini_eval_challenge).invert() - + shplonk_batching_challenge * (shplonk_eval_challenge + gemini_eval_challenge).invert()); - ShpleminiVerifier::batch_multivariate_opening_claims(RefVector(unshifted_commitments), - RefVector(shifted_commitments), - RefArray{ eval1, eval2, eval3 }, - RefArray{ eval2_shift, eval3_shift }, + ShpleminiVerifier::batch_multivariate_opening_claims(RefVector(pcs_instance_witness.unshifted_commitments), + RefVector(pcs_instance_witness.to_be_shifted_commitments), + RefVector(pcs_instance_witness.unshifted_evals), + RefVector(pcs_instance_witness.shifted_evals), rho, unshifted_scalar, shifted_scalar, @@ -114,22 +119,25 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching) // Final pairing check GroupElement shplemini_result = batch_mul_native(commitments, scalars); - EXPECT_EQ(commitments.size(), unshifted_commitments.size() + shifted_commitments.size()); + EXPECT_EQ(commitments.size(), + pcs_instance_witness.unshifted_commitments.size() + + pcs_instance_witness.to_be_shifted_commitments.size()); EXPECT_EQ(batched_evaluation, verifier_batched_evaluation); EXPECT_EQ(-expected_result, shplemini_result); } TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) { - using GeminiProver = GeminiProver_; - using ShpleminiVerifier = ShpleminiVerifier_; - using ShplonkVerifier = ShplonkVerifier_; - using Fr = typename TypeParam::ScalarField; - using GroupElement = typename TypeParam::Element; - using Commitment = typename TypeParam::AffineElement; + using Curve = TypeParam::Curve; + using GeminiProver = GeminiProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + using ShplonkVerifier = ShplonkVerifier_; + using Fr = typename Curve::ScalarField; + using GroupElement = typename Curve::Element; + using Commitment = typename Curve::AffineElement; using Polynomial = typename bb::Polynomial; + using CK = typename TypeParam::CommitmentKey; - const size_t n = 16; - const size_t log_n = 4; + std::shared_ptr ck = create_commitment_key(this->n); // Generate mock challenges Fr rho = Fr::random_element(); @@ -137,62 +145,58 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) Fr shplonk_batching_challenge = Fr::random_element(); Fr shplonk_eval_challenge = Fr::random_element(); - // Generate multilinear polynomials and compute their commitments - auto mle_opening_point = this->random_evaluation_point(log_n); - auto poly1 = Polynomial::random(n); - auto poly2 = Polynomial::random(n, /*shiftable*/ 1); - Polynomial poly3 = Polynomial::shiftable(n); - - // Evaluate the polynomials at the multivariate challenge, poly3 is not evaluated, because it is 0. - auto eval1 = poly1.evaluate_mle(mle_opening_point); - auto eval2 = poly2.evaluate_mle(mle_opening_point); - Fr eval3{ 0 }; - Fr eval3_shift{ 0 }; - auto eval2_shift = poly2.evaluate_mle(mle_opening_point, true); + std::vector mle_opening_point = this->random_evaluation_point(this->log_n); + + auto pcs_instance_witness = + InstanceWitnessGenerator(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck); // Collect multilinear evaluations - std::vector multilinear_evaluations = { eval1, eval2, eval3, eval2_shift, eval3_shift }; - std::vector rhos = gemini::powers_of_rho(rho, multilinear_evaluations.size()); + std::vector rhos = gemini::powers_of_rho(rho, this->num_polynomials + this->num_shiftable); + + Polynomial batched_unshifted(this->n); + Polynomial batched_to_be_shifted = Polynomial::shiftable(this->n); - Polynomial batched_unshifted(n); - Polynomial batched_to_be_shifted = Polynomial::shiftable(n); - batched_unshifted.add_scaled(poly1, rhos[0]); - batched_unshifted.add_scaled(poly2, rhos[1]); - batched_unshifted.add_scaled(poly3, rhos[2]); - batched_to_be_shifted.add_scaled(poly2, rhos[3]); - batched_to_be_shifted.add_scaled(poly3, rhos[4]); + size_t idx = 0; + for (auto& poly : pcs_instance_witness.unshifted_polynomials) { + batched_unshifted.add_scaled(poly, rhos[idx]); + idx++; + } + + for (auto& poly : pcs_instance_witness.to_be_shifted_polynomials) { + batched_unshifted.add_scaled(poly, rhos[idx]); + idx++; + } // Compute: // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1 auto fold_polynomials = GeminiProver::compute_fold_polynomials( - log_n, mle_opening_point, std::move(batched_unshifted), std::move(batched_to_be_shifted)); + this->log_n, mle_opening_point, std::move(batched_unshifted), std::move(batched_to_be_shifted)); std::vector prover_commitments; - for (size_t l = 0; l < log_n - 1; ++l) { - auto commitment = this->ck()->commit(fold_polynomials[l + 2]); + for (size_t l = 0; l < this->log_n - 1; ++l) { + auto commitment = ck->commit(fold_polynomials[l + 2]); prover_commitments.emplace_back(commitment); } - const auto opening_claims = - GeminiProver::compute_fold_polynomial_evaluations(log_n, std::move(fold_polynomials), gemini_eval_challenge); + const auto opening_claims = GeminiProver::compute_fold_polynomial_evaluations( + this->log_n, std::move(fold_polynomials), gemini_eval_challenge); std::vector prover_evaluations; - for (size_t l = 0; l < log_n; ++l) { + for (size_t l = 0; l < this->log_n; ++l) { const auto& evaluation = opening_claims[l + 1].opening_pair.evaluation; prover_evaluations.emplace_back(evaluation); } - std::vector r_squares = gemini::powers_of_evaluation_challenge(gemini_eval_challenge, log_n); + std::vector r_squares = gemini::powers_of_evaluation_challenge(gemini_eval_challenge, this->log_n); GroupElement expected_result = GroupElement::zero(); - std::vector expected_inverse_vanishing_evals(log_n + 1); + std::vector expected_inverse_vanishing_evals(this->log_n + 1); // Compute expected inverses expected_inverse_vanishing_evals[0] = (shplonk_eval_challenge - r_squares[0]).invert(); - expected_inverse_vanishing_evals[1] = (shplonk_eval_challenge + r_squares[0]).invert(); - expected_inverse_vanishing_evals[2] = (shplonk_eval_challenge + r_squares[1]).invert(); - expected_inverse_vanishing_evals[3] = (shplonk_eval_challenge + r_squares[2]).invert(); - expected_inverse_vanishing_evals[4] = (shplonk_eval_challenge + r_squares[3]).invert(); + for (size_t idx = 1; idx < this->log_n + 1; idx++) { + expected_inverse_vanishing_evals[idx] = (shplonk_eval_challenge + r_squares[idx - 1]).invert(); + } Fr current_challenge{ shplonk_batching_challenge * shplonk_batching_challenge }; for (size_t idx = 0; idx < prover_commitments.size(); ++idx) { @@ -202,13 +206,13 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) // Run the ShepliminiVerifier batching method std::vector inverse_vanishing_evals = - ShplonkVerifier::compute_inverted_gemini_denominators(log_n + 1, shplonk_eval_challenge, r_squares); + ShplonkVerifier::compute_inverted_gemini_denominators(this->log_n + 1, shplonk_eval_challenge, r_squares); std::vector commitments; std::vector scalars; Fr expected_constant_term_accumulator{ 0 }; - ShpleminiVerifier::batch_gemini_claims_received_from_prover(log_n, + ShpleminiVerifier::batch_gemini_claims_received_from_prover(this->log_n, prover_commitments, prover_evaluations, inverse_vanishing_evals, @@ -223,4 +227,118 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) EXPECT_EQ(shplemini_result, expected_result); } -} // namespace bb +/** + * @brief Test Shplemini with ZK data consisting of a hiding polynomial generated by GeminiProver and Libra polynomials + * used to mask Sumcheck Round Univariates. + * + */ +TYPED_TEST(ShpleminiTest, ShpleminiWithZK) +{ + using ZKData = ZKSumcheckData; + using Curve = TypeParam::Curve; + using ShpleminiProver = ShpleminiProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + using Fr = typename Curve::ScalarField; + using Commitment = typename Curve::AffineElement; + using CK = typename TypeParam::CommitmentKey; + + // Initialize transcript and commitment key + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck. + static constexpr size_t log_subgroup_size = static_cast(numeric::get_msb(Curve::SUBGROUP_SIZE)); + std::shared_ptr ck = create_commitment_key(std::max(this->n, 1ULL << (log_subgroup_size + 1))); + + // Generate Libra polynomials, compute masked concatenated Libra polynomial, commit to it + ZKData zk_sumcheck_data(this->log_n, prover_transcript, ck); + + // Generate multivariate challenge of size CONST_PROOF_SIZE_LOG_N + std::vector const_size_mle_opening_point = this->random_evaluation_point(CONST_PROOF_SIZE_LOG_N); + // Truncate the multivariate challenge to evaluate prover polynomials (As in Sumcheck) + const std::vector mle_opening_point(const_size_mle_opening_point.begin(), + const_size_mle_opening_point.begin() + this->log_n); + + // Generate random prover polynomials, compute their evaluations and commitments + auto pcs_instance_witness = + InstanceWitnessGenerator(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck); + + // Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges + const Fr claimed_inner_product = SmallSubgroupIPAProver::compute_claimed_inner_product( + zk_sumcheck_data, const_size_mle_opening_point, this->log_n); + + prover_transcript->template send_to_verifier("Libra:claimed_evaluation", claimed_inner_product); + + // Instantiate SmallSubgroupIPAProver, this prover sends commitments to Big Sum and Quotient polynomials + auto small_subgroup_ipa_prover = SmallSubgroupIPAProver( + zk_sumcheck_data, const_size_mle_opening_point, claimed_inner_product, prover_transcript, ck); + + // Reduce to KZG or IPA based on the curve used in the test Flavor + const auto opening_claim = ShpleminiProver::prove(this->n, + RefVector(pcs_instance_witness.unshifted_polynomials), + RefVector(pcs_instance_witness.to_be_shifted_polynomials), + const_size_mle_opening_point, + ck, + prover_transcript, + small_subgroup_ipa_prover.get_witness_polynomials()); + + if constexpr (std::is_same_v) { + IPA::compute_opening_proof(this->ck(), opening_claim, prover_transcript); + } else { + KZG::compute_opening_proof(this->ck(), opening_claim, prover_transcript); + } + + // Initialize verifier's transcript + auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript); + + // Start populating Verifier's array of Libra commitments + std::array libra_commitments = {}; + libra_commitments[0] = + verifier_transcript->template receive_from_prover("Libra:concatenation_commitment"); + + // Place Libra data to the transcript + const Fr libra_total_sum = verifier_transcript->template receive_from_prover("Libra:Sum"); + const Fr libra_challenge = verifier_transcript->template get_challenge("Libra:Challenge"); + const Fr libra_evaluation = verifier_transcript->template receive_from_prover("Libra:claimed_evaluation"); + + // Check that transcript is consistent + EXPECT_EQ(libra_total_sum, zk_sumcheck_data.libra_total_sum); + EXPECT_EQ(libra_challenge, zk_sumcheck_data.libra_challenge); + EXPECT_EQ(libra_evaluation, claimed_inner_product); + + // Finalize the array of Libra/SmallSubgroupIpa commitments + libra_commitments[1] = verifier_transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = verifier_transcript->template receive_from_prover("Libra:quotient_commitment"); + + // Used to verify the consistency of the evaluations of the concatenated libra polynomial, big sum polynomial, and + // the quotient polynomial computed by SmallSubgroupIPAProver + bool consistency_checked = true; + + // Run Shplemini + const auto batch_opening_claim = + ShpleminiVerifier::compute_batch_opening_claim(this->n, + RefVector(pcs_instance_witness.unshifted_commitments), + RefVector(pcs_instance_witness.to_be_shifted_commitments), + RefVector(pcs_instance_witness.unshifted_evals), + RefVector(pcs_instance_witness.shifted_evals), + const_size_mle_opening_point, + this->vk()->get_g1_identity(), + verifier_transcript, + {}, + true, + &consistency_checked, + libra_commitments, + libra_evaluation); + // Verify claim using KZG or IPA + if constexpr (std::is_same_v) { + auto result = + IPA::reduce_verify_batch_opening_claim(batch_opening_claim, this->vk(), verifier_transcript); + EXPECT_EQ(result, true); + } else { + const auto pairing_points = + KZG::reduce_verify_batch_opening_claim(batch_opening_claim, verifier_transcript); + // Final pairing check: e([Q] - [Q_z] + z[W], [1]_2) = e([W], [x]_2) + EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true); + } +} + +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp index 39f9b27b625..54af7999a2a 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp @@ -374,6 +374,32 @@ template class SmallSubgroupIPAProver { remainder.at(idx - SUBGROUP_SIZE) += remainder.at(idx); } } + + /** + * @brief For test purposes: Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck + * challenges. + * + * @param zk_sumcheck_data Contains Libra constant term and scaled Libra univariates + * @param multivariate_challenge Sumcheck challenge + * @param log_circuit_size + */ + static FF compute_claimed_inner_product(ZKSumcheckData& zk_sumcheck_data, + const std::vector& multivariate_challenge, + const size_t& log_circuit_size) + { + const FF libra_challenge_inv = zk_sumcheck_data.libra_challenge.invert(); + // Compute claimed inner product similarly to the SumcheckProver + FF claimed_inner_product = FF{ 0 }; + size_t idx = 0; + for (const auto& univariate : zk_sumcheck_data.libra_univariates) { + claimed_inner_product += univariate.evaluate(multivariate_challenge[idx]); + idx++; + } + // Libra Univariates are mutiplied by the Libra challenge in setup_auxiliary_data(), needs to be undone + claimed_inner_product *= libra_challenge_inv / FF(1 << (log_circuit_size - 1)); + claimed_inner_product += zk_sumcheck_data.constant_term; + return claimed_inner_product; + } }; /** @@ -426,6 +452,18 @@ template class SmallSubgroupIPAVerifier { // Compute the evaluation of the vanishing polynomia Z_H(X) at X = gemini_evaluation_challenge const FF vanishing_poly_eval = gemini_evaluation_challenge.pow(SUBGROUP_SIZE) - FF(1); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1194). Handle edge cases in PCS + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1186). Insecure pattern. + bool gemini_challenge_in_small_subgroup = false; + if constexpr (Curve::is_stdlib_type) { + gemini_challenge_in_small_subgroup = (vanishing_poly_eval.get_value() == FF(0).get_value()); + } else { + gemini_challenge_in_small_subgroup = (vanishing_poly_eval == FF(0)); + } + // The probability of this event is negligible but it has to be processed correctly + if (gemini_challenge_in_small_subgroup) { + throw_or_abort("Gemini evaluation challenge is in the SmallSubgroup."); + } // Construct the challenge polynomial from the sumcheck challenge, the verifier has to evaluate it on its own const std::vector challenge_polynomial_lagrange = compute_challenge_polynomial(multilinear_challenge); @@ -542,4 +580,4 @@ template class SmallSubgroupIPAVerifier { return result; } }; -} // namespace bb +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.test.cpp new file mode 100644 index 00000000000..cc445d3a394 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.test.cpp @@ -0,0 +1,248 @@ +#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp" +#include "../commitment_key.test.hpp" +#include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" +#include "barretenberg/commitment_schemes/utils/test_settings.hpp" + +#include +#include +#include + +namespace bb { +template class SmallSubgroupIPATest : public ::testing::Test { + public: + using Curve = typename Flavor::Curve; + using Transcript = typename Flavor::Transcript; + using FF = typename Curve::ScalarField; + + static constexpr size_t log_circuit_size = 7; + static constexpr size_t circuit_size = 1ULL << log_circuit_size; + + FF evaluation_challenge; + + void SetUp() override { evaluation_challenge = FF::random_element(); } + + static std::vector generate_random_vector(const size_t size) + { + std::vector multivariate_challenge(size); + for (auto& challenge : multivariate_challenge) { + challenge = FF::random_element(); + } + return multivariate_challenge; + } +}; + +using TestFlavors = ::testing::Types; +TYPED_TEST_SUITE(SmallSubgroupIPATest, TestFlavors); + +// Check the correctness of the computation of the claimed inner product and various polynomials needed for the +// SmallSubgroupIPA. +TYPED_TEST(SmallSubgroupIPATest, ProverComputationsCorrectness) +{ + using ZKData = ZKSumcheckData; + using SmallSubgroupIPA = SmallSubgroupIPAProver; + using FF = typename TypeParam::FF; + static constexpr size_t SUBGROUP_SIZE = TypeParam::SUBGROUP_SIZE; + + using CK = typename TypeParam::CommitmentKey; + + // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck. + static constexpr size_t log_subgroup_size = static_cast(numeric::get_msb(SUBGROUP_SIZE)); + std::shared_ptr ck = + create_commitment_key(std::max(this->circuit_size, 1ULL << (log_subgroup_size + 1))); + + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + ZKData zk_sumcheck_data(this->log_circuit_size, prover_transcript, ck); + std::vector multivariate_challenge = this->generate_random_vector(this->log_circuit_size); + + const FF claimed_inner_product = SmallSubgroupIPA::compute_claimed_inner_product( + zk_sumcheck_data, multivariate_challenge, this->log_circuit_size); + + SmallSubgroupIPA small_subgroup_ipa_prover = + SmallSubgroupIPA(zk_sumcheck_data, multivariate_challenge, claimed_inner_product, prover_transcript, ck); + + const Polynomial batched_polynomial = small_subgroup_ipa_prover.get_batched_polynomial(); + const Polynomial libra_concatenated_polynomial = small_subgroup_ipa_prover.get_witness_polynomials()[0]; + const Polynomial batched_quotient = small_subgroup_ipa_prover.get_witness_polynomials()[3]; + const Polynomial challenge_polynomial = small_subgroup_ipa_prover.get_challenge_polynomial(); + + // Check that claimed inner product coincides with the inner product of libra_concatenated_polynomial and + // challenge_polynomial. Since libra_concatenated_polynomial is masked, we also check that masking does not affect + // the evaluations over H + FF inner_product = FF(0); + const std::array domain = zk_sumcheck_data.interpolation_domain; + for (size_t idx = 0; idx < SUBGROUP_SIZE; idx++) { + inner_product += + challenge_polynomial.evaluate(domain[idx]) * libra_concatenated_polynomial.evaluate(domain[idx]); + } + EXPECT_TRUE(inner_product == claimed_inner_product); + + // Check that batched polynomial is divisible by Z_H(X) + bool ipa_claim_consistency = true; + for (size_t idx = 0; idx < SUBGROUP_SIZE; idx++) { + ipa_claim_consistency = (batched_polynomial.evaluate(zk_sumcheck_data.interpolation_domain[idx]) == FF{ 0 }) && + ipa_claim_consistency; + } + EXPECT_EQ(ipa_claim_consistency, true); + + // Check that Z_H(X) * Q(X) = batched_polynomial + std::vector Z_H(SUBGROUP_SIZE + 1); + Z_H[0] = -FF(1); + Z_H[SUBGROUP_SIZE] = FF(1); + Polynomial product(batched_polynomial.size()); + + for (size_t i = 0; i < Z_H.size(); i++) { + for (size_t j = 0; j < batched_quotient.size(); j++) { + product.at(i + j) += Z_H[i] * batched_quotient.at(j); + } + } + bool quotient_is_correct = true; + for (const auto& [coeff_expected, coeff] : zip_view(product.coeffs(), batched_polynomial.coeffs())) { + quotient_is_correct = (coeff_expected == coeff) && quotient_is_correct; + } + EXPECT_EQ(quotient_is_correct, true); +} + +// Check the correctness of the evaluations of the challenge_polynomial, Lagrange first, and Lagrange last that the +// verifier has to compute on its own. Compare the values against the evaluations obtaned by applying Lagrange +// interpolation method used by Polynomial class constructor. +TYPED_TEST(SmallSubgroupIPATest, VerifierEvaluations) +{ + using FF = typename TypeParam::FF; + using Curve = typename TypeParam::Curve; + using SmallSubgroupIPA = SmallSubgroupIPAVerifier; + + // Extract the constants + static constexpr size_t SUBGROUP_SIZE = TypeParam::SUBGROUP_SIZE; + const FF subgroup_generator_inverse = Curve::subgroup_generator_inverse; + const FF subgroup_generator = subgroup_generator_inverse.invert(); + + // Sample random Lagrange coefficients over H + std::vector challenge_poly_lagrange = this->generate_random_vector(SUBGROUP_SIZE); + + // Evaluate Verifier's polynomials at the challenge + const FF vanishing_poly_eval = this->evaluation_challenge.pow(SUBGROUP_SIZE) - 1; + + // Compute required evaluations using efficient batch evaluation + const auto [challenge_poly_eval, lagrange_first, lagrange_last] = + SmallSubgroupIPA::compute_batched_barycentric_evaluations( + challenge_poly_lagrange, this->evaluation_challenge, subgroup_generator_inverse, vanishing_poly_eval); + + // Compute the evaluations differently, namely, using Lagrange interpolation + std::array interpolation_domain; + interpolation_domain[0] = FF(1); + for (size_t idx = 1; idx < SUBGROUP_SIZE; idx++) { + interpolation_domain[idx] = interpolation_domain[idx - 1] * subgroup_generator; + } + Polynomial challenge_poly_monomial = + Polynomial(interpolation_domain, challenge_poly_lagrange, SUBGROUP_SIZE); + + // Evaluate at the challenge + const FF challenge_poly_expected_eval = challenge_poly_monomial.evaluate(this->evaluation_challenge); + + EXPECT_EQ(challenge_poly_eval, challenge_poly_expected_eval); + + // Compute Lagrange polynomials using interpolation + std::vector lagrange_poly(SUBGROUP_SIZE); + lagrange_poly.at(0) = FF(1); + Polynomial lagrange_first_monomial = Polynomial(interpolation_domain, lagrange_poly, SUBGROUP_SIZE); + EXPECT_EQ(lagrange_first, lagrange_first_monomial.evaluate(this->evaluation_challenge)); + + lagrange_poly.at(0) = FF(0); + lagrange_poly.at(SUBGROUP_SIZE - 1) = FF(1); + Polynomial lagrange_last_monomial = Polynomial(interpolation_domain, lagrange_poly, SUBGROUP_SIZE); + EXPECT_EQ(lagrange_last, lagrange_last_monomial.evaluate(this->evaluation_challenge)); +} + +// Simulate the interaction between the prover and the verifier leading to the consistency check performed by the +// verifier. +TYPED_TEST(SmallSubgroupIPATest, ProverAndVerifierSimple) +{ + using FF = typename TypeParam::FF; + using Curve = typename TypeParam::Curve; + using Verifier = SmallSubgroupIPAVerifier; + using Prover = SmallSubgroupIPAProver; + using ZKData = ZKSumcheckData; + using CK = typename TypeParam::CommitmentKey; + + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck. + static constexpr size_t log_subgroup_size = static_cast(numeric::get_msb(Curve::SUBGROUP_SIZE)); + std::shared_ptr ck = + create_commitment_key(std::max(this->circuit_size, 1ULL << (log_subgroup_size + 1))); + + ZKData zk_sumcheck_data(this->log_circuit_size, prover_transcript, ck); + + std::vector multivariate_challenge = this->generate_random_vector(CONST_PROOF_SIZE_LOG_N); + + const FF claimed_inner_product = + Prover::compute_claimed_inner_product(zk_sumcheck_data, multivariate_challenge, this->log_circuit_size); + + Prover small_subgroup_ipa_prover = + Prover(zk_sumcheck_data, multivariate_challenge, claimed_inner_product, prover_transcript, ck); + + const std::array, NUM_LIBRA_EVALUATIONS> witness_polynomials = + small_subgroup_ipa_prover.get_witness_polynomials(); + + std::array libra_evaluations = { + witness_polynomials[0].evaluate(this->evaluation_challenge), + witness_polynomials[1].evaluate(this->evaluation_challenge * Curve::subgroup_generator), + witness_polynomials[2].evaluate(this->evaluation_challenge), + witness_polynomials[3].evaluate(this->evaluation_challenge) + }; + + bool consistency_checked = Verifier::check_evaluations_consistency( + libra_evaluations, this->evaluation_challenge, multivariate_challenge, claimed_inner_product); + + EXPECT_TRUE(consistency_checked); +} + +// Check that consistency check fails when some of the prover's data is corrupted. +TYPED_TEST(SmallSubgroupIPATest, ProverAndVerifierSimpleFailure) +{ + using FF = typename TypeParam::FF; + using Curve = typename TypeParam::Curve; + using Verifier = SmallSubgroupIPAVerifier; + using Prover = SmallSubgroupIPAProver; + using ZKData = ZKSumcheckData; + using CK = typename TypeParam::CommitmentKey; + + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck. + static constexpr size_t log_subgroup_size = static_cast(numeric::get_msb(Curve::SUBGROUP_SIZE)); + std::shared_ptr ck = + create_commitment_key(std::max(this->circuit_size, 1ULL << (log_subgroup_size + 1))); + + ZKData zk_sumcheck_data(this->log_circuit_size, prover_transcript, ck); + + std::vector multivariate_challenge = this->generate_random_vector(CONST_PROOF_SIZE_LOG_N); + + const FF claimed_inner_product = + Prover::compute_claimed_inner_product(zk_sumcheck_data, multivariate_challenge, this->log_circuit_size); + + Prover small_subgroup_ipa_prover = + Prover(zk_sumcheck_data, multivariate_challenge, claimed_inner_product, prover_transcript, ck); + + std::array, NUM_LIBRA_EVALUATIONS> witness_polynomials = + small_subgroup_ipa_prover.get_witness_polynomials(); + + // Tamper with witness polynomials + witness_polynomials[0].at(0) = FF::random_element(); + + std::array libra_evaluations = { + witness_polynomials[0].evaluate(this->evaluation_challenge), + witness_polynomials[1].evaluate(this->evaluation_challenge * Curve::subgroup_generator), + witness_polynomials[2].evaluate(this->evaluation_challenge), + witness_polynomials[3].evaluate(this->evaluation_challenge) + }; + + bool consistency_checked = Verifier::check_evaluations_consistency( + libra_evaluations, this->evaluation_challenge, multivariate_challenge, claimed_inner_product); + + // Since witness polynomials were modified, the consistency check must fail + EXPECT_FALSE(consistency_checked); +} + +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp new file mode 100644 index 00000000000..f3b99924b9f --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/ecc/curves/bn254/bn254.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/transcript/transcript.hpp" + +namespace bb { +/** + * @brief Constructs random polynomials, computes commitments and corresponding evaluations. + * + * @tparam Curve + */ +template struct InstanceWitnessGenerator { + public: + using CommitmentKey = bb::CommitmentKey; + using Fr = typename Curve::ScalarField; + using Commitment = typename Curve::AffineElement; + using Polynomial = bb::Polynomial; + + std::shared_ptr ck; + std::vector unshifted_polynomials; + std::vector to_be_shifted_polynomials; + std::vector const_size_mle_opening_point; + std::vector unshifted_commitments; + std::vector to_be_shifted_commitments; + std::vector unshifted_evals; + std::vector shifted_evals; + + InstanceWitnessGenerator(const size_t n, + const size_t num_polynomials, + const size_t num_shiftable, + const std::vector& mle_opening_point, + std::shared_ptr& commitment_key) + : ck(commitment_key) // Initialize the commitment key + , unshifted_polynomials(num_polynomials) + , to_be_shifted_polynomials(num_shiftable) + + { + construct_instance_and_witnesses(n, mle_opening_point); + } + + void construct_instance_and_witnesses(size_t n, const std::vector& mle_opening_point) + { + + const size_t num_unshifted = unshifted_polynomials.size() - to_be_shifted_polynomials.size(); + + // Constructs polynomials that are not shifted + for (size_t idx = 0; idx < num_unshifted; idx++) { + unshifted_polynomials[idx] = Polynomial::random(n); + unshifted_commitments.push_back(ck->commit(unshifted_polynomials[idx])); + unshifted_evals.push_back(unshifted_polynomials[idx].evaluate_mle(mle_opening_point)); + } + + // Constructs polynomials that are being shifted + size_t idx = num_unshifted; + for (auto& poly : to_be_shifted_polynomials) { + poly = Polynomial::random(n, /*shiftable*/ 1); + unshifted_polynomials[idx] = poly; + const Commitment comm = this->ck->commit(poly); + unshifted_commitments.push_back(comm); + to_be_shifted_commitments.push_back(comm); + unshifted_evals.push_back(poly.evaluate_mle(mle_opening_point)); + shifted_evals.push_back(poly.evaluate_mle(mle_opening_point, true)); + idx++; + } + } +}; + +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/test_settings.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/test_settings.hpp new file mode 100644 index 00000000000..5023d02a8f7 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/test_settings.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/ecc/curves/bn254/bn254.hpp" +#include "barretenberg/transcript/transcript.hpp" + +namespace bb { +/** + * @brief Mock Flavors to use ZKSumcheckData and SmallSubgroupIPAProver in the PCS tests. + * + */ +class BN254Settings { + public: + using Curve = curve::BN254; + using CommitmentKey = bb::CommitmentKey; + using Transcript = NativeTranscript; + using FF = typename Curve::ScalarField; + static constexpr size_t SUBGROUP_SIZE = Curve::SUBGROUP_SIZE; +}; + +class GrumpkinSettings { + public: + using Curve = curve::Grumpkin; + using CommitmentKey = bb::CommitmentKey; + using Transcript = NativeTranscript; + using FF = typename Curve::ScalarField; + static constexpr size_t SUBGROUP_SIZE = Curve::SUBGROUP_SIZE; +}; +} // namespace bb \ No newline at end of file