Skip to content

Commit

Permalink
feat: Implement ProposedBlock::new (partially)
Browse files Browse the repository at this point in the history
  • Loading branch information
PhilippGackstatter committed Feb 5, 2025
1 parent e82dee0 commit 65b6da8
Show file tree
Hide file tree
Showing 7 changed files with 691 additions and 11 deletions.
8 changes: 8 additions & 0 deletions crates/miden-objects/src/batch/proven_batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ impl ProvenBatch {
&self.account_updates
}

/// Takes the map of account IDs mapped to their [`BatchAccountUpdate`]s from the proven batch.
///
/// This has the semantics of [`core::mem::take`], i.e. the account updates are set to an empty
/// `BTreeMap` after this operation.
pub fn take_account_updates(&mut self) -> BTreeMap<AccountId, BatchAccountUpdate> {
core::mem::take(&mut self.account_updates)
}

/// Returns the [`InputNotes`] of this batch.
pub fn input_notes(&self) -> &InputNotes<InputNoteCommitment> {
&self.input_notes
Expand Down
127 changes: 127 additions & 0 deletions crates/miden-objects/src/block/block_inputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use std::collections::BTreeMap;

use crate::{
account::AccountId,
block::BlockHeader,
crypto::merkle::{MerklePath, SmtProof},
note::{NoteId, NoteInclusionProof, Nullifier},
transaction::ChainMmr,
Digest,
};

// BLOCK INPUTS
// ================================================================================================

/// Information needed from the store to build a block
#[derive(Clone, Debug)]
pub struct BlockInputs {
/// Previous block header
prev_block_header: BlockHeader,

/// MMR peaks for the current chain state
chain_mmr: ChainMmr,

/// The hashes of the requested accounts and their authentication paths
accounts: BTreeMap<AccountId, AccountWitness>,

/// The requested nullifiers and their authentication paths
nullifiers: BTreeMap<Nullifier, SmtProof>,

/// List of unauthenticated notes found in the store
unauthenticated_note_proofs: BTreeMap<NoteId, NoteInclusionProof>,
}

impl BlockInputs {
pub fn new(
prev_block_header: BlockHeader,
chain_mmr: ChainMmr,
accounts: BTreeMap<AccountId, AccountWitness>,
nullifiers: BTreeMap<Nullifier, SmtProof>,
unauthenticated_note_proofs: BTreeMap<NoteId, NoteInclusionProof>,
) -> Self {
Self {
prev_block_header,
chain_mmr,
accounts,
nullifiers,
unauthenticated_note_proofs,
}
}

pub fn prev_block_header(&self) -> &BlockHeader {
&self.prev_block_header
}

pub fn chain_mmr(&self) -> &ChainMmr {
&self.chain_mmr
}

pub fn accounts(&self) -> &BTreeMap<AccountId, AccountWitness> {
&self.accounts
}

pub fn accounts_mut(&mut self) -> &mut BTreeMap<AccountId, AccountWitness> {
&mut self.accounts
}

pub fn nullifiers(&self) -> &BTreeMap<Nullifier, SmtProof> {
&self.nullifiers
}

/// Takes the map of nullifiers to their proofs from the block inputs.
///
/// This has the semantics of [`core::mem::take`], i.e. the nullifiers are set to an empty
/// `BTreeMap` after this operation.
pub fn take_nullifiers(&mut self) -> BTreeMap<Nullifier, SmtProof> {
core::mem::take(&mut self.nullifiers)
}

pub fn unauthenticated_note_proofs(&self) -> &BTreeMap<NoteId, NoteInclusionProof> {
&self.unauthenticated_note_proofs
}

pub fn into_parts(
self,
) -> (
BlockHeader,
ChainMmr,
BTreeMap<AccountId, AccountWitness>,
BTreeMap<Nullifier, SmtProof>,
BTreeMap<NoteId, NoteInclusionProof>,
) {
(
self.prev_block_header,
self.chain_mmr,
self.accounts,
self.nullifiers,
self.unauthenticated_note_proofs,
)
}
}

// ACCOUNT WITNESS
// ================================================================================================

#[derive(Clone, Debug, Default)]
pub struct AccountWitness {
initial_state_commitment: Digest,
proof: MerklePath,
}

impl AccountWitness {
pub fn new(initial_state_commitment: Digest, proof: MerklePath) -> Self {
Self { initial_state_commitment, proof }
}

pub fn initial_state_commitment(&self) -> Digest {
self.initial_state_commitment
}

pub fn proof(&self) -> &MerklePath {
&self.proof
}

pub fn into_parts(self) -> (Digest, MerklePath) {
(self.initial_state_commitment, self.proof)
}
}
30 changes: 21 additions & 9 deletions crates/miden-objects/src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ pub use header::BlockHeader;
mod block_number;
pub use block_number::BlockNumber;

mod proposed_block;
pub use proposed_block::{AccountUpdateWitness, ProposedBlock};

mod block_inputs;
pub use block_inputs::{AccountWitness, BlockInputs};

mod nullifier_tree;
pub use nullifier_tree::NullifierTree;

mod partial_nullifier_tree;
pub use partial_nullifier_tree::PartialNullifierTree;

mod note_tree;
pub use note_tree::{BlockNoteIndex, BlockNoteTree};

Expand Down Expand Up @@ -251,8 +263,8 @@ pub struct BlockAccountUpdate {
/// ID of the updated account.
account_id: AccountId,

/// Hash of the new state of the account.
new_state_hash: Digest,
/// Final commitment to the new state of the account after this update.
final_state_commitment: Digest,

/// A set of changes which can be applied to the previous account state (i.e., the state as of
/// the last block) to get the new account state. For private accounts, this is set to
Expand All @@ -267,13 +279,13 @@ impl BlockAccountUpdate {
/// Returns a new [BlockAccountUpdate] instantiated from the specified components.
pub const fn new(
account_id: AccountId,
new_state_hash: Digest,
final_state_commitment: Digest,
details: AccountUpdateDetails,
transactions: Vec<TransactionId>,
) -> Self {
Self {
account_id,
new_state_hash,
final_state_commitment,
details,
transactions,
}
Expand All @@ -284,9 +296,9 @@ impl BlockAccountUpdate {
self.account_id
}

/// Returns the hash of the new account state.
pub fn new_state_hash(&self) -> Digest {
self.new_state_hash
/// Returns the state commitment of the account after this update.
pub fn final_state_commitment(&self) -> Digest {
self.final_state_commitment
}

/// Returns the description of the updates for public accounts.
Expand All @@ -311,7 +323,7 @@ impl BlockAccountUpdate {
impl Serializable for BlockAccountUpdate {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.account_id.write_into(target);
self.new_state_hash.write_into(target);
self.final_state_commitment.write_into(target);
self.details.write_into(target);
self.transactions.write_into(target);
}
Expand All @@ -321,7 +333,7 @@ impl Deserializable for BlockAccountUpdate {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
Ok(Self {
account_id: AccountId::read_from(source)?,
new_state_hash: Digest::read_from(source)?,
final_state_commitment: Digest::read_from(source)?,
details: AccountUpdateDetails::read_from(source)?,
transactions: Vec::<TransactionId>::read_from(source)?,
})
Expand Down
114 changes: 114 additions & 0 deletions crates/miden-objects/src/block/nullifier_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use crate::{
block::BlockNumber,
crypto::{
hash::rpo::RpoDigest,
merkle::{MutationSet, Smt, SmtProof, SMT_DEPTH},
},
errors::NullifierTreeError,
note::Nullifier,
Felt, FieldElement, Word,
};

/// Nullifier SMT.
#[derive(Debug, Clone)]
pub struct NullifierTree(Smt);

impl NullifierTree {
/// Construct new nullifier tree from list of items.
pub fn with_entries(
entries: impl IntoIterator<Item = (Nullifier, BlockNumber)>,
) -> Result<Self, NullifierTreeError> {
let leaves = entries
.into_iter()
.map(|(nullifier, block_num)| (nullifier.inner(), block_num_to_leaf_value(block_num)));

let inner = Smt::with_entries(leaves).map_err(NullifierTreeError::CreationFailed)?;

Ok(Self(inner))
}

/// Returns the root of the nullifier SMT.
pub fn root(&self) -> RpoDigest {
self.0.root()
}

/// Returns an opening of the leaf associated with the given nullifier.
pub fn open(&self, nullifier: &Nullifier) -> SmtProof {
self.0.open(&nullifier.inner())
}

/// Returns block number stored for the given nullifier or `None` if the nullifier wasn't
/// consumed.
pub fn get_block_num(&self, nullifier: &Nullifier) -> Option<BlockNumber> {
let value = self.0.get_value(&nullifier.inner());
if value == Smt::EMPTY_VALUE {
return None;
}

Some(Self::leaf_value_to_block_num(value))
}

/// Computes mutations for the nullifier SMT.
pub fn compute_mutations(
&self,
kv_pairs: impl IntoIterator<Item = (Nullifier, BlockNumber)>,
) -> MutationSet<SMT_DEPTH, RpoDigest, Word> {
self.0.compute_mutations(
kv_pairs.into_iter().map(|(nullifier, block_num)| {
(nullifier.inner(), block_num_to_leaf_value(block_num))
}),
)
}

/// Applies mutations to the nullifier SMT.
pub fn apply_mutations(
&mut self,
mutations: MutationSet<SMT_DEPTH, RpoDigest, Word>,
) -> Result<(), NullifierTreeError> {
self.0.apply_mutations(mutations).map_err(NullifierTreeError::MutationFailed)
}

// HELPER FUNCTIONS
// --------------------------------------------------------------------------------------------

/// Given the leaf value of the nullifier SMT, returns the nullifier's block number.
///
/// There are no nullifiers in the genesis block. The value zero is instead used to signal
/// absence of a value.
fn leaf_value_to_block_num(value: Word) -> BlockNumber {
let block_num: u32 =
value[0].as_int().try_into().expect("invalid block number found in store");

block_num.into()
}
}

/// Returns the nullifier's leaf value in the SMT by its block number.
pub(super) fn block_num_to_leaf_value(block: BlockNumber) -> Word {
[Felt::from(block), Felt::ZERO, Felt::ZERO, Felt::ZERO]
}

#[cfg(test)]
mod tests {
use miden_objects::{Felt, ZERO};

use super::NullifierTree;
use crate::block::nullifier_tree::block_num_to_leaf_value;

#[test]
fn leaf_value_encoding() {
let block_num = 123;
let nullifier_value = block_num_to_leaf_value(block_num.into());

assert_eq!(nullifier_value, [Felt::from(block_num), ZERO, ZERO, ZERO]);
}

#[test]
fn leaf_value_decoding() {
let block_num = 123;
let nullifier_value = [Felt::from(block_num), ZERO, ZERO, ZERO];
let decoded_block_num = NullifierTree::leaf_value_to_block_num(nullifier_value);

assert_eq!(decoded_block_num, block_num.into());
}
}
41 changes: 41 additions & 0 deletions crates/miden-objects/src/block/partial_nullifier_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use miden_crypto::merkle::{PartialSmt, SmtProof};
use vm_core::{Word, EMPTY_WORD};
use vm_processor::Digest;

use crate::{
block::{nullifier_tree::block_num_to_leaf_value, BlockNumber},
note::Nullifier,
};

pub struct PartialNullifierTree(PartialSmt);

impl PartialNullifierTree {
pub const UNSPENT_NULLIFIER_VALUE: Word = EMPTY_WORD;

pub fn new() -> Self {
PartialNullifierTree(PartialSmt::new())
}

pub fn add_nullifier_path(&mut self, nullifier: Nullifier, proof: SmtProof) {
let (path, leaf) = proof.into_parts();

for (key, value) in leaf.into_entries() {
// We only need to check that the nullifier is unspent, the other key-value pairs of the
// leaf entries are unimportant here but still need to be added to the SMT to produce
// the correct nullifier tree root.
if key == nullifier.inner() && value != Self::UNSPENT_NULLIFIER_VALUE {
todo!("error: nullifier is already spent")
}

self.0.add_path(key, value, path.clone()).expect("TODO: Error");
}
}

pub fn mark_spent(&mut self, nullifier: Nullifier, block_num: BlockNumber) {
self.0.insert(nullifier.inner(), block_num_to_leaf_value(block_num));
}

pub fn root(&self) -> Digest {
self.0.root()
}
}
Loading

0 comments on commit 65b6da8

Please sign in to comment.