diff --git a/CHANGELOG.md b/CHANGELOG.md index b9bd903ba..37ded380a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,11 @@ - Optimized state synchronizations by removing unnecessary fetching and parsing of note details (#462). - [BREAKING] Changed `GetAccountDetailsResponse` field to `details` (#481). - Improve `--version` by adding build metadata (#495). -- [BREAKING] Introduced additional limits for note/account number (#503). +- [BREAKING] Introduced additional limits for note/account number (#503). - [BREAKING] Removed support for basic wallets in genesis creation (#510). - Added `GetAccountProofs` endpoint (#506). - Migrated faucet from actix-web to axum (#511). +- Changed the `BlockWitness` to pass the inputs to the VM using only advice provider (#516). ## 0.5.1 (2024-09-12) diff --git a/crates/block-producer/src/block_builder/prover/asm/block_kernel.masm b/crates/block-producer/src/block_builder/prover/asm/block_kernel.masm index 023f2c39a..48139646b 100644 --- a/crates/block-producer/src/block_builder/prover/asm/block_kernel.masm +++ b/crates/block-producer/src/block_builder/prover/asm/block_kernel.masm @@ -13,154 +13,228 @@ const.CHAIN_MMR_PTR=1000 #! Compute the account root #! -#! Stack: [num_accounts_updated, OLD_ACCOUNT_ROOT, -#! NEW_ACCOUNT_HASH_0, account_id_0, ... , NEW_ACCOUNT_HASH_n, account_id_n] -#! Output: [NEW_ACCOUNT_ROOT] +#! Inputs: +#! Operand stack: [] +#! Advice stack: [num_accounts_updated, OLD_ACCOUNT_ROOT, [NEW_ACCOUNT_HASH_i, account_id_i]] +#! Outputs: +#! Operand stack: [NEW_ACCOUNT_ROOT] proc.compute_account_root - dup neq.0 - # => [0 or 1, num_accounts_updated, OLD_ACCOUNT_ROOT, - # NEW_ACCOUNT_HASH_0, account_id_0, ... , NEW_ACCOUNT_HASH_n, account_id_n] + # move the number of updated accounts and an old account root to the operand stack + adv_push.5 + # OS => [OLD_ACCOUNT_ROOT, num_accounts_updated] + # AS => [[NEW_ACCOUNT_HASH_i, account_id_i]] + + # assess if we should loop + dup.4 neq.0 + # OS => [flag, OLD_ACCOUNT_ROOT, num_accounts_updated] + # AS => [[NEW_ACCOUNT_HASH_i, account_id_i]] while.true - # stack: [counter, ROOT_0, ..., NEW_ACCOUNT_HASH_i, account_id_i , ...] + # num_accounts_updated here serves as a counter, so rename it accordingly + # old account root will be updated in each iteration, so rename it to the ROOT_i + # OS => [ROOT_i, counter] + # AS => [[NEW_ACCOUNT_HASH_i, account_id_i]] - # Move counter down for next iteration - movdn.9 - # => [ROOT_i, NEW_ACCOUNT_HASH_i, account_id_i, counter, ...] + # move the account hash to the operand stack and move it below the root + adv_push.4 swapw + # OS => [ROOT_i, NEW_ACCOUNT_HASH_i, counter] + # AS => [account_id_i, [NEW_ACCOUNT_HASH_{i+1}, account_id_{i+1}]] - # Prepare stack for `mtree_set` - movup.8 push.ACCOUNT_TREE_DEPTH - # => [account_tree_depth, account_id_i, ROOT_i, NEW_ACCOUNT_HASH_i, counter, ...] + # move the account id to the operand stack, push the account tree depth + adv_push.1 push.ACCOUNT_TREE_DEPTH + # OS => [account_tree_depth, account_id_i, ROOT_i, NEW_ACCOUNT_HASH_i, counter] + # AS => [[NEW_ACCOUNT_HASH_{i+1}, account_id_{i+1}]] # set new value in SMT mtree_set dropw - # => [ROOT_{i+1}, counter, ...] + # OS => [ROOT_{i+1}, counter] + # AS => [[NEW_ACCOUNT_HASH_{i+1}, account_id_{i+1}]] # loop counter - movup.4 sub.1 dup neq.0 - # => [0 or 1, counter-1, ROOT_{i+1}, ...] + movup.4 sub.1 dup movdn.5 neq.0 + # OS => [flag, ROOT_{i+1}, counter] + # AS => [[NEW_ACCOUNT_HASH_{i+1}, account_id_{i+1}]] end - drop - # => [ROOT_{n-1}] + # drop the counter + movup.4 drop + # OS => [ROOT_{n-1}] + # AS => [] end #! Compute the note root. #! -#! Each batch contains a tree of depth 10 for its created notes. The block's created notes tree is created -#! by aggregating up to 2^6 tree roots coming from the batches contained in the block. +#! Each batch contains a tree of depth 10 for its created notes. The block's created notes tree is +#! created by aggregating up to 2^6 tree roots coming from the batches contained in the block. #! -#! `SMT_EMPTY_ROOT` must be `E16`, the root of the empty tree of depth 16. If less than 2^6 batches are -#! contained in the block, `E10` is used as the padding value; this is derived from the fact that -#! `SMT_EMPTY_ROOT` is `E16`, and that our tree has depth 6. +#! `SMT_EMPTY_ROOT` must be `E16`, the root of the empty tree of depth 16. If less than 2^6 batches +#! are contained in the block, `E10` is used as the padding value; this is derived from the fact +#! that `SMT_EMPTY_ROOT` is `E16`, and that our tree has depth 6. #! -#! Stack: [num_notes_updated, SMT_EMPTY_ROOT, -#! batch_note_root_idx_0, BATCH_NOTE_TREE_ROOT_0, -#! ... , -#! batch_note_root_idx_{n-1}, BATCH_NOTE_TREE_ROOT_{n-1}] -#! Output: [NOTES_ROOT] +#! Inputs: +#! Operand stack: [] +#! Advice stack: [num_notes_updated, SMT_EMPTY_ROOT, [BATCH_NOTE_TREE_ROOT_i, batch_note_root_idx_i]] +#! Outputs: +#! Operand stack: [NOTES_ROOT] proc.compute_note_root + # move the number of updated notes and empty root to the operand stack + adv_push.5 + # OS => [SMT_EMPTY_ROOT, num_notes_updated] + # AS => [[BATCH_NOTE_TREE_ROOT_i, batch_note_root_idx_i]] + # assess if we should loop - dup neq.0 - #=> [0 or 1, num_notes_updated, SMT_EMPTY_ROOT, ... ] + dup.4 neq.0 + # OS => [flag, SMT_EMPTY_ROOT, num_notes_updated] + # AS => [[BATCH_NOTE_TREE_ROOT_i, batch_note_root_idx_i]] while.true - #=> [note_roots_left_to_update, ROOT_i, batch_note_root_idx_i, BATCH_NOTE_TREE_ROOT_i, ... ] + # num_notes_updated here serves as a counter, so rename it accordingly + # empty root will be updated in each iteration, so rename it to the ROOT_i + # OS => [ROOT_i, counter] + # AS => [[BATCH_NOTE_TREE_ROOT_i, batch_note_root_idx_i]] - # Move counter down for next iteration - movdn.9 - #=> [ROOT_i, batch_note_root_idx_i, BATCH_NOTE_TREE_ROOT_i, note_roots_left_to_update, ... ] + # move the batch note tree root to the operand stack and move it below the root + adv_push.4 swapw + # OS => [ROOT_i, BATCH_NOTE_TREE_ROOT_i, counter] + # AS => [batch_note_root_idx_i, [BATCH_NOTE_TREE_ROOT_{i+1}, batch_note_root_idx_{i+1}]] - # Prepare stack for mtree_set - movup.4 push.BLOCK_NOTES_BATCH_TREE_DEPTH - #=> [depth=batch_tree_depth, batch_note_root_idx_i, ROOT_i, - # BATCH_NOTE_TREE_ROOT_i, note_roots_left_to_update, ... ] + # move the batch note root index to the operand stack, push the block notes batch tree depth + adv_push.1 push.BLOCK_NOTES_BATCH_TREE_DEPTH + # OS => [batch_tree_depth, batch_note_root_idx_i, ROOT_i, BATCH_NOTE_TREE_ROOT_i, counter] + # AS => [[BATCH_NOTE_TREE_ROOT_{i+1}, batch_note_root_idx_{i+1}]] + # set new value in SMT mtree_set dropw - #=> [ROOT_{i+1}, note_roots_left_to_update, ... ] - + # OS => [ROOT_{i+1}, counter] + # AS => [[BATCH_NOTE_TREE_ROOT_{i+1}, batch_note_root_idx_{i+1}]] + # loop counter - movup.4 sub.1 dup neq.0 - #=> [0 or 1, note_roots_left_to_update - 1, ROOT_{i+1}, ... ] + movup.4 sub.1 dup movdn.5 neq.0 + # OS => [flag, ROOT_{i+1}, counter] + # AS => [[BATCH_NOTE_TREE_ROOT_{i+1}, batch_note_root_idx_{i+1}]] end - drop - # => [ROOT_{n-1}] + # drop the counter + movup.4 drop + # OS => [ROOT_{n-1}] + # AS => [] end -#! Stack: [num_produced_nullifiers, OLD_NULLIFIER_ROOT, NULLIFIER_VALUE, -#! NULLIFIER_0, ..., NULLIFIER_n] -#! Output: [NULLIFIER_ROOT] +#! Compute the nullifier root. +#! +#! Inputs: +#! Operand stack: [] +#! Advice stack: [num_produced_nullifiers, OLD_NULLIFIER_ROOT, NULLIFIER_VALUE, [NULLIFIER_i]] +#! Outputs: +#! Operand stack: [NULLIFIER_ROOT] proc.compute_nullifier_root + # move the number of produced nullifiers, old root and nullifier value to the operand stack; + # move nullifier value below the root + adv_push.9 swapw + # OS => [OLD_NULLIFIER_ROOT, NULLIFIER_VALUE, num_produced_nullifiers] + # AS => [[NULLIFIER_i]] + # assess if we should loop - dup neq.0 - #=> [0 or 1, num_produced_nullifiers, OLD_NULLIFIER_ROOT, NULLIFIER_VALUE, NULLIFIER_0, ..., NULLIFIER_n ] + dup.8 neq.0 + # OS => [flag, OLD_NULLIFIER_ROOT, NULLIFIER_VALUE, num_produced_nullifiers] + # AS => [[NULLIFIER_i]] while.true - #=> [num_nullifiers_left_to_update, ROOT_i, NULLIFIER_VALUE, NULLIFIER_i, ... ] + # num_produced_nullifiers here serves as a counter, so rename it accordingly + # old nullifier root will be updated in each iteration, so rename it to the ROOT_i + # OS => [ROOT_i, NULLIFIER_VALUE, counter] + # AS => [[NULLIFIER_i]] + + # move the nullifier hash to the operand stack + adv_push.4 + # OS => [NULLIFIER_i, ROOT_i, NULLIFIER_VALUE, counter] + # AS => [[NULLIFIER_{i+1}]] - # Prepare stack for `smt::set` - movdn.12 movupw.2 dupw.2 - #=> [NULLIFIER_VALUE, NULLIFIER_i, ROOT_i, NULLIFIER_VALUE, num_nullifiers_left_to_update, ... ] + # dup the nullifier value + dupw.2 + # OS => [NULLIFIER_VALUE, NULLIFIER_i, ROOT_i, NULLIFIER_VALUE, counter] + # AS => [[NULLIFIER_{i+1}]] exec.smt::set - #=> [OLD_VALUE, ROOT_{i+1}, NULLIFIER_VALUE, num_nullifiers_left_to_update, ... ] + # OS => [OLD_VALUE, ROOT_{i+1}, NULLIFIER_VALUE, counter] + # AS => [[NULLIFIER_{i+1}]] # Check that OLD_VALUE == 0 (i.e. that nullifier was indeed not previously produced) assertz assertz assertz assertz - #=> [ROOT_{i+1}, NULLIFIER_VALUE, num_nullifiers_left_to_update, ... ] + # OS => [ROOT_{i+1}, NULLIFIER_VALUE, counter] + # AS => [[NULLIFIER_{i+1}]] # loop counter - movup.8 sub.1 dup neq.0 - #=> [0 or 1, num_nullifiers_left_to_update - 1, ROOT_{i+1}, NULLIFIER_VALUE, ... ] + movup.8 sub.1 dup movdn.9 neq.0 + # OS => [flag, ROOT_{i+1}, NULLIFIER_VALUE, counter] + # AS => [[NULLIFIER_{i+1}]] end - #=> [0, ROOT_{n-1}, NULLIFIER_VALUE ] - drop swapw dropw - # => [ROOT_{n-1}] + # drop the counter and the nullifier value + swapw dropw movup.4 drop + # OS => [ROOT_{n-1}] + # AS => [] end #! Compute the chain MMR root #! -#! Stack: [ PREV_CHAIN_MMR_HASH, PREV_BLOCK_HASH_TO_INSERT ] -#! Advice map: PREV_CHAIN_MMR_HASH -> NUM_LEAVES || peak_0 || .. || peak_{n-1} || -#! -#! Output: [ CHAIN_MMR_ROOT ] +#! Inputs: +#! Operand stack: [] +#! Advice stack: [PREV_BLOCK_HASH_TO_INSERT, PREV_CHAIN_MMR_HASH] +#! Advice map: { +#! PREV_CHAIN_MMR_HASH: [NUM_LEAVES, [peak_i], ] +#! } +#! Outputs: +#! Operand stack: [CHAIN_MMR_ROOT] proc.compute_chain_mmr_root + # move the previous block hash and chain MMR hash to the operand stack + adv_push.8 + # OS => [PREV_CHAIN_MMR_HASH, PREV_BLOCK_HASH_TO_INSERT] + # AS => [] + + # push chain MMR pointer to the operand stack push.CHAIN_MMR_PTR movdn.4 - # => [ PREV_CHAIN_MMR_HASH, chain_mmr_ptr, PREV_BLOCK_HASH_TO_INSERT ] + # OS => [PREV_CHAIN_MMR_HASH, chain_mmr_ptr, PREV_BLOCK_HASH_TO_INSERT] # load the chain MMR (as of previous block) at memory location CHAIN_MMR_PTR exec.mmr::unpack - # => [ PREV_BLOCK_HASH_TO_INSERT ] + # OS => [PREV_BLOCK_HASH_TO_INSERT] + # push chain MMR pointer to the operand stack push.CHAIN_MMR_PTR movdn.4 - # => [ PREV_BLOCK_HASH_TO_INSERT, chain_mmr_ptr ] + # OS => [PREV_BLOCK_HASH_TO_INSERT, chain_mmr_ptr] # add PREV_BLOCK_HASH_TO_INSERT to chain MMR exec.mmr::add - # => [ ] + # OS => [] # Compute new MMR root push.CHAIN_MMR_PTR exec.mmr::pack - # => [ CHAIN_MMR_ROOT ] + # OS => [CHAIN_MMR_ROOT] end -# Stack: [, , , ] +#! Inputs: +#! Operand stack: [] +#! Advice stack: [, , , ] +#! Advice map: { +#! PREV_CHAIN_MMR_HASH: [NUM_LEAVES, [peak_i], ] +#! } +#! Outputs: +#! Operand stack: [ACCOUNT_ROOT, NOTE_ROOT, NULLIFIER_ROOT, CHAIN_MMR_ROOT] begin exec.compute_account_root mem_storew.0 dropw # => [, , ] exec.compute_note_root mem_storew.1 dropw - # => [ , ] + # => [, ] exec.compute_nullifier_root mem_storew.2 dropw - # => [ ] + # => [] exec.compute_chain_mmr_root - # => [ CHAIN_MMR_ROOT ] + # => [CHAIN_MMR_ROOT] # Load output on stack padw mem_loadw.2 padw mem_loadw.1 padw mem_loadw.0 - #=> [ ACCOUNT_ROOT, NOTE_ROOT, NULLIFIER_ROOT, CHAIN_MMR_ROOT ] -end \ No newline at end of file + # => [ACCOUNT_ROOT, NOTE_ROOT, NULLIFIER_ROOT, CHAIN_MMR_ROOT] +end diff --git a/crates/block-producer/src/block_builder/prover/block_witness.rs b/crates/block-producer/src/block_builder/prover/block_witness.rs index 9bbe473c8..0b4f06567 100644 --- a/crates/block-producer/src/block_builder/prover/block_witness.rs +++ b/crates/block-producer/src/block_builder/prover/block_witness.rs @@ -136,10 +136,9 @@ impl BlockWitness { pub(super) fn into_program_inputs( self, ) -> Result<(AdviceInputs, StackInputs), BlockProverError> { - let stack_inputs = self.build_stack_inputs(); let advice_inputs = self.build_advice_inputs()?; - Ok((advice_inputs, stack_inputs)) + Ok((advice_inputs, StackInputs::default())) } /// Returns an iterator over all transactions which affected accounts in the block with @@ -178,84 +177,88 @@ impl BlockWitness { } } - /// Builds the stack inputs to the block kernel - fn build_stack_inputs(&self) -> StackInputs { - // Note: `StackInputs::new()` reverses the input vector, so we need to construct the stack - // from the bottom to the top - let mut stack_inputs = Vec::new(); - - // Chain MMR stack inputs - { - stack_inputs.extend(self.prev_header.hash()); - stack_inputs.extend(self.chain_peaks.hash_peaks()); - } - - // Nullifiers stack inputs - { - let num_produced_nullifiers: Felt = (self.produced_nullifiers.len() as u64) - .try_into() - .expect("nullifiers number is greater than or equal to the field modulus"); + /// Builds the advice inputs to the block kernel + fn build_advice_inputs(self) -> Result { + let advice_stack = { + let mut advice_stack = Vec::new(); + + // add account stack inputs to the advice stack + { + let mut account_data = Vec::new(); + let mut num_accounts_updated: u64 = 0; + for (idx, (account_id, account_update)) in self.updated_accounts.iter().enumerate() + { + account_data.extend(account_update.final_state_hash); + account_data.push((*account_id).into()); + + let idx = u64::try_from(idx).expect("can't be more than 2^64 - 1 accounts"); + num_accounts_updated = idx + 1; + } + + // append number of accounts updated + advice_stack.push(num_accounts_updated.try_into().expect( + "updated accounts number is greater than or equal to the field modulus", + )); + + // append initial account root + advice_stack.extend(self.prev_header.account_root()); + + // append the updated accounts data + advice_stack.extend(account_data); + } - for nullifier in self.produced_nullifiers.keys() { - stack_inputs.extend(nullifier.inner()); + // add notes stack inputs to the advice stack + { + // append the number of updated notes + advice_stack + .push(Felt::try_from(self.batch_created_notes_roots.len() as u64).expect( + "notes roots number is greater than or equal to the field modulus", + )); + + // append the empty root + let empty_root = EmptySubtreeRoots::entry(BLOCK_NOTE_TREE_DEPTH, 0); + advice_stack.extend(*empty_root); + + for (batch_index, batch_created_notes_root) in self.batch_created_notes_roots.iter() + { + advice_stack.extend(batch_created_notes_root.iter()); + + let batch_index = Felt::try_from(*batch_index as u64) + .expect("batch index is greater than or equal to the field modulus"); + advice_stack.push(batch_index); + } } - // append nullifier value (`[block_num, 0, 0, 0]`) - let block_num = self.prev_header.block_num() + 1; - stack_inputs.extend([block_num.into(), ZERO, ZERO, ZERO]); + // Nullifiers stack inputs + { + let num_produced_nullifiers: Felt = (self.produced_nullifiers.len() as u64) + .try_into() + .expect("nullifiers number is greater than or equal to the field modulus"); - // append initial nullifier root - stack_inputs.extend(self.prev_header.nullifier_root()); + // append number of nullifiers + advice_stack.push(num_produced_nullifiers); - // append number of nullifiers - stack_inputs.push(num_produced_nullifiers); - } + // append initial nullifier root + advice_stack.extend(self.prev_header.nullifier_root()); - // Notes stack inputs - { - for (batch_index, batch_created_notes_root) in self.batch_created_notes_roots.iter() { - stack_inputs.extend(batch_created_notes_root.iter()); + // append nullifier value (`[block_num, 0, 0, 0]`) + let block_num = self.prev_header.block_num() + 1; + advice_stack.extend([block_num.into(), ZERO, ZERO, ZERO]); - let batch_index = Felt::try_from(*batch_index as u64) - .expect("batch index is greater than or equal to the field modulus"); - stack_inputs.push(batch_index); + for nullifier in self.produced_nullifiers.keys() { + advice_stack.extend(nullifier.inner()); + } } - let empty_root = EmptySubtreeRoots::entry(BLOCK_NOTE_TREE_DEPTH, 0); - stack_inputs.extend(*empty_root); - stack_inputs.push( - Felt::try_from(self.batch_created_notes_roots.len() as u64) - .expect("notes roots number is greater than or equal to the field modulus"), - ); - } - - // Account stack inputs - let mut num_accounts_updated: u64 = 0; - for (idx, (account_id, account_update)) in self.updated_accounts.iter().enumerate() { - stack_inputs.push((*account_id).into()); - stack_inputs.extend(account_update.final_state_hash); - - let idx = u64::try_from(idx).expect("can't be more than 2^64 - 1 accounts"); - num_accounts_updated = idx + 1; - } - - // append initial account root - stack_inputs.extend(self.prev_header.account_root()); - - // append number of accounts updated - stack_inputs.push( - num_accounts_updated - .try_into() - .expect("updated accounts number is greater than or equal to the field modulus"), - ); + // Chain MMR stack inputs + { + advice_stack.extend(self.prev_header.hash()); + advice_stack.extend(self.chain_peaks.hash_peaks()); + } - // TODO: We need provide produced nullifier different way, because such big stack inputs - // will cause problem in recursive proofs - StackInputs::new(stack_inputs).expect("Stack inputs count extends max limit") - } + advice_stack + }; - /// Builds the advice inputs to the block kernel - fn build_advice_inputs(self) -> Result { let merkle_store = { let mut merkle_store = MerkleStore::default(); @@ -290,8 +293,10 @@ impl BlockWitness { .chain(std::iter::once(mmr_peaks_advice_map_key_value(&self.chain_peaks))) .collect(); - let advice_inputs = - AdviceInputs::default().with_merkle_store(merkle_store).with_map(advice_map); + let advice_inputs = AdviceInputs::default() + .with_merkle_store(merkle_store) + .with_map(advice_map) + .with_stack(advice_stack); Ok(advice_inputs) }