diff --git a/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/lmdb_store/lmdb_tree_store.cpp b/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/lmdb_store/lmdb_tree_store.cpp index 742f36d0395..4599df682c8 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/lmdb_store/lmdb_tree_store.cpp +++ b/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/lmdb_store/lmdb_tree_store.cpp @@ -188,7 +188,7 @@ void LMDBTreeStore::increment_node_reference_count(const fr& nodeHash, WriteTran NodePayload nodePayload; bool success = get_node_data(nodeHash, nodePayload, tx); if (!success) { - throw std::runtime_error("Failed to find node when attempting to increases reference count"); + throw std::runtime_error("Failed to find node when attempting to increase reference count"); } ++nodePayload.ref; // std::cout << "Incrementing siblng at " << nodeHash << ", to " << nodePayload.ref << std::endl; @@ -212,7 +212,7 @@ void LMDBTreeStore::decrement_node_reference_count(const fr& nodeHash, NodePaylo { bool success = get_node_data(nodeHash, nodeData, tx); if (!success) { - throw std::runtime_error("Failed to find node when attempting to increases reference count"); + throw std::runtime_error("Failed to find node when attempting to decrease reference count"); } if (--nodeData.ref == 0) { // std::cout << "Deleting node at " << nodeHash << std::endl; diff --git a/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/node_store/cached_content_addressed_tree_store.hpp b/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/node_store/cached_content_addressed_tree_store.hpp index 0de6a328450..72c4ec34cf3 100644 --- a/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/node_store/cached_content_addressed_tree_store.hpp +++ b/barretenberg/cpp/src/barretenberg/crypto/merkle_tree/node_store/cached_content_addressed_tree_store.hpp @@ -12,6 +12,7 @@ #include "msgpack/assert.hpp" #include #include +#include #include #include #include @@ -643,17 +644,13 @@ void ContentAddressedCachedTreeStore::commit(TreeMeta& finalMeta, // if the meta datas are different, we have uncommitted data bool metaToCommit = committedMeta != uncommittedMeta; - if (!metaToCommit) { + if (!metaToCommit && !asBlock) { return; } + auto currentRootIter = nodes_.find(uncommittedMeta.root); dataPresent = currentRootIter != nodes_.end(); - if (!dataPresent) { - // no uncommitted data present, if we were asked to commit as a block then we can't - if (asBlock) { - throw std::runtime_error("Can't commit as block if no data present"); - } - } else { + if (dataPresent) { // data is present, hydrate persisted indices hydrate_indices_from_persisted_store(*tx); } @@ -665,19 +662,27 @@ void ContentAddressedCachedTreeStore::commit(TreeMeta& finalMeta, // std::cout << "Persisting data for block " << uncommittedMeta.unfinalisedBlockHeight + 1 << std::endl; persist_leaf_indices(*tx); persist_leaf_keys(uncommittedMeta.committedSize, *tx); + } + // If we are commiting a block, we need to persist the root, since the new block "references" this root + // However, if the root is the empty root we can't persist it, since it's not a real node + // We are abusing the trees in some tests, trying to add empty blocks to initial empty trees + // That is not expected behavior since the unwind operation will fail trying to decrease refcount + // for the empty root, which doesn't exist. + if (dataPresent || (asBlock && uncommittedMeta.size > 0)) { persist_node(std::optional(uncommittedMeta.root), 0, *tx); - if (asBlock) { - ++uncommittedMeta.unfinalisedBlockHeight; - if (uncommittedMeta.oldestHistoricBlock == 0) { - uncommittedMeta.oldestHistoricBlock = 1; - } - // std::cout << "New root " << uncommittedMeta.root << std::endl; - BlockPayload block{ .size = uncommittedMeta.size, - .blockNumber = uncommittedMeta.unfinalisedBlockHeight, - .root = uncommittedMeta.root }; - dataStore_->write_block_data(uncommittedMeta.unfinalisedBlockHeight, block, *tx); + } + if (asBlock) { + ++uncommittedMeta.unfinalisedBlockHeight; + if (uncommittedMeta.oldestHistoricBlock == 0) { + uncommittedMeta.oldestHistoricBlock = 1; } + // std::cout << "New root " << uncommittedMeta.root << std::endl; + BlockPayload block{ .size = uncommittedMeta.size, + .blockNumber = uncommittedMeta.unfinalisedBlockHeight, + .root = uncommittedMeta.root }; + dataStore_->write_block_data(uncommittedMeta.unfinalisedBlockHeight, block, *tx); } + uncommittedMeta.committedSize = uncommittedMeta.size; persist_meta(uncommittedMeta, *tx); tx->commit(); diff --git a/barretenberg/cpp/src/barretenberg/world_state_napi/addon.cpp b/barretenberg/cpp/src/barretenberg/world_state_napi/addon.cpp index ce0e8c301a4..f5b1ce4b129 100644 --- a/barretenberg/cpp/src/barretenberg/world_state_napi/addon.cpp +++ b/barretenberg/cpp/src/barretenberg/world_state_napi/addon.cpp @@ -560,7 +560,7 @@ bool WorldStateAddon::sync_block(msgpack::object& obj, msgpack::sbuffer& buf) request.value.paddedNoteHashes, request.value.paddedL1ToL2Messages, request.value.paddedNullifiers, - request.value.batchesOfPaddedPublicDataWrites); + request.value.batchesOfPublicDataWrites); MsgHeader header(request.header.messageId); messaging::TypedMessage resp_msg(WorldStateMessageType::SYNC_BLOCK, header, { status }); diff --git a/barretenberg/cpp/src/barretenberg/world_state_napi/message.hpp b/barretenberg/cpp/src/barretenberg/world_state_napi/message.hpp index 4d686c9362e..23f293fbebe 100644 --- a/barretenberg/cpp/src/barretenberg/world_state_napi/message.hpp +++ b/barretenberg/cpp/src/barretenberg/world_state_napi/message.hpp @@ -181,7 +181,7 @@ struct SyncBlockRequest { bb::fr blockHeaderHash; std::vector paddedNoteHashes, paddedL1ToL2Messages; std::vector paddedNullifiers; - std::vector> batchesOfPaddedPublicDataWrites; + std::vector> batchesOfPublicDataWrites; MSGPACK_FIELDS(blockNumber, blockStateRef, @@ -189,7 +189,7 @@ struct SyncBlockRequest { paddedNoteHashes, paddedL1ToL2Messages, paddedNullifiers, - batchesOfPaddedPublicDataWrites); + batchesOfPublicDataWrites); }; } // namespace bb::world_state diff --git a/l1-contracts/src/core/libraries/ConstantsGen.sol b/l1-contracts/src/core/libraries/ConstantsGen.sol index 8334c595208..4aea0600b23 100644 --- a/l1-contracts/src/core/libraries/ConstantsGen.sol +++ b/l1-contracts/src/core/libraries/ConstantsGen.sol @@ -52,7 +52,6 @@ library Constants { uint256 internal constant L1_TO_L2_MSG_SUBTREE_HEIGHT = 4; uint256 internal constant NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH = 34; uint256 internal constant NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH = 34; - uint256 internal constant PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH = 34; uint256 internal constant L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH = 35; uint256 internal constant MAX_NOTE_HASHES_PER_TX = 64; uint256 internal constant MAX_NULLIFIERS_PER_TX = 64; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr index 4cdddc31ab8..c591c30cdfa 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/base_or_merge_rollup_public_inputs.nr @@ -6,8 +6,8 @@ use dep::types::{ utils::reader::Reader, }; -global BASE_ROLLUP_TYPE = 0; -global MERGE_ROLLUP_TYPE = 1; +pub(crate) global BASE_ROLLUP_TYPE = 0; +pub(crate) global MERGE_ROLLUP_TYPE = 1; pub struct BaseOrMergeRollupPublicInputs { // rollup_type is either 0 (base) or 1 (merge) diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/constant_rollup_data.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/constant_rollup_data.nr index ee9c307fc48..9eab56a2092 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/constant_rollup_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/constant_rollup_data.nr @@ -7,10 +7,10 @@ use dep::types::{ pub struct ConstantRollupData { // Archive tree snapshot at the very beginning of the entire rollup. - last_archive: AppendOnlyTreeSnapshot, - vk_tree_root: Field, - protocol_contract_tree_root: Field, - global_variables: GlobalVariables, + pub last_archive: AppendOnlyTreeSnapshot, + pub vk_tree_root: Field, + pub protocol_contract_tree_root: Field, + pub global_variables: GlobalVariables, } impl Eq for ConstantRollupData { diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/mod.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/mod.nr index b57aeaa5fa0..716b67bf953 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/abis/mod.nr @@ -1,5 +1,5 @@ -mod constant_rollup_data; -mod base_or_merge_rollup_public_inputs; -mod block_root_or_block_merge_public_inputs; -mod previous_rollup_data; -mod previous_rollup_block_data; +pub(crate) mod constant_rollup_data; +pub(crate) mod base_or_merge_rollup_public_inputs; +pub(crate) mod block_root_or_block_merge_public_inputs; +pub(crate) mod previous_rollup_data; +pub(crate) mod previous_rollup_block_data; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr deleted file mode 100644 index 87f5cf3fa5b..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/base_rollup_inputs.nr +++ /dev/null @@ -1,1465 +0,0 @@ -use crate::{ - abis::{ - base_or_merge_rollup_public_inputs::{BASE_ROLLUP_TYPE, BaseOrMergeRollupPublicInputs}, - constant_rollup_data::ConstantRollupData, - }, - base::state_diff_hints::StateDiffHints, - components::{compute_kernel_out_hash, compute_tx_effects_hash}, -}; -use dep::types::{ - abis::{ - append_only_tree_snapshot::AppendOnlyTreeSnapshot, - kernel_circuit_public_inputs::KernelCircuitPublicInputs, - nullifier_leaf_preimage::NullifierLeafPreimage, public_data_write::PublicDataWrite, - }, - address::AztecAddress, - constants::{ - ARCHIVE_HEIGHT, FEE_JUICE_ADDRESS, MAX_NOTE_HASHES_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - NOTE_HASH_SUBTREE_HEIGHT, NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_TREE_HEIGHT, - PRIVATE_KERNEL_EMPTY_INDEX, PUBLIC_DATA_SUBTREE_HEIGHT, - PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, - }, - data::{ - hash::{compute_public_data_tree_index, compute_public_data_tree_value}, - public_data_hint::PublicDataHint, - PublicDataTreeLeaf, - PublicDataTreeLeafPreimage, - }, - hash::silo_l2_to_l1_message, - merkle_tree::{ - append_only_tree, assert_check_membership, calculate_empty_tree_root, - calculate_subtree_root, indexed_tree, MembershipWitness, - }, - messaging::l2_to_l1_message::ScopedL2ToL1Message, - partial_state_reference::PartialStateReference, - storage::map::derive_storage_slot_in_map, - traits::is_empty, - utils::{arrays::find_index_hint, field::{full_field_greater_than, full_field_less_than}}, -}; - -// Temporary struct to avoid changing all the references to kernel_data. Will remove it and just pass in the public_inputs. -struct KernelData { - public_inputs: KernelCircuitPublicInputs, -} - -pub struct BaseRollupInputs { - kernel_data: KernelData, - start: PartialStateReference, - - state_diff_hints: StateDiffHints, - transaction_fee: Field, - fee_payer_fee_juice_balance_read_hint: PublicDataHint, - - // TODO: The following 6 values are eventually going to be nuked from here. See discussion: - // https://aztecprotocol.slack.com/archives/C060PU5R327/p1701965354071269 - sorted_public_data_writes: [PublicDataTreeLeaf; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - sorted_public_data_writes_indexes: [u32; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - low_public_data_writes_preimages: [PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - low_public_data_writes_witnesses: [MembershipWitness; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - - archive_root_membership_witness: MembershipWitness, - - constants: ConstantRollupData, -} - -impl BaseRollupInputs { - pub fn base_rollup_circuit(self) -> BaseOrMergeRollupPublicInputs { - // Verify the kernel chain_id and versions - assert( - self.kernel_data.public_inputs.constants.tx_context.chain_id - == self.constants.global_variables.chain_id, - "kernel chain_id does not match the rollup chain_id", - ); - assert( - self.kernel_data.public_inputs.constants.tx_context.version - == self.constants.global_variables.version, - "kernel version does not match the rollup version", - ); - assert( - self.kernel_data.public_inputs.constants.vk_tree_root == self.constants.vk_tree_root, - "kernel vk_tree_root does not match the rollup vk_tree_root", - ); - assert( - self.kernel_data.public_inputs.constants.protocol_contract_tree_root - == self.constants.protocol_contract_tree_root, - "kernel protocol_contract_tree_root does not match the rollup protocol_contract_tree_root", - ); - - // Verify the kernel global variables if set, note these can be empty if this is a request coming directly from the private kernel tail. - // TODO(@spalladino) How can we check that this is a request coming from the private kernel tail? - assert( - self.kernel_data.public_inputs.constants.global_variables.is_empty() - | ( - self.kernel_data.public_inputs.constants.global_variables - == self.constants.global_variables - ), - "kernel global variables do not match the rollup global variables", - ); - - self.validate_kernel_start_state(); - - let rollup_validation_requests = self.kernel_data.public_inputs.rollup_validation_requests; - - // Verify the max block number - // TODO #5345: why is block_number a Field and not u32? - if rollup_validation_requests.max_block_number.is_some() { - assert( - self.constants.global_variables.block_number as u32 - <= rollup_validation_requests.max_block_number.unwrap_unchecked(), - "kernel max_block_number is smaller than block number", - ); - } - - let commitments_tree_subroot = self.calculate_commitments_subtree(); - - let empty_commitments_subtree_root = calculate_empty_tree_root(NOTE_HASH_SUBTREE_HEIGHT); - - let end_note_hash_tree_snapshot = append_only_tree::insert_subtree_to_snapshot_tree( - self.start.note_hash_tree, - self.state_diff_hints.note_hash_subtree_sibling_path, - empty_commitments_subtree_root, - commitments_tree_subroot, - NOTE_HASH_SUBTREE_HEIGHT as u8, - ); - - // Insert nullifiers: - let end_nullifier_tree_snapshot = - self.check_nullifier_tree_non_membership_and_insert_to_tree(); - - // Inject protocol update requests for deducting tx_fee from fee_payer's balance - let all_public_data_update_requests = - self.calculate_all_public_data_update_requests(self.transaction_fee); - - // Validate public data update requests and update public data tree - let end_public_data_tree_snapshot = - self.validate_and_process_public_state(all_public_data_update_requests); - - // Calculate the tx effects hash of the transaction - let siloed_l2_to_l1_msgs = self.kernel_data.public_inputs.end.l2_to_l1_msgs.map( - |message: ScopedL2ToL1Message| silo_l2_to_l1_message( - message, - self.kernel_data.public_inputs.constants.tx_context.version, - self.kernel_data.public_inputs.constants.tx_context.chain_id, - ), - ); - let out_hash = compute_kernel_out_hash(siloed_l2_to_l1_msgs); - let tx_effects_hash = compute_tx_effects_hash( - self.kernel_data.public_inputs.end, - self.kernel_data.public_inputs.revert_code, - self.transaction_fee, - all_public_data_update_requests, - out_hash, - ); - - // Perform membership checks that the notes provided exist within the historical trees data - self.perform_archive_membership_checks(); - - BaseOrMergeRollupPublicInputs { - rollup_type: BASE_ROLLUP_TYPE, - num_txs: 1, - constants: self.constants, - start: self.start, - end: PartialStateReference { - note_hash_tree: end_note_hash_tree_snapshot, - nullifier_tree: end_nullifier_tree_snapshot, - public_data_tree: end_public_data_tree_snapshot, - }, - txs_effects_hash: tx_effects_hash, - out_hash, - accumulated_fees: self.transaction_fee, - } - } - - // TODO(Kev): This should say calculate_commitments_subtree_root - // Cpp code says calculate_commitments_subtree, so I'm leaving it as is for now - fn calculate_commitments_subtree(self) -> Field { - calculate_subtree_root(self.kernel_data.public_inputs.end.note_hashes) - } - - fn check_nullifier_tree_non_membership_and_insert_to_tree(self) -> AppendOnlyTreeSnapshot { - indexed_tree::batch_insert( - self.start.nullifier_tree, - self.kernel_data.public_inputs.end.nullifiers, - self.state_diff_hints.sorted_nullifiers, - self.state_diff_hints.sorted_nullifier_indexes, - self.state_diff_hints.nullifier_subtree_sibling_path, - self.state_diff_hints.nullifier_predecessor_preimages, - self.state_diff_hints.nullifier_predecessor_membership_witnesses.map( - |witness: MembershipWitness| { - MembershipWitness { - leaf_index: witness.leaf_index, - sibling_path: witness.sibling_path, - } - }, - ), - |low_leaf: NullifierLeafPreimage, nullifier: Field| { - // Is valid low leaf - let is_less_than_nullifier = full_field_less_than(low_leaf.nullifier, nullifier); - let is_next_greater_than = full_field_less_than(nullifier, low_leaf.next_nullifier); - - (!low_leaf.is_empty()) - & is_less_than_nullifier - & ( - is_next_greater_than - | ((low_leaf.next_index == 0) & (low_leaf.next_nullifier == 0)) - ) - }, - |low_leaf: NullifierLeafPreimage, nullifier: Field, nullifier_index: u32| { - // Update low leaf - NullifierLeafPreimage { - nullifier: low_leaf.nullifier, - next_nullifier: nullifier, - next_index: nullifier_index, - } - }, - |nullifier: Field, low_leaf: NullifierLeafPreimage| { - // Build insertion leaf - NullifierLeafPreimage { - nullifier: nullifier, - next_nullifier: low_leaf.next_nullifier, - next_index: low_leaf.next_index, - } - }, - [0; NULLIFIER_SUBTREE_HEIGHT], - [0; NULLIFIER_TREE_HEIGHT], - ) - } - - fn create_nullifier_subtree(leaves: [NullifierLeafPreimage; N]) -> Field { - calculate_subtree_root(leaves.map(|leaf: NullifierLeafPreimage| leaf.hash())) - } - - fn validate_kernel_start_state(self) { - let kernel_state = self.kernel_data.public_inputs.start_state; - if !is_empty(kernel_state) { - assert( - kernel_state.note_hash_tree.eq(self.start.note_hash_tree), - "Mismatch start state for note hash tree", - ); - assert( - kernel_state.nullifier_tree.eq(self.start.nullifier_tree), - "Mismatch start state for nullifier tree", - ); - assert( - kernel_state.public_data_tree.eq(self.start.public_data_tree), - "Mismatch start state for public data tree", - ); - } - } - - fn validate_and_process_public_state( - self, - all_update_requests: [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - ) -> AppendOnlyTreeSnapshot { - let end_public_data_tree_snapshot = insert_public_data_update_requests( - self.start.public_data_tree, - all_update_requests.map(|w: PublicDataWrite| { - PublicDataTreeLeaf { slot: w.leaf_slot, value: w.value } - }), - self.sorted_public_data_writes, - self.sorted_public_data_writes_indexes, - self.low_public_data_writes_preimages, - self.low_public_data_writes_witnesses, - self.state_diff_hints.public_data_sibling_path, - ); - - end_public_data_tree_snapshot - } - - // Returns an array with all public data update requests for this tx. This includes all update requests - // generated by app circuits, plus the protocol update requests injected by this circuit. The only protocol - // update request we have at the time of this writing is deducting the tx_fee from the fee_payer balance. - fn calculate_all_public_data_update_requests( - self, - tx_fee: Field, - ) -> [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] { - let mut all_update_requests: [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = - [PublicDataWrite::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - all_update_requests[i] = self.kernel_data.public_inputs.end.public_data_writes[i]; - } - - let (payment_update_request, payment_update_index) = - self.build_or_patch_payment_update_request(tx_fee); - all_update_requests[payment_update_index] = payment_update_request; - - all_update_requests - } - - // Deducts the tx_fee from the FeeJuice balance of the fee_payer. If there is already a PublicDataWrite - // in this tx for their balance (because they issued a 'claim' to increase their balance by bridging from L1), - // update it by subtracting the tx_fee. Otherwise, build a new PublicDataWrite to subtract the tx_fee - // from the balance of the fee_payer, using the fee_payer_fee_juice_balance_read_hint to read the current balance. - // Returns the data update request that subtracts the tx_fee from the fee_payer's balance, and the index where it - // should be inserted in the public data update requests array. - fn build_or_patch_payment_update_request(self, tx_fee: Field) -> (PublicDataWrite, u32) { - let fee_payer = self.kernel_data.public_inputs.fee_payer; - - // TODO(@spalladino) Eventually remove the is_zero condition as we should always charge fees to every tx - if !fee_payer.is_zero() { - let read_hint = self.fee_payer_fee_juice_balance_read_hint; - let leaf_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - let existing_update_index = unsafe { - find_index_hint( - self.kernel_data.public_inputs.end.public_data_writes, - |w: PublicDataWrite| w.leaf_slot == leaf_slot, - ) - }; - - if existing_update_index != MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - // Is there a balance update already in this tx? If so, update it and return its index. - let existing_update = - self.kernel_data.public_inputs.end.public_data_writes[existing_update_index]; - assert( - existing_update.leaf_slot == leaf_slot, - "Wrong leaf slot for Fee Juice balance update request", - ); - assert( - !existing_update.value.lt(tx_fee), - "Not enough balance for fee payer after claim to pay for transaction", - ); - - let value = compute_public_data_tree_value(existing_update.value - tx_fee); - let protocol_update_request = PublicDataWrite { leaf_slot, value }; - (protocol_update_request, existing_update_index as u32) - } else { - // Otherwise, build a new one to be inserted into the protocol update requests at the end of the array. - read_hint.validate(self.start.public_data_tree.root); - - let balance = read_hint.value; - assert( - read_hint.leaf_slot == leaf_slot, - "Wrong leaf slot for Fee Juice balance read hint", - ); - assert( - !balance.lt(tx_fee), - "Not enough balance for fee payer to pay for transaction", - ); - - let value = compute_public_data_tree_value(balance - tx_fee); - let protocol_update_request = PublicDataWrite { leaf_slot, value }; - (protocol_update_request, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) - } - } else { - // Nothing to do, just place an empty update request at the end of the array - (PublicDataWrite::empty(), MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) - } - } - - // Check that the block header used by each kernel is a member of the blocks tree --> since the block header - // contains roots of all the trees this is sufficient to verify that the tree roots used by kernels are correct - fn perform_archive_membership_checks(self) { - // For each of the block header (their block hashes), we need to do an inclusion proof - // against the blocks tree root from the beginning of a rollup provided in the rollup constants - let archive_root = self.constants.last_archive.root; - - // Rebuild the block hash - let header = self.kernel_data.public_inputs.constants.historical_header; - let previous_block_hash = header.hash(); - - let previous_block_hash_witness = self.archive_root_membership_witness; - - // Now check that the previous block hash is in the blocks tree from the beginning of the rollup - assert_check_membership( - previous_block_hash, - previous_block_hash_witness.leaf_index, - previous_block_hash_witness.sibling_path, - archive_root, - ); - } -} - -fn insert_public_data_update_requests( - prev_snapshot: AppendOnlyTreeSnapshot, - public_data_writes: [PublicDataTreeLeaf; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - sorted_public_data_writes: [PublicDataTreeLeaf; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - sorted_public_data_writes_indexes: [u32; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - low_public_data_writes_preimages: [PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - low_public_data_writes_witnesses: [MembershipWitness; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - public_data_writes_subtree_sibling_path: [Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH], -) -> AppendOnlyTreeSnapshot { - indexed_tree::batch_insert( - prev_snapshot, - public_data_writes, - sorted_public_data_writes, - sorted_public_data_writes_indexes, - public_data_writes_subtree_sibling_path, - low_public_data_writes_preimages, - low_public_data_writes_witnesses.map( - |witness: MembershipWitness| { - MembershipWitness { - leaf_index: witness.leaf_index, - sibling_path: witness.sibling_path, - } - }, - ), - |low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf| { - // Is valid low preimage - let is_update = low_preimage.slot == write.slot; - let is_low_empty = low_preimage.is_empty(); - - let is_less_than_slot = full_field_less_than(low_preimage.slot, write.slot); - let is_next_greater_than = full_field_less_than(write.slot, low_preimage.next_slot); - let is_in_range = is_less_than_slot - & ( - is_next_greater_than - | ((low_preimage.next_index == 0) & (low_preimage.next_slot == 0)) - ); - - (!is_low_empty) & (is_update | is_in_range) - }, - |low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf, write_index: u32| { - // Update low leaf - let is_update = low_preimage.slot == write.slot; - if is_update { - PublicDataTreeLeafPreimage { - slot: low_preimage.slot, - value: write.value, - next_slot: low_preimage.next_slot, - next_index: low_preimage.next_index, - } - } else { - PublicDataTreeLeafPreimage { - slot: low_preimage.slot, - value: low_preimage.value, - next_slot: write.slot, - next_index: write_index, - } - } - }, - |write: PublicDataTreeLeaf, low_preimage: PublicDataTreeLeafPreimage| { - // Build insertion leaf - let is_update = low_preimage.slot == write.slot; - if is_update { - PublicDataTreeLeafPreimage::empty() - } else { - PublicDataTreeLeafPreimage { - slot: write.slot, - value: write.value, - next_slot: low_preimage.next_slot, - next_index: low_preimage.next_index, - } - } - }, - [0; PUBLIC_DATA_SUBTREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT], - ) -} - -fn compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer: AztecAddress) -> Field { - let balances_slot_in_fee_juice_contract = 1; - let fee_payer_balance_slot_in_fee_juice_contract = - derive_storage_slot_in_map(balances_slot_in_fee_juice_contract, fee_payer); - compute_public_data_tree_index( - FEE_JUICE_ADDRESS, - fee_payer_balance_slot_in_fee_juice_contract, - ) -} - -#[test] -fn consistent_not_hash_subtree_width() { - assert_eq( - MAX_NOTE_HASHES_PER_TX as Field, - 2.pow_32(NOTE_HASH_SUBTREE_HEIGHT as Field), - "note hash subtree width is incorrect", - ); -} - -#[test] -fn test_u256_less_than() { - assert(full_field_less_than(1, 1000)); - assert(!full_field_less_than(1000, 1000)); - assert(!full_field_less_than(1000, 1)); - assert(full_field_less_than(0, 0 - 1)); - assert(!full_field_less_than(0 - 1, 0)); -} - -#[test] -fn test_u256_greater_than() { - assert(full_field_greater_than(1000, 1)); - assert(!full_field_greater_than(1000, 1000)); - assert(!full_field_greater_than(1, 1000)); - assert(!full_field_greater_than(0, 0 - 1)); - assert(full_field_greater_than(0 - 1, 0)); -} - -mod tests { - use crate::{ - abis::{ - base_or_merge_rollup_public_inputs::BaseOrMergeRollupPublicInputs, - constant_rollup_data::ConstantRollupData, - }, - base::{ - base_rollup_inputs::{ - BaseRollupInputs, compute_fee_payer_fee_juice_balance_leaf_slot, KernelData, - }, - state_diff_hints::StateDiffHints, - }, - components::TX_EFFECTS_HASH_INPUT_FIELDS, - }; - use dep::types::{ - abis::{ - append_only_tree_snapshot::AppendOnlyTreeSnapshot, - nullifier_leaf_preimage::NullifierLeafPreimage, public_data_write::PublicDataWrite, - }, - address::{AztecAddress, EthAddress}, - constants::{ - ARCHIVE_HEIGHT, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, - MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - NOTE_HASH_SUBTREE_HEIGHT, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NOTE_HASH_TREE_HEIGHT, - NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, - PRIVATE_KERNEL_EMPTY_INDEX, PRIVATE_KERNEL_TAIL_INDEX, - PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PUBLIC_DATA_SUBTREE_HEIGHT, - PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, - }, - data::{public_data_hint::PublicDataHint, PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, - hash::silo_l2_to_l1_message, - merkle_tree::MembershipWitness, - messaging::l2_to_l1_message::ScopedL2ToL1Message, - partial_state_reference::PartialStateReference, - tests::{fixture_builder::FixtureBuilder, fixtures, merkle_tree_utils::NonEmptyMerkleTree}, - traits::Empty, - utils::{ - arrays::get_sorted_tuple::get_sorted_tuple, - field::{field_from_bytes_32_trunc, full_field_less_than}, - }, - }; - - struct NullifierInsertion { - existing_index: u32, - value: Field, - } - - global MAX_nullifiers_PER_TEST: u32 = 4; - global MAX_PUBLIC_DATA_READS_PER_TEST: u32 = 2; - - fn update_public_data_tree( - public_data_tree: &mut NonEmptyMerkleTree, - kernel_data: &mut KernelData, - snapshot: AppendOnlyTreeSnapshot, - user_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, - protocol_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, - mut final_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, - mut pre_existing_public_data: [PublicDataTreeLeafPreimage; EXISTING_LEAVES], - ) -> ([Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH], [PublicDataTreeLeaf; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], [u32; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], [PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], [MembershipWitness; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], [PublicDataTreeLeafPreimage; EXISTING_LEAVES]) { - let mut subtree_path = [0; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH]; - let mut sorted_public_data_writes = - [PublicDataTreeLeaf::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - let mut sorted_public_data_writes_indexes = - [0 as u32; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - let mut low_public_data_writes_preimages = - [PublicDataTreeLeafPreimage::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - let mut low_public_data_writes_witnesses = - [MembershipWitness::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - let mut new_subtree = - [PublicDataTreeLeafPreimage::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; - - for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - if i < (user_public_data_writes.len()) { - let leaf = user_public_data_writes.get_unchecked(i).1; - - kernel_data.public_inputs.end.public_data_writes[i] = - PublicDataWrite { leaf_slot: leaf.slot, value: leaf.value }; - } - } - - // Default final_public_data_writes to user_public_data_writes + protocol_public_data_writes if not set - if final_public_data_writes.len() == 0 { - final_public_data_writes.extend_from_array(user_public_data_writes.storage); - final_public_data_writes.extend_from_array(protocol_public_data_writes.storage); - } - - let mut sorted_write_tuples = unsafe { - get_sorted_tuple( - final_public_data_writes.storage(), - |(_, leaf_a): (u32, PublicDataTreeLeaf), (_, leaf_b): (u32, PublicDataTreeLeaf)| { - full_field_less_than(leaf_b.slot, leaf_a.slot) - }, - ) - }; - - for i in 0..MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - if i < final_public_data_writes.len() { - let (low_leaf_index, leaf): (u32, PublicDataTreeLeaf) = sorted_write_tuples[i].elem; - - sorted_public_data_writes[i] = leaf; - sorted_public_data_writes_indexes[i] = sorted_write_tuples[i].original_index; - - if !leaf.is_empty() { - let low_leaf = pre_existing_public_data[low_leaf_index]; - if low_leaf.slot == leaf.slot { - pre_existing_public_data[low_leaf_index].value = leaf.value; - } else { - new_subtree[sorted_write_tuples[i].original_index] = PublicDataTreeLeafPreimage { - slot: leaf.slot, - value: leaf.value, - next_slot: low_leaf.next_slot, - next_index: low_leaf.next_index, - }; - pre_existing_public_data[low_leaf_index] = PublicDataTreeLeafPreimage { - slot: low_leaf.slot, - value: low_leaf.value, - next_slot: leaf.slot, - next_index: MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + i, - }; - } - low_public_data_writes_preimages[i] = low_leaf; - low_public_data_writes_witnesses[i] = MembershipWitness { - leaf_index: low_leaf_index as Field, - sibling_path: public_data_tree.get_sibling_path(low_leaf_index), - }; - - public_data_tree.update_leaf( - low_leaf_index, - pre_existing_public_data[low_leaf_index].hash(), - ); - } - } else { - sorted_public_data_writes[i] = PublicDataTreeLeaf::empty(); - sorted_public_data_writes_indexes[i] = i; - } - } - - subtree_path = BaseRollupInputsBuilder::extract_subtree_sibling_path( - public_data_tree.get_sibling_path(snapshot.next_available_leaf_index as u32), - [0; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH], - ); - - ( - subtree_path, sorted_public_data_writes, sorted_public_data_writes_indexes, - low_public_data_writes_preimages, low_public_data_writes_witnesses, - pre_existing_public_data, - ) - } - - struct BaseRollupInputsBuilder { - kernel_data: FixtureBuilder, - transaction_fee: Field, - pre_existing_notes: [Field; MAX_NOTE_HASHES_PER_TX], - pre_existing_nullifiers: [NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], - pre_existing_contracts: [Field; 2], - pre_existing_public_data: [PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - pre_existing_blocks: [Field; 2], - public_data_reads: BoundedVec, - // Public data writes as returned from the last public kernel (ie all data writes generated by app code) - public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, - // New public data writes to be created by the protocol (eg a data update request for updating fee payer balance) - protocol_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, - // Public data writes after processing by the base rollup circuit (defaults to public_data_writes ++ protocol_public_data_writes if empty) - final_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, - nullifiers: BoundedVec, - constants: ConstantRollupData, - // Index of the item in the pre_existing_public_data array that contains the fee payer's Fee Juice balance. - // Used for building the public data hint read for the payment update request. If set to none, no hint is built. - fee_payer_fee_juice_balance_pre_existing_public_data_index: Option, - } - - impl BaseRollupInputsBuilder { - fn new() -> Self { - let mut inputs = BaseRollupInputsBuilder::empty(); - inputs.kernel_data = FixtureBuilder::new().in_vk_tree(PRIVATE_KERNEL_TAIL_INDEX); - inputs.constants.global_variables.chain_id = fixtures::CHAIN_ID; - inputs.constants.global_variables.version = fixtures::VERSION; - inputs.constants.vk_tree_root = inputs.kernel_data.vk_tree_root; - - inputs.pre_existing_blocks[0] = inputs.kernel_data.historical_header.hash(); - - inputs - } - - unconstrained fn new_with_previous_kernel(previous_vk_index: u32) -> Self { - let mut builder = BaseRollupInputsBuilder::new(); - builder.kernel_data = builder.kernel_data.in_vk_tree(previous_vk_index); - builder - } - - fn build_fee_payer_fee_juice_balance_read_hint( - self, - start_public_data_tree: NonEmptyMerkleTree, - ) -> PublicDataHint { - self.fee_payer_fee_juice_balance_pre_existing_public_data_index.map_or( - PublicDataHint::empty(), - |leaf_index_u32: u32| { - let leaf_index = leaf_index_u32 as Field; - let leaf_preimage = self.pre_existing_public_data[leaf_index]; - let membership_witness = MembershipWitness { - leaf_index, - sibling_path: start_public_data_tree.get_sibling_path(leaf_index_u32), - }; - PublicDataHint { - leaf_slot: leaf_preimage.slot, - value: leaf_preimage.value, - membership_witness, - leaf_preimage, - } - }, - ) - } - - fn extract_subtree_sibling_path( - path: [Field; FULL_HEIGHT], - mut sibling_path: [Field; SIBLING_PATH_LENGTH], - ) -> [Field; SIBLING_PATH_LENGTH] { - let subtree_height = FULL_HEIGHT - SIBLING_PATH_LENGTH; - for i in subtree_height..FULL_HEIGHT { - sibling_path[i - subtree_height] = path[i]; - } - sibling_path - } - - fn update_nullifier_tree_with_new_leaves( - mut self, - nullifier_tree: &mut NonEmptyMerkleTree, - kernel_data: &mut KernelData, - start_nullifier_tree_snapshot: AppendOnlyTreeSnapshot, - ) -> ([NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], [MembershipWitness; MAX_NULLIFIERS_PER_TX], [Field; MAX_NULLIFIERS_PER_TX], [u32; MAX_NULLIFIERS_PER_TX]) { - let mut nullifier_predecessor_preimages = - [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX]; - let mut low_nullifier_membership_witness = - [MembershipWitness::empty(); MAX_NULLIFIERS_PER_TX]; - - let sorted_new_nullifier_tuples = unsafe { - get_sorted_tuple( - self.nullifiers.storage.map(|insertion: NullifierInsertion| insertion.value), - |a, b| full_field_less_than(b, a), - ) - }; - - let mut sorted_nullifiers = [0; MAX_NULLIFIERS_PER_TX]; - let mut sorted_nullifiers_indexes = [0; MAX_NULLIFIERS_PER_TX]; - - for i in 0..MAX_NULLIFIERS_PER_TX { - if (i as u32) < (MAX_nullifiers_PER_TEST as u32) { - sorted_nullifiers[i] = sorted_new_nullifier_tuples[i].elem; - sorted_nullifiers_indexes[i] = sorted_new_nullifier_tuples[i].original_index; - } else { - sorted_nullifiers[i] = 0; - sorted_nullifiers_indexes[i] = i; - } - } - - let mut pre_existing_nullifiers = self.pre_existing_nullifiers; - - for i in 0..MAX_nullifiers_PER_TEST { - if i < self.nullifiers.len() { - let sorted_tuple = sorted_new_nullifier_tuples[i]; - let new_nullifier = sorted_tuple.elem; - let original_index = sorted_tuple.original_index; - - let low_index = self.nullifiers.get_unchecked(original_index).existing_index; - - kernel_data.public_inputs.end.nullifiers[original_index] = new_nullifier; - - let mut low_preimage = pre_existing_nullifiers[low_index]; - nullifier_predecessor_preimages[i] = low_preimage; - low_nullifier_membership_witness[i] = MembershipWitness { - leaf_index: low_index as Field, - sibling_path: nullifier_tree.get_sibling_path(low_index), - }; - - low_preimage.next_nullifier = new_nullifier; - low_preimage.next_index = start_nullifier_tree_snapshot - .next_available_leaf_index as u32 - + original_index; - pre_existing_nullifiers[low_index] = low_preimage; - - nullifier_tree.update_leaf(low_index, low_preimage.hash()); - } - } - - ( - nullifier_predecessor_preimages, low_nullifier_membership_witness, - sorted_nullifiers, sorted_nullifiers_indexes, - ) - } - - unconstrained fn build_inputs(mut self) -> BaseRollupInputs { - let mut kernel_data = - KernelData { public_inputs: self.kernel_data.to_kernel_circuit_public_inputs() }; - - let start_note_hash_tree = NonEmptyMerkleTree::new( - self.pre_existing_notes, - [0; NOTE_HASH_TREE_HEIGHT], - [0; NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT], - [0; NOTE_HASH_SUBTREE_HEIGHT], - ); - let start_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { - root: start_note_hash_tree.get_root(), - next_available_leaf_index: start_note_hash_tree.get_next_available_index() as u32, - }; - let note_hash_subtree_sibling_path = BaseRollupInputsBuilder::extract_subtree_sibling_path( - start_note_hash_tree.get_sibling_path(self.pre_existing_notes.len()), - [0; NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH], - ); - - let mut start_nullifier_tree = NonEmptyMerkleTree::new( - self.pre_existing_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), - [0; NULLIFIER_TREE_HEIGHT], - [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT], - [0; NULLIFIER_SUBTREE_HEIGHT], - ); - - let start_nullifier_tree_snapshot = AppendOnlyTreeSnapshot { - root: start_nullifier_tree.get_root(), - next_available_leaf_index: start_nullifier_tree.get_next_available_index() as u32, - }; - - let mut start_public_data_tree = NonEmptyMerkleTree::new( - self.pre_existing_public_data.map(|preimage: PublicDataTreeLeafPreimage| { - preimage.hash() - }), - [0; PUBLIC_DATA_TREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT - PUBLIC_DATA_SUBTREE_HEIGHT], - [0; PUBLIC_DATA_SUBTREE_HEIGHT], - ); - let start_public_data_tree_snapshot = AppendOnlyTreeSnapshot { - root: start_public_data_tree.get_root(), - next_available_leaf_index: start_public_data_tree.get_next_available_index() as u32, - }; - - let fee_payer_fee_juice_balance_read_hint = - self.build_fee_payer_fee_juice_balance_read_hint(start_public_data_tree); - - let start_archive = NonEmptyMerkleTree::new( - self.pre_existing_blocks, - [0; ARCHIVE_HEIGHT], - [0; ARCHIVE_HEIGHT - 1], - [0; 1], - ); - self.constants.last_archive = AppendOnlyTreeSnapshot { - root: start_archive.get_root(), - next_available_leaf_index: start_archive.get_next_available_index() as u32, - }; - - let (nullifier_predecessor_preimages, nullifier_predecessor_membership_witnesses, sorted_nullifiers, sorted_nullifier_indexes) = self - .update_nullifier_tree_with_new_leaves( - &mut start_nullifier_tree, - &mut kernel_data, - start_nullifier_tree_snapshot, - ); - - let nullifier_subtree_sibling_path = BaseRollupInputsBuilder::extract_subtree_sibling_path( - start_nullifier_tree.get_sibling_path(self.pre_existing_nullifiers.len()), - [0; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], - ); - - let (public_data_sibling_path, sorted_public_data_writes, sorted_public_data_writes_indexes, low_public_data_writes_preimages, low_public_data_writes_witnesses, _new_subtree) = update_public_data_tree( - &mut start_public_data_tree, - &mut kernel_data, - start_public_data_tree_snapshot, - self.public_data_writes, - self.protocol_public_data_writes, - self.final_public_data_writes, - self.pre_existing_public_data, - ); - - let start = PartialStateReference { - note_hash_tree: start_note_hash_tree_snapshot, - nullifier_tree: start_nullifier_tree_snapshot, - public_data_tree: start_public_data_tree_snapshot, - }; - - let state_diff_hints = StateDiffHints { - nullifier_predecessor_preimages, - nullifier_predecessor_membership_witnesses, - sorted_nullifiers, - sorted_nullifier_indexes, - note_hash_subtree_sibling_path, - nullifier_subtree_sibling_path, - public_data_sibling_path, - }; - - BaseRollupInputs { - kernel_data, - start, - state_diff_hints, - sorted_public_data_writes, - sorted_public_data_writes_indexes, - low_public_data_writes_preimages, - low_public_data_writes_witnesses, - archive_root_membership_witness: MembershipWitness { - leaf_index: 0, - sibling_path: start_archive.get_sibling_path(0), - }, - constants: self.constants, - transaction_fee: self.transaction_fee, - fee_payer_fee_juice_balance_read_hint, - } - } - - fn execute(self) -> BaseOrMergeRollupPublicInputs { - let inputs = unsafe { self.build_inputs() }; - inputs.base_rollup_circuit() - } - - fn succeeds(self) { - let _ = self.execute(); - } - - fn fails(self) { - let _ = self.execute(); - } - } - - impl Empty for BaseRollupInputsBuilder { - fn empty() -> Self { - BaseRollupInputsBuilder { - kernel_data: FixtureBuilder::new(), - transaction_fee: 0, - pre_existing_notes: [0; MAX_NOTE_HASHES_PER_TX], - pre_existing_nullifiers: [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX], - pre_existing_contracts: [0; 2], - pre_existing_public_data: [ - PublicDataTreeLeafPreimage::empty(); - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - ], - pre_existing_blocks: [0; 2], - public_data_reads: BoundedVec::new(), - public_data_writes: BoundedVec::new(), - protocol_public_data_writes: BoundedVec::new(), - final_public_data_writes: BoundedVec::new(), - nullifiers: BoundedVec::new(), - constants: ConstantRollupData::empty(), - fee_payer_fee_juice_balance_pre_existing_public_data_index: Option::none(), - } - } - } - - #[test] - unconstrained fn note_hashes_tree() { - let mut builder = BaseRollupInputsBuilder::new(); - - let note_hashes = [27, 28, 29, 30, 31, 32]; - for i in 0..note_hashes.len() { - builder.kernel_data.add_new_note_hash(note_hashes[i]); - } - let mut expected_commitments_tree = NonEmptyMerkleTree::new( - [0; MAX_NOTE_HASHES_PER_TX * 2], - [0; NOTE_HASH_TREE_HEIGHT], - [0; NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT - 1], - [0; NOTE_HASH_SUBTREE_HEIGHT + 1], - ); - - let outputs = builder.execute(); - let expected_start_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { - root: expected_commitments_tree.get_root(), - next_available_leaf_index: MAX_NOTE_HASHES_PER_TX as u32, - }; - assert(outputs.start.note_hash_tree.eq(expected_start_note_hash_tree_snapshot)); - - for i in 0..note_hashes.len() { - expected_commitments_tree.update_leaf(i + MAX_NOTE_HASHES_PER_TX, note_hashes[i]); - } - let expected_end_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { - root: expected_commitments_tree.get_root(), - next_available_leaf_index: (MAX_NOTE_HASHES_PER_TX * 2) as u32, - }; - assert(outputs.end.note_hash_tree.eq(expected_end_note_hash_tree_snapshot)); - } - - #[test] - unconstrained fn new_nullifier_tree_empty() { - // This test checks for insertions of all 0 values - // In this special case we will not need to provide sibling paths to check insertion of the nullifier values - // This is because 0 values are not actually inserted into the tree, rather the inserted subtree is left - // empty to begin with. - let mut builder = BaseRollupInputsBuilder::new(); - - builder.pre_existing_nullifiers[0] = - NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; - builder.pre_existing_nullifiers[1] = - NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; - - builder.succeeds(); - } - - #[test] - unconstrained fn nullifier_insertion_test() { - let mut builder = BaseRollupInputsBuilder::new(); - - builder.pre_existing_nullifiers[0] = - NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; - builder.pre_existing_nullifiers[1] = - NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; - - builder.nullifiers.push(NullifierInsertion { existing_index: 0, value: 1 }); - let mut tree_nullifiers = [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX * 2]; - tree_nullifiers[0] = NullifierLeafPreimage { - nullifier: 0, - next_nullifier: 1, - next_index: MAX_NULLIFIERS_PER_TX, - }; - tree_nullifiers[1] = builder.pre_existing_nullifiers[1]; - tree_nullifiers[MAX_NULLIFIERS_PER_TX] = - NullifierLeafPreimage { nullifier: 1, next_nullifier: 7, next_index: 1 }; - - let mut end_nullifier_tree = NonEmptyMerkleTree::new( - tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), - [0; NULLIFIER_TREE_HEIGHT], - [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], - [0; NULLIFIER_SUBTREE_HEIGHT + 1], - ); - - let output = builder.execute(); - - assert(output.end.nullifier_tree.eq( - AppendOnlyTreeSnapshot { - root: end_nullifier_tree.get_root(), - next_available_leaf_index: 2 * MAX_NULLIFIERS_PER_TX as u32, - }, - )); - } - - #[test] - unconstrained fn new_nullifier_tree_all_larger() { - let mut builder = BaseRollupInputsBuilder::new(); - - builder.pre_existing_nullifiers[0] = - NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; - builder.pre_existing_nullifiers[1] = - NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; - - builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); - for i in 1..builder.nullifiers.max_len() { - builder.nullifiers.push( - NullifierInsertion { existing_index: 1, value: (8 + i) as Field }, - ); - } - - let output = builder.execute(); - let mut tree_nullifiers = [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX * 2]; - tree_nullifiers[0] = builder.pre_existing_nullifiers[0]; - - tree_nullifiers[1] = NullifierLeafPreimage { - nullifier: 7, - next_nullifier: 8, - next_index: MAX_NULLIFIERS_PER_TX, - }; - - let last_index = builder.nullifiers.max_len() - 1; - for i in 0..last_index { - tree_nullifiers[MAX_NULLIFIERS_PER_TX + i] = NullifierLeafPreimage { - nullifier: (8 + i) as Field, - next_nullifier: (8 + i + 1) as Field, - next_index: MAX_NULLIFIERS_PER_TX + i + 1, - }; - } - tree_nullifiers[MAX_NULLIFIERS_PER_TX + last_index] = NullifierLeafPreimage { - nullifier: (8 + last_index) as Field, - next_nullifier: 0, - next_index: 0, - }; - - let mut end_nullifier_tree = NonEmptyMerkleTree::new( - tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), - [0; NULLIFIER_TREE_HEIGHT], - [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], - [0; NULLIFIER_SUBTREE_HEIGHT + 1], - ); - - assert(output.end.nullifier_tree.eq( - AppendOnlyTreeSnapshot { - root: end_nullifier_tree.get_root(), - next_available_leaf_index: 2 * MAX_NULLIFIERS_PER_TX as u32, - }, - )); - } - - #[test(should_fail_with = "Invalid low leaf")] - unconstrained fn new_nullifier_tree_double_spend() { - let mut builder = BaseRollupInputsBuilder::new(); - - builder.pre_existing_nullifiers[0] = - NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; - builder.pre_existing_nullifiers[1] = - NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; - - builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); - builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); - - builder.fails(); - } - - #[test(should_fail_with = "Invalid low leaf")] - unconstrained fn new_nullifier_tree_double_spend_same_batch() { - let mut builder = BaseRollupInputsBuilder::new(); - - builder.pre_existing_nullifiers[0] = - NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; - builder.pre_existing_nullifiers[1] = - NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; - - builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); - builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); - - builder.fails(); - } - - #[test] - unconstrained fn empty_tx_effects_hash() { - let outputs = BaseRollupInputsBuilder::new().execute(); - - let hash_input_flattened = [0; TX_EFFECTS_HASH_INPUT_FIELDS * 32]; - let sha_digest = std::hash::sha256(hash_input_flattened); - let expected_tx_effects_hash = field_from_bytes_32_trunc(sha_digest); - assert_eq(outputs.txs_effects_hash, expected_tx_effects_hash); - } - - #[test] - unconstrained fn empty_block_out_hash() { - let outputs = BaseRollupInputsBuilder::new().execute(); - assert_eq(outputs.out_hash, 0); - } - - #[test] - unconstrained fn nonempty_block_out_hash() { - let mut builder = BaseRollupInputsBuilder::new(); - - for i in 0..MAX_L2_TO_L1_MSGS_PER_TX { - builder.kernel_data.add_exposed_l2_to_l1_message( - i as Field, - EthAddress::from_field(1 + i as Field), - ); - } - - let out_hash = builder.execute().out_hash; - let siloed_l2_to_l1_msgs = builder.kernel_data.l2_to_l1_msgs.map( - |l2_to_l1_message: ScopedL2ToL1Message| silo_l2_to_l1_message( - l2_to_l1_message, - builder.constants.global_variables.version, - builder.constants.global_variables.chain_id, - ), - ); - - // Since we fill the tree completely, we know to expect a full tree as below - let expected_tree = dep::types::merkle_tree::variable_merkle_tree::tests::generate_full_sha_tree( - siloed_l2_to_l1_msgs.storage(), - ); - assert_eq(out_hash, expected_tree.get_root()); - } - - #[test(should_fail_with = "membership check failed")] - unconstrained fn compute_membership_archive_negative() { - let mut inputs = BaseRollupInputsBuilder::new().build_inputs(); - inputs.archive_root_membership_witness.sibling_path[0] = 27; - let _output = inputs.base_rollup_circuit(); - } - - #[test] - unconstrained fn constants_dont_change() { - let inputs = BaseRollupInputsBuilder::new().build_inputs(); - let outputs = inputs.base_rollup_circuit(); - - assert(inputs.constants.eq(outputs.constants)); - } - - #[test(should_fail_with = "kernel chain_id does not match the rollup chain_id")] - unconstrained fn constants_dont_match_kernels_chain_id() { - let mut builder = BaseRollupInputsBuilder::new(); - builder.constants.global_variables.chain_id = 3; - builder.fails(); - } - - #[test(should_fail_with = "kernel version does not match the rollup version")] - unconstrained fn constants_dont_match_kernels_version() { - let mut builder = BaseRollupInputsBuilder::new(); - builder.constants.global_variables.version += 1; - builder.fails(); - } - - #[test(should_fail_with = "kernel global variables do not match the rollup global variables")] - unconstrained fn constants_global_variables_dont_match_kernels() { - let mut builder = BaseRollupInputsBuilder::new(); - builder.kernel_data.global_variables.block_number = 6; - builder.constants.global_variables.block_number = 7; - builder.fails(); - } - - #[test(should_fail_with = "kernel max_block_number is smaller than block number")] - unconstrained fn constants_dont_satisfy_smaller_max_block_number() { - let mut builder = BaseRollupInputsBuilder::new(); - builder.constants.global_variables.block_number = 42; - builder.kernel_data.set_max_block_number(5); - builder.fails(); - } - - #[test] - unconstrained fn constants_satisfy_equal_max_block_number() { - let mut builder = BaseRollupInputsBuilder::new(); - builder.constants.global_variables.block_number = 42; - builder.kernel_data.set_max_block_number(42); - builder.succeeds(); - } - - #[test] - unconstrained fn constants_satisfy_larger_max_block_number() { - let mut builder = BaseRollupInputsBuilder::new(); - builder.constants.global_variables.block_number = 42; - builder.kernel_data.set_max_block_number(4294967295); - builder.succeeds(); - } - - #[test] - unconstrained fn num_txs_is_1() { - let outputs = BaseRollupInputsBuilder::new().execute(); - - assert_eq(outputs.num_txs, 1); - } - - #[test] - unconstrained fn single_public_state_write() { - let mut builder = BaseRollupInputsBuilder::new(); - - builder.pre_existing_public_data[0] = - PublicDataTreeLeafPreimage { slot: 27, value: 28, next_slot: 0, next_index: 0 }; - builder.public_data_writes.push((0, PublicDataTreeLeaf { slot: 27, value: 29 })); - let outputs = builder.execute(); - - let updated_leaf = - PublicDataTreeLeafPreimage { slot: 27, value: 29, next_slot: 0, next_index: 0 }; - - let mut expected_public_data_tree = NonEmptyMerkleTree::new( - [updated_leaf.hash(), 0], - [0; PUBLIC_DATA_TREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT - 1], - [0; 1], - ); - - assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); - } - - #[test] - unconstrained fn multiple_public_state_read_writes() { - let mut builder = BaseRollupInputsBuilder::new(); - - builder.pre_existing_public_data[0] = - PublicDataTreeLeafPreimage { slot: 20, value: 40, next_slot: 28, next_index: 1 }; - builder.pre_existing_public_data[1] = - PublicDataTreeLeafPreimage { slot: 28, value: 41, next_slot: 29, next_index: 2 }; - builder.pre_existing_public_data[2] = - PublicDataTreeLeafPreimage { slot: 29, value: 42, next_slot: 30, next_index: 3 }; - builder.pre_existing_public_data[3] = - PublicDataTreeLeafPreimage { slot: 30, value: 43, next_slot: 0, next_index: 0 }; - builder.public_data_reads.push(0); - builder.public_data_writes.push((0, PublicDataTreeLeaf { slot: 25, value: 60 })); - builder.public_data_reads.push(4); - builder.public_data_writes.push((0, PublicDataTreeLeaf { slot: 20, value: 90 })); - - let outputs = builder.execute(); - - let mut public_data_leaves = [0; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX * 2]; - public_data_leaves[0] = PublicDataTreeLeafPreimage { - slot: 20, - value: 90, - next_slot: 25, - next_index: MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - } - .hash(); - public_data_leaves[1] = - PublicDataTreeLeafPreimage { slot: 28, value: 41, next_slot: 29, next_index: 2 }.hash(); - public_data_leaves[2] = - PublicDataTreeLeafPreimage { slot: 29, value: 42, next_slot: 30, next_index: 3 }.hash(); - public_data_leaves[3] = - PublicDataTreeLeafPreimage { slot: 30, value: 43, next_slot: 0, next_index: 0 }.hash(); - public_data_leaves[MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = - PublicDataTreeLeafPreimage { slot: 25, value: 60, next_slot: 28, next_index: 1 }.hash(); - - let mut expected_public_data_tree = NonEmptyMerkleTree::new( - public_data_leaves, - [0; PUBLIC_DATA_TREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT - PUBLIC_DATA_SUBTREE_HEIGHT - 1], - [0; PUBLIC_DATA_SUBTREE_HEIGHT + 1], - ); - - assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); - } - - #[test] - unconstrained fn updates_fee_payer_balance_with_new_data_write() { - let fee_payer = AztecAddress::from_field(0x1234); - let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - let initial_balance = 300_000; - let tx_fee = 100_000; - let expected_balance = 200_000; - - let mut builder = BaseRollupInputsBuilder::new(); - - // Set fee payer - builder.kernel_data.fee_payer = fee_payer; - - // Set pre-existing balance - builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: initial_balance, - next_slot: 0, - next_index: 0, - }; - builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(0); - - // Set values for computing exact tx_fee - builder.transaction_fee = tx_fee; - - // Set expected protocol data update - builder.protocol_public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, - )); - - let outputs = builder.execute(); - - // The new public data tree should have updated the balance of the fee payer - let updated_leaf = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: expected_balance, - next_slot: 0, - next_index: 0, - }; - let mut expected_public_data_tree = NonEmptyMerkleTree::new( - [updated_leaf.hash(), 0], - [0; PUBLIC_DATA_TREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT - 1], - [0; 1], - ); - - assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); - } - - #[test] - unconstrained fn updates_fee_payer_balance_in_existing_data_write() { - let fee_payer = AztecAddress::from_field(0x1234); - let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - let initial_balance = 100_000; - let after_claim_balance = 300_000; - let tx_fee = 100_000; - let expected_balance = 200_000; - - let mut builder = BaseRollupInputsBuilder::new(); - - // Set fee payer - builder.kernel_data.fee_payer = fee_payer; - - // Set pre-existing balance, but set no hint for it since we'll update a user update request - builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: initial_balance, - next_slot: 0, - next_index: 0, - }; - - // Set values for computing exact tx_fee - builder.transaction_fee = tx_fee; - - // Create an existing data update that corresponds to a claim - builder.public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: after_claim_balance }, - )); - - // Set expected data updates after base rollup runs - // Note that we tweak the final_public_data_writes directly, since we need to overwrite the output of the user public_data_writes - builder.final_public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, - )); - - let outputs = builder.execute(); - - // The new public data tree should have updated the balance of the fee payer - let updated_leaf = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: expected_balance, - next_slot: 0, - next_index: 0, - }; - let mut expected_public_data_tree = NonEmptyMerkleTree::new( - [updated_leaf.hash(), 0], - [0; PUBLIC_DATA_TREE_HEIGHT], - [0; PUBLIC_DATA_TREE_HEIGHT - 1], - [0; 1], - ); - - assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); - } - - #[test(should_fail_with = "Not enough balance for fee payer to pay for transaction")] - unconstrained fn fails_to_update_fee_payer_balance_if_not_enough_funds() { - let fee_payer = AztecAddress::from_field(0x1234); - let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - // Set low initial balance so it fails! - let initial_balance = 10_000; - let tx_fee = 100_000; - - let mut builder = BaseRollupInputsBuilder::new(); - - // Set fee payer - builder.kernel_data.fee_payer = fee_payer; - - // Set pre-existing balance - builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: initial_balance, - next_slot: 0, - next_index: 0, - }; - builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(0); - - // Set values for computing exact tx_fee - builder.transaction_fee = tx_fee; - - // Set expected protocol data update - builder.protocol_public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: -90_000 }, - )); - - builder.fails(); - } - - #[test(should_fail_with = "Wrong leaf slot for Fee Juice balance read hint")] - unconstrained fn fails_to_update_fee_payer_balance_if_wrong_read_hint() { - let fee_payer = AztecAddress::from_field(0x1234); - let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); - let initial_balance = 300_000; - let expected_balance = 200_000; - let tx_fee = 100_000; - - let mut builder = BaseRollupInputsBuilder::new(); - - // Set fee payer - builder.kernel_data.fee_payer = fee_payer; - - // Set pre-existing balance in index 0 - builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { - slot: balance_slot, - value: initial_balance, - next_slot: 0, - next_index: 0, - }; - - builder.pre_existing_public_data[1] = PublicDataTreeLeafPreimage { - slot: 1, - value: initial_balance, - next_slot: balance_slot, - next_index: 0, - }; - - // But point the read hint to the wrong one! - builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(1); - - // Set values for computing exact tx_fee - builder.transaction_fee = tx_fee; - - // Set expected protocol data update - builder.protocol_public_data_writes.push(( - 0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, - )); - - builder.fails(); - } - - #[test] - fn valid_previous_kernel_empty() { - let builder = unsafe { - BaseRollupInputsBuilder::new_with_previous_kernel(PRIVATE_KERNEL_EMPTY_INDEX) - }; - - let _res = builder.execute(); - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/archive.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/archive.nr new file mode 100644 index 00000000000..e97541be158 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/archive.nr @@ -0,0 +1,24 @@ +use types::{ + constants::ARCHIVE_HEIGHT, + header::Header, + merkle_tree::membership::{assert_check_membership, MembershipWitness}, +}; + +// Check that the block header used is a member of the blocks tree --> since the block header +// contains roots of all the trees this is sufficient to verify that the tree roots used by kernels are correct +pub(crate) fn perform_archive_membership_check( + archive_root: Field, + previous_block_hash_witness: MembershipWitness, + header: Header, +) { + // Rebuild the block hash + let previous_block_hash = header.hash(); + + // Now check that the previous block hash is in the blocks tree from the beginning of the rollup + assert_check_membership( + previous_block_hash, + previous_block_hash_witness.leaf_index, + previous_block_hash_witness.sibling_path, + archive_root, + ); +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/avm_proof_data.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/avm_proof_data.nr deleted file mode 100644 index 5a2aefbf616..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/avm_proof_data.nr +++ /dev/null @@ -1,17 +0,0 @@ -use dep::types::{ - abis::avm_circuit_public_inputs::AvmCircuitPublicInputs, - constants::AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS, - proof::{avm_proof::AvmProof, traits::Verifiable, vk_data::VkData}, -}; - -pub struct AvmProofData { - public_inputs: AvmCircuitPublicInputs, - proof: AvmProof, - vk_data: VkData, -} - -impl Verifiable for AvmProofData { - fn verify(self) { - // TODO(#8470) - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/constants.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/constants.nr new file mode 100644 index 00000000000..0e7f459fecd --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/constants.nr @@ -0,0 +1,33 @@ +use crate::abis::constant_rollup_data::ConstantRollupData; +use types::abis::combined_constant_data::CombinedConstantData; + +pub(crate) fn validate_combined_constant_data( + constants: CombinedConstantData, + rollup_constants: ConstantRollupData, +) { + // Verify the kernel chain_id and versions + assert( + constants.tx_context.chain_id == rollup_constants.global_variables.chain_id, + "kernel chain_id does not match the rollup chain_id", + ); + assert( + constants.tx_context.version == rollup_constants.global_variables.version, + "kernel version does not match the rollup version", + ); + assert( + constants.vk_tree_root == rollup_constants.vk_tree_root, + "kernel vk_tree_root does not match the rollup vk_tree_root", + ); + assert( + constants.protocol_contract_tree_root == rollup_constants.protocol_contract_tree_root, + "kernel protocol_contract_tree_root does not match the rollup protocol_contract_tree_root", + ); + + // Verify the kernel global variables if set, note these can be empty if this is a request coming directly from the private kernel tail. + // TODO(@spalladino) How can we check that this is a request coming from the private kernel tail? + assert( + constants.global_variables.is_empty() + | (constants.global_variables == rollup_constants.global_variables), + "kernel global variables do not match the rollup global variables", + ); +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/fees.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/fees.nr new file mode 100644 index 00000000000..f47a0f1e7b3 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/fees.nr @@ -0,0 +1,14 @@ +use dep::types::{ + address::AztecAddress, constants::FEE_JUICE_ADDRESS, data::hash::compute_public_data_tree_index, + storage::map::derive_storage_slot_in_map, +}; + +pub(crate) fn compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer: AztecAddress) -> Field { + let balances_slot_in_fee_juice_contract = 1; + let fee_payer_balance_slot_in_fee_juice_contract = + derive_storage_slot_in_map(balances_slot_in_fee_juice_contract, fee_payer); + compute_public_data_tree_index( + FEE_JUICE_ADDRESS, + fee_payer_balance_slot_in_fee_juice_contract, + ) +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/mod.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/mod.nr index c998111d9a6..9e3e70f9433 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/mod.nr @@ -1,3 +1,5 @@ -pub mod avm_proof_data; -pub mod private_tube_data; -pub mod public_tube_data; +pub(crate) mod archive; +pub(crate) mod constants; +pub(crate) mod fees; +pub(crate) mod nullifier_tree; +pub(crate) mod public_data_tree; diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/nullifier_tree.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/nullifier_tree.nr new file mode 100644 index 00000000000..6f83aa7e5ee --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/nullifier_tree.nr @@ -0,0 +1,69 @@ +use dep::types::{ + abis::{ + append_only_tree_snapshot::AppendOnlyTreeSnapshot, + nullifier_leaf_preimage::NullifierLeafPreimage, + }, + constants::{ + MAX_NULLIFIERS_PER_TX, NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, + NULLIFIER_TREE_HEIGHT, + }, + merkle_tree::{indexed_tree, MembershipWitness}, + utils::field::full_field_less_than, +}; + +pub(crate) fn nullifier_tree_batch_insert( + start_snapshot: AppendOnlyTreeSnapshot, + nullifiers: [Field; MAX_NULLIFIERS_PER_TX], + sorted_nullifiers: [Field; MAX_NULLIFIERS_PER_TX], + sorted_nullifiers_indexes: [u32; MAX_NULLIFIERS_PER_TX], + nullifier_subtree_sibling_path: [Field; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], + nullifier_predecessor_preimages: [NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], + nullifier_predecessor_membership_witnesses: [MembershipWitness; MAX_NULLIFIERS_PER_TX], +) -> AppendOnlyTreeSnapshot { + indexed_tree::batch_insert( + start_snapshot, + nullifiers, + sorted_nullifiers, + sorted_nullifiers_indexes, + nullifier_subtree_sibling_path, + nullifier_predecessor_preimages, + nullifier_predecessor_membership_witnesses.map( + |witness: MembershipWitness| { + MembershipWitness { + leaf_index: witness.leaf_index, + sibling_path: witness.sibling_path, + } + }, + ), + |low_leaf: NullifierLeafPreimage, nullifier: Field| { + // Is valid low leaf + let is_less_than_nullifier = full_field_less_than(low_leaf.nullifier, nullifier); + let is_next_greater_than = full_field_less_than(nullifier, low_leaf.next_nullifier); + + (!low_leaf.is_empty()) + & is_less_than_nullifier + & ( + is_next_greater_than + | ((low_leaf.next_index == 0) & (low_leaf.next_nullifier == 0)) + ) + }, + |low_leaf: NullifierLeafPreimage, nullifier: Field, nullifier_index: u32| { + // Update low leaf + NullifierLeafPreimage { + nullifier: low_leaf.nullifier, + next_nullifier: nullifier, + next_index: nullifier_index, + } + }, + |nullifier: Field, low_leaf: NullifierLeafPreimage| { + // Build insertion leaf + NullifierLeafPreimage { + nullifier: nullifier, + next_nullifier: low_leaf.next_nullifier, + next_index: low_leaf.next_index, + } + }, + [0; NULLIFIER_SUBTREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT], + ) +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_tube_data.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_tube_data.nr deleted file mode 100644 index 74eac402aee..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/private_tube_data.nr +++ /dev/null @@ -1,23 +0,0 @@ -use dep::types::{ - abis::kernel_circuit_public_inputs::KernelCircuitPublicInputs, - constants::HONK_VERIFICATION_KEY_LENGTH_IN_FIELDS, - proof::{traits::Verifiable, tube_proof::TubeProof, vk_data::VkData}, -}; - -pub struct PrivateTubeData { - public_inputs: KernelCircuitPublicInputs, - proof: TubeProof, - vk_data: VkData, -} - -impl Verifiable for PrivateTubeData { - fn verify(self) { - let inputs = KernelCircuitPublicInputs::serialize(self.public_inputs); - std::verify_proof( - self.vk_data.vk.key, - self.proof.fields, - inputs, - self.vk_data.vk.hash, - ); - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/public_data_tree.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/public_data_tree.nr new file mode 100644 index 00000000000..e3d152ba98d --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/public_data_tree.nr @@ -0,0 +1,76 @@ +use dep::types::{ + abis::{append_only_tree_snapshot::AppendOnlyTreeSnapshot, public_data_write::PublicDataWrite}, + constants::PUBLIC_DATA_TREE_HEIGHT, + data::{PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, + merkle_tree::{indexed_tree, MembershipWitness}, + traits::is_empty, + utils::field::full_field_less_than, +}; + +pub(crate) fn public_data_tree_insert( + write: PublicDataWrite, + start_snapshot: AppendOnlyTreeSnapshot, + low_leaf_preimage: PublicDataTreeLeafPreimage, + low_leaf_membership_witness: MembershipWitness, + sibling_path: [Field; PUBLIC_DATA_TREE_HEIGHT], +) -> AppendOnlyTreeSnapshot { + if !is_empty(write) { + indexed_tree::insert::<_, _, PUBLIC_DATA_TREE_HEIGHT>( + start_snapshot, + PublicDataTreeLeaf { slot: write.leaf_slot, value: write.value }, + low_leaf_preimage, + low_leaf_membership_witness, + sibling_path, + |low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf| { + // Is valid low preimage + let is_update = low_preimage.slot == write.slot; + let is_low_empty = low_preimage.is_empty(); + + let is_less_than_slot = full_field_less_than(low_preimage.slot, write.slot); + let is_next_greater_than = full_field_less_than(write.slot, low_preimage.next_slot); + let is_in_range = is_less_than_slot + & ( + is_next_greater_than + | ((low_preimage.next_index == 0) & (low_preimage.next_slot == 0)) + ); + + (!is_low_empty) & (is_update | is_in_range) + }, + |low_preimage: PublicDataTreeLeafPreimage, write: PublicDataTreeLeaf, write_index: u32| { + // Update low leaf + let is_update = low_preimage.slot == write.slot; + if is_update { + PublicDataTreeLeafPreimage { + slot: low_preimage.slot, + value: write.value, + next_slot: low_preimage.next_slot, + next_index: low_preimage.next_index, + } + } else { + PublicDataTreeLeafPreimage { + slot: low_preimage.slot, + value: low_preimage.value, + next_slot: write.slot, + next_index: write_index, + } + } + }, + |write: PublicDataTreeLeaf, low_preimage: PublicDataTreeLeafPreimage| { + // Build insertion leaf + let is_update = low_preimage.slot == write.slot; + if is_update { + PublicDataTreeLeafPreimage::empty() + } else { + PublicDataTreeLeafPreimage { + slot: write.slot, + value: write.value, + next_slot: low_preimage.next_slot, + next_index: low_preimage.next_index, + } + } + }, + ) + } else { + start_snapshot + } +} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/public_tube_data.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/public_tube_data.nr deleted file mode 100644 index 3fe33cc8734..00000000000 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/components/public_tube_data.nr +++ /dev/null @@ -1,23 +0,0 @@ -use dep::types::{ - abis::kernel_circuit_public_inputs::PrivateToPublicKernelCircuitPublicInputs, - constants::HONK_VERIFICATION_KEY_LENGTH_IN_FIELDS, - proof::{traits::Verifiable, tube_proof::TubeProof, vk_data::VkData}, -}; - -pub struct PublicTubeData { - public_inputs: PrivateToPublicKernelCircuitPublicInputs, - proof: TubeProof, - vk_data: VkData, -} - -impl Verifiable for PublicTubeData { - fn verify(self) { - let inputs = PrivateToPublicKernelCircuitPublicInputs::serialize(self.public_inputs); - std::verify_proof( - self.vk_data.vk.key, - self.proof.fields, - inputs, - self.vk_data.vk.hash, - ); - } -} diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/mod.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/mod.nr index 6bee28884ee..03b442b4eaa 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/mod.nr @@ -1,8 +1,7 @@ -mod base_rollup_inputs; -mod components; +pub(crate) mod components; +pub(crate) mod state_diff_hints; mod private_base_rollup; mod public_base_rollup; -mod state_diff_hints; pub use crate::abis::base_or_merge_rollup_public_inputs::BaseOrMergeRollupPublicInputs; pub use private_base_rollup::PrivateBaseRollupInputs; 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 848147c61b9..30b20e4b88f 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 @@ -1,22 +1,36 @@ use crate::{ abis::{ - base_or_merge_rollup_public_inputs::BaseOrMergeRollupPublicInputs, + base_or_merge_rollup_public_inputs::{BASE_ROLLUP_TYPE, BaseOrMergeRollupPublicInputs}, constant_rollup_data::ConstantRollupData, }, base::{ - base_rollup_inputs::{BaseRollupInputs, KernelData}, - components::private_tube_data::PrivateTubeData, - state_diff_hints::StateDiffHints, + components::{ + archive::perform_archive_membership_check, constants::validate_combined_constant_data, + fees::compute_fee_payer_fee_juice_balance_leaf_slot, + nullifier_tree::nullifier_tree_batch_insert, public_data_tree::public_data_tree_insert, + }, + state_diff_hints::PrivateBaseStateDiffHints, }, + components::{compute_kernel_out_hash, compute_tx_effects_hash}, }; use dep::types::{ + abis::{ + append_only_tree_snapshot::AppendOnlyTreeSnapshot, + nullifier_leaf_preimage::NullifierLeafPreimage, public_data_write::PublicDataWrite, + tube::PrivateTubeData, + }, constants::{ - ARCHIVE_HEIGHT, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PRIVATE_KERNEL_EMPTY_INDEX, - PUBLIC_DATA_TREE_HEIGHT, TUBE_VK_INDEX, + ARCHIVE_HEIGHT, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_HEIGHT, + PRIVATE_KERNEL_EMPTY_INDEX, TUBE_VK_INDEX, }, - data::{public_data_hint::PublicDataHint, PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, - merkle_tree::MembershipWitness, + data::{hash::compute_public_data_tree_value, public_data_hint::PublicDataHint}, + hash::silo_l2_to_l1_message, + merkle_tree::{ + append_only_tree, calculate_empty_tree_root, calculate_subtree_root, MembershipWitness, + }, + messaging::l2_to_l1_message::ScopedL2ToL1Message, partial_state_reference::PartialStateReference, + traits::is_empty, }; global ALLOWED_PREVIOUS_CIRCUITS = [PRIVATE_KERNEL_EMPTY_INDEX, TUBE_VK_INDEX]; @@ -26,14 +40,9 @@ pub struct PrivateBaseRollupInputs { start: PartialStateReference, - state_diff_hints: StateDiffHints, + state_diff_hints: PrivateBaseStateDiffHints, fee_payer_fee_juice_balance_read_hint: PublicDataHint, - sorted_public_data_writes: [PublicDataTreeLeaf; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - sorted_public_data_writes_indexes: [u32; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - low_public_data_writes_preimages: [PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - low_public_data_writes_witnesses: [MembershipWitness; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - archive_root_membership_witness: MembershipWitness, constants: ConstantRollupData, } @@ -54,19 +63,947 @@ impl PrivateBaseRollupInputs { let transaction_fee = self.compute_transaction_fee(); - BaseRollupInputs { - kernel_data: KernelData { public_inputs: self.tube_data.public_inputs }, - start: self.start, - state_diff_hints: self.state_diff_hints, + validate_combined_constant_data(self.tube_data.public_inputs.constants, self.constants); + + self.validate_kernel_start_state(); + + let rollup_validation_requests = self.tube_data.public_inputs.rollup_validation_requests; + + // Verify the max block number + // TODO #5345: why is block_number a Field and not u32? + if rollup_validation_requests.max_block_number.is_some() { + assert( + self.constants.global_variables.block_number as u32 + <= rollup_validation_requests.max_block_number.unwrap_unchecked(), + "kernel max_block_number is smaller than block number", + ); + } + + let commitments_tree_subroot = self.calculate_commitments_subtree(); + + let empty_commitments_subtree_root = calculate_empty_tree_root(NOTE_HASH_SUBTREE_HEIGHT); + + let end_note_hash_tree_snapshot = append_only_tree::insert_subtree_to_snapshot_tree( + self.start.note_hash_tree, + self.state_diff_hints.note_hash_subtree_sibling_path, + empty_commitments_subtree_root, + commitments_tree_subroot, + NOTE_HASH_SUBTREE_HEIGHT as u8, + ); + + // Insert nullifiers: + let end_nullifier_tree_snapshot = + self.check_nullifier_tree_non_membership_and_insert_to_tree(); + + // Write fee to public data tree + let fee_public_data_write = self.build_fee_public_data_write(transaction_fee); + let end_public_data_tree_snapshot = + self.insert_fee_public_data_write(fee_public_data_write); + let mut all_public_data_update_requests = + [PublicDataWrite::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; + all_public_data_update_requests[0] = fee_public_data_write; + + // Calculate the tx effects hash of the transaction + let siloed_l2_to_l1_msgs = self.tube_data.public_inputs.end.l2_to_l1_msgs.map( + |message: ScopedL2ToL1Message| silo_l2_to_l1_message( + message, + self.tube_data.public_inputs.constants.tx_context.version, + self.tube_data.public_inputs.constants.tx_context.chain_id, + ), + ); + let out_hash = compute_kernel_out_hash(siloed_l2_to_l1_msgs); + let tx_effects_hash = compute_tx_effects_hash( + self.tube_data.public_inputs.end, + self.tube_data.public_inputs.revert_code, transaction_fee, - fee_payer_fee_juice_balance_read_hint: self.fee_payer_fee_juice_balance_read_hint, - sorted_public_data_writes: self.sorted_public_data_writes, - sorted_public_data_writes_indexes: self.sorted_public_data_writes_indexes, - low_public_data_writes_preimages: self.low_public_data_writes_preimages, - low_public_data_writes_witnesses: self.low_public_data_writes_witnesses, - archive_root_membership_witness: self.archive_root_membership_witness, + all_public_data_update_requests, + out_hash, + ); + + // Perform membership checks that the notes provided exist within the historical trees data + perform_archive_membership_check( + self.constants.last_archive.root, + self.archive_root_membership_witness, + self.tube_data.public_inputs.constants.historical_header, + ); + + BaseOrMergeRollupPublicInputs { + rollup_type: BASE_ROLLUP_TYPE, + num_txs: 1, constants: self.constants, + start: self.start, + end: PartialStateReference { + note_hash_tree: end_note_hash_tree_snapshot, + nullifier_tree: end_nullifier_tree_snapshot, + public_data_tree: end_public_data_tree_snapshot, + }, + txs_effects_hash: tx_effects_hash, + out_hash, + accumulated_fees: transaction_fee, + } + } + + // TODO(Kev): This should say calculate_commitments_subtree_root + // Cpp code says calculate_commitments_subtree, so I'm leaving it as is for now + fn calculate_commitments_subtree(self) -> Field { + calculate_subtree_root(self.tube_data.public_inputs.end.note_hashes) + } + + fn check_nullifier_tree_non_membership_and_insert_to_tree(self) -> AppendOnlyTreeSnapshot { + nullifier_tree_batch_insert( + self.start.nullifier_tree, + self.tube_data.public_inputs.end.nullifiers, + self.state_diff_hints.sorted_nullifiers, + self.state_diff_hints.sorted_nullifier_indexes, + self.state_diff_hints.nullifier_subtree_sibling_path, + self.state_diff_hints.nullifier_predecessor_preimages, + self.state_diff_hints.nullifier_predecessor_membership_witnesses, + ) + } + + fn create_nullifier_subtree(leaves: [NullifierLeafPreimage; N]) -> Field { + calculate_subtree_root(leaves.map(|leaf: NullifierLeafPreimage| leaf.hash())) + } + + fn validate_kernel_start_state(self) { + let kernel_state = self.tube_data.public_inputs.start_state; + if !is_empty(kernel_state) { + assert( + kernel_state.note_hash_tree.eq(self.start.note_hash_tree), + "Mismatch start state for note hash tree", + ); + assert( + kernel_state.nullifier_tree.eq(self.start.nullifier_tree), + "Mismatch start state for nullifier tree", + ); + assert( + kernel_state.public_data_tree.eq(self.start.public_data_tree), + "Mismatch start state for public data tree", + ); + } + } + + fn build_fee_public_data_write(self, tx_fee: Field) -> PublicDataWrite { + let fee_payer = self.tube_data.public_inputs.fee_payer; + + if fee_payer.is_zero() { + PublicDataWrite::empty() + } else { + let read_hint = self.fee_payer_fee_juice_balance_read_hint; + let leaf_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + + // Otherwise, build a new one to be inserted into the protocol update requests at the end of the array. + read_hint.validate(self.start.public_data_tree.root); + + let balance = read_hint.value; + assert( + read_hint.leaf_slot == leaf_slot, + "Wrong leaf slot for Fee Juice balance read hint", + ); + assert(!balance.lt(tx_fee), "Not enough balance for fee payer to pay for transaction"); + + let value = compute_public_data_tree_value(balance - tx_fee); + PublicDataWrite { leaf_slot, value } + } + } + + fn insert_fee_public_data_write(self, fee_write: PublicDataWrite) -> AppendOnlyTreeSnapshot { + public_data_tree_insert( + fee_write, + self.start.public_data_tree, + self.state_diff_hints.fee_write_low_leaf_preimage, + self.state_diff_hints.fee_write_low_leaf_membership_witness, + self.state_diff_hints.fee_write_sibling_path, + ) + } +} + +mod tests { + use crate::{ + abis::{ + base_or_merge_rollup_public_inputs::BaseOrMergeRollupPublicInputs, + constant_rollup_data::ConstantRollupData, + }, + base::{ + components::fees::compute_fee_payer_fee_juice_balance_leaf_slot, + private_base_rollup::PrivateBaseRollupInputs, + state_diff_hints::PrivateBaseStateDiffHints, + }, + components::TX_EFFECTS_HASH_INPUT_FIELDS, + }; + use dep::types::{ + abis::{ + append_only_tree_snapshot::AppendOnlyTreeSnapshot, gas::Gas, gas_fees::GasFees, + kernel_circuit_public_inputs::KernelCircuitPublicInputs, + nullifier_leaf_preimage::NullifierLeafPreimage, + }, + address::{AztecAddress, EthAddress}, + constants::{ + ARCHIVE_HEIGHT, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, + NOTE_HASH_SUBTREE_HEIGHT, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NOTE_HASH_TREE_HEIGHT, + NULLIFIER_SUBTREE_HEIGHT, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, + PRIVATE_KERNEL_EMPTY_INDEX, PUBLIC_DATA_TREE_HEIGHT, TUBE_VK_INDEX, + }, + data::{public_data_hint::PublicDataHint, PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, + hash::silo_l2_to_l1_message, + merkle_tree::MembershipWitness, + messaging::l2_to_l1_message::ScopedL2ToL1Message, + partial_state_reference::PartialStateReference, + tests::{fixture_builder::FixtureBuilder, fixtures, merkle_tree_utils::NonEmptyMerkleTree}, + traits::{Empty, is_empty}, + utils::{ + arrays::get_sorted_tuple::get_sorted_tuple, + field::{field_from_bytes_32_trunc, full_field_less_than}, + }, + }; + + struct NullifierInsertion { + existing_index: u32, + value: Field, + } + + global MAX_NULLIFIERS_PER_TEST: u32 = 4; + global AVAILABLE_PUBLIC_DATA_LEAVES_FOR_TEST = 64; + global AVAILABLE_PUBLIC_DATA_SUBTREE_HEIGHT_FOR_TEST = 6; + global PRE_EXISTING_PUBLIC_DATA_LEAVES = 10; + + fn update_public_data_tree( + public_data_tree: &mut NonEmptyMerkleTree, + snapshot: AppendOnlyTreeSnapshot, + fee_write: (u32, PublicDataTreeLeaf), + mut pre_existing_public_data: [PublicDataTreeLeafPreimage; EXISTING_LEAVES], + ) -> (PublicDataTreeLeafPreimage, MembershipWitness, [Field; PUBLIC_DATA_TREE_HEIGHT]) { + let (low_leaf_index, fee_write) = fee_write; + if (!is_empty(fee_write)) { + let low_leaf = pre_existing_public_data[low_leaf_index]; + let mut new_leaf = PublicDataTreeLeafPreimage::empty(); + if low_leaf.slot == fee_write.slot { + pre_existing_public_data[low_leaf_index].value = fee_write.value; + } else { + new_leaf = PublicDataTreeLeafPreimage { + slot: fee_write.slot, + value: fee_write.value, + next_slot: low_leaf.next_slot, + next_index: low_leaf.next_index, + }; + pre_existing_public_data[low_leaf_index] = PublicDataTreeLeafPreimage { + slot: low_leaf.slot, + value: low_leaf.value, + next_slot: fee_write.slot, + next_index: PRE_EXISTING_PUBLIC_DATA_LEAVES, + }; + } + let low_public_data_writes_witness = MembershipWitness { + leaf_index: low_leaf_index as Field, + sibling_path: public_data_tree.get_sibling_path(low_leaf_index), + }; + + public_data_tree.update_leaf( + low_leaf_index, + pre_existing_public_data[low_leaf_index].hash(), + ); + + let insertion_witness = + public_data_tree.get_sibling_path(snapshot.next_available_leaf_index); + + public_data_tree.update_leaf(snapshot.next_available_leaf_index, new_leaf.hash()); + + (low_leaf, low_public_data_writes_witness, insertion_witness) + } else { + ( + PublicDataTreeLeafPreimage::empty(), MembershipWitness::empty(), + [0; PUBLIC_DATA_TREE_HEIGHT], + ) + } + } + + struct PrivateBaseRollupInputsBuilder { + tube_data: FixtureBuilder, + pre_existing_notes: [Field; MAX_NOTE_HASHES_PER_TX], + pre_existing_nullifiers: [NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], + pre_existing_contracts: [Field; 2], + pre_existing_public_data: [PublicDataTreeLeafPreimage; PRE_EXISTING_PUBLIC_DATA_LEAVES], + pre_existing_blocks: [Field; 2], + fee_write: (u32, PublicDataTreeLeaf), + + nullifiers: BoundedVec, + constants: ConstantRollupData, + // Index of the item in the pre_existing_public_data array that contains the fee payer's Fee Juice balance. + // Used for building the public data hint read for the payment update request. If set to none, no hint is built. + fee_payer_fee_juice_balance_pre_existing_public_data_index: Option, + } + + impl PrivateBaseRollupInputsBuilder { + fn new() -> Self { + let mut inputs = PrivateBaseRollupInputsBuilder::empty(); + inputs.tube_data = FixtureBuilder::new().in_vk_tree(TUBE_VK_INDEX); + inputs.constants.global_variables.chain_id = fixtures::CHAIN_ID; + inputs.constants.global_variables.version = fixtures::VERSION; + inputs.constants.vk_tree_root = inputs.tube_data.vk_tree_root; + + inputs.pre_existing_blocks[0] = inputs.tube_data.historical_header.hash(); + + inputs + } + + unconstrained fn new_with_previous_kernel(previous_vk_index: u32) -> Self { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + builder.tube_data = builder.tube_data.in_vk_tree(previous_vk_index); + builder + } + + fn build_fee_payer_fee_juice_balance_read_hint( + self, + start_public_data_tree: NonEmptyMerkleTree, + ) -> PublicDataHint { + self.fee_payer_fee_juice_balance_pre_existing_public_data_index.map_or( + PublicDataHint::empty(), + |leaf_index_u32: u32| { + let leaf_index = leaf_index_u32 as Field; + let leaf_preimage = self.pre_existing_public_data[leaf_index]; + let membership_witness = MembershipWitness { + leaf_index, + sibling_path: start_public_data_tree.get_sibling_path(leaf_index_u32), + }; + PublicDataHint { + leaf_slot: leaf_preimage.slot, + value: leaf_preimage.value, + membership_witness, + leaf_preimage, + } + }, + ) + } + + fn extract_subtree_sibling_path( + path: [Field; FULL_HEIGHT], + mut sibling_path: [Field; SIBLING_PATH_LENGTH], + ) -> [Field; SIBLING_PATH_LENGTH] { + let subtree_height = FULL_HEIGHT - SIBLING_PATH_LENGTH; + for i in subtree_height..FULL_HEIGHT { + sibling_path[i - subtree_height] = path[i]; + } + sibling_path + } + + fn update_nullifier_tree_with_new_leaves( + mut self, + nullifier_tree: &mut NonEmptyMerkleTree, + kernel_public_inputs: &mut KernelCircuitPublicInputs, + start_nullifier_tree_snapshot: AppendOnlyTreeSnapshot, + ) -> ([NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], [MembershipWitness; MAX_NULLIFIERS_PER_TX], [Field; MAX_NULLIFIERS_PER_TX], [u32; MAX_NULLIFIERS_PER_TX]) { + let mut nullifier_predecessor_preimages = + [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX]; + let mut low_nullifier_membership_witness = + [MembershipWitness::empty(); MAX_NULLIFIERS_PER_TX]; + + let sorted_new_nullifier_tuples = unsafe { + get_sorted_tuple( + self.nullifiers.storage.map(|insertion: NullifierInsertion| insertion.value), + |a, b| full_field_less_than(b, a), + ) + }; + + let mut sorted_nullifiers = [0; MAX_NULLIFIERS_PER_TX]; + let mut sorted_nullifiers_indexes = [0; MAX_NULLIFIERS_PER_TX]; + + for i in 0..MAX_NULLIFIERS_PER_TX { + if (i as u32) < (MAX_NULLIFIERS_PER_TEST as u32) { + sorted_nullifiers[i] = sorted_new_nullifier_tuples[i].elem; + sorted_nullifiers_indexes[i] = sorted_new_nullifier_tuples[i].original_index; + } else { + sorted_nullifiers[i] = 0; + sorted_nullifiers_indexes[i] = i; + } + } + + let mut pre_existing_nullifiers = self.pre_existing_nullifiers; + + for i in 0..MAX_NULLIFIERS_PER_TEST { + if i < self.nullifiers.len() { + let sorted_tuple = sorted_new_nullifier_tuples[i]; + let new_nullifier = sorted_tuple.elem; + let original_index = sorted_tuple.original_index; + + let low_index = self.nullifiers.get_unchecked(original_index).existing_index; + + kernel_public_inputs.end.nullifiers[original_index] = new_nullifier; + + let mut low_preimage = pre_existing_nullifiers[low_index]; + nullifier_predecessor_preimages[i] = low_preimage; + low_nullifier_membership_witness[i] = MembershipWitness { + leaf_index: low_index as Field, + sibling_path: nullifier_tree.get_sibling_path(low_index), + }; + + low_preimage.next_nullifier = new_nullifier; + low_preimage.next_index = start_nullifier_tree_snapshot + .next_available_leaf_index as u32 + + original_index; + pre_existing_nullifiers[low_index] = low_preimage; + + nullifier_tree.update_leaf(low_index, low_preimage.hash()); + } + } + + ( + nullifier_predecessor_preimages, low_nullifier_membership_witness, + sorted_nullifiers, sorted_nullifiers_indexes, + ) + } + + unconstrained fn build_inputs(mut self) -> PrivateBaseRollupInputs { + let mut tube_data = self.tube_data.to_private_tube_data(); + + let start_note_hash_tree = NonEmptyMerkleTree::new( + self.pre_existing_notes, + [0; NOTE_HASH_TREE_HEIGHT], + [0; NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT], + [0; NOTE_HASH_SUBTREE_HEIGHT], + ); + let start_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { + root: start_note_hash_tree.get_root(), + next_available_leaf_index: start_note_hash_tree.get_next_available_index() as u32, + }; + let note_hash_subtree_sibling_path = PrivateBaseRollupInputsBuilder::extract_subtree_sibling_path( + start_note_hash_tree.get_sibling_path(self.pre_existing_notes.len()), + [0; NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH], + ); + + let mut start_nullifier_tree = NonEmptyMerkleTree::new( + self.pre_existing_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT], + [0; NULLIFIER_SUBTREE_HEIGHT], + ); + + let start_nullifier_tree_snapshot = AppendOnlyTreeSnapshot { + root: start_nullifier_tree.get_root(), + next_available_leaf_index: start_nullifier_tree.get_next_available_index() as u32, + }; + + let mut pre_existing_leaves = [0; AVAILABLE_PUBLIC_DATA_LEAVES_FOR_TEST]; + + for i in 0..self.pre_existing_public_data.len() { + pre_existing_leaves[i] = self.pre_existing_public_data[i].hash(); + } + + let mut start_public_data_tree = NonEmptyMerkleTree::new( + pre_existing_leaves, + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - AVAILABLE_PUBLIC_DATA_SUBTREE_HEIGHT_FOR_TEST], + [0; AVAILABLE_PUBLIC_DATA_SUBTREE_HEIGHT_FOR_TEST], + ); + let start_public_data_tree_snapshot = AppendOnlyTreeSnapshot { + root: start_public_data_tree.get_root(), + next_available_leaf_index: self.pre_existing_public_data.len(), + }; + + let fee_payer_fee_juice_balance_read_hint = + self.build_fee_payer_fee_juice_balance_read_hint(start_public_data_tree); + + let start_archive = NonEmptyMerkleTree::new( + self.pre_existing_blocks, + [0; ARCHIVE_HEIGHT], + [0; ARCHIVE_HEIGHT - 1], + [0; 1], + ); + self.constants.last_archive = AppendOnlyTreeSnapshot { + root: start_archive.get_root(), + next_available_leaf_index: start_archive.get_next_available_index() as u32, + }; + + let (nullifier_predecessor_preimages, nullifier_predecessor_membership_witnesses, sorted_nullifiers, sorted_nullifier_indexes) = self + .update_nullifier_tree_with_new_leaves( + &mut start_nullifier_tree, + &mut tube_data.public_inputs, + start_nullifier_tree_snapshot, + ); + + let nullifier_subtree_sibling_path = PrivateBaseRollupInputsBuilder::extract_subtree_sibling_path( + start_nullifier_tree.get_sibling_path(self.pre_existing_nullifiers.len()), + [0; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], + ); + + let (fee_write_low_leaf_preimage, fee_write_low_leaf_membership_witness, fee_write_sibling_path) = update_public_data_tree( + &mut start_public_data_tree, + start_public_data_tree_snapshot, + self.fee_write, + self.pre_existing_public_data, + ); + + let start = PartialStateReference { + note_hash_tree: start_note_hash_tree_snapshot, + nullifier_tree: start_nullifier_tree_snapshot, + public_data_tree: start_public_data_tree_snapshot, + }; + + let state_diff_hints = PrivateBaseStateDiffHints { + nullifier_predecessor_preimages, + nullifier_predecessor_membership_witnesses, + sorted_nullifiers, + sorted_nullifier_indexes, + note_hash_subtree_sibling_path, + nullifier_subtree_sibling_path, + fee_write_low_leaf_preimage, + fee_write_low_leaf_membership_witness, + fee_write_sibling_path, + }; + + PrivateBaseRollupInputs { + tube_data, + start, + state_diff_hints, + archive_root_membership_witness: MembershipWitness { + leaf_index: 0, + sibling_path: start_archive.get_sibling_path(0), + }, + constants: self.constants, + fee_payer_fee_juice_balance_read_hint, + } + } + + fn execute(self) -> BaseOrMergeRollupPublicInputs { + let inputs = unsafe { self.build_inputs() }; + inputs.execute() + } + + fn succeeds(self) { + let _ = self.execute(); + } + + fn fails(self) { + let _ = self.execute(); + } + } + + impl Empty for PrivateBaseRollupInputsBuilder { + fn empty() -> Self { + Self { + tube_data: FixtureBuilder::new(), + pre_existing_notes: [0; MAX_NOTE_HASHES_PER_TX], + pre_existing_nullifiers: [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX], + pre_existing_contracts: [0; 2], + pre_existing_public_data: [ + PublicDataTreeLeafPreimage::empty(); PRE_EXISTING_PUBLIC_DATA_LEAVES + ], + fee_write: (0, PublicDataTreeLeaf::empty()), + pre_existing_blocks: [0; 2], + nullifiers: BoundedVec::new(), + constants: ConstantRollupData::empty(), + fee_payer_fee_juice_balance_pre_existing_public_data_index: Option::none(), + } } - .base_rollup_circuit() + } + + #[test] + unconstrained fn note_hashes_tree() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + let note_hashes = [27, 28, 29, 30, 31, 32]; + for i in 0..note_hashes.len() { + builder.tube_data.add_new_note_hash(note_hashes[i]); + } + let mut expected_commitments_tree = NonEmptyMerkleTree::new( + [0; MAX_NOTE_HASHES_PER_TX * 2], + [0; NOTE_HASH_TREE_HEIGHT], + [0; NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT - 1], + [0; NOTE_HASH_SUBTREE_HEIGHT + 1], + ); + + let outputs = builder.execute(); + let expected_start_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { + root: expected_commitments_tree.get_root(), + next_available_leaf_index: MAX_NOTE_HASHES_PER_TX as u32, + }; + assert(outputs.start.note_hash_tree.eq(expected_start_note_hash_tree_snapshot)); + + for i in 0..note_hashes.len() { + expected_commitments_tree.update_leaf(i + MAX_NOTE_HASHES_PER_TX, note_hashes[i]); + } + let expected_end_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { + root: expected_commitments_tree.get_root(), + next_available_leaf_index: (MAX_NOTE_HASHES_PER_TX * 2) as u32, + }; + assert(outputs.end.note_hash_tree.eq(expected_end_note_hash_tree_snapshot)); + } + + #[test] + unconstrained fn new_nullifier_tree_empty() { + // This test checks for insertions of all 0 values + // In this special case we will not need to provide sibling paths to check insertion of the nullifier values + // This is because 0 values are not actually inserted into the tree, rather the inserted subtree is left + // empty to begin with. + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.succeeds(); + } + + #[test] + unconstrained fn nullifier_insertion_test() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.nullifiers.push(NullifierInsertion { existing_index: 0, value: 1 }); + let mut tree_nullifiers = [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX * 2]; + tree_nullifiers[0] = NullifierLeafPreimage { + nullifier: 0, + next_nullifier: 1, + next_index: MAX_NULLIFIERS_PER_TX, + }; + tree_nullifiers[1] = builder.pre_existing_nullifiers[1]; + tree_nullifiers[MAX_NULLIFIERS_PER_TX] = + NullifierLeafPreimage { nullifier: 1, next_nullifier: 7, next_index: 1 }; + + let mut end_nullifier_tree = NonEmptyMerkleTree::new( + tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], + [0; NULLIFIER_SUBTREE_HEIGHT + 1], + ); + + let output = builder.execute(); + + assert(output.end.nullifier_tree.eq( + AppendOnlyTreeSnapshot { + root: end_nullifier_tree.get_root(), + next_available_leaf_index: 2 * MAX_NULLIFIERS_PER_TX as u32, + }, + )); + } + + #[test] + unconstrained fn new_nullifier_tree_all_larger() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + for i in 1..builder.nullifiers.max_len() { + builder.nullifiers.push( + NullifierInsertion { existing_index: 1, value: (8 + i) as Field }, + ); + } + + let output = builder.execute(); + let mut tree_nullifiers = [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX * 2]; + tree_nullifiers[0] = builder.pre_existing_nullifiers[0]; + + tree_nullifiers[1] = NullifierLeafPreimage { + nullifier: 7, + next_nullifier: 8, + next_index: MAX_NULLIFIERS_PER_TX, + }; + + let last_index = builder.nullifiers.max_len() - 1; + for i in 0..last_index { + tree_nullifiers[MAX_NULLIFIERS_PER_TX + i] = NullifierLeafPreimage { + nullifier: (8 + i) as Field, + next_nullifier: (8 + i + 1) as Field, + next_index: MAX_NULLIFIERS_PER_TX + i + 1, + }; + } + tree_nullifiers[MAX_NULLIFIERS_PER_TX + last_index] = NullifierLeafPreimage { + nullifier: (8 + last_index) as Field, + next_nullifier: 0, + next_index: 0, + }; + + let mut end_nullifier_tree = NonEmptyMerkleTree::new( + tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], + [0; NULLIFIER_SUBTREE_HEIGHT + 1], + ); + + assert(output.end.nullifier_tree.eq( + AppendOnlyTreeSnapshot { + root: end_nullifier_tree.get_root(), + next_available_leaf_index: 2 * MAX_NULLIFIERS_PER_TX as u32, + }, + )); + } + + #[test(should_fail_with = "Invalid low leaf")] + unconstrained fn new_nullifier_tree_double_spend() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + + builder.fails(); + } + + #[test(should_fail_with = "Invalid low leaf")] + unconstrained fn new_nullifier_tree_double_spend_same_batch() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + + builder.fails(); + } + + #[test] + unconstrained fn empty_tx_effects_hash() { + let outputs = PrivateBaseRollupInputsBuilder::new().execute(); + + let hash_input_flattened = [0; TX_EFFECTS_HASH_INPUT_FIELDS * 32]; + let sha_digest = std::hash::sha256(hash_input_flattened); + let expected_tx_effects_hash = field_from_bytes_32_trunc(sha_digest); + assert_eq(outputs.txs_effects_hash, expected_tx_effects_hash); + } + + #[test] + unconstrained fn empty_block_out_hash() { + let outputs = PrivateBaseRollupInputsBuilder::new().execute(); + assert_eq(outputs.out_hash, 0); + } + + #[test] + unconstrained fn nonempty_block_out_hash() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + for i in 0..MAX_L2_TO_L1_MSGS_PER_TX { + builder.tube_data.add_exposed_l2_to_l1_message( + i as Field, + EthAddress::from_field(1 + i as Field), + ); + } + + let out_hash = builder.execute().out_hash; + let siloed_l2_to_l1_msgs = builder.tube_data.l2_to_l1_msgs.map( + |l2_to_l1_message: ScopedL2ToL1Message| silo_l2_to_l1_message( + l2_to_l1_message, + builder.constants.global_variables.version, + builder.constants.global_variables.chain_id, + ), + ); + + // Since we fill the tree completely, we know to expect a full tree as below + let expected_tree = dep::types::merkle_tree::variable_merkle_tree::tests::generate_full_sha_tree( + siloed_l2_to_l1_msgs.storage(), + ); + assert_eq(out_hash, expected_tree.get_root()); + } + + #[test(should_fail_with = "membership check failed")] + unconstrained fn compute_membership_archive_negative() { + let mut inputs = PrivateBaseRollupInputsBuilder::new().build_inputs(); + inputs.archive_root_membership_witness.sibling_path[0] = 27; + let _output = inputs.execute(); + } + + #[test] + unconstrained fn constants_dont_change() { + let inputs = PrivateBaseRollupInputsBuilder::new().build_inputs(); + let outputs = inputs.execute(); + + assert(inputs.constants.eq(outputs.constants)); + } + + #[test(should_fail_with = "kernel chain_id does not match the rollup chain_id")] + unconstrained fn constants_dont_match_kernels_chain_id() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + builder.constants.global_variables.chain_id = 3; + builder.fails(); + } + + #[test(should_fail_with = "kernel version does not match the rollup version")] + unconstrained fn constants_dont_match_kernels_version() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + builder.constants.global_variables.version += 1; + builder.fails(); + } + + #[test(should_fail_with = "kernel global variables do not match the rollup global variables")] + unconstrained fn constants_global_variables_dont_match_kernels() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + builder.tube_data.global_variables.block_number = 6; + builder.constants.global_variables.block_number = 7; + builder.fails(); + } + + #[test(should_fail_with = "kernel max_block_number is smaller than block number")] + unconstrained fn constants_dont_satisfy_smaller_max_block_number() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + builder.constants.global_variables.block_number = 42; + builder.tube_data.set_max_block_number(5); + builder.fails(); + } + + #[test] + unconstrained fn constants_satisfy_equal_max_block_number() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + builder.constants.global_variables.block_number = 42; + builder.tube_data.set_max_block_number(42); + builder.succeeds(); + } + + #[test] + unconstrained fn constants_satisfy_larger_max_block_number() { + let mut builder = PrivateBaseRollupInputsBuilder::new(); + builder.constants.global_variables.block_number = 42; + builder.tube_data.set_max_block_number(4294967295); + builder.succeeds(); + } + + #[test] + unconstrained fn num_txs_is_1() { + let outputs = PrivateBaseRollupInputsBuilder::new().execute(); + + assert_eq(outputs.num_txs, 1); + } + + #[test] + unconstrained fn updates_fee_payer_balance_with_new_data_write() { + let fee_payer = AztecAddress::from_field(0x1234); + let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + let initial_balance = 300_000; + let gas_fees = GasFees { fee_per_da_gas: 1, fee_per_l2_gas: 2 }; + let gas_used = Gas::new(50_000, 25_000); + let expected_balance = 200_000; + + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + // Set gas + builder.tube_data.gas_used = gas_used; + builder.constants.global_variables.gas_fees = gas_fees; + + // Set fee payer + builder.tube_data.fee_payer = fee_payer; + + // Set pre-existing balance + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: initial_balance, + next_slot: 0, + next_index: 0, + }; + builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(0); + + // Set expected protocol data update + builder.fee_write = (0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }); + + let outputs = builder.execute(); + + // The new public data tree should have updated the balance of the fee payer + let updated_leaf = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: expected_balance, + next_slot: 0, + next_index: 0, + }; + let mut expected_public_data_tree = NonEmptyMerkleTree::new( + [updated_leaf.hash(), 0], + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - 1], + [0; 1], + ); + + assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); + } + + #[test(should_fail_with = "Not enough balance for fee payer to pay for transaction")] + unconstrained fn fails_to_update_fee_payer_balance_if_not_enough_funds() { + let fee_payer = AztecAddress::from_field(0x1234); + let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + // Set low initial balance so it fails! + let initial_balance = 10_000; + let gas_fees = GasFees { fee_per_da_gas: 1, fee_per_l2_gas: 2 }; + let gas_used = Gas::new(50_000, 25_000); + + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + // Set gas + builder.tube_data.gas_used = gas_used; + builder.constants.global_variables.gas_fees = gas_fees; + + // Set fee payer + builder.tube_data.fee_payer = fee_payer; + + // Set pre-existing balance + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: initial_balance, + next_slot: 0, + next_index: 0, + }; + builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(0); + + builder.fails(); + } + + #[test(should_fail_with = "Wrong leaf slot for Fee Juice balance read hint")] + unconstrained fn fails_to_update_fee_payer_balance_if_wrong_read_hint() { + let fee_payer = AztecAddress::from_field(0x1234); + let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + let initial_balance = 300_000; + let expected_balance = 200_000; + let gas_fees = GasFees { fee_per_da_gas: 1, fee_per_l2_gas: 2 }; + let gas_used = Gas::new(50_000, 25_000); + + let mut builder = PrivateBaseRollupInputsBuilder::new(); + + // Set gas + builder.tube_data.gas_used = gas_used; + builder.constants.global_variables.gas_fees = gas_fees; + + // Set fee payer + builder.tube_data.fee_payer = fee_payer; + + // Set pre-existing balance in index 0 + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: initial_balance, + next_slot: 0, + next_index: 0, + }; + + builder.pre_existing_public_data[1] = PublicDataTreeLeafPreimage { + slot: 1, + value: initial_balance, + next_slot: balance_slot, + next_index: 0, + }; + + // But point the read hint to the wrong one! + builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(1); + + // Set expected protocol data update + builder.fee_write = (0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }); + + builder.fails(); + } + + #[test] + fn valid_previous_kernel_empty() { + let builder = unsafe { + PrivateBaseRollupInputsBuilder::new_with_previous_kernel(PRIVATE_KERNEL_EMPTY_INDEX) + }; + + let _res = builder.execute(); } } 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 54f2c17f54a..1fff4bb07ff 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 @@ -1,29 +1,42 @@ use crate::{ abis::{ - base_or_merge_rollup_public_inputs::BaseOrMergeRollupPublicInputs, + base_or_merge_rollup_public_inputs::{BASE_ROLLUP_TYPE, BaseOrMergeRollupPublicInputs}, constant_rollup_data::ConstantRollupData, }, base::{ - base_rollup_inputs::{BaseRollupInputs, KernelData}, - components::{avm_proof_data::AvmProofData, public_tube_data::PublicTubeData}, - state_diff_hints::StateDiffHints, + components::{ + archive::perform_archive_membership_check, constants::validate_combined_constant_data, + fees::compute_fee_payer_fee_juice_balance_leaf_slot, + nullifier_tree::nullifier_tree_batch_insert, public_data_tree::public_data_tree_insert, + }, + state_diff_hints::PublicBaseStateDiffHints, }, + components::{compute_kernel_out_hash, compute_tx_effects_hash}, }; use dep::types::{ abis::{ accumulated_data::CombinedAccumulatedData, + append_only_tree_snapshot::AppendOnlyTreeSnapshot, + avm_circuit_public_inputs::AvmProofData, combined_constant_data::CombinedConstantData, log_hash::{LogHash, ScopedLogHash}, + nullifier_leaf_preimage::NullifierLeafPreimage, + public_data_write::PublicDataWrite, + tube::PublicTubeData, }, constants::{ - ARCHIVE_HEIGHT, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PUBLIC_DATA_TREE_HEIGHT, - TUBE_VK_INDEX, + ARCHIVE_HEIGHT, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_HEIGHT, }, - data::{public_data_hint::PublicDataHint, PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, - KernelCircuitPublicInputs, - merkle_tree::MembershipWitness, + data::{hash::compute_public_data_tree_value, public_data_hint::PublicDataHint}, + hash::silo_l2_to_l1_message, + merkle_tree::{ + append_only_tree, calculate_empty_tree_root, calculate_subtree_root, MembershipWitness, + }, + messaging::l2_to_l1_message::ScopedL2ToL1Message, partial_state_reference::PartialStateReference, - utils::arrays::array_merge, + traits::is_empty, + utils::arrays::{array_merge, find_index_hint}, }; pub struct PublicBaseRollupInputs { @@ -32,23 +45,17 @@ pub struct PublicBaseRollupInputs { start: PartialStateReference, - state_diff_hints: StateDiffHints, + state_diff_hints: PublicBaseStateDiffHints, fee_payer_fee_juice_balance_read_hint: PublicDataHint, - sorted_public_data_writes: [PublicDataTreeLeaf; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - sorted_public_data_writes_indexes: [u32; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - low_public_data_writes_preimages: [PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - low_public_data_writes_witnesses: [MembershipWitness; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - archive_root_membership_witness: MembershipWitness, constants: ConstantRollupData, } impl PublicBaseRollupInputs { - fn generate_kernel_circuit_public_inputs(self) -> KernelCircuitPublicInputs { + fn generate_combined_accumulated_data(self, reverted: bool) -> CombinedAccumulatedData { let from_private = self.tube_data.public_inputs; let from_public = self.avm_proof_data.public_inputs; - let reverted = from_public.reverted; let note_encrypted_logs_hashes = if reverted { from_private.non_revertible_accumulated_data.note_encrypted_logs_hashes @@ -84,7 +91,8 @@ impl PublicBaseRollupInputs { .accumulated_data .unencrypted_logs_hashes .fold(0, |len, l: ScopedLogHash| len + l.log_hash.length); - let end = CombinedAccumulatedData { + + CombinedAccumulatedData { note_hashes: from_public.accumulated_data.note_hashes, nullifiers: from_public.accumulated_data.nullifiers, l2_to_l1_msgs: from_public.accumulated_data.l2_to_l1_msgs, @@ -97,27 +105,6 @@ impl PublicBaseRollupInputs { unencrypted_log_preimages_length, contract_class_log_preimages_length, public_data_writes: from_public.accumulated_data.public_data_writes, - }; - - let constants = - CombinedConstantData::combine(from_private.constants, from_public.global_variables); - - let start_state = PartialStateReference { - note_hash_tree: from_public.start_tree_snapshots.note_hash_tree, - nullifier_tree: from_public.start_tree_snapshots.nullifier_tree, - public_data_tree: from_public.start_tree_snapshots.public_data_tree, - }; - - let revert_code = if from_public.reverted { 1 } else { 0 }; - - KernelCircuitPublicInputs { - constants, - rollup_validation_requests: from_private.rollup_validation_requests, - end, - start_state, - revert_code, - gas_used: from_public.end_gas_used, - fee_payer: from_private.fee_payer, } } @@ -134,24 +121,1208 @@ impl PublicBaseRollupInputs { // self.avm_proof_data.vk_data.validate_in_vk_tree([AVM_VK_INDEX]); // } // TODO: Validate tube_data.public_inputs vs avm_proof_data.public_inputs - // TODO: Deprecate KernelData. - // Temporary workaround to create KernelCircuitPublicInputs from PrivateToPublicKernelCircuitPublicInputs and AvmCircuitPublicInputs - // so that we don't have to modify base_rollup_inputs. - let public_inputs = self.generate_kernel_circuit_public_inputs(); - BaseRollupInputs { - kernel_data: KernelData { public_inputs }, - start: self.start, - state_diff_hints: self.state_diff_hints, - transaction_fee: self.avm_proof_data.public_inputs.transaction_fee, - fee_payer_fee_juice_balance_read_hint: self.fee_payer_fee_juice_balance_read_hint, - sorted_public_data_writes: self.sorted_public_data_writes, - sorted_public_data_writes_indexes: self.sorted_public_data_writes_indexes, - low_public_data_writes_preimages: self.low_public_data_writes_preimages, - low_public_data_writes_witnesses: self.low_public_data_writes_witnesses, - archive_root_membership_witness: self.archive_root_membership_witness, + let reverted = self.avm_proof_data.public_inputs.reverted; + + let combined_accumulated_data = self.generate_combined_accumulated_data(reverted); + + let combined_constant_data = CombinedConstantData::combine( + self.tube_data.public_inputs.constants, + self.avm_proof_data.public_inputs.global_variables, + ); + validate_combined_constant_data(combined_constant_data, self.constants); + + self.validate_kernel_start_state(); + + let rollup_validation_requests = self.tube_data.public_inputs.rollup_validation_requests; + + // Verify the max block number + // TODO #5345: why is block_number a Field and not u32? + if rollup_validation_requests.max_block_number.is_some() { + assert( + self.constants.global_variables.block_number as u32 + <= rollup_validation_requests.max_block_number.unwrap_unchecked(), + "kernel max_block_number is smaller than block number", + ); + } + + let commitments_tree_subroot = + calculate_subtree_root(combined_accumulated_data.note_hashes); + + let empty_commitments_subtree_root = calculate_empty_tree_root(NOTE_HASH_SUBTREE_HEIGHT); + + let end_note_hash_tree_snapshot = append_only_tree::insert_subtree_to_snapshot_tree( + self.start.note_hash_tree, + self.state_diff_hints.note_hash_subtree_sibling_path, + empty_commitments_subtree_root, + commitments_tree_subroot, + NOTE_HASH_SUBTREE_HEIGHT as u8, + ); + + // Insert nullifiers: + let end_nullifier_tree_snapshot = + self.check_nullifier_tree_non_membership_and_insert_to_tree(combined_accumulated_data); + + // Inject protocol update requests for deducting tx_fee from fee_payer's balance + let all_public_data_update_requests = self.calculate_all_public_data_update_requests( + self.avm_proof_data.public_inputs.transaction_fee, + combined_accumulated_data, + ); + + // Validate public data update requests and update public data tree + let end_public_data_tree_snapshot = + self.validate_and_process_public_state(all_public_data_update_requests); + + // Calculate the tx effects hash of the transaction + let siloed_l2_to_l1_msgs = combined_accumulated_data.l2_to_l1_msgs.map( + |message: ScopedL2ToL1Message| silo_l2_to_l1_message( + message, + combined_constant_data.tx_context.version, + combined_constant_data.tx_context.chain_id, + ), + ); + let out_hash = compute_kernel_out_hash(siloed_l2_to_l1_msgs); + + let revert_code = if reverted { 1 } else { 0 }; + + let tx_effects_hash = compute_tx_effects_hash( + combined_accumulated_data, + revert_code, + self.avm_proof_data.public_inputs.transaction_fee, + all_public_data_update_requests, + out_hash, + ); + + // Perform membership checks that the notes provided exist within the historical trees data + perform_archive_membership_check( + self.constants.last_archive.root, + self.archive_root_membership_witness, + combined_constant_data.historical_header, + ); + + BaseOrMergeRollupPublicInputs { + rollup_type: BASE_ROLLUP_TYPE, + num_txs: 1, constants: self.constants, + start: self.start, + end: PartialStateReference { + note_hash_tree: end_note_hash_tree_snapshot, + nullifier_tree: end_nullifier_tree_snapshot, + public_data_tree: end_public_data_tree_snapshot, + }, + txs_effects_hash: tx_effects_hash, + out_hash, + accumulated_fees: self.avm_proof_data.public_inputs.transaction_fee, + } + } + + fn check_nullifier_tree_non_membership_and_insert_to_tree( + self, + accumulated_data: CombinedAccumulatedData, + ) -> AppendOnlyTreeSnapshot { + nullifier_tree_batch_insert( + self.start.nullifier_tree, + accumulated_data.nullifiers, + self.state_diff_hints.sorted_nullifiers, + self.state_diff_hints.sorted_nullifier_indexes, + self.state_diff_hints.nullifier_subtree_sibling_path, + self.state_diff_hints.nullifier_predecessor_preimages, + self.state_diff_hints.nullifier_predecessor_membership_witnesses, + ) + } + + fn create_nullifier_subtree(leaves: [NullifierLeafPreimage; N]) -> Field { + calculate_subtree_root(leaves.map(|leaf: NullifierLeafPreimage| leaf.hash())) + } + + fn validate_kernel_start_state(self) { + let start_tree_snapshots = self.avm_proof_data.public_inputs.start_tree_snapshots; + let start_state = PartialStateReference { + note_hash_tree: start_tree_snapshots.note_hash_tree, + nullifier_tree: start_tree_snapshots.nullifier_tree, + public_data_tree: start_tree_snapshots.public_data_tree, + }; + if !is_empty(start_state) { + assert( + start_state.note_hash_tree.eq(self.start.note_hash_tree), + "Mismatch start state for note hash tree", + ); + assert( + start_state.nullifier_tree.eq(self.start.nullifier_tree), + "Mismatch start state for nullifier tree", + ); + assert( + start_state.public_data_tree.eq(self.start.public_data_tree), + "Mismatch start state for public data tree", + ); + } + } + + fn validate_and_process_public_state( + self, + all_update_requests: [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + ) -> AppendOnlyTreeSnapshot { + let mut snapshot = self.start.public_data_tree; + for i in 0..all_update_requests.len() { + let update_request = all_update_requests[i]; + snapshot = public_data_tree_insert( + update_request, + snapshot, + self.state_diff_hints.low_public_data_writes_preimages[i], + self.state_diff_hints.low_public_data_writes_witnesses[i], + self.state_diff_hints.public_data_tree_sibling_paths[i], + ); + } + snapshot + } + + // Returns an array with all public data update requests for this tx. This includes all update requests + // generated by app circuits, plus the protocol update requests injected by this circuit. The only protocol + // update request we have at the time of this writing is deducting the tx_fee from the fee_payer balance. + fn calculate_all_public_data_update_requests( + self, + tx_fee: Field, + accumulated_data: CombinedAccumulatedData, + ) -> [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] { + let mut all_update_requests: [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX] = + [PublicDataWrite::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; + for i in 0..MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { + all_update_requests[i] = accumulated_data.public_data_writes[i]; + } + + let (payment_update_request, payment_update_index) = + self.build_or_patch_payment_update_request(tx_fee, accumulated_data); + all_update_requests[payment_update_index] = payment_update_request; + + all_update_requests + } + + // Deducts the tx_fee from the FeeJuice balance of the fee_payer. If there is already a PublicDataWrite + // in this tx for their balance (because they issued a 'claim' to increase their balance by bridging from L1), + // update it by subtracting the tx_fee. Otherwise, build a new PublicDataWrite to subtract the tx_fee + // from the balance of the fee_payer, using the fee_payer_fee_juice_balance_read_hint to read the current balance. + // Returns the data update request that subtracts the tx_fee from the fee_payer's balance, and the index where it + // should be inserted in the public data update requests array. + fn build_or_patch_payment_update_request( + self, + tx_fee: Field, + accumulated_data: CombinedAccumulatedData, + ) -> (PublicDataWrite, u32) { + let fee_payer = self.tube_data.public_inputs.fee_payer; + + // TODO(@spalladino) Eventually remove the is_zero condition as we should always charge fees to every tx + if !fee_payer.is_zero() { + let read_hint = self.fee_payer_fee_juice_balance_read_hint; + let leaf_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + let existing_update_index = unsafe { + find_index_hint( + accumulated_data.public_data_writes, + |w: PublicDataWrite| w.leaf_slot == leaf_slot, + ) + }; + + if existing_update_index != MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { + // Is there a balance update already in this tx? If so, update it and return its index. + let existing_update = accumulated_data.public_data_writes[existing_update_index]; + assert( + existing_update.leaf_slot == leaf_slot, + "Wrong leaf slot for Fee Juice balance update request", + ); + assert( + !existing_update.value.lt(tx_fee), + "Not enough balance for fee payer after claim to pay for transaction", + ); + + let value = compute_public_data_tree_value(existing_update.value - tx_fee); + let protocol_update_request = PublicDataWrite { leaf_slot, value }; + (protocol_update_request, existing_update_index as u32) + } else { + // Otherwise, build a new one to be inserted into the protocol update requests at the end of the array. + read_hint.validate(self.start.public_data_tree.root); + + let balance = read_hint.value; + assert( + read_hint.leaf_slot == leaf_slot, + "Wrong leaf slot for Fee Juice balance read hint", + ); + assert( + !balance.lt(tx_fee), + "Not enough balance for fee payer to pay for transaction", + ); + + let value = compute_public_data_tree_value(balance - tx_fee); + let protocol_update_request = PublicDataWrite { leaf_slot, value }; + (protocol_update_request, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) + } + } else { + // Nothing to do, just place an empty update request at the end of the array + (PublicDataWrite::empty(), MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX) + } + } +} + +mod tests { + use crate::{ + abis::{ + base_or_merge_rollup_public_inputs::BaseOrMergeRollupPublicInputs, + constant_rollup_data::ConstantRollupData, + }, + base::{ + components::fees::compute_fee_payer_fee_juice_balance_leaf_slot, + public_base_rollup::PublicBaseRollupInputs, state_diff_hints::PublicBaseStateDiffHints, + }, + components::TX_EFFECTS_HASH_INPUT_FIELDS, + }; + use dep::types::{ + abis::{ + append_only_tree_snapshot::AppendOnlyTreeSnapshot, + nullifier_leaf_preimage::NullifierLeafPreimage, public_data_write::PublicDataWrite, + }, + address::{AztecAddress, EthAddress}, + constants::{ + ARCHIVE_HEIGHT, AVM_VK_INDEX, MAX_L2_TO_L1_MSGS_PER_TX, MAX_NOTE_HASHES_PER_TX, + MAX_NULLIFIERS_PER_TX, MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_HEIGHT, + NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NOTE_HASH_TREE_HEIGHT, NULLIFIER_SUBTREE_HEIGHT, + NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, + PRIVATE_KERNEL_EMPTY_INDEX, PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + PUBLIC_DATA_TREE_HEIGHT, TUBE_VK_INDEX, + }, + data::{public_data_hint::PublicDataHint, PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, + hash::silo_l2_to_l1_message, + merkle_tree::MembershipWitness, + messaging::l2_to_l1_message::ScopedL2ToL1Message, + partial_state_reference::PartialStateReference, + tests::{fixture_builder::FixtureBuilder, fixtures, merkle_tree_utils::NonEmptyMerkleTree}, + traits::{Empty, is_empty}, + utils::{ + arrays::get_sorted_tuple::get_sorted_tuple, + field::{field_from_bytes_32_trunc, full_field_less_than}, + }, + }; + + struct NullifierInsertion { + existing_index: u32, + value: Field, + } + + global MAX_NULLIFIERS_PER_TEST: u32 = 4; + global AVAILABLE_PUBLIC_DATA_LEAVES_FOR_TEST = 128; + global AVAILABLE_PUBLIC_DATA_SUBTREE_HEIGHT_FOR_TEST = 7; + global PRE_EXISTING_PUBLIC_DATA_LEAVES = 10; + + fn update_public_data_tree( + public_data_tree: &mut NonEmptyMerkleTree, + snapshot: AppendOnlyTreeSnapshot, + writes: [(u32, PublicDataTreeLeaf); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + mut pre_existing_public_data: [PublicDataTreeLeafPreimage; EXISTING_LEAVES], + ) -> ([PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], [MembershipWitness; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], [[Field; PUBLIC_DATA_TREE_HEIGHT]; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]) { + let mut low_leaves = + [PublicDataTreeLeafPreimage::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; + let mut low_public_data_writes_witnesses = + [MembershipWitness::empty(); MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; + let mut insertion_witnesses = + [[0; PUBLIC_DATA_TREE_HEIGHT]; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX]; + + let mut current_next_leaf_index = snapshot.next_available_leaf_index; + + for i in 0..writes.len() { + let (low_leaf_index, write) = writes[i]; + if (!is_empty(write)) { + let low_leaf = pre_existing_public_data[low_leaf_index]; + let mut new_leaf = PublicDataTreeLeafPreimage::empty(); + if low_leaf.slot == write.slot { + pre_existing_public_data[low_leaf_index].value = write.value; + } else { + new_leaf = PublicDataTreeLeafPreimage { + slot: write.slot, + value: write.value, + next_slot: low_leaf.next_slot, + next_index: low_leaf.next_index, + }; + pre_existing_public_data[low_leaf_index] = PublicDataTreeLeafPreimage { + slot: low_leaf.slot, + value: low_leaf.value, + next_slot: write.slot, + next_index: PRE_EXISTING_PUBLIC_DATA_LEAVES + i, + }; + } + let low_public_data_writes_witness = MembershipWitness { + leaf_index: low_leaf_index as Field, + sibling_path: public_data_tree.get_sibling_path(low_leaf_index), + }; + + public_data_tree.update_leaf( + low_leaf_index, + pre_existing_public_data[low_leaf_index].hash(), + ); + + let insertion_witness = public_data_tree.get_sibling_path(current_next_leaf_index); + + public_data_tree.update_leaf(current_next_leaf_index, new_leaf.hash()); + + low_leaves[i] = low_leaf; + low_public_data_writes_witnesses[i] = low_public_data_writes_witness; + insertion_witnesses[i] = insertion_witness; + current_next_leaf_index += 1; + } + } + (low_leaves, low_public_data_writes_witnesses, insertion_witnesses) + } + + struct PublicBaseRollupInputsBuilder { + tube_data: FixtureBuilder, + avm_data: FixtureBuilder, + reverted: bool, + transaction_fee: Field, + pre_existing_notes: [Field; MAX_NOTE_HASHES_PER_TX], + pre_existing_nullifiers: [NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], + pre_existing_contracts: [Field; 2], + pre_existing_public_data: [PublicDataTreeLeafPreimage; PRE_EXISTING_PUBLIC_DATA_LEAVES], + pre_existing_blocks: [Field; 2], + // Public data writes generated by app code + public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, + // Public data writes overwritten by the base rollup circuit + overwritten_public_data_writes: [Option; MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + // New public data writes to be created by the protocol (eg a data update request for updating fee payer balance) + protocol_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), PROTOCOL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX>, + nullifiers: BoundedVec, + constants: ConstantRollupData, + // Index of the item in the pre_existing_public_data array that contains the fee payer's Fee Juice balance. + // Used for building the public data hint read for the payment update request. If set to none, no hint is built. + fee_payer_fee_juice_balance_pre_existing_public_data_index: Option, + } + + impl PublicBaseRollupInputsBuilder { + fn new() -> Self { + let mut inputs = PublicBaseRollupInputsBuilder::empty(); + inputs.tube_data = FixtureBuilder::new().in_vk_tree(TUBE_VK_INDEX); + inputs.avm_data = FixtureBuilder::new().in_vk_tree(AVM_VK_INDEX); + + inputs.constants.global_variables.chain_id = fixtures::CHAIN_ID; + inputs.constants.global_variables.version = fixtures::VERSION; + inputs.constants.vk_tree_root = inputs.tube_data.vk_tree_root; + + inputs.pre_existing_blocks[0] = inputs.tube_data.historical_header.hash(); + + inputs + } + + unconstrained fn new_with_previous_kernel(previous_vk_index: u32) -> Self { + let mut builder = PublicBaseRollupInputsBuilder::new(); + builder.tube_data = builder.tube_data.in_vk_tree(previous_vk_index); + builder + } + + fn build_fee_payer_fee_juice_balance_read_hint( + self, + start_public_data_tree: NonEmptyMerkleTree, + ) -> PublicDataHint { + self.fee_payer_fee_juice_balance_pre_existing_public_data_index.map_or( + PublicDataHint::empty(), + |leaf_index_u32: u32| { + let leaf_index = leaf_index_u32 as Field; + let leaf_preimage = self.pre_existing_public_data[leaf_index]; + let membership_witness = MembershipWitness { + leaf_index, + sibling_path: start_public_data_tree.get_sibling_path(leaf_index_u32), + }; + PublicDataHint { + leaf_slot: leaf_preimage.slot, + value: leaf_preimage.value, + membership_witness, + leaf_preimage, + } + }, + ) } - .base_rollup_circuit() + + fn extract_subtree_sibling_path( + path: [Field; FULL_HEIGHT], + mut sibling_path: [Field; SIBLING_PATH_LENGTH], + ) -> [Field; SIBLING_PATH_LENGTH] { + let subtree_height = FULL_HEIGHT - SIBLING_PATH_LENGTH; + for i in subtree_height..FULL_HEIGHT { + sibling_path[i - subtree_height] = path[i]; + } + sibling_path + } + + fn update_nullifier_tree_with_new_leaves( + mut self, + nullifier_tree: &mut NonEmptyMerkleTree, + start_nullifier_tree_snapshot: AppendOnlyTreeSnapshot, + ) -> ([NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], [MembershipWitness; MAX_NULLIFIERS_PER_TX], [Field; MAX_NULLIFIERS_PER_TX], [u32; MAX_NULLIFIERS_PER_TX]) { + let mut nullifier_predecessor_preimages = + [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX]; + let mut low_nullifier_membership_witness = + [MembershipWitness::empty(); MAX_NULLIFIERS_PER_TX]; + + let sorted_new_nullifier_tuples = unsafe { + get_sorted_tuple( + self.nullifiers.storage.map(|insertion: NullifierInsertion| insertion.value), + |a, b| full_field_less_than(b, a), + ) + }; + + let mut sorted_nullifiers = [0; MAX_NULLIFIERS_PER_TX]; + let mut sorted_nullifiers_indexes = [0; MAX_NULLIFIERS_PER_TX]; + + for i in 0..MAX_NULLIFIERS_PER_TX { + if (i as u32) < (MAX_NULLIFIERS_PER_TEST as u32) { + sorted_nullifiers[i] = sorted_new_nullifier_tuples[i].elem; + sorted_nullifiers_indexes[i] = sorted_new_nullifier_tuples[i].original_index; + } else { + sorted_nullifiers[i] = 0; + sorted_nullifiers_indexes[i] = i; + } + } + + let mut pre_existing_nullifiers = self.pre_existing_nullifiers; + + for i in 0..MAX_NULLIFIERS_PER_TEST { + if i < self.nullifiers.len() { + let sorted_tuple = sorted_new_nullifier_tuples[i]; + let new_nullifier = sorted_tuple.elem; + let original_index = sorted_tuple.original_index; + + let low_index = self.nullifiers.get_unchecked(original_index).existing_index; + + let mut low_preimage = pre_existing_nullifiers[low_index]; + nullifier_predecessor_preimages[i] = low_preimage; + low_nullifier_membership_witness[i] = MembershipWitness { + leaf_index: low_index as Field, + sibling_path: nullifier_tree.get_sibling_path(low_index), + }; + + low_preimage.next_nullifier = new_nullifier; + low_preimage.next_index = start_nullifier_tree_snapshot + .next_available_leaf_index as u32 + + original_index; + pre_existing_nullifiers[low_index] = low_preimage; + + nullifier_tree.update_leaf(low_index, low_preimage.hash()); + } + } + + ( + nullifier_predecessor_preimages, low_nullifier_membership_witness, + sorted_nullifiers, sorted_nullifiers_indexes, + ) + } + + unconstrained fn build_inputs(mut self) -> PublicBaseRollupInputs { + let mut tube_data = self.tube_data.to_public_tube_data(); + let mut avm_proof_data = self.avm_data.to_avm_proof_data(self.reverted); + + avm_proof_data.public_inputs.transaction_fee = self.transaction_fee; + + let start_note_hash_tree = NonEmptyMerkleTree::new( + self.pre_existing_notes, + [0; NOTE_HASH_TREE_HEIGHT], + [0; NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT], + [0; NOTE_HASH_SUBTREE_HEIGHT], + ); + let start_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { + root: start_note_hash_tree.get_root(), + next_available_leaf_index: start_note_hash_tree.get_next_available_index() as u32, + }; + let note_hash_subtree_sibling_path = PublicBaseRollupInputsBuilder::extract_subtree_sibling_path( + start_note_hash_tree.get_sibling_path(self.pre_existing_notes.len()), + [0; NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH], + ); + + let mut start_nullifier_tree = NonEmptyMerkleTree::new( + self.pre_existing_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT], + [0; NULLIFIER_SUBTREE_HEIGHT], + ); + + let start_nullifier_tree_snapshot = AppendOnlyTreeSnapshot { + root: start_nullifier_tree.get_root(), + next_available_leaf_index: start_nullifier_tree.get_next_available_index() as u32, + }; + + let mut pre_existing_leaves = [0; AVAILABLE_PUBLIC_DATA_LEAVES_FOR_TEST]; + + for i in 0..self.pre_existing_public_data.len() { + pre_existing_leaves[i] = self.pre_existing_public_data[i].hash(); + } + + let mut start_public_data_tree = NonEmptyMerkleTree::new( + pre_existing_leaves, + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - AVAILABLE_PUBLIC_DATA_SUBTREE_HEIGHT_FOR_TEST], + [0; AVAILABLE_PUBLIC_DATA_SUBTREE_HEIGHT_FOR_TEST], + ); + let start_public_data_tree_snapshot = AppendOnlyTreeSnapshot { + root: start_public_data_tree.get_root(), + next_available_leaf_index: self.pre_existing_public_data.len(), + }; + + let fee_payer_fee_juice_balance_read_hint = + self.build_fee_payer_fee_juice_balance_read_hint(start_public_data_tree); + + let start_archive = NonEmptyMerkleTree::new( + self.pre_existing_blocks, + [0; ARCHIVE_HEIGHT], + [0; ARCHIVE_HEIGHT - 1], + [0; 1], + ); + self.constants.last_archive = AppendOnlyTreeSnapshot { + root: start_archive.get_root(), + next_available_leaf_index: start_archive.get_next_available_index() as u32, + }; + + let (nullifier_predecessor_preimages, nullifier_predecessor_membership_witnesses, sorted_nullifiers, sorted_nullifier_indexes) = self + .update_nullifier_tree_with_new_leaves( + &mut start_nullifier_tree, + start_nullifier_tree_snapshot, + ); + + for i in 0..self.nullifiers.len() { + let nullifier = self.nullifiers.get_unchecked(i); + avm_proof_data.public_inputs.accumulated_data.nullifiers[i] = nullifier.value; + } + + let nullifier_subtree_sibling_path = PublicBaseRollupInputsBuilder::extract_subtree_sibling_path( + start_nullifier_tree.get_sibling_path(self.pre_existing_nullifiers.len()), + [0; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], + ); + + let mut final_public_data_writes: BoundedVec<(u32, PublicDataTreeLeaf), MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX> = + BoundedVec::new(); + final_public_data_writes.extend_from_array(self.public_data_writes.storage); + final_public_data_writes.extend_from_array(self.protocol_public_data_writes.storage); + for i in 0..self.overwritten_public_data_writes.len() { + if self.overwritten_public_data_writes[i].is_some() { + final_public_data_writes.set( + i, + ( + final_public_data_writes.get(i).0, + self.overwritten_public_data_writes[i].unwrap_unchecked(), + ), + ); + } + } + + let (low_public_data_writes_preimages, low_public_data_writes_witnesses, public_data_tree_sibling_paths) = update_public_data_tree( + &mut start_public_data_tree, + start_public_data_tree_snapshot, + final_public_data_writes.storage(), + self.pre_existing_public_data, + ); + + for i in 0..self.public_data_writes.len() { + let leaf = self.public_data_writes.get_unchecked(i).1; + avm_proof_data.public_inputs.accumulated_data.public_data_writes[i] = + PublicDataWrite { leaf_slot: leaf.slot, value: leaf.value }; + } + + let start = PartialStateReference { + note_hash_tree: start_note_hash_tree_snapshot, + nullifier_tree: start_nullifier_tree_snapshot, + public_data_tree: start_public_data_tree_snapshot, + }; + + let state_diff_hints = PublicBaseStateDiffHints { + nullifier_predecessor_preimages, + nullifier_predecessor_membership_witnesses, + sorted_nullifiers, + sorted_nullifier_indexes, + note_hash_subtree_sibling_path, + nullifier_subtree_sibling_path, + low_public_data_writes_preimages, + low_public_data_writes_witnesses, + public_data_tree_sibling_paths, + }; + + PublicBaseRollupInputs { + tube_data, + avm_proof_data, + start, + state_diff_hints, + archive_root_membership_witness: MembershipWitness { + leaf_index: 0, + sibling_path: start_archive.get_sibling_path(0), + }, + constants: self.constants, + fee_payer_fee_juice_balance_read_hint, + } + } + + fn execute(self) -> BaseOrMergeRollupPublicInputs { + let inputs = unsafe { self.build_inputs() }; + inputs.execute() + } + + fn succeeds(self) { + let _ = self.execute(); + } + + fn fails(self) { + let _ = self.execute(); + } + } + + impl Empty for PublicBaseRollupInputsBuilder { + fn empty() -> Self { + Self { + tube_data: FixtureBuilder::empty(), + avm_data: FixtureBuilder::empty(), + transaction_fee: 0, + reverted: false, + pre_existing_notes: [0; MAX_NOTE_HASHES_PER_TX], + pre_existing_nullifiers: [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX], + pre_existing_contracts: [0; 2], + pre_existing_public_data: [ + PublicDataTreeLeafPreimage::empty(); PRE_EXISTING_PUBLIC_DATA_LEAVES + ], + pre_existing_blocks: [0; 2], + public_data_writes: BoundedVec::new(), + overwritten_public_data_writes: [ + Option::none(); MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + ], + protocol_public_data_writes: BoundedVec::new(), + nullifiers: BoundedVec::new(), + constants: ConstantRollupData::empty(), + fee_payer_fee_juice_balance_pre_existing_public_data_index: Option::none(), + } + } + } + + #[test] + unconstrained fn note_hashes_tree() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + + let note_hashes = [27, 28, 29, 30, 31, 32]; + for i in 0..note_hashes.len() { + builder.avm_data.add_new_note_hash(note_hashes[i]); + } + let mut expected_commitments_tree = NonEmptyMerkleTree::new( + [0; MAX_NOTE_HASHES_PER_TX * 2], + [0; NOTE_HASH_TREE_HEIGHT], + [0; NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT - 1], + [0; NOTE_HASH_SUBTREE_HEIGHT + 1], + ); + + let outputs = builder.execute(); + let expected_start_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { + root: expected_commitments_tree.get_root(), + next_available_leaf_index: MAX_NOTE_HASHES_PER_TX as u32, + }; + assert(outputs.start.note_hash_tree.eq(expected_start_note_hash_tree_snapshot)); + + for i in 0..note_hashes.len() { + expected_commitments_tree.update_leaf(i + MAX_NOTE_HASHES_PER_TX, note_hashes[i]); + } + let expected_end_note_hash_tree_snapshot = AppendOnlyTreeSnapshot { + root: expected_commitments_tree.get_root(), + next_available_leaf_index: (MAX_NOTE_HASHES_PER_TX * 2) as u32, + }; + assert(outputs.end.note_hash_tree.eq(expected_end_note_hash_tree_snapshot)); + } + + #[test] + unconstrained fn new_nullifier_tree_empty() { + // This test checks for insertions of all 0 values + // In this special case we will not need to provide sibling paths to check insertion of the nullifier values + // This is because 0 values are not actually inserted into the tree, rather the inserted subtree is left + // empty to begin with. + let mut builder = PublicBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.succeeds(); + } + + #[test] + unconstrained fn nullifier_insertion_test() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.nullifiers.push(NullifierInsertion { existing_index: 0, value: 1 }); + let mut tree_nullifiers = [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX * 2]; + tree_nullifiers[0] = NullifierLeafPreimage { + nullifier: 0, + next_nullifier: 1, + next_index: MAX_NULLIFIERS_PER_TX, + }; + tree_nullifiers[1] = builder.pre_existing_nullifiers[1]; + tree_nullifiers[MAX_NULLIFIERS_PER_TX] = + NullifierLeafPreimage { nullifier: 1, next_nullifier: 7, next_index: 1 }; + + let mut end_nullifier_tree = NonEmptyMerkleTree::new( + tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], + [0; NULLIFIER_SUBTREE_HEIGHT + 1], + ); + + let output = builder.execute(); + + assert(output.end.nullifier_tree.eq( + AppendOnlyTreeSnapshot { + root: end_nullifier_tree.get_root(), + next_available_leaf_index: 2 * MAX_NULLIFIERS_PER_TX as u32, + }, + )); + } + + #[test] + unconstrained fn new_nullifier_tree_all_larger() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + for i in 1..builder.nullifiers.max_len() { + builder.nullifiers.push( + NullifierInsertion { existing_index: 1, value: (8 + i) as Field }, + ); + } + + let output = builder.execute(); + let mut tree_nullifiers = [NullifierLeafPreimage::empty(); MAX_NULLIFIERS_PER_TX * 2]; + tree_nullifiers[0] = builder.pre_existing_nullifiers[0]; + + tree_nullifiers[1] = NullifierLeafPreimage { + nullifier: 7, + next_nullifier: 8, + next_index: MAX_NULLIFIERS_PER_TX, + }; + + let last_index = builder.nullifiers.max_len() - 1; + for i in 0..last_index { + tree_nullifiers[MAX_NULLIFIERS_PER_TX + i] = NullifierLeafPreimage { + nullifier: (8 + i) as Field, + next_nullifier: (8 + i + 1) as Field, + next_index: MAX_NULLIFIERS_PER_TX + i + 1, + }; + } + tree_nullifiers[MAX_NULLIFIERS_PER_TX + last_index] = NullifierLeafPreimage { + nullifier: (8 + last_index) as Field, + next_nullifier: 0, + next_index: 0, + }; + + let mut end_nullifier_tree = NonEmptyMerkleTree::new( + tree_nullifiers.map(|preimage: NullifierLeafPreimage| preimage.hash()), + [0; NULLIFIER_TREE_HEIGHT], + [0; NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT - 1], + [0; NULLIFIER_SUBTREE_HEIGHT + 1], + ); + + assert(output.end.nullifier_tree.eq( + AppendOnlyTreeSnapshot { + root: end_nullifier_tree.get_root(), + next_available_leaf_index: 2 * MAX_NULLIFIERS_PER_TX as u32, + }, + )); + } + + #[test(should_fail_with = "Invalid low leaf")] + unconstrained fn new_nullifier_tree_double_spend() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + + builder.fails(); + } + + #[test(should_fail_with = "Invalid low leaf")] + unconstrained fn new_nullifier_tree_double_spend_same_batch() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + + builder.pre_existing_nullifiers[0] = + NullifierLeafPreimage { nullifier: 0, next_nullifier: 7, next_index: 1 }; + builder.pre_existing_nullifiers[1] = + NullifierLeafPreimage { nullifier: 7, next_nullifier: 0, next_index: 0 }; + + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + builder.nullifiers.push(NullifierInsertion { existing_index: 1, value: 8 }); + + builder.fails(); + } + + #[test] + unconstrained fn empty_tx_effects_hash() { + let outputs = PublicBaseRollupInputsBuilder::new().execute(); + + let hash_input_flattened = [0; TX_EFFECTS_HASH_INPUT_FIELDS * 32]; + let sha_digest = std::hash::sha256(hash_input_flattened); + let expected_tx_effects_hash = field_from_bytes_32_trunc(sha_digest); + assert_eq(outputs.txs_effects_hash, expected_tx_effects_hash); + } + + #[test] + unconstrained fn empty_block_out_hash() { + let outputs = PublicBaseRollupInputsBuilder::new().execute(); + assert_eq(outputs.out_hash, 0); + } + + #[test] + unconstrained fn nonempty_block_out_hash() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + + for i in 0..MAX_L2_TO_L1_MSGS_PER_TX { + builder.avm_data.add_exposed_l2_to_l1_message( + i as Field, + EthAddress::from_field(1 + i as Field), + ); + } + + let out_hash = builder.execute().out_hash; + let siloed_l2_to_l1_msgs = builder.avm_data.l2_to_l1_msgs.map( + |l2_to_l1_message: ScopedL2ToL1Message| silo_l2_to_l1_message( + l2_to_l1_message, + builder.constants.global_variables.version, + builder.constants.global_variables.chain_id, + ), + ); + + // Since we fill the tree completely, we know to expect a full tree as below + let expected_tree = dep::types::merkle_tree::variable_merkle_tree::tests::generate_full_sha_tree( + siloed_l2_to_l1_msgs.storage(), + ); + assert_eq(out_hash, expected_tree.get_root()); + } + + #[test(should_fail_with = "membership check failed")] + unconstrained fn compute_membership_archive_negative() { + let mut inputs = PublicBaseRollupInputsBuilder::new().build_inputs(); + inputs.archive_root_membership_witness.sibling_path[0] = 27; + let _output = inputs.execute(); + } + + #[test] + unconstrained fn constants_dont_change() { + let inputs = PublicBaseRollupInputsBuilder::new().build_inputs(); + let outputs = inputs.execute(); + + assert(inputs.constants.eq(outputs.constants)); + } + + #[test(should_fail_with = "kernel chain_id does not match the rollup chain_id")] + unconstrained fn constants_dont_match_kernels_chain_id() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + builder.constants.global_variables.chain_id = 3; + builder.fails(); + } + + #[test(should_fail_with = "kernel version does not match the rollup version")] + unconstrained fn constants_dont_match_kernels_version() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + builder.constants.global_variables.version += 1; + builder.fails(); + } + + #[test(should_fail_with = "kernel global variables do not match the rollup global variables")] + unconstrained fn constants_global_variables_dont_match_kernels() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + builder.avm_data.global_variables.block_number = 6; + builder.constants.global_variables.block_number = 7; + builder.fails(); + } + + #[test(should_fail_with = "kernel max_block_number is smaller than block number")] + unconstrained fn constants_dont_satisfy_smaller_max_block_number() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + builder.constants.global_variables.block_number = 42; + builder.tube_data.set_max_block_number(5); + builder.fails(); + } + + #[test] + unconstrained fn constants_satisfy_equal_max_block_number() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + builder.constants.global_variables.block_number = 42; + builder.tube_data.set_max_block_number(42); + builder.succeeds(); + } + + #[test] + unconstrained fn constants_satisfy_larger_max_block_number() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + builder.constants.global_variables.block_number = 42; + builder.tube_data.set_max_block_number(4294967295); + builder.succeeds(); + } + + #[test] + unconstrained fn num_txs_is_1() { + let outputs = PublicBaseRollupInputsBuilder::new().execute(); + + assert_eq(outputs.num_txs, 1); + } + + #[test] + unconstrained fn single_public_state_write() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + + builder.pre_existing_public_data[0] = + PublicDataTreeLeafPreimage { slot: 27, value: 28, next_slot: 0, next_index: 0 }; + builder.public_data_writes.push((0, PublicDataTreeLeaf { slot: 27, value: 29 })); + let outputs = builder.execute(); + + let updated_leaf = + PublicDataTreeLeafPreimage { slot: 27, value: 29, next_slot: 0, next_index: 0 }; + + let mut expected_public_data_tree = NonEmptyMerkleTree::new( + [updated_leaf.hash(), 0], + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - 1], + [0; 1], + ); + + assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); + } + + #[test] + unconstrained fn multiple_public_state_read_writes() { + let mut builder = PublicBaseRollupInputsBuilder::new(); + + builder.pre_existing_public_data[0] = + PublicDataTreeLeafPreimage { slot: 20, value: 40, next_slot: 28, next_index: 1 }; + builder.pre_existing_public_data[1] = + PublicDataTreeLeafPreimage { slot: 28, value: 41, next_slot: 29, next_index: 2 }; + builder.pre_existing_public_data[2] = + PublicDataTreeLeafPreimage { slot: 29, value: 42, next_slot: 30, next_index: 3 }; + builder.pre_existing_public_data[3] = + PublicDataTreeLeafPreimage { slot: 30, value: 43, next_slot: 0, next_index: 0 }; + builder.public_data_writes.push((0, PublicDataTreeLeaf { slot: 25, value: 60 })); + builder.public_data_writes.push((0, PublicDataTreeLeaf { slot: 20, value: 90 })); + + let outputs = builder.execute(); + + let mut public_data_leaves = [0; AVAILABLE_PUBLIC_DATA_LEAVES_FOR_TEST]; + public_data_leaves[0] = PublicDataTreeLeafPreimage { + slot: 20, + value: 90, + next_slot: 25, + next_index: PRE_EXISTING_PUBLIC_DATA_LEAVES, + } + .hash(); + public_data_leaves[1] = + PublicDataTreeLeafPreimage { slot: 28, value: 41, next_slot: 29, next_index: 2 }.hash(); + public_data_leaves[2] = + PublicDataTreeLeafPreimage { slot: 29, value: 42, next_slot: 30, next_index: 3 }.hash(); + public_data_leaves[3] = + PublicDataTreeLeafPreimage { slot: 30, value: 43, next_slot: 0, next_index: 0 }.hash(); + public_data_leaves[PRE_EXISTING_PUBLIC_DATA_LEAVES] = + PublicDataTreeLeafPreimage { slot: 25, value: 60, next_slot: 28, next_index: 1 }.hash(); + + let mut expected_public_data_tree = NonEmptyMerkleTree::new( + public_data_leaves, + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - AVAILABLE_PUBLIC_DATA_SUBTREE_HEIGHT_FOR_TEST], + [0; AVAILABLE_PUBLIC_DATA_SUBTREE_HEIGHT_FOR_TEST], + ); + + assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); + } + + #[test] + unconstrained fn updates_fee_payer_balance_with_new_data_write() { + let fee_payer = AztecAddress::from_field(0x1234); + let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + let initial_balance = 300_000; + let tx_fee = 100_000; + let expected_balance = 200_000; + + let mut builder = PublicBaseRollupInputsBuilder::new(); + + // Set fee payer + builder.tube_data.fee_payer = fee_payer; + + // Set pre-existing balance + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: initial_balance, + next_slot: 0, + next_index: 0, + }; + builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(0); + + // Set values for computing exact tx_fee + builder.transaction_fee = tx_fee; + + // Set expected protocol data update + builder.protocol_public_data_writes.push(( + 0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, + )); + + let outputs = builder.execute(); + + // The new public data tree should have updated the balance of the fee payer + let updated_leaf = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: expected_balance, + next_slot: 0, + next_index: 0, + }; + let mut expected_public_data_tree = NonEmptyMerkleTree::new( + [updated_leaf.hash(), 0], + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - 1], + [0; 1], + ); + + assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); + } + + #[test] + unconstrained fn updates_fee_payer_balance_in_existing_data_write() { + let fee_payer = AztecAddress::from_field(0x1234); + let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + let initial_balance = 100_000; + let after_claim_balance = 300_000; + let tx_fee = 100_000; + let expected_balance = 200_000; + + let mut builder = PublicBaseRollupInputsBuilder::new(); + + // Set fee payer + builder.tube_data.fee_payer = fee_payer; + + // Set pre-existing balance, but set no hint for it since we'll update a user update request + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: initial_balance, + next_slot: 0, + next_index: 0, + }; + + // Set values for computing exact tx_fee + builder.transaction_fee = tx_fee; + + // Rollup will overwrite the public data write we are about to push + builder.overwritten_public_data_writes[builder.public_data_writes.len()] = Option::some( + PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, + ); + + // Create an existing data update that corresponds to a claim + builder.public_data_writes.push(( + 0, PublicDataTreeLeaf { slot: balance_slot, value: after_claim_balance }, + )); + + let outputs = builder.execute(); + + // The new public data tree should have updated the balance of the fee payer + let updated_leaf = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: expected_balance, + next_slot: 0, + next_index: 0, + }; + let mut expected_public_data_tree = NonEmptyMerkleTree::new( + [updated_leaf.hash(), 0], + [0; PUBLIC_DATA_TREE_HEIGHT], + [0; PUBLIC_DATA_TREE_HEIGHT - 1], + [0; 1], + ); + + assert_eq(outputs.end.public_data_tree.root, expected_public_data_tree.get_root()); + } + + #[test(should_fail_with = "Not enough balance for fee payer to pay for transaction")] + unconstrained fn fails_to_update_fee_payer_balance_if_not_enough_funds() { + let fee_payer = AztecAddress::from_field(0x1234); + let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + // Set low initial balance so it fails! + let initial_balance = 10_000; + let tx_fee = 100_000; + + let mut builder = PublicBaseRollupInputsBuilder::new(); + + // Set fee payer + builder.tube_data.fee_payer = fee_payer; + + // Set pre-existing balance + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: initial_balance, + next_slot: 0, + next_index: 0, + }; + builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(0); + + // Set values for computing exact tx_fee + builder.transaction_fee = tx_fee; + + // Set expected protocol data update + builder.protocol_public_data_writes.push(( + 0, PublicDataTreeLeaf { slot: balance_slot, value: -90_000 }, + )); + + builder.fails(); + } + + #[test(should_fail_with = "Wrong leaf slot for Fee Juice balance read hint")] + unconstrained fn fails_to_update_fee_payer_balance_if_wrong_read_hint() { + let fee_payer = AztecAddress::from_field(0x1234); + let balance_slot = compute_fee_payer_fee_juice_balance_leaf_slot(fee_payer); + let initial_balance = 300_000; + let expected_balance = 200_000; + let tx_fee = 100_000; + + let mut builder = PublicBaseRollupInputsBuilder::new(); + + // Set fee payer + builder.tube_data.fee_payer = fee_payer; + + // Set pre-existing balance in index 0 + builder.pre_existing_public_data[0] = PublicDataTreeLeafPreimage { + slot: balance_slot, + value: initial_balance, + next_slot: 0, + next_index: 0, + }; + + builder.pre_existing_public_data[1] = PublicDataTreeLeafPreimage { + slot: 1, + value: initial_balance, + next_slot: balance_slot, + next_index: 0, + }; + + // But point the read hint to the wrong one! + builder.fee_payer_fee_juice_balance_pre_existing_public_data_index = Option::some(1); + + // Set values for computing exact tx_fee + builder.transaction_fee = tx_fee; + + // Set expected protocol data update + builder.protocol_public_data_writes.push(( + 0, PublicDataTreeLeaf { slot: balance_slot, value: expected_balance }, + )); + + builder.fails(); + } + + #[test] + fn valid_previous_kernel_empty() { + let builder = unsafe { + PublicBaseRollupInputsBuilder::new_with_previous_kernel(PRIVATE_KERNEL_EMPTY_INDEX) + }; + + let _res = builder.execute(); } } diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/state_diff_hints.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/state_diff_hints.nr index 5015f7ed779..65d27104795 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/state_diff_hints.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/base/state_diff_hints.nr @@ -1,24 +1,46 @@ use dep::types::{ abis::nullifier_leaf_preimage::NullifierLeafPreimage, constants::{ - MAX_NULLIFIERS_PER_TX, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, - NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, - PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, + MAX_NULLIFIERS_PER_TX, MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, + NULLIFIER_TREE_HEIGHT, PUBLIC_DATA_TREE_HEIGHT, }, + data::{PublicDataTreeLeaf, PublicDataTreeLeafPreimage}, merkle_tree::MembershipWitness, }; -pub struct StateDiffHints { - nullifier_predecessor_preimages: [NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], - nullifier_predecessor_membership_witnesses: [MembershipWitness; MAX_NULLIFIERS_PER_TX], +pub(crate) struct PrivateBaseStateDiffHints { + pub(crate) nullifier_predecessor_preimages: [NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], + pub(crate) nullifier_predecessor_membership_witnesses: [MembershipWitness; MAX_NULLIFIERS_PER_TX], - sorted_nullifiers: [Field; MAX_NULLIFIERS_PER_TX], - sorted_nullifier_indexes: [u32; MAX_NULLIFIERS_PER_TX], + pub(crate) sorted_nullifiers: [Field; MAX_NULLIFIERS_PER_TX], + pub(crate) sorted_nullifier_indexes: [u32; MAX_NULLIFIERS_PER_TX], // For inserting the new subtrees into their respective trees: // Note: the insertion leaf index can be derived from the snapshots' `next_available_leaf_index` values (tree // snapshots of the relevant trees are stored in partial state reference). - note_hash_subtree_sibling_path: [Field; NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH], - nullifier_subtree_sibling_path: [Field; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], - public_data_sibling_path: [Field; PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH], + pub(crate) note_hash_subtree_sibling_path: [Field; NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH], + pub(crate) nullifier_subtree_sibling_path: [Field; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], + + pub(crate) fee_write_low_leaf_preimage: PublicDataTreeLeafPreimage, + pub(crate) fee_write_low_leaf_membership_witness: MembershipWitness, + pub(crate) fee_write_sibling_path: [Field; PUBLIC_DATA_TREE_HEIGHT], +} + +pub(crate) struct PublicBaseStateDiffHints { + pub(crate) nullifier_predecessor_preimages: [NullifierLeafPreimage; MAX_NULLIFIERS_PER_TX], + pub(crate) nullifier_predecessor_membership_witnesses: [MembershipWitness; MAX_NULLIFIERS_PER_TX], + + pub(crate) sorted_nullifiers: [Field; MAX_NULLIFIERS_PER_TX], + pub(crate) sorted_nullifier_indexes: [u32; MAX_NULLIFIERS_PER_TX], + + // For inserting the new subtrees into their respective trees: + // Note: the insertion leaf index can be derived from the snapshots' `next_available_leaf_index` values (tree + // snapshots of the relevant trees are stored in partial state reference). + pub(crate) note_hash_subtree_sibling_path: [Field; NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH], + pub(crate) nullifier_subtree_sibling_path: [Field; NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH], + + pub(crate) low_public_data_writes_preimages: [PublicDataTreeLeafPreimage; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + pub(crate) low_public_data_writes_witnesses: [MembershipWitness; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], + pub(crate) public_data_tree_sibling_paths: [[Field; PUBLIC_DATA_TREE_HEIGHT]; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/avm_circuit_public_inputs.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/avm_circuit_public_inputs.nr index 1ce06e8f53a..7affd9031c9 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/avm_circuit_public_inputs.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/avm_circuit_public_inputs.nr @@ -12,7 +12,11 @@ use crate::{ public_call_request::PublicCallRequest, tree_snapshots::TreeSnapshots, }, - constants::{AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH, MAX_ENQUEUED_CALLS_PER_TX}, + constants::{ + AVM_CIRCUIT_PUBLIC_INPUTS_LENGTH, AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS, + MAX_ENQUEUED_CALLS_PER_TX, + }, + proof::{avm_proof::AvmProof, traits::Verifiable, vk_data::VkData}, traits::{Deserialize, Empty, Serialize}, utils::reader::Reader, }; @@ -170,6 +174,18 @@ impl Deserialize for AvmCircuitPublicInputs { } } +pub struct AvmProofData { + pub public_inputs: AvmCircuitPublicInputs, + pub proof: AvmProof, + pub vk_data: VkData, +} + +impl Verifiable for AvmProofData { + fn verify(self) { + // TODO(#8470) + } +} + #[test] fn serialization_of_empty_avm_circuit_public_inputs() { let item = AvmCircuitPublicInputs::empty(); @@ -177,3 +193,4 @@ fn serialization_of_empty_avm_circuit_public_inputs() { let deserialized = AvmCircuitPublicInputs::deserialize(serialized); assert(item.eq(deserialized)); } + diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr index deb4de74aa2..936cd08b645 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/global_variables.nr @@ -20,7 +20,7 @@ pub struct GlobalVariables { // docs:end:global-variables impl GlobalVariables { - fn is_empty(self) -> bool { + pub fn is_empty(self) -> bool { (self.chain_id == 0) & (self.version == 0) & (self.block_number == 0) diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/mod.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/mod.nr index a0eb63aead9..890d2d4429d 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/mod.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/mod.nr @@ -41,3 +41,4 @@ pub mod gas_settings; pub mod gas; pub mod tree_snapshots; +pub mod tube; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_write.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_write.nr index 9a1d57e262c..5f28fed80d9 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_write.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_data_write.nr @@ -1,8 +1,8 @@ use crate::{constants::PUBLIC_DATA_WRITE_LENGTH, traits::{Deserialize, Empty, Serialize}}; pub struct PublicDataWrite { - leaf_slot: Field, - value: Field, + pub leaf_slot: Field, + pub value: Field, } impl Eq for PublicDataWrite { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/tube.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/tube.nr new file mode 100644 index 00000000000..e36c2ead478 --- /dev/null +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/tube.nr @@ -0,0 +1,43 @@ +use crate::{ + abis::kernel_circuit_public_inputs::{ + KernelCircuitPublicInputs, PrivateToPublicKernelCircuitPublicInputs, + }, + constants::HONK_VERIFICATION_KEY_LENGTH_IN_FIELDS, + proof::{traits::Verifiable, tube_proof::TubeProof, vk_data::VkData}, +}; + +pub struct PublicTubeData { + pub public_inputs: PrivateToPublicKernelCircuitPublicInputs, + pub proof: TubeProof, + pub vk_data: VkData, +} + +impl Verifiable for PublicTubeData { + fn verify(self) { + let inputs = PrivateToPublicKernelCircuitPublicInputs::serialize(self.public_inputs); + std::verify_proof( + self.vk_data.vk.key, + self.proof.fields, + inputs, + self.vk_data.vk.hash, + ); + } +} + +pub struct PrivateTubeData { + public_inputs: KernelCircuitPublicInputs, + proof: TubeProof, + vk_data: VkData, +} + +impl Verifiable for PrivateTubeData { + fn verify(self) { + let inputs = KernelCircuitPublicInputs::serialize(self.public_inputs); + std::verify_proof( + self.vk_data.vk.key, + self.proof.fields, + inputs, + self.vk_data.vk.hash, + ); + } +} 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 9d875fcf4fe..fcf4127112f 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -62,14 +62,13 @@ pub global ARCHIVE_TREE_ID = 4; // SUB-TREES RELATED CONSTANTS pub global NOTE_HASH_SUBTREE_HEIGHT: u32 = 6; pub global NULLIFIER_SUBTREE_HEIGHT: u32 = 6; +// Deprecated: to be removed after removal of legacy ts trees pub global PUBLIC_DATA_SUBTREE_HEIGHT: u32 = 6; pub global L1_TO_L2_MSG_SUBTREE_HEIGHT: u32 = 4; pub global NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH: u32 = NOTE_HASH_TREE_HEIGHT - NOTE_HASH_SUBTREE_HEIGHT; pub global NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH: u32 = NULLIFIER_TREE_HEIGHT - NULLIFIER_SUBTREE_HEIGHT; -pub global PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH: u32 = - PUBLIC_DATA_TREE_HEIGHT - PUBLIC_DATA_SUBTREE_HEIGHT; pub global L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH: u32 = L1_TO_L2_MSG_TREE_HEIGHT - L1_TO_L2_MSG_SUBTREE_HEIGHT; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr index f0cc741a909..d6d2a363fd3 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/merkle_tree/indexed_tree.nr @@ -29,6 +29,8 @@ where Leaf: Hash + Empty, { // A permutation to the values is provided to make the insertion use only one insertion strategy + // However, for the actual insertion in the tree the original order is respected, the sorting is only used for validation of the links + // and low leaf updates. check_permutation(values_to_insert, sorted_values, sorted_values_indexes); // Now, update the existing leaves with the new leaves @@ -98,3 +100,58 @@ where next_available_leaf_index: start_insertion_index + (values_to_insert.len() as u32), } } + +pub fn insert( + mut snapshot: AppendOnlyTreeSnapshot, + value: Value, + low_leaf_preimage: Leaf, + low_leaf_membership_witness: MembershipWitness, + insertion_sibling_path: [Field; TreeHeight], + is_valid_low_leaf: fn(Leaf, Value) -> bool, + update_low_leaf: fn(Leaf, Value, u32) -> Leaf, + build_insertion_leaf: fn(Value, Leaf) -> Leaf, +) -> AppendOnlyTreeSnapshot +where + Value: Eq + Empty, + Leaf: Hash + Empty, +{ + assert(is_valid_low_leaf(low_leaf_preimage, value), "Invalid low leaf"); + + // perform membership check for the low leaf against the original root + assert_check_membership( + low_leaf_preimage.hash(), + low_leaf_membership_witness.leaf_index, + low_leaf_membership_witness.sibling_path, + snapshot.root, + ); + + // Calculate the new value of the low_leaf + let updated_low_leaf = + update_low_leaf(low_leaf_preimage, value, snapshot.next_available_leaf_index); + + snapshot.root = root_from_sibling_path( + updated_low_leaf.hash(), + low_leaf_membership_witness.leaf_index, + low_leaf_membership_witness.sibling_path, + ); + + let insertion_leaf = build_insertion_leaf(value, low_leaf_preimage); + + assert_check_membership( + 0, + snapshot.next_available_leaf_index as Field, + insertion_sibling_path, + snapshot.root, + ); + + // Calculate the new root + snapshot.root = root_from_sibling_path( + insertion_leaf.hash(), + snapshot.next_available_leaf_index as Field, + insertion_sibling_path, + ); + + snapshot.next_available_leaf_index += 1; + + snapshot +} diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr index a2bf6a855d6..94294b65c16 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixture_builder.nr @@ -1,9 +1,10 @@ use crate::{ abis::{ accumulated_data::{ - CombinedAccumulatedData, PrivateAccumulatedData, PrivateAccumulatedDataBuilder, - PrivateToPublicAccumulatedData, + avm_accumulated_data::AvmAccumulatedData, CombinedAccumulatedData, + PrivateAccumulatedData, PrivateAccumulatedDataBuilder, PrivateToPublicAccumulatedData, }, + avm_circuit_public_inputs::AvmProofData, call_context::CallContext, combined_constant_data::CombinedConstantData, function_data::FunctionData, @@ -26,6 +27,7 @@ use crate::{ public_data_write::PublicDataWrite, read_request::{ReadRequest, ScopedReadRequest}, side_effect::Counted, + tube::{PrivateTubeData, PublicTubeData}, tx_constant_data::TxConstantData, validation_requests::{ KeyValidationRequest, KeyValidationRequestAndGenerator, PrivateValidationRequests, @@ -1044,6 +1046,38 @@ impl FixtureBuilder { fn vk_tree_root() -> Field { fixtures::vk_tree::get_vk_merkle_tree().get_root() } + + fn to_private_tube_data(self) -> PrivateTubeData { + let mut result: PrivateTubeData = std::mem::zeroed(); + result.public_inputs = self.to_kernel_circuit_public_inputs(); + result + } + + fn to_public_tube_data(self) -> PublicTubeData { + let mut result: PublicTubeData = std::mem::zeroed(); + result.public_inputs = self.to_private_to_public_kernel_circuit_public_inputs(true); + result + } + + fn to_avm_accumulated_data(self) -> AvmAccumulatedData { + AvmAccumulatedData { + note_hashes: self.note_hashes.storage().map(|n: ScopedNoteHash| n.note_hash.value), + nullifiers: self.nullifiers.storage().map(|n: ScopedNullifier| n.nullifier.value), + l2_to_l1_msgs: self.l2_to_l1_msgs.storage(), + unencrypted_logs_hashes: self.unencrypted_logs_hashes.storage(), + public_data_writes: self.public_data_writes.storage(), + } + } + + fn to_avm_proof_data(self, reverted: bool) -> AvmProofData { + let mut result: AvmProofData = std::mem::zeroed(); + + result.public_inputs.reverted = reverted; + result.public_inputs.global_variables = self.global_variables; + result.public_inputs.accumulated_data = self.to_avm_accumulated_data(); + + result + } } impl Empty for FixtureBuilder { diff --git a/noir/noir-repo/tooling/noir_js_types/src/types.ts b/noir/noir-repo/tooling/noir_js_types/src/types.ts index 03e2a93f851..0cfe5d05a17 100644 --- a/noir/noir-repo/tooling/noir_js_types/src/types.ts +++ b/noir/noir-repo/tooling/noir_js_types/src/types.ts @@ -1,5 +1,5 @@ export type Field = string | number | boolean; -export type InputValue = Field | InputMap | (Field | InputMap)[]; +export type InputValue = Field | InputMap | InputValue[]; export type InputMap = { [key: string]: InputValue }; export type Visibility = 'public' | 'private' | 'databus'; diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index d315587a46f..ff6911cf4f3 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -38,7 +38,6 @@ export const PUBLIC_DATA_SUBTREE_HEIGHT = 6; export const L1_TO_L2_MSG_SUBTREE_HEIGHT = 4; export const NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH = 34; export const NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH = 34; -export const PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH = 34; export const L1_TO_L2_MSG_SUBTREE_SIBLING_PATH_LENGTH = 35; export const MAX_NOTE_HASHES_PER_TX = 64; export const MAX_NULLIFIERS_PER_TX = 64; diff --git a/yarn-project/circuits.js/src/structs/rollup/base_rollup_hints.ts b/yarn-project/circuits.js/src/structs/rollup/base_rollup_hints.ts index 15364ffb636..d9176c82f6d 100644 --- a/yarn-project/circuits.js/src/structs/rollup/base_rollup_hints.ts +++ b/yarn-project/circuits.js/src/structs/rollup/base_rollup_hints.ts @@ -1,53 +1,98 @@ -import { makeTuple } from '@aztec/foundation/array'; -import { BufferReader, type Tuple, serializeToBuffer } from '@aztec/foundation/serialize'; +import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; -import { - ARCHIVE_HEIGHT, - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - PUBLIC_DATA_TREE_HEIGHT, -} from '../../constants.gen.js'; +import { ARCHIVE_HEIGHT } from '../../constants.gen.js'; import { MembershipWitness } from '../membership_witness.js'; import { PartialStateReference } from '../partial_state_reference.js'; import { PublicDataHint } from '../public_data_hint.js'; -import { type UInt32 } from '../shared.js'; -import { PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '../trees/index.js'; import { ConstantRollupData } from './constant_rollup_data.js'; -import { StateDiffHints } from './state_diff_hints.js'; +import { PrivateBaseStateDiffHints, PublicBaseStateDiffHints } from './state_diff_hints.js'; -export class BaseRollupHints { +export type BaseRollupHints = PrivateBaseRollupHints | PublicBaseRollupHints; + +export class PrivateBaseRollupHints { constructor( /** Partial state reference at the start of the rollup. */ public start: PartialStateReference, /** Hints used while proving state diff validity. */ - public stateDiffHints: StateDiffHints, + public stateDiffHints: PrivateBaseStateDiffHints, /** Public data read hint for accessing the balance of the fee payer. */ public feePayerFeeJuiceBalanceReadHint: PublicDataHint, - /** - * The public data writes to be inserted in the tree, sorted high slot to low slot. - */ - public sortedPublicDataWrites: Tuple, /** - * The indexes of the sorted public data writes to the original ones. - */ - public sortedPublicDataWritesIndexes: Tuple, - /** - * The public data writes which need to be updated to perform the batch insertion of the new public data writes. - * See `StandardIndexedTree.batchInsert` function for more details. + * Membership witnesses of blocks referred by each of the 2 kernels. */ - public lowPublicDataWritesPreimages: Tuple< - PublicDataTreeLeafPreimage, - typeof MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - >, + public archiveRootMembershipWitness: MembershipWitness, /** - * Membership witnesses for the nullifiers which need to be updated to perform the batch insertion of the new - * nullifiers. + * Data which is not modified by the base rollup circuit. */ - public lowPublicDataWritesMembershipWitnesses: Tuple< - MembershipWitness, - typeof MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - >, + public constants: ConstantRollupData, + ) {} + + static from(fields: FieldsOf): PrivateBaseRollupHints { + return new PrivateBaseRollupHints(...PrivateBaseRollupHints.getFields(fields)); + } + + static getFields(fields: FieldsOf) { + return [ + fields.start, + fields.stateDiffHints, + fields.feePayerFeeJuiceBalanceReadHint, + fields.archiveRootMembershipWitness, + fields.constants, + ] as const; + } + + /** + * Serializes the inputs to a buffer. + * @returns The inputs serialized to a buffer. + */ + toBuffer() { + return serializeToBuffer(...PrivateBaseRollupHints.getFields(this)); + } + + /** + * Serializes the inputs to a hex string. + * @returns The instance serialized to a hex string. + */ + toString() { + return this.toBuffer().toString('hex'); + } + + static fromBuffer(buffer: Buffer | BufferReader): PrivateBaseRollupHints { + const reader = BufferReader.asReader(buffer); + return new PrivateBaseRollupHints( + reader.readObject(PartialStateReference), + reader.readObject(PrivateBaseStateDiffHints), + reader.readObject(PublicDataHint), + MembershipWitness.fromBuffer(reader, ARCHIVE_HEIGHT), + reader.readObject(ConstantRollupData), + ); + } + + static fromString(str: string) { + return PrivateBaseRollupHints.fromBuffer(Buffer.from(str, 'hex')); + } + + static empty() { + return new PrivateBaseRollupHints( + PartialStateReference.empty(), + PrivateBaseStateDiffHints.empty(), + PublicDataHint.empty(), + MembershipWitness.empty(ARCHIVE_HEIGHT), + ConstantRollupData.empty(), + ); + } +} + +export class PublicBaseRollupHints { + constructor( + /** Partial state reference at the start of the rollup. */ + public start: PartialStateReference, + /** Hints used while proving state diff validity. */ + public stateDiffHints: PublicBaseStateDiffHints, + /** Public data read hint for accessing the balance of the fee payer. */ + public feePayerFeeJuiceBalanceReadHint: PublicDataHint, /** * Membership witnesses of blocks referred by each of the 2 kernels. */ @@ -58,19 +103,15 @@ export class BaseRollupHints { public constants: ConstantRollupData, ) {} - static from(fields: FieldsOf): BaseRollupHints { - return new BaseRollupHints(...BaseRollupHints.getFields(fields)); + static from(fields: FieldsOf): PublicBaseRollupHints { + return new PublicBaseRollupHints(...PublicBaseRollupHints.getFields(fields)); } - static getFields(fields: FieldsOf) { + static getFields(fields: FieldsOf) { return [ fields.start, fields.stateDiffHints, fields.feePayerFeeJuiceBalanceReadHint, - fields.sortedPublicDataWrites, - fields.sortedPublicDataWritesIndexes, - fields.lowPublicDataWritesPreimages, - fields.lowPublicDataWritesMembershipWitnesses, fields.archiveRootMembershipWitness, fields.constants, ] as const; @@ -81,7 +122,7 @@ export class BaseRollupHints { * @returns The inputs serialized to a buffer. */ toBuffer() { - return serializeToBuffer(...BaseRollupHints.getFields(this)); + return serializeToBuffer(...PublicBaseRollupHints.getFields(this)); } /** @@ -92,36 +133,26 @@ export class BaseRollupHints { return this.toBuffer().toString('hex'); } - static fromBuffer(buffer: Buffer | BufferReader): BaseRollupHints { + static fromBuffer(buffer: Buffer | BufferReader): PublicBaseRollupHints { const reader = BufferReader.asReader(buffer); - return new BaseRollupHints( + return new PublicBaseRollupHints( reader.readObject(PartialStateReference), - reader.readObject(StateDiffHints), + reader.readObject(PublicBaseStateDiffHints), reader.readObject(PublicDataHint), - reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataTreeLeaf), - reader.readNumbers(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX), - reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataTreeLeafPreimage), - reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, { - fromBuffer: buffer => MembershipWitness.fromBuffer(buffer, PUBLIC_DATA_TREE_HEIGHT), - }), MembershipWitness.fromBuffer(reader, ARCHIVE_HEIGHT), reader.readObject(ConstantRollupData), ); } static fromString(str: string) { - return BaseRollupHints.fromBuffer(Buffer.from(str, 'hex')); + return PublicBaseRollupHints.fromBuffer(Buffer.from(str, 'hex')); } static empty() { - return new BaseRollupHints( + return new PublicBaseRollupHints( PartialStateReference.empty(), - StateDiffHints.empty(), + PublicBaseStateDiffHints.empty(), PublicDataHint.empty(), - makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataTreeLeaf.empty), - makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, () => 0), - makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataTreeLeafPreimage.empty), - makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, () => MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT)), MembershipWitness.empty(ARCHIVE_HEIGHT), ConstantRollupData.empty(), ); diff --git a/yarn-project/circuits.js/src/structs/rollup/private_base_rollup_inputs.ts b/yarn-project/circuits.js/src/structs/rollup/private_base_rollup_inputs.ts index 5cc93fdeb4e..1d354861e1a 100644 --- a/yarn-project/circuits.js/src/structs/rollup/private_base_rollup_inputs.ts +++ b/yarn-project/circuits.js/src/structs/rollup/private_base_rollup_inputs.ts @@ -2,11 +2,11 @@ import { hexSchemaFor } from '@aztec/foundation/schemas'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; -import { BaseRollupHints } from './base_rollup_hints.js'; +import { PrivateBaseRollupHints } from './base_rollup_hints.js'; import { PrivateTubeData } from './private_tube_data.js'; export class PrivateBaseRollupInputs { - constructor(public tubeData: PrivateTubeData, public hints: BaseRollupHints) {} + constructor(public tubeData: PrivateTubeData, public hints: PrivateBaseRollupHints) {} static from(fields: FieldsOf): PrivateBaseRollupInputs { return new PrivateBaseRollupInputs(...PrivateBaseRollupInputs.getFields(fields)); @@ -18,7 +18,7 @@ export class PrivateBaseRollupInputs { static fromBuffer(buffer: Buffer | BufferReader): PrivateBaseRollupInputs { const reader = BufferReader.asReader(buffer); - return new PrivateBaseRollupInputs(reader.readObject(PrivateTubeData), reader.readObject(BaseRollupHints)); + return new PrivateBaseRollupInputs(reader.readObject(PrivateTubeData), reader.readObject(PrivateBaseRollupHints)); } toBuffer() { @@ -34,7 +34,7 @@ export class PrivateBaseRollupInputs { } static empty() { - return new PrivateBaseRollupInputs(PrivateTubeData.empty(), BaseRollupHints.empty()); + return new PrivateBaseRollupInputs(PrivateTubeData.empty(), PrivateBaseRollupHints.empty()); } /** Returns a hex representation for JSON serialization. */ diff --git a/yarn-project/circuits.js/src/structs/rollup/public_base_rollup_inputs.ts b/yarn-project/circuits.js/src/structs/rollup/public_base_rollup_inputs.ts index b8ab34a167f..1fa11ed3688 100644 --- a/yarn-project/circuits.js/src/structs/rollup/public_base_rollup_inputs.ts +++ b/yarn-project/circuits.js/src/structs/rollup/public_base_rollup_inputs.ts @@ -3,11 +3,15 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; import { type FieldsOf } from '@aztec/foundation/types'; import { AvmProofData } from './avm_proof_data.js'; -import { BaseRollupHints } from './base_rollup_hints.js'; +import { PublicBaseRollupHints } from './base_rollup_hints.js'; import { PublicTubeData } from './public_tube_data.js'; export class PublicBaseRollupInputs { - constructor(public tubeData: PublicTubeData, public avmProofData: AvmProofData, public hints: BaseRollupHints) {} + constructor( + public tubeData: PublicTubeData, + public avmProofData: AvmProofData, + public hints: PublicBaseRollupHints, + ) {} static from(fields: FieldsOf): PublicBaseRollupInputs { return new PublicBaseRollupInputs(...PublicBaseRollupInputs.getFields(fields)); @@ -22,7 +26,7 @@ export class PublicBaseRollupInputs { return new PublicBaseRollupInputs( reader.readObject(PublicTubeData), reader.readObject(AvmProofData), - reader.readObject(BaseRollupHints), + reader.readObject(PublicBaseRollupHints), ); } @@ -38,7 +42,7 @@ export class PublicBaseRollupInputs { } static empty() { - return new PublicBaseRollupInputs(PublicTubeData.empty(), AvmProofData.empty(), BaseRollupHints.empty()); + return new PublicBaseRollupInputs(PublicTubeData.empty(), AvmProofData.empty(), PublicBaseRollupHints.empty()); } /** Returns a hex representation for JSON serialization. */ diff --git a/yarn-project/circuits.js/src/structs/rollup/state_diff_hints.test.ts b/yarn-project/circuits.js/src/structs/rollup/state_diff_hints.test.ts index b3c9a9f0ac6..a473a1375fa 100644 --- a/yarn-project/circuits.js/src/structs/rollup/state_diff_hints.test.ts +++ b/yarn-project/circuits.js/src/structs/rollup/state_diff_hints.test.ts @@ -1,11 +1,18 @@ -import { makeStateDiffHints } from '../../tests/factories.js'; -import { StateDiffHints } from './state_diff_hints.js'; +import { makePrivateBaseStateDiffHints, makePublicBaseStateDiffHints } from '../../tests/factories.js'; +import { PrivateBaseStateDiffHints, PublicBaseStateDiffHints } from './state_diff_hints.js'; describe('StateDiffHints', () => { - it('serializes to buffer and deserializes it back', () => { - const expected = makeStateDiffHints(); + it('serializes private hints to buffer and deserializes it back', () => { + const expected = makePrivateBaseStateDiffHints(); const buffer = expected.toBuffer(); - const res = StateDiffHints.fromBuffer(buffer); + const res = PrivateBaseStateDiffHints.fromBuffer(buffer); + expect(res).toEqual(expected); + }); + + it('serializes public hints to buffer and deserializes it back', () => { + const expected = makePublicBaseStateDiffHints(); + const buffer = expected.toBuffer(); + const res = PublicBaseStateDiffHints.fromBuffer(buffer); expect(res).toEqual(expected); }); }); diff --git a/yarn-project/circuits.js/src/structs/rollup/state_diff_hints.ts b/yarn-project/circuits.js/src/structs/rollup/state_diff_hints.ts index 288f5c5d85b..f1b2a5e986b 100644 --- a/yarn-project/circuits.js/src/structs/rollup/state_diff_hints.ts +++ b/yarn-project/circuits.js/src/structs/rollup/state_diff_hints.ts @@ -5,18 +5,19 @@ import { type FieldsOf } from '@aztec/foundation/types'; import { MAX_NULLIFIERS_PER_TX, + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, NULLIFIER_TREE_HEIGHT, - PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, + PUBLIC_DATA_TREE_HEIGHT, } from '../../constants.gen.js'; import { MembershipWitness } from '../membership_witness.js'; -import { NullifierLeafPreimage } from '../trees/index.js'; +import { NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '../trees/index.js'; /** - * Hints used while proving state diff validity. + * Hints used while proving state diff validity for the private base rollup. */ -export class StateDiffHints { +export class PrivateBaseStateDiffHints { constructor( /** * The nullifiers which need to be updated to perform the batch insertion of the new nullifiers. @@ -47,17 +48,26 @@ export class StateDiffHints { * Sibling path "pointing to" where the new nullifiers subtree should be inserted into the nullifier tree. */ public nullifierSubtreeSiblingPath: Tuple, + + /** + * Low leaf for the fee write in the public data tree. + */ + public feeWriteLowLeafPreimage: PublicDataTreeLeafPreimage, /** - * Sibling path "pointing to" where the new public data subtree should be inserted into the public data tree. + * Membership witness for the low leaf for the fee write in the public data tree. */ - public publicDataSiblingPath: Tuple, + public feeWriteLowLeafMembershipWitness: MembershipWitness, + /** + * Sibling path "pointing to" where the fee write should be inserted into the public data tree. + */ + public feeWriteSiblingPath: Tuple, ) {} - static from(fields: FieldsOf): StateDiffHints { - return new StateDiffHints(...StateDiffHints.getFields(fields)); + static from(fields: FieldsOf): PrivateBaseStateDiffHints { + return new PrivateBaseStateDiffHints(...PrivateBaseStateDiffHints.getFields(fields)); } - static getFields(fields: FieldsOf) { + static getFields(fields: FieldsOf) { return [ fields.nullifierPredecessorPreimages, fields.nullifierPredecessorMembershipWitnesses, @@ -65,7 +75,9 @@ export class StateDiffHints { fields.sortedNullifierIndexes, fields.noteHashSubtreeSiblingPath, fields.nullifierSubtreeSiblingPath, - fields.publicDataSiblingPath, + fields.feeWriteLowLeafPreimage, + fields.feeWriteLowLeafMembershipWitness, + fields.feeWriteSiblingPath, ] as const; } @@ -74,17 +86,17 @@ export class StateDiffHints { * @returns A buffer of the serialized state diff hints. */ toBuffer(): Buffer { - return serializeToBuffer(...StateDiffHints.getFields(this)); + return serializeToBuffer(...PrivateBaseStateDiffHints.getFields(this)); } /** * Deserializes the state diff hints from a buffer. * @param buffer - A buffer to deserialize from. - * @returns A new StateDiffHints instance. + * @returns A new PrivateBaseStateDiffHints instance. */ - static fromBuffer(buffer: Buffer | BufferReader): StateDiffHints { + static fromBuffer(buffer: Buffer | BufferReader): PrivateBaseStateDiffHints { const reader = BufferReader.asReader(buffer); - return new StateDiffHints( + return new PrivateBaseStateDiffHints( reader.readArray(MAX_NULLIFIERS_PER_TX, NullifierLeafPreimage), reader.readArray(MAX_NULLIFIERS_PER_TX, { fromBuffer: buffer => MembershipWitness.fromBuffer(buffer, NULLIFIER_TREE_HEIGHT), @@ -93,19 +105,149 @@ export class StateDiffHints { reader.readNumbers(MAX_NULLIFIERS_PER_TX), reader.readArray(NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, Fr), reader.readArray(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, Fr), - reader.readArray(PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, Fr), + reader.readObject(PublicDataTreeLeafPreimage), + MembershipWitness.fromBuffer(reader, PUBLIC_DATA_TREE_HEIGHT), + reader.readArray(PUBLIC_DATA_TREE_HEIGHT, Fr), + ); + } + + static empty() { + return new PrivateBaseStateDiffHints( + makeTuple(MAX_NULLIFIERS_PER_TX, NullifierLeafPreimage.empty), + makeTuple(MAX_NULLIFIERS_PER_TX, () => MembershipWitness.empty(NULLIFIER_TREE_HEIGHT)), + makeTuple(MAX_NULLIFIERS_PER_TX, Fr.zero), + makeTuple(MAX_NULLIFIERS_PER_TX, () => 0), + makeTuple(NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, Fr.zero), + makeTuple(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, Fr.zero), + PublicDataTreeLeafPreimage.empty(), + MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT), + makeTuple(PUBLIC_DATA_TREE_HEIGHT, Fr.zero), + ); + } +} + +export class PublicBaseStateDiffHints { + constructor( + /** + * The nullifiers which need to be updated to perform the batch insertion of the new nullifiers. + * See `StandardIndexedTree.batchInsert` function for more details. + */ + public nullifierPredecessorPreimages: Tuple, + /** + * Membership witnesses for the nullifiers which need to be updated to perform the batch insertion of the new + * nullifiers. + */ + public nullifierPredecessorMembershipWitnesses: Tuple< + MembershipWitness, + typeof MAX_NULLIFIERS_PER_TX + >, + /** + * The nullifiers to be inserted in the tree, sorted high to low. + */ + public sortedNullifiers: Tuple, + /** + * The indexes of the sorted nullifiers to the original ones. + */ + public sortedNullifierIndexes: Tuple, + /** + * Sibling path "pointing to" where the new note hash subtree should be inserted into the note hash tree. + */ + public noteHashSubtreeSiblingPath: Tuple, + /** + * Sibling path "pointing to" where the new nullifiers subtree should be inserted into the nullifier tree. + */ + public nullifierSubtreeSiblingPath: Tuple, + + /** + * Preimages of the low leaves for the public data writes + */ + public lowPublicDataWritesPreimages: Tuple< + PublicDataTreeLeafPreimage, + typeof MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + >, + + /** + * Membership witnesses for the low leaves for the public data writes + */ + public lowPublicDataWritesMembershipWitnesses: Tuple< + MembershipWitness, + typeof MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + >, + + /** + * Sibling paths "pointing to" where the new public data writes should be inserted (one by one) into the public data tree. + */ + public publicDataTreeSiblingPaths: Tuple< + Tuple, + typeof MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX + >, + ) {} + + static from(fields: FieldsOf): PublicBaseStateDiffHints { + return new PublicBaseStateDiffHints(...PublicBaseStateDiffHints.getFields(fields)); + } + + static getFields(fields: FieldsOf) { + return [ + fields.nullifierPredecessorPreimages, + fields.nullifierPredecessorMembershipWitnesses, + fields.sortedNullifiers, + fields.sortedNullifierIndexes, + fields.noteHashSubtreeSiblingPath, + fields.nullifierSubtreeSiblingPath, + fields.lowPublicDataWritesPreimages, + fields.lowPublicDataWritesMembershipWitnesses, + fields.publicDataTreeSiblingPaths, + ] as const; + } + + /** + * Serializes the state diff hints to a buffer. + * @returns A buffer of the serialized state diff hints. + */ + toBuffer(): Buffer { + return serializeToBuffer(...PublicBaseStateDiffHints.getFields(this)); + } + + /** + * Deserializes the state diff hints from a buffer. + * @param buffer - A buffer to deserialize from. + * @returns A new PublicBaseStateDiffHints instance. + */ + static fromBuffer(buffer: Buffer | BufferReader): PublicBaseStateDiffHints { + const reader = BufferReader.asReader(buffer); + return new PublicBaseStateDiffHints( + reader.readArray(MAX_NULLIFIERS_PER_TX, NullifierLeafPreimage), + reader.readArray(MAX_NULLIFIERS_PER_TX, { + fromBuffer: buffer => MembershipWitness.fromBuffer(buffer, NULLIFIER_TREE_HEIGHT), + }), + reader.readArray(MAX_NULLIFIERS_PER_TX, Fr), + reader.readNumbers(MAX_NULLIFIERS_PER_TX), + reader.readArray(NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, Fr), + reader.readArray(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, Fr), + reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataTreeLeafPreimage), + reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, { + fromBuffer: buffer => MembershipWitness.fromBuffer(buffer, PUBLIC_DATA_TREE_HEIGHT), + }), + reader.readArray(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, { + fromBuffer(reader) { + return BufferReader.asReader(reader).readArray(PUBLIC_DATA_TREE_HEIGHT, Fr); + }, + }), ); } static empty() { - return new StateDiffHints( + return new PublicBaseStateDiffHints( makeTuple(MAX_NULLIFIERS_PER_TX, NullifierLeafPreimage.empty), makeTuple(MAX_NULLIFIERS_PER_TX, () => MembershipWitness.empty(NULLIFIER_TREE_HEIGHT)), makeTuple(MAX_NULLIFIERS_PER_TX, Fr.zero), makeTuple(MAX_NULLIFIERS_PER_TX, () => 0), makeTuple(NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, Fr.zero), makeTuple(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, Fr.zero), - makeTuple(PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, Fr.zero), + makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, PublicDataTreeLeafPreimage.empty), + makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, () => MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT)), + makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, () => makeTuple(PUBLIC_DATA_TREE_HEIGHT, Fr.zero)), ); } } diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 1b3bdfbd804..3a2da20f3e9 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -79,7 +79,6 @@ import { NoteLogHash, Nullifier, NullifierLeafPreimage, - PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, ParityPublicInputs, PartialPrivateTailPublicInputsForPublic, @@ -107,7 +106,6 @@ import { RootRollupInputs, RootRollupPublicInputs, ScopedLogHash, - StateDiffHints, StateReference, TUBE_PROOF_LENGTH, TxContext, @@ -139,15 +137,18 @@ import { AvmProofData, AvmPublicDataReadTreeHint, AvmPublicDataWriteTreeHint, - BaseRollupHints, CountedPublicCallRequest, + PrivateBaseRollupHints, PrivateBaseRollupInputs, + PrivateBaseStateDiffHints, PrivateToAvmAccumulatedData, PrivateToAvmAccumulatedDataArrayLengths, PrivateToPublicAccumulatedData, PrivateToPublicKernelCircuitPublicInputs, PrivateTubeData, + PublicBaseRollupHints, PublicBaseRollupInputs, + PublicBaseStateDiffHints, PublicDataWrite, PublicTubeData, ScopedL2ToL1Message, @@ -976,11 +977,11 @@ export function makePublicDataTreeLeafPreimage(seed = 0): PublicDataTreeLeafPrei } /** - * Creates an instance of StateDiffHints with arbitrary values based on the provided seed. + * Creates an instance of PrivateBaseStateDiffHints with arbitrary values based on the provided seed. * @param seed - The seed to use for generating the hints. - * @returns A StateDiffHints object. + * @returns A PrivateBaseStateDiffHints object. */ -export function makeStateDiffHints(seed = 1): StateDiffHints { +export function makePrivateBaseStateDiffHints(seed = 1): PrivateBaseStateDiffHints { const nullifierPredecessorPreimages = makeTuple( MAX_NULLIFIERS_PER_TX, x => new NullifierLeafPreimage(fr(x), fr(x + 0x100), BigInt(x + 0x200)), @@ -1001,16 +1002,77 @@ export function makeStateDiffHints(seed = 1): StateDiffHints { const nullifierSubtreeSiblingPath = makeTuple(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, fr, seed + 0x6000); - const publicDataSiblingPath = makeTuple(PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, fr, 0x8000); + const feeWriteLowLeafPreimage = makePublicDataTreeLeafPreimage(seed + 0x7000); + const feeWriteLowLeafMembershipWitness = makeMembershipWitness(PUBLIC_DATA_TREE_HEIGHT, seed + 0x8000); + const feeWriteSiblingPath = makeTuple(PUBLIC_DATA_TREE_HEIGHT, fr, seed + 0x9000); - return new StateDiffHints( + return new PrivateBaseStateDiffHints( nullifierPredecessorPreimages, nullifierPredecessorMembershipWitnesses, sortedNullifiers, sortedNullifierIndexes, noteHashSubtreeSiblingPath, nullifierSubtreeSiblingPath, - publicDataSiblingPath, + feeWriteLowLeafPreimage, + feeWriteLowLeafMembershipWitness, + feeWriteSiblingPath, + ); +} + +/** + * Creates an instance of PublicBaseStateDiffHints with arbitrary values based on the provided seed. + * @param seed - The seed to use for generating the hints. + * @returns A PublicBaseStateDiffHints object. + */ +export function makePublicBaseStateDiffHints(seed = 1): PublicBaseStateDiffHints { + const nullifierPredecessorPreimages = makeTuple( + MAX_NULLIFIERS_PER_TX, + x => new NullifierLeafPreimage(fr(x), fr(x + 0x100), BigInt(x + 0x200)), + seed + 0x1000, + ); + + const nullifierPredecessorMembershipWitnesses = makeTuple( + MAX_NULLIFIERS_PER_TX, + x => makeMembershipWitness(NULLIFIER_TREE_HEIGHT, x), + seed + 0x2000, + ); + + const sortedNullifiers = makeTuple(MAX_NULLIFIERS_PER_TX, fr, seed + 0x3000); + + const sortedNullifierIndexes = makeTuple(MAX_NULLIFIERS_PER_TX, i => i, seed + 0x4000); + + const noteHashSubtreeSiblingPath = makeTuple(NOTE_HASH_SUBTREE_SIBLING_PATH_LENGTH, fr, seed + 0x5000); + + const nullifierSubtreeSiblingPath = makeTuple(NULLIFIER_SUBTREE_SIBLING_PATH_LENGTH, fr, seed + 0x6000); + + const lowPublicDataWritesPreimages = makeTuple( + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + makePublicDataTreeLeafPreimage, + seed + 0x7000, + ); + + const lowPublicDataWritesMembershipWitnesses = makeTuple( + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + i => makeMembershipWitness(PUBLIC_DATA_TREE_HEIGHT, i), + seed + 0x8000, + ); + + const publicDataTreeSiblingPaths = makeTuple( + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + i => makeTuple(PUBLIC_DATA_TREE_HEIGHT, fr, i), + seed + 0x9000, + ); + + return new PublicBaseStateDiffHints( + nullifierPredecessorPreimages, + nullifierPredecessorMembershipWitnesses, + sortedNullifiers, + sortedNullifierIndexes, + noteHashSubtreeSiblingPath, + nullifierSubtreeSiblingPath, + lowPublicDataWritesPreimages, + lowPublicDataWritesMembershipWitnesses, + publicDataTreeSiblingPaths, ); } @@ -1026,30 +1088,30 @@ function makePrivateTubeData(seed = 1, kernelPublicInputs?: KernelCircuitPublicI ); } -function makeBaseRollupHints(seed = 1) { +function makePrivateBaseRollupHints(seed = 1) { const start = makePartialStateReference(seed + 0x100); - const stateDiffHints = makeStateDiffHints(seed + 0x600); + const stateDiffHints = makePrivateBaseStateDiffHints(seed + 0x600); - const sortedPublicDataWrites = makeTuple( - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - makePublicDataTreeLeaf, - seed + 0x8000, - ); + const archiveRootMembershipWitness = makeMembershipWitness(ARCHIVE_HEIGHT, seed + 0x9000); - const sortedPublicDataWritesIndexes = makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => i, 0); + const constants = makeConstantBaseRollupData(0x100); - const lowPublicDataWritesPreimages = makeTuple( - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - makePublicDataTreeLeafPreimage, - seed + 0x8200, - ); + const feePayerFeeJuiceBalanceReadHint = PublicDataHint.empty(); - const lowPublicDataWritesMembershipWitnesses = makeTuple( - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - i => makeMembershipWitness(PUBLIC_DATA_TREE_HEIGHT, i), - seed + 0x8400, - ); + return PrivateBaseRollupHints.from({ + start, + stateDiffHints, + archiveRootMembershipWitness, + constants, + feePayerFeeJuiceBalanceReadHint, + }); +} + +function makePublicBaseRollupHints(seed = 1) { + const start = makePartialStateReference(seed + 0x100); + + const stateDiffHints = makePublicBaseStateDiffHints(seed + 0x600); const archiveRootMembershipWitness = makeMembershipWitness(ARCHIVE_HEIGHT, seed + 0x9000); @@ -1057,13 +1119,9 @@ function makeBaseRollupHints(seed = 1) { const feePayerFeeJuiceBalanceReadHint = PublicDataHint.empty(); - return BaseRollupHints.from({ + return PublicBaseRollupHints.from({ start, stateDiffHints, - sortedPublicDataWrites, - sortedPublicDataWritesIndexes, - lowPublicDataWritesPreimages, - lowPublicDataWritesMembershipWitnesses, archiveRootMembershipWitness, constants, feePayerFeeJuiceBalanceReadHint, @@ -1072,7 +1130,7 @@ function makeBaseRollupHints(seed = 1) { export function makePrivateBaseRollupInputs(seed = 0) { const tubeData = makePrivateTubeData(seed); - const hints = makeBaseRollupHints(seed + 0x100); + const hints = makePrivateBaseRollupHints(seed + 0x100); return PrivateBaseRollupInputs.from({ tubeData, @@ -1099,7 +1157,7 @@ function makeAvmProofData(seed = 1) { export function makePublicBaseRollupInputs(seed = 0) { const tubeData = makePublicTubeData(seed); const avmProofData = makeAvmProofData(seed + 0x100); - const hints = makeBaseRollupHints(seed + 0x100); + const hints = makePublicBaseRollupHints(seed + 0x200); return PublicBaseRollupInputs.from({ tubeData, diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts index 68bf1e98cc0..27391eb641f 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -78,6 +78,7 @@ import { type PreviousRollupData, PrivateAccumulatedData, type PrivateBaseRollupInputs, + type PrivateBaseStateDiffHints, type PrivateCallData, PrivateCallRequest, type PrivateCircuitPublicInputs, @@ -93,9 +94,9 @@ import { type PrivateTubeData, PrivateValidationRequests, type PublicBaseRollupInputs, + type PublicBaseStateDiffHints, PublicCallRequest, type PublicDataHint, - type PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, PublicDataWrite, type PublicKeys, @@ -118,7 +119,6 @@ import { ScopedNullifier, ScopedReadRequest, type SettledReadHint, - type StateDiffHints, StateReference, type TUBE_PROOF_LENGTH, type TransientDataIndexHint, @@ -193,6 +193,7 @@ import type { PreviousRollupData as PreviousRollupDataNoir, PrivateAccumulatedData as PrivateAccumulatedDataNoir, PrivateBaseRollupInputs as PrivateBaseRollupInputsNoir, + PrivateBaseStateDiffHints as PrivateBaseStateDiffHintsNoir, PrivateCallDataWithoutPublicInputs as PrivateCallDataWithoutPublicInputsNoir, PrivateCallRequest as PrivateCallRequestNoir, PrivateCircuitPublicInputs as PrivateCircuitPublicInputsNoir, @@ -207,9 +208,9 @@ import type { PrivateTubeData as PrivateTubeDataNoir, PrivateValidationRequests as PrivateValidationRequestsNoir, PublicBaseRollupInputs as PublicBaseRollupInputsNoir, + PublicBaseStateDiffHints as PublicBaseStateDiffHintsNoir, PublicCallRequest as PublicCallRequestNoir, PublicDataHint as PublicDataHintNoir, - PublicDataTreeLeaf as PublicDataTreeLeafNoir, PublicDataTreeLeafPreimage as PublicDataTreeLeafPreimageNoir, PublicDataWrite as PublicDataWriteNoir, PublicKeys as PublicKeysNoir, @@ -228,7 +229,6 @@ import type { ScopedNoteHash as ScopedNoteHashNoir, ScopedNullifier as ScopedNullifierNoir, ScopedReadRequest as ScopedReadRequestNoir, - StateDiffHints as StateDiffHintsNoir, StateReference as StateReferenceNoir, TransientDataIndexHint as TransientDataIndexHintNoir, TreeSnapshots as TreeSnapshotsNoir, @@ -2095,16 +2095,6 @@ function mapMembershipWitnessToNoir(witness: MembershipWitness }; } -/** - * Maps a leaf of the public data tree to noir. - */ -export function mapPublicDataTreeLeafToNoir(leaf: PublicDataTreeLeaf): PublicDataTreeLeafNoir { - return { - slot: mapFieldToNoir(leaf.slot), - value: mapFieldToNoir(leaf.value), - }; -} - /** * Maps a leaf preimage of the public data tree to noir. */ @@ -2133,11 +2123,33 @@ export function mapPartialStateReferenceToNoir( } /** - * Maps state diff hints to a noir state diff hints. + * Maps private base state diff hints to a noir state diff hints. + * @param hints - The state diff hints. + * @returns The noir state diff hints. + */ +export function mapPrivateBaseStateDiffHintsToNoir(hints: PrivateBaseStateDiffHints): PrivateBaseStateDiffHintsNoir { + return { + nullifier_predecessor_preimages: mapTuple(hints.nullifierPredecessorPreimages, mapNullifierLeafPreimageToNoir), + nullifier_predecessor_membership_witnesses: mapTuple( + hints.nullifierPredecessorMembershipWitnesses, + (witness: MembershipWitness) => mapMembershipWitnessToNoir(witness), + ), + sorted_nullifiers: mapTuple(hints.sortedNullifiers, mapFieldToNoir), + sorted_nullifier_indexes: mapTuple(hints.sortedNullifierIndexes, (index: number) => mapNumberToNoir(index)), + note_hash_subtree_sibling_path: mapTuple(hints.noteHashSubtreeSiblingPath, mapFieldToNoir), + nullifier_subtree_sibling_path: mapTuple(hints.nullifierSubtreeSiblingPath, mapFieldToNoir), + fee_write_low_leaf_preimage: mapPublicDataTreePreimageToNoir(hints.feeWriteLowLeafPreimage), + fee_write_low_leaf_membership_witness: mapMembershipWitnessToNoir(hints.feeWriteLowLeafMembershipWitness), + fee_write_sibling_path: mapTuple(hints.feeWriteSiblingPath, mapFieldToNoir), + }; +} + +/** + * Maps public base state diff hints to a noir state diff hints. * @param hints - The state diff hints. * @returns The noir state diff hints. */ -export function mapStateDiffHintsToNoir(hints: StateDiffHints): StateDiffHintsNoir { +export function mapPublicBaseStateDiffHintsToNoir(hints: PublicBaseStateDiffHints): PublicBaseStateDiffHintsNoir { return { nullifier_predecessor_preimages: mapTuple(hints.nullifierPredecessorPreimages, mapNullifierLeafPreimageToNoir), nullifier_predecessor_membership_witnesses: mapTuple( @@ -2148,7 +2160,12 @@ export function mapStateDiffHintsToNoir(hints: StateDiffHints): StateDiffHintsNo sorted_nullifier_indexes: mapTuple(hints.sortedNullifierIndexes, (index: number) => mapNumberToNoir(index)), note_hash_subtree_sibling_path: mapTuple(hints.noteHashSubtreeSiblingPath, mapFieldToNoir), nullifier_subtree_sibling_path: mapTuple(hints.nullifierSubtreeSiblingPath, mapFieldToNoir), - public_data_sibling_path: mapTuple(hints.publicDataSiblingPath, mapFieldToNoir), + low_public_data_writes_preimages: mapTuple(hints.lowPublicDataWritesPreimages, mapPublicDataTreePreimageToNoir), + low_public_data_writes_witnesses: mapTuple( + hints.lowPublicDataWritesMembershipWitnesses, + (witness: MembershipWitness) => mapMembershipWitnessToNoir(witness), + ), + public_data_tree_sibling_paths: mapTuple(hints.publicDataTreeSiblingPaths, path => mapTuple(path, mapFieldToNoir)), }; } @@ -2193,21 +2210,7 @@ export function mapPrivateBaseRollupInputsToNoir(inputs: PrivateBaseRollupInputs tube_data: mapPrivateTubeDataToNoir(inputs.tubeData), start: mapPartialStateReferenceToNoir(inputs.hints.start), - state_diff_hints: mapStateDiffHintsToNoir(inputs.hints.stateDiffHints), - - sorted_public_data_writes: mapTuple(inputs.hints.sortedPublicDataWrites, mapPublicDataTreeLeafToNoir), - - sorted_public_data_writes_indexes: mapTuple(inputs.hints.sortedPublicDataWritesIndexes, mapNumberToNoir), - - low_public_data_writes_preimages: mapTuple( - inputs.hints.lowPublicDataWritesPreimages, - mapPublicDataTreePreimageToNoir, - ), - - low_public_data_writes_witnesses: mapTuple( - inputs.hints.lowPublicDataWritesMembershipWitnesses, - (witness: MembershipWitness) => mapMembershipWitnessToNoir(witness), - ), + state_diff_hints: mapPrivateBaseStateDiffHintsToNoir(inputs.hints.stateDiffHints), archive_root_membership_witness: mapMembershipWitnessToNoir(inputs.hints.archiveRootMembershipWitness), constants: mapConstantRollupDataToNoir(inputs.hints.constants), @@ -2237,21 +2240,7 @@ export function mapPublicBaseRollupInputsToNoir(inputs: PublicBaseRollupInputs): avm_proof_data: mapAvmProofDataToNoir(inputs.avmProofData), start: mapPartialStateReferenceToNoir(inputs.hints.start), - state_diff_hints: mapStateDiffHintsToNoir(inputs.hints.stateDiffHints), - - sorted_public_data_writes: mapTuple(inputs.hints.sortedPublicDataWrites, mapPublicDataTreeLeafToNoir), - - sorted_public_data_writes_indexes: mapTuple(inputs.hints.sortedPublicDataWritesIndexes, mapNumberToNoir), - - low_public_data_writes_preimages: mapTuple( - inputs.hints.lowPublicDataWritesPreimages, - mapPublicDataTreePreimageToNoir, - ), - - low_public_data_writes_witnesses: mapTuple( - inputs.hints.lowPublicDataWritesMembershipWitnesses, - (witness: MembershipWitness) => mapMembershipWitnessToNoir(witness), - ), + state_diff_hints: mapPublicBaseStateDiffHintsToNoir(inputs.hints.stateDiffHints), archive_root_membership_witness: mapMembershipWitnessToNoir(inputs.hints.archiveRootMembershipWitness), constants: mapConstantRollupDataToNoir(inputs.hints.constants), diff --git a/yarn-project/prover-client/src/mocks/fixtures.ts b/yarn-project/prover-client/src/mocks/fixtures.ts index a3b507b13b8..34b7cee5935 100644 --- a/yarn-project/prover-client/src/mocks/fixtures.ts +++ b/yarn-project/prover-client/src/mocks/fixtures.ts @@ -13,10 +13,7 @@ import { GlobalVariables, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NULLIFIER_TREE_HEIGHT, - PUBLIC_DATA_SUBTREE_HEIGHT, - PublicDataWrite, } from '@aztec/circuits.js'; import { padArrayEnd } from '@aztec/foundation/collection'; import { randomBytes } from '@aztec/foundation/crypto'; @@ -114,12 +111,8 @@ export const updateExpectedTreesFromTxs = async (db: MerkleTreeWriteOperations, for (const tx of txs) { await db.batchInsert( MerkleTreeId.PUBLIC_DATA_TREE, - padArrayEnd( - tx.txEffect.publicDataWrites, - PublicDataWrite.empty(), - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - ).map(write => write.toBuffer()), - PUBLIC_DATA_SUBTREE_HEIGHT, + tx.txEffect.publicDataWrites.map(write => write.toBuffer()), + 0, ); } }; diff --git a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts index 77551c3693e..a7377c2cd75 100644 --- a/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts +++ b/yarn-project/prover-client/src/orchestrator/block-building-helpers.ts @@ -10,7 +10,6 @@ import { ARCHIVE_HEIGHT, AppendOnlyTreeSnapshot, type BaseOrMergeRollupPublicInputs, - BaseRollupHints, BlockMergeRollupInputs, type BlockRootOrBlockMergePublicInputs, ConstantRollupData, @@ -32,25 +31,25 @@ import { NULLIFIER_TREE_HEIGHT, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NullifierLeafPreimage, - PUBLIC_DATA_SUBTREE_HEIGHT, - PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, PUBLIC_DATA_TREE_HEIGHT, type ParityPublicInputs, PartialStateReference, PreviousRollupBlockData, PreviousRollupData, + PrivateBaseRollupHints, + PrivateBaseStateDiffHints, + PublicBaseRollupHints, + PublicBaseStateDiffHints, PublicDataHint, PublicDataTreeLeaf, - type PublicDataTreeLeafPreimage, - PublicDataWrite, + PublicDataTreeLeafPreimage, type RecursiveProof, RootRollupInputs, - StateDiffHints, StateReference, VK_TREE_HEIGHT, type VerificationKeyAsFields, } from '@aztec/circuits.js'; -import { assertPermutation, makeTuple } from '@aztec/foundation/array'; +import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd } from '@aztec/foundation/collection'; import { sha256Trunc } from '@aztec/foundation/crypto'; import { type DebugLogger } from '@aztec/foundation/log'; @@ -139,45 +138,109 @@ export async function buildBaseRollupHints( i < nullifierSubtreeSiblingPathArray.length ? nullifierSubtreeSiblingPathArray[i] : Fr.ZERO, ); - const publicDataSiblingPath = txPublicDataUpdateRequestInfo.newPublicDataSubtreeSiblingPath; - - const stateDiffHints = StateDiffHints.from({ - nullifierPredecessorPreimages: makeTuple(MAX_NULLIFIERS_PER_TX, i => - i < nullifierWitnessLeaves.length - ? (nullifierWitnessLeaves[i].leafPreimage as NullifierLeafPreimage) - : NullifierLeafPreimage.empty(), - ), - nullifierPredecessorMembershipWitnesses: makeTuple(MAX_NULLIFIERS_PER_TX, i => - i < nullifierPredecessorMembershipWitnessesWithoutPadding.length - ? nullifierPredecessorMembershipWitnessesWithoutPadding[i] - : makeEmptyMembershipWitness(NULLIFIER_TREE_HEIGHT), - ), - sortedNullifiers: makeTuple(MAX_NULLIFIERS_PER_TX, i => Fr.fromBuffer(sortednullifiers[i])), - sortedNullifierIndexes: makeTuple(MAX_NULLIFIERS_PER_TX, i => sortedNewLeavesIndexes[i]), - noteHashSubtreeSiblingPath, - nullifierSubtreeSiblingPath, - publicDataSiblingPath, - }); + if (tx.avmProvingRequest) { + // Build public base rollup hints + const stateDiffHints = PublicBaseStateDiffHints.from({ + nullifierPredecessorPreimages: makeTuple(MAX_NULLIFIERS_PER_TX, i => + i < nullifierWitnessLeaves.length + ? (nullifierWitnessLeaves[i].leafPreimage as NullifierLeafPreimage) + : NullifierLeafPreimage.empty(), + ), + nullifierPredecessorMembershipWitnesses: makeTuple(MAX_NULLIFIERS_PER_TX, i => + i < nullifierPredecessorMembershipWitnessesWithoutPadding.length + ? nullifierPredecessorMembershipWitnessesWithoutPadding[i] + : makeEmptyMembershipWitness(NULLIFIER_TREE_HEIGHT), + ), + sortedNullifiers: makeTuple(MAX_NULLIFIERS_PER_TX, i => Fr.fromBuffer(sortednullifiers[i])), + sortedNullifierIndexes: makeTuple(MAX_NULLIFIERS_PER_TX, i => sortedNewLeavesIndexes[i]), + noteHashSubtreeSiblingPath, + nullifierSubtreeSiblingPath, + lowPublicDataWritesPreimages: padArrayEnd( + txPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages, + PublicDataTreeLeafPreimage.empty(), + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ), + lowPublicDataWritesMembershipWitnesses: padArrayEnd( + txPublicDataUpdateRequestInfo.lowPublicDataWritesMembershipWitnesses, + MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT), + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ), + publicDataTreeSiblingPaths: padArrayEnd( + txPublicDataUpdateRequestInfo.publicDataWritesSiblingPaths, + makeTuple(PUBLIC_DATA_TREE_HEIGHT, () => Fr.ZERO), + MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, + ), + }); + + const blockHash = tx.constants.historicalHeader.hash(); + const archiveRootMembershipWitness = await getMembershipWitnessFor( + blockHash, + MerkleTreeId.ARCHIVE, + ARCHIVE_HEIGHT, + db, + ); - const blockHash = tx.constants.historicalHeader.hash(); - const archiveRootMembershipWitness = await getMembershipWitnessFor( - blockHash, - MerkleTreeId.ARCHIVE, - ARCHIVE_HEIGHT, - db, - ); + return PublicBaseRollupHints.from({ + start, + stateDiffHints, + feePayerFeeJuiceBalanceReadHint: feePayerFeeJuiceBalanceReadHint, + archiveRootMembershipWitness, + constants, + }); + } else { + if ( + txPublicDataUpdateRequestInfo.lowPublicDataWritesMembershipWitnesses.length > 1 || + txPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages.length > 1 || + txPublicDataUpdateRequestInfo.publicDataWritesSiblingPaths.length > 1 + ) { + throw new Error(`More than one public data write in a private only tx`); + } + + const feeWriteLowLeafPreimage = + txPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages[0] || PublicDataTreeLeafPreimage.empty(); + const feeWriteLowLeafMembershipWitness = + txPublicDataUpdateRequestInfo.lowPublicDataWritesMembershipWitnesses[0] || + MembershipWitness.empty(PUBLIC_DATA_TREE_HEIGHT); + const feeWriteSiblingPath = + txPublicDataUpdateRequestInfo.publicDataWritesSiblingPaths[0] || + makeTuple(PUBLIC_DATA_TREE_HEIGHT, () => Fr.ZERO); + + const stateDiffHints = PrivateBaseStateDiffHints.from({ + nullifierPredecessorPreimages: makeTuple(MAX_NULLIFIERS_PER_TX, i => + i < nullifierWitnessLeaves.length + ? (nullifierWitnessLeaves[i].leafPreimage as NullifierLeafPreimage) + : NullifierLeafPreimage.empty(), + ), + nullifierPredecessorMembershipWitnesses: makeTuple(MAX_NULLIFIERS_PER_TX, i => + i < nullifierPredecessorMembershipWitnessesWithoutPadding.length + ? nullifierPredecessorMembershipWitnessesWithoutPadding[i] + : makeEmptyMembershipWitness(NULLIFIER_TREE_HEIGHT), + ), + sortedNullifiers: makeTuple(MAX_NULLIFIERS_PER_TX, i => Fr.fromBuffer(sortednullifiers[i])), + sortedNullifierIndexes: makeTuple(MAX_NULLIFIERS_PER_TX, i => sortedNewLeavesIndexes[i]), + noteHashSubtreeSiblingPath, + nullifierSubtreeSiblingPath, + feeWriteLowLeafPreimage, + feeWriteLowLeafMembershipWitness, + feeWriteSiblingPath, + }); + + const blockHash = tx.constants.historicalHeader.hash(); + const archiveRootMembershipWitness = await getMembershipWitnessFor( + blockHash, + MerkleTreeId.ARCHIVE, + ARCHIVE_HEIGHT, + db, + ); - return BaseRollupHints.from({ - start, - stateDiffHints, - feePayerFeeJuiceBalanceReadHint: feePayerFeeJuiceBalanceReadHint, - sortedPublicDataWrites: txPublicDataUpdateRequestInfo.sortedPublicDataWrites, - sortedPublicDataWritesIndexes: txPublicDataUpdateRequestInfo.sortedPublicDataWritesIndexes, - lowPublicDataWritesPreimages: txPublicDataUpdateRequestInfo.lowPublicDataWritesPreimages, - lowPublicDataWritesMembershipWitnesses: txPublicDataUpdateRequestInfo.lowPublicDataWritesMembershipWitnesses, - archiveRootMembershipWitness, - constants, - }); + return PrivateBaseRollupHints.from({ + start, + stateDiffHints, + feePayerFeeJuiceBalanceReadHint: feePayerFeeJuiceBalanceReadHint, + archiveRootMembershipWitness, + constants, + }); + } } async function getPublicDataHint(db: MerkleTreeWriteOperations, leafSlot: bigint) { @@ -416,67 +479,50 @@ export function makeEmptyMembershipWitness(height: N) { } async function processPublicDataUpdateRequests(tx: ProcessedTx, db: MerkleTreeWriteOperations) { - const allPublicDataUpdateRequests = padArrayEnd( - tx.txEffect.publicDataWrites, - PublicDataWrite.empty(), - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - ); - - const allPublicDataWrites = allPublicDataUpdateRequests.map( + const allPublicDataWrites = tx.txEffect.publicDataWrites.map( ({ leafSlot, value }) => new PublicDataTreeLeaf(leafSlot, value), ); - const { lowLeavesWitnessData, newSubtreeSiblingPath, sortedNewLeaves, sortedNewLeavesIndexes } = await db.batchInsert( - MerkleTreeId.PUBLIC_DATA_TREE, - allPublicDataWrites.map(x => x.toBuffer()), - // TODO(#3675) remove oldValue from update requests - PUBLIC_DATA_SUBTREE_HEIGHT, - ); - - if (lowLeavesWitnessData === undefined) { - throw new Error(`Could not craft public data batch insertion proofs`); - } - - const sortedPublicDataWrites = makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => { - return PublicDataTreeLeaf.fromBuffer(sortedNewLeaves[i]); - }); - - const sortedPublicDataWritesIndexes = makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => { - return sortedNewLeavesIndexes[i]; - }); - const subtreeSiblingPathAsFields = newSubtreeSiblingPath.toFields(); - const newPublicDataSubtreeSiblingPath = makeTuple(PUBLIC_DATA_SUBTREE_SIBLING_PATH_LENGTH, i => { - return subtreeSiblingPathAsFields[i]; - }); + const lowPublicDataWritesPreimages = []; + const lowPublicDataWritesMembershipWitnesses = []; + const publicDataWritesSiblingPaths = []; + + for (const write of allPublicDataWrites) { + if (write.isEmpty()) { + throw new Error(`Empty public data write in tx: ${toFriendlyJSON(tx)}`); + } + + // TODO(Alvaro) write a specialized function for this? Internally add_or_update_value uses batch insertion anyway + const { lowLeavesWitnessData, newSubtreeSiblingPath } = await db.batchInsert( + MerkleTreeId.PUBLIC_DATA_TREE, + [write.toBuffer()], + // TODO(#3675) remove oldValue from update requests + 0, + ); - const lowPublicDataWritesMembershipWitnesses: Tuple< - MembershipWitness, - typeof MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - > = makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => { - const witness = lowLeavesWitnessData[i]; - return MembershipWitness.fromBufferArray( - witness.index, - assertLength(witness.siblingPath.toBufferArray(), PUBLIC_DATA_TREE_HEIGHT), + if (lowLeavesWitnessData === undefined) { + throw new Error(`Could not craft public data batch insertion proofs`); + } + + const [lowLeafWitness] = lowLeavesWitnessData; + lowPublicDataWritesPreimages.push(lowLeafWitness.leafPreimage as PublicDataTreeLeafPreimage); + lowPublicDataWritesMembershipWitnesses.push( + MembershipWitness.fromBufferArray( + lowLeafWitness.index, + assertLength(lowLeafWitness.siblingPath.toBufferArray(), PUBLIC_DATA_TREE_HEIGHT), + ), ); - }); - const lowPublicDataWritesPreimages: Tuple< - PublicDataTreeLeafPreimage, - typeof MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX - > = makeTuple(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, i => { - return lowLeavesWitnessData[i].leafPreimage as PublicDataTreeLeafPreimage; - }); + const insertionSiblingPath = newSubtreeSiblingPath.toFields(); + assertLength(insertionSiblingPath, PUBLIC_DATA_TREE_HEIGHT); - // validate that the sortedPublicDataWrites and sortedPublicDataWritesIndexes are in the correct order - // otherwise it will just fail in the circuit - assertPermutation(allPublicDataWrites, sortedPublicDataWrites, sortedPublicDataWritesIndexes, (a, b) => a.equals(b)); + publicDataWritesSiblingPaths.push(insertionSiblingPath as Tuple); + } return { lowPublicDataWritesPreimages, lowPublicDataWritesMembershipWitnesses, - newPublicDataSubtreeSiblingPath, - sortedPublicDataWrites, - sortedPublicDataWritesIndexes, + publicDataWritesSiblingPaths, }; } 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 00dde60f9b6..d3bcec2b7d5 100644 --- a/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts +++ b/yarn-project/prover-client/src/orchestrator/tx-proving-state.ts @@ -13,8 +13,10 @@ import { AvmProofData, type BaseRollupHints, Fr, + PrivateBaseRollupHints, PrivateBaseRollupInputs, PrivateTubeData, + PublicBaseRollupHints, PublicBaseRollupInputs, PublicTubeData, type TUBE_PROOF_LENGTH, @@ -66,6 +68,9 @@ export class TxProvingState { const vkData = this.getTubeVkData(); const tubeData = new PrivateTubeData(this.processedTx.data.toKernelCircuitPublicInputs(), this.tube.proof, vkData); + if (!(this.baseRollupHints instanceof PrivateBaseRollupHints)) { + throw new Error('Mismatched base rollup hints, expected private base rollup hints'); + } return new PrivateBaseRollupInputs(tubeData, this.baseRollupHints); } @@ -92,6 +97,10 @@ export class TxProvingState { this.getAvmVkData(), ); + if (!(this.baseRollupHints instanceof PublicBaseRollupHints)) { + throw new Error('Mismatched base rollup hints, expected public base rollup hints'); + } + return new PublicBaseRollupInputs(tubeData, avmProofData, this.baseRollupHints); } diff --git a/yarn-project/prover-client/src/test/bb_prover_base_rollup.test.ts b/yarn-project/prover-client/src/test/bb_prover_base_rollup.test.ts index de9828a3c70..e8b644a8a26 100644 --- a/yarn-project/prover-client/src/test/bb_prover_base_rollup.test.ts +++ b/yarn-project/prover-client/src/test/bb_prover_base_rollup.test.ts @@ -2,6 +2,7 @@ import { BBNativeRollupProver, type BBProverConfig } from '@aztec/bb-prover'; import { makeEmptyProcessedTx } from '@aztec/circuit-types'; import { PRIVATE_KERNEL_EMPTY_INDEX, + type PrivateBaseRollupHints, PrivateBaseRollupInputs, PrivateKernelEmptyInputData, PrivateTubeData, @@ -59,7 +60,7 @@ describe('prover/bb_prover/base-rollup', () => { const tubeData = new PrivateTubeData(tubeProof.inputs, tubeProof.proof, vkData); const baseRollupHints = await buildBaseRollupHints(tx, context.globalVariables, context.actualDb); - const baseRollupInputs = new PrivateBaseRollupInputs(tubeData, baseRollupHints); + const baseRollupInputs = new PrivateBaseRollupInputs(tubeData, baseRollupHints as PrivateBaseRollupHints); logger.verbose('Proving base rollups'); const proofOutputs = await context.prover.getPrivateBaseRollupProof(baseRollupInputs); diff --git a/yarn-project/prover-node/src/job/epoch-proving-job.ts b/yarn-project/prover-node/src/job/epoch-proving-job.ts index bed463abef8..c50e6682be4 100644 --- a/yarn-project/prover-node/src/job/epoch-proving-job.ts +++ b/yarn-project/prover-node/src/job/epoch-proving-job.ts @@ -12,14 +12,7 @@ import { type Tx, type TxHash, } from '@aztec/circuit-types'; -import { - KernelCircuitPublicInputs, - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - NULLIFIER_SUBTREE_HEIGHT, - PUBLIC_DATA_SUBTREE_HEIGHT, - PublicDataTreeLeaf, -} from '@aztec/circuits.js'; -import { padArrayEnd } from '@aztec/foundation/collection'; +import { KernelCircuitPublicInputs, NULLIFIER_SUBTREE_HEIGHT, PublicDataTreeLeaf } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { promiseWithResolvers } from '@aztec/foundation/promise'; import { Timer } from '@aztec/foundation/timer'; @@ -201,15 +194,14 @@ export class EpochProvingJob { emptyKernelOutput.end.nullifiers.map(n => n.toBuffer()), NULLIFIER_SUBTREE_HEIGHT, ); - const allPublicDataWrites = padArrayEnd( - emptyKernelOutput.end.publicDataWrites.map(({ leafSlot, value }) => new PublicDataTreeLeaf(leafSlot, value)), - PublicDataTreeLeaf.empty(), - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - ); + const allPublicDataWrites = emptyKernelOutput.end.publicDataWrites + .filter(write => !write.isEmpty()) + .map(({ leafSlot, value }) => new PublicDataTreeLeaf(leafSlot, value)); + await this.db.batchInsert( MerkleTreeId.PUBLIC_DATA_TREE, allPublicDataWrites.map(x => x.toBuffer()), - PUBLIC_DATA_SUBTREE_HEIGHT, + 0, ); } } diff --git a/yarn-project/sequencer-client/src/block_builder/light.test.ts b/yarn-project/sequencer-client/src/block_builder/light.test.ts index ebb0dbcf9ad..43cbd91a83b 100644 --- a/yarn-project/sequencer-client/src/block_builder/light.test.ts +++ b/yarn-project/sequencer-client/src/block_builder/light.test.ts @@ -23,6 +23,7 @@ import { NUM_BASE_PARITY_PER_ROOT_PARITY, type ParityPublicInputs, PreviousRollupData, + type PrivateBaseRollupHints, PrivateBaseRollupInputs, PrivateTubeData, type RecursiveProof, @@ -268,7 +269,7 @@ describe('LightBlockBuilder', () => { const vkData = new VkWitnessData(TubeVk, vkIndex, vkPath); const tubeData = new PrivateTubeData(tx.data.toKernelCircuitPublicInputs(), emptyProof, vkData); const hints = await buildBaseRollupHints(tx, globalVariables, expectsFork); - const inputs = new PrivateBaseRollupInputs(tubeData, hints); + const inputs = new PrivateBaseRollupInputs(tubeData, hints as PrivateBaseRollupHints); const result = await simulator.getPrivateBaseRollupProof(inputs); rollupOutputs.push(result.inputs); } diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index 421b9edd14e..b8a0b1ac49a 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -20,9 +20,7 @@ import { type Header, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NULLIFIER_SUBTREE_HEIGHT, - PUBLIC_DATA_SUBTREE_HEIGHT, PublicDataWrite, } from '@aztec/circuits.js'; import { padArrayEnd } from '@aztec/foundation/collection'; @@ -184,15 +182,10 @@ export class PublicProcessor { } } - const allPublicDataWrites = padArrayEnd( - processedTx.txEffect.publicDataWrites, - PublicDataWrite.empty(), - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - ); await this.db.batchInsert( MerkleTreeId.PUBLIC_DATA_TREE, - allPublicDataWrites.map(x => x.toBuffer()), - PUBLIC_DATA_SUBTREE_HEIGHT, + processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()), + 0, ); result.push(processedTx); returns = returns.concat(returnValues ?? []); diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index b6c0f55b7ec..de3ec11f1fc 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -27,7 +27,6 @@ import { type NULLIFIER_TREE_HEIGHT, type NullifierLeafPreimage, PRIVATE_CONTEXT_INPUTS_LENGTH, - PUBLIC_DATA_SUBTREE_HEIGHT, type PUBLIC_DATA_TREE_HEIGHT, PUBLIC_DISPATCH_SELECTOR, PrivateContextInputs, @@ -511,7 +510,7 @@ export class TXE implements TypedOracle { await db.batchInsert( MerkleTreeId.PUBLIC_DATA_TREE, publicDataWrites.map(write => write.toBuffer()), - PUBLIC_DATA_SUBTREE_HEIGHT, + 0, ); return publicDataWrites.map(write => write.value); } diff --git a/yarn-project/txe/src/txe_service/txe_service.ts b/yarn-project/txe/src/txe_service/txe_service.ts index 75c7f5b5a20..6d073db0d7e 100644 --- a/yarn-project/txe/src/txe_service/txe_service.ts +++ b/yarn-project/txe/src/txe_service/txe_service.ts @@ -4,7 +4,6 @@ import { Fr, FunctionSelector, Header, - PUBLIC_DATA_SUBTREE_HEIGHT, PublicDataTreeLeaf, PublicKeys, computePartialAddress, @@ -157,7 +156,7 @@ export class TXEService { await db.batchInsert( MerkleTreeId.PUBLIC_DATA_TREE, publicDataWrites.map(write => write.toBuffer()), - PUBLIC_DATA_SUBTREE_HEIGHT, + 0, ); return toForeignCallResult([toArray(publicDataWrites.map(write => write.value))]); } diff --git a/yarn-project/txe/src/util/txe_world_state_db.ts b/yarn-project/txe/src/util/txe_world_state_db.ts index f5545c6f8ba..d1a2b6f1737 100644 --- a/yarn-project/txe/src/util/txe_world_state_db.ts +++ b/yarn-project/txe/src/util/txe_world_state_db.ts @@ -3,7 +3,6 @@ import { type AztecAddress, type ContractDataSource, Fr, - PUBLIC_DATA_SUBTREE_HEIGHT, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, } from '@aztec/circuits.js'; @@ -35,7 +34,7 @@ export class TXEWorldStateDB extends WorldStateDB { await this.merkleDb.batchInsert( MerkleTreeId.PUBLIC_DATA_TREE, [new PublicDataTreeLeaf(computePublicDataTreeLeafSlot(contract, slot), newValue).toBuffer()], - PUBLIC_DATA_SUBTREE_HEIGHT, + 0, ); return newValue.toBigInt(); } diff --git a/yarn-project/world-state/src/native/message.ts b/yarn-project/world-state/src/native/message.ts index 51b6163c223..3ee22e1aed3 100644 --- a/yarn-project/world-state/src/native/message.ts +++ b/yarn-project/world-state/src/native/message.ts @@ -400,7 +400,7 @@ interface SyncBlockRequest { paddedNoteHashes: readonly SerializedLeafValue[]; paddedL1ToL2Messages: readonly SerializedLeafValue[]; paddedNullifiers: readonly SerializedLeafValue[]; - batchesOfPaddedPublicDataWrites: readonly SerializedLeafValue[][]; + batchesOfPublicDataWrites: readonly SerializedLeafValue[][]; } interface CreateForkRequest { diff --git a/yarn-project/world-state/src/native/native_world_state.ts b/yarn-project/world-state/src/native/native_world_state.ts index 8dc112fecfc..6f66bfcdd4b 100644 --- a/yarn-project/world-state/src/native/native_world_state.ts +++ b/yarn-project/world-state/src/native/native_world_state.ts @@ -12,7 +12,6 @@ import { Header, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, NullifierLeaf, type NullifierLeafPreimage, @@ -162,16 +161,16 @@ export class NativeWorldStateService implements MerkleTreeDatabase { .map(nullifier => new NullifierLeaf(nullifier)); // We insert the public data tree leaves with one batch per tx to avoid updating the same key twice - const batchesOfPaddedPublicDataWrites: PublicDataTreeLeaf[][] = []; + const batchesOfPublicDataWrites: PublicDataTreeLeaf[][] = []; for (const txEffect of paddedTxEffects) { - const batch: PublicDataTreeLeaf[] = Array(MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX).fill( - PublicDataTreeLeaf.empty(), + batchesOfPublicDataWrites.push( + txEffect.publicDataWrites.map(write => { + if (write.isEmpty()) { + throw new Error('Public data write must not be empty when syncing'); + } + return new PublicDataTreeLeaf(write.leafSlot, write.value); + }), ); - for (const [i, write] of txEffect.publicDataWrites.entries()) { - batch[i] = new PublicDataTreeLeaf(write.leafSlot, write.value); - } - - batchesOfPaddedPublicDataWrites.push(batch); } const response = await this.instance.call(WorldStateMessageType.SYNC_BLOCK, { @@ -180,7 +179,7 @@ export class NativeWorldStateService implements MerkleTreeDatabase { paddedL1ToL2Messages: paddedL1ToL2Messages.map(serializeLeaf), paddedNoteHashes: paddedNoteHashes.map(serializeLeaf), paddedNullifiers: paddedNullifiers.map(serializeLeaf), - batchesOfPaddedPublicDataWrites: batchesOfPaddedPublicDataWrites.map(batch => batch.map(serializeLeaf)), + batchesOfPublicDataWrites: batchesOfPublicDataWrites.map(batch => batch.map(serializeLeaf)), blockStateRef: blockStateReference(l2Block.header.state), }); return sanitiseFullStatus(response); diff --git a/yarn-project/world-state/src/native/native_world_state_instance.ts b/yarn-project/world-state/src/native/native_world_state_instance.ts index 671baaa4fe6..ab63e2d6e5a 100644 --- a/yarn-project/world-state/src/native/native_world_state_instance.ts +++ b/yarn-project/world-state/src/native/native_world_state_instance.ts @@ -188,10 +188,7 @@ export class NativeWorldState implements NativeWorldStateInstance { data['notesCount'] = body.paddedNoteHashes.length; data['nullifiersCount'] = body.paddedNullifiers.length; data['l1ToL2MessagesCount'] = body.paddedL1ToL2Messages.length; - data['publicDataWritesCount'] = body.batchesOfPaddedPublicDataWrites.reduce( - (acc, batch) => acc + batch.length, - 0, - ); + data['publicDataWritesCount'] = body.batchesOfPublicDataWrites.reduce((acc, batch) => acc + batch.length, 0); } this.log.debug(`Calling messageId=${messageId} ${WorldStateMessageType[messageType]} with ${fmtLogData(data)}`); diff --git a/yarn-project/world-state/src/test/utils.ts b/yarn-project/world-state/src/test/utils.ts index e6148637fef..4edd1888384 100644 --- a/yarn-project/world-state/src/test/utils.ts +++ b/yarn-project/world-state/src/test/utils.ts @@ -10,11 +10,8 @@ import { Fr, MAX_NOTE_HASHES_PER_TX, MAX_NULLIFIERS_PER_TX, - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, NULLIFIER_SUBTREE_HEIGHT, NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP, - PUBLIC_DATA_SUBTREE_HEIGHT, - PublicDataWrite, } from '@aztec/circuits.js'; import { padArrayEnd } from '@aztec/foundation/collection'; @@ -45,16 +42,10 @@ export async function mockBlock(blockNum: number, size: number, fork: MerkleTree { // We insert the public data tree leaves with one batch per tx to avoid updating the same key twice for (const txEffect of paddedTxEffects) { - const publicDataWrites = padArrayEnd( - txEffect.publicDataWrites, - PublicDataWrite.empty(), - MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX, - ); - await fork.batchInsert( MerkleTreeId.PUBLIC_DATA_TREE, - publicDataWrites.map(write => write.toBuffer()), - PUBLIC_DATA_SUBTREE_HEIGHT, + txEffect.publicDataWrites.map(write => write.toBuffer()), + 0, ); const nullifiersPadded = padArrayEnd(txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX);