Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored NoteOrigin and NoteInclusionProof structs #810

Merged
merged 9 commits into from
Aug 2, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Added `CHANGELOG.md` warning message on CI (#799).
- Account deltas can now be merged (#797).
- Changed `AccountCode` procedures from merkle tree to sequential hash + added storage_offset support (#763).
- [BREAKING] Refactored and simplified `NoteOrigin` and `NoteInclusionProof` structs (#810).

## 0.4.0 (2024-07-03)

Expand Down
39 changes: 24 additions & 15 deletions miden-lib/src/transaction/inputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use alloc::vec::Vec;
use miden_objects::{
accounts::{Account, AccountProcedureInfo},
transaction::{
ChainMmr, ExecutedTransaction, InputNote, InputNotes, PreparedTransaction, TransactionArgs,
ChainMmr, ExecutedTransaction, InputNote, PreparedTransaction, TransactionArgs,
TransactionInputs, TransactionScript, TransactionWitness,
},
vm::{AdviceInputs, StackInputs},
Expand Down Expand Up @@ -92,7 +92,7 @@ fn extend_advice_inputs(
// build the advice map and Merkle store for relevant components
add_chain_mmr_to_advice_inputs(tx_inputs.block_chain(), advice_inputs);
add_account_to_advice_inputs(tx_inputs.account(), tx_inputs.account_seed(), advice_inputs);
add_input_notes_to_advice_inputs(tx_inputs.input_notes(), tx_args, advice_inputs);
add_input_notes_to_advice_inputs(tx_inputs, tx_args, advice_inputs);
advice_inputs.extend(tx_args.advice_inputs().clone());
}

Expand Down Expand Up @@ -267,29 +267,29 @@ fn add_account_to_advice_inputs(
/// The advice provider is populated with:
///
/// - For each note:
/// - The note's details (serial number, script root, and its' input / assets hash).
/// - The note's details (serial number, script root, and its input / assets hash).
/// - The note's private arguments.
/// - The note's public metadata.
/// - The note's public inputs data. Prefixed by its length and padded to an even word length.
/// - The note's asset padded. Prefixed by its length and padded to an even word length.
/// - For autheticated notes (determined by the `is_authenticated` flag):
/// - For authenticated notes (determined by the `is_authenticated` flag):
/// - The note's authentication path against its block's note tree.
/// - The block number, sub hash, note root.
/// - The note's position in the note tree
///
/// The data above is processed by `prologue::process_input_notes_data`.
fn add_input_notes_to_advice_inputs(
notes: &InputNotes<InputNote>,
tx_inputs: &TransactionInputs,
tx_args: &TransactionArgs,
inputs: &mut AdviceInputs,
) {
// if there are no input notes, nothing is added to the advice inputs
if notes.is_empty() {
if tx_inputs.input_notes().is_empty() {
return;
}

let mut note_data = Vec::new();
for input_note in notes.iter() {
for input_note in tx_inputs.input_notes().iter() {
let note = input_note.note();
let assets = note.assets();
let recipient = note.recipient();
Expand Down Expand Up @@ -320,6 +320,16 @@ fn add_input_notes_to_advice_inputs(
// insert note authentication path nodes into the Merkle store
match input_note {
InputNote::Authenticated { note, proof } => {
let block_num = proof.location().block_num();
let note_block_header = if block_num == tx_inputs.block_header().block_num() {
tx_inputs.block_header()
} else {
tx_inputs
.block_chain()
.get_block(block_num)
.expect("block not found in chain MMR")
};

// NOTE: keep in sync with the `prologue::process_input_note` kernel procedure
// Push the `is_authenticated` flag
note_data.push(Felt::ONE);
Expand All @@ -328,17 +338,16 @@ fn add_input_notes_to_advice_inputs(
inputs.extend_merkle_store(
proof
.note_path()
.inner_nodes(proof.origin().node_index.value(), note.hash())
.inner_nodes(proof.location().node_index_in_block(), note.hash())
.unwrap(),
);
note_data.push(proof.origin().block_num.into());
note_data.extend(*proof.sub_hash());
note_data.extend(*proof.note_root());
note_data.push(proof.location().block_num().into());
note_data.extend(note_block_header.sub_hash());
note_data.extend(note_block_header.note_root());
note_data.push(
proof
.origin()
.node_index
.value()
.location()
.node_index_in_block()
.try_into()
.expect("value is greater than or equal to the field modulus"),
);
Expand All @@ -352,5 +361,5 @@ fn add_input_notes_to_advice_inputs(
}

// NOTE: keep map in sync with the `prologue::process_input_notes_data` kernel procedure
inputs.extend_map([(notes.commitment(), note_data)]);
inputs.extend_map([(tx_inputs.input_notes().commitment(), note_data)]);
}
9 changes: 1 addition & 8 deletions miden-tx/src/compiler/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,7 @@ fn test_transaction_compilation_succeeds() {
let _account_code = tx_compiler.load_account(account_id, account_code_ast).unwrap();

let notes = mock_input_notes(&mut tx_compiler, account_id);
let mock_inclusion_proof = NoteInclusionProof::new(
Default::default(),
Default::default(),
Default::default(),
0,
Default::default(),
)
.unwrap();
let mock_inclusion_proof = NoteInclusionProof::new(0, 0, Default::default()).unwrap();
let notes = notes
.into_iter()
.map(|note| InputNote::authenticated(note, mock_inclusion_proof.clone()))
Expand Down
15 changes: 9 additions & 6 deletions objects/src/block/note_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use miden_crypto::{
use crate::{
notes::NoteMetadata,
utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
BLOCK_OUTPUT_NOTES_TREE_DEPTH, MAX_NOTES_PER_BATCH,
BLOCK_OUTPUT_NOTES_TREE_DEPTH, MAX_NOTES_PER_BATCH, MAX_OUTPUT_NOTES_PER_BLOCK,
};

/// Wrapper over [SimpleSmt<BLOCK_OUTPUT_NOTES_TREE_DEPTH>] for notes tree.
Expand All @@ -35,7 +35,7 @@ impl BlockNoteTree {
entries: impl IntoIterator<Item = (BlockNoteIndex, RpoDigest, NoteMetadata)>,
) -> Result<Self, MerkleError> {
let interleaved = entries.into_iter().flat_map(|(index, note_id, metadata)| {
let id_index = index.leaf_index();
let id_index = index.leaf_index().into();
[(id_index, note_id.into()), (id_index + 1, metadata.into())]
});

Expand All @@ -52,7 +52,7 @@ impl BlockNoteTree {
/// The returned path is to the node which is the parent of both note and note metadata node.
pub fn get_note_path(&self, index: BlockNoteIndex) -> Result<MerklePath, MerkleError> {
// get the path to the leaf containing the note (path len = 21)
let leaf_index = LeafIndex::new(index.leaf_index())?;
let leaf_index = LeafIndex::new(index.leaf_index().into())?;

// move up the path by removing the first node, this path now points to the parent of the
// note path
Expand Down Expand Up @@ -92,11 +92,14 @@ impl BlockNoteIndex {
}

/// Returns an index to the node which the parent of both the note and note metadata.
pub fn to_absolute_index(&self) -> u64 {
(self.batch_idx() * MAX_NOTES_PER_BATCH + self.note_idx_in_batch()) as u64
pub fn to_absolute_index(&self) -> u32 {
const _: () = assert!(MAX_OUTPUT_NOTES_PER_BLOCK <= u32::MAX as usize);
(self.batch_idx() * MAX_NOTES_PER_BATCH + self.note_idx_in_batch()) as u32
}

fn leaf_index(&self) -> u64 {
/// Returns an index of the leaf containing the note.
fn leaf_index(&self) -> u32 {
const _: () = assert!(MAX_OUTPUT_NOTES_PER_BLOCK * 2 <= u32::MAX as usize);
self.to_absolute_index() * 2
}
}
Expand Down
10 changes: 5 additions & 5 deletions objects/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ pub const BATCH_OUTPUT_NOTES_TREE_DEPTH: u8 = 13;
/// number of notes is the number of leaves in the tree.
pub const MAX_NOTES_PER_BATCH: usize = 2_usize.pow((BATCH_OUTPUT_NOTES_TREE_DEPTH - 1) as u32);

/// The maximum number of transaction in a single batch.
pub const MAX_TRANSACTIONS_PER_BATCH: usize = MAX_NOTES_PER_BATCH / MAX_OUTPUT_NOTES_PER_TX;

// BLOCK
// ================================================================================================

Expand All @@ -48,8 +45,8 @@ pub const MAX_TRANSACTIONS_PER_BATCH: usize = MAX_NOTES_PER_BATCH / MAX_OUTPUT_N
/// This value can be interpreted as:
///
/// - The depth of a tree with the leaves set to a batch output note tree root.
/// - The level at which the batches create note trees are merged, creating a new tree with this many
/// additional new levels.
/// - The level at which the batches create note trees are merged, creating a new tree with this
/// many additional new levels.
pub const BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH: u8 = 8;

/// The final depth of the Sparse Merkle Tree used to store all notes created in a block.
Expand All @@ -59,5 +56,8 @@ pub const BLOCK_OUTPUT_NOTES_TREE_DEPTH: u8 =
/// Maximum number of batches that can be inserted into a single block.
pub const MAX_BATCHES_PER_BLOCK: usize = 2_usize.pow(BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH as u32);

/// Maximum number of output notes that can be created in a single block.
pub const MAX_OUTPUT_NOTES_PER_BLOCK: usize = MAX_NOTES_PER_BATCH * MAX_BATCHES_PER_BLOCK;

/// The block height of the genesis block
pub const GENESIS_BLOCK: u32 = 0;
6 changes: 3 additions & 3 deletions objects/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ pub enum NoteError {
InvalidNoteTagUseCase(u16),
InvalidNoteType(NoteType),
InvalidNoteTypeValue(u64),
InvalidOriginIndex(String),
InvalidLocationIndex(String),
InvalidStubDataLen(usize),
NetworkExecutionRequiresOnChainAccount,
NetworkExecutionRequiresPublicNote(NoteType),
Expand All @@ -166,8 +166,8 @@ impl NoteError {
Self::DuplicateNonFungibleAsset(asset)
}

pub fn invalid_origin_index(msg: String) -> Self {
Self::InvalidOriginIndex(msg)
pub fn invalid_location_index(msg: String) -> Self {
Self::InvalidLocationIndex(msg)
}

pub fn too_many_assets(num_assets: usize) -> Self {
Expand Down
9 changes: 1 addition & 8 deletions objects/src/notes/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,7 @@ mod tests {
#[test]
fn serialize_with_proof() {
let note = create_example_note();
let mock_inclusion_proof = NoteInclusionProof::new(
Default::default(),
Default::default(),
Default::default(),
0,
Default::default(),
)
.unwrap();
let mock_inclusion_proof = NoteInclusionProof::new(0, 0, Default::default()).unwrap();
let file = NoteFile::NoteWithProof(note.clone(), mock_inclusion_proof.clone());
let mut buffer = Vec::new();
file.write_into(&mut buffer);
Expand Down
99 changes: 46 additions & 53 deletions objects/src/notes/origin.rs → objects/src/notes/location.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
use alloc::string::ToString;

use super::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Digest, NoteError, Serializable,
NOTE_TREE_DEPTH,
ByteReader, ByteWriter, Deserializable, DeserializationError, NoteError, Serializable,
};
use crate::crypto::merkle::{MerklePath, NodeIndex};
use crate::{crypto::merkle::MerklePath, MAX_BATCHES_PER_BLOCK, MAX_NOTES_PER_BATCH};

/// Contains information about the origin of a note.
/// Contains information about the location of a note.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct NoteOrigin {
pub struct NoteLocation {
/// The block number the note was created in.
pub block_num: u32,
block_num: u32,

/// The index of the note in the note Merkle tree of the block the note was created in.
pub node_index: NodeIndex, // TODO: should be a u32 because the depth is always the same
node_index_in_block: u32,
}

impl NoteLocation {
/// Returns the block number the note was created in.
pub fn block_num(&self) -> u32 {
self.block_num
}

/// Returns the index of the note in the note Merkle tree of the block the note was created in.
///
/// # Note
///
/// The height of the Merkle tree is `BLOCK_OUTPUT_NOTES_TREE_DEPTH`. Thus, the maximum index
/// is `2 ^ BLOCK_OUTPUT_NOTES_TREE_DEPTH - 1`.
pub fn node_index_in_block(&self) -> u64 {
self.node_index_in_block as u64
}
}

/// Contains the data required to prove inclusion of a note in the canonical chain.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct NoteInclusionProof {
/// Details about the note's origin.
origin: NoteOrigin,

/// The sub hash of the block the note was created in.
sub_hash: Digest,

/// The note root of the block the note was created in.
note_root: Digest,
/// Details about the note's location.
location: NoteLocation,

/// The note's authentication Merkle path its block's the note root.
note_path: MerklePath,
Expand All @@ -38,37 +46,26 @@ impl NoteInclusionProof {
/// Returns a new [NoteInclusionProof].
pub fn new(
block_num: u32,
sub_hash: Digest,
note_root: Digest,
index: u64,
node_index_in_block: u32,
note_path: MerklePath,
) -> Result<Self, NoteError> {
let node_index = NodeIndex::new(NOTE_TREE_DEPTH, index)
.map_err(|e| NoteError::invalid_origin_index(e.to_string()))?;
Ok(Self {
origin: NoteOrigin { block_num, node_index },
sub_hash,
note_root,
note_path,
})
const HIGHEST_INDEX: usize = MAX_BATCHES_PER_BLOCK * MAX_NOTES_PER_BATCH - 1;
if node_index_in_block as usize > HIGHEST_INDEX {
return Err(NoteError::InvalidLocationIndex(format!(
"Node index ({node_index_in_block}) is out of bounds (0..={HIGHEST_INDEX})."
)));
}
let location = NoteLocation { block_num, node_index_in_block };

Ok(Self { location, note_path })
}

// ACCESSORS
// --------------------------------------------------------------------------------------------

/// Returns the sub hash of the block header the note was created in.
pub fn sub_hash(&self) -> Digest {
self.sub_hash
}

/// Returns the note root of the block header the note was created in.
pub fn note_root(&self) -> Digest {
self.note_root
}

/// Returns the origin of the note.
pub fn origin(&self) -> &NoteOrigin {
&self.origin
/// Returns the location of the note.
pub fn location(&self) -> &NoteLocation {
&self.location
}

/// Returns the Merkle path to the note in the note Merkle tree of the block the note was
Expand All @@ -81,38 +78,34 @@ impl NoteInclusionProof {
// SERIALIZATION
// ================================================================================================

impl Serializable for NoteOrigin {
impl Serializable for NoteLocation {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u32(self.block_num);
self.node_index.write_into(target);
target.write_u32(self.node_index_in_block);
}
}

impl Deserializable for NoteOrigin {
impl Deserializable for NoteLocation {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let block_num = source.read_u32()?;
let node_index = NodeIndex::read_from(source)?;
let node_index_in_block = source.read_u32()?;

Ok(Self { block_num, node_index })
Ok(Self { block_num, node_index_in_block })
}
}

impl Serializable for NoteInclusionProof {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.origin.write_into(target);
self.sub_hash.write_into(target);
self.note_root.write_into(target);
self.location.write_into(target);
self.note_path.write_into(target);
}
}

impl Deserializable for NoteInclusionProof {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let origin = NoteOrigin::read_from(source)?;
let sub_hash = Digest::read_from(source)?;
let note_root = Digest::read_from(source)?;
let location = NoteLocation::read_from(source)?;
let note_path = MerklePath::read_from(source)?;

Ok(Self { origin, sub_hash, note_root, note_path })
Ok(Self { location, note_path })
}
}
Loading
Loading