From c48d582aa2cb025447c19fcb3a82c4c1b41c3eb8 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 28 Apr 2026 14:50:16 +0200 Subject: [PATCH 01/23] feat: add get_attachments_commitment kernel API --- .../asm/kernels/transaction/api.masm | 27 ++++++++ .../kernels/transaction/lib/output_note.masm | 35 +++++++++++ .../asm/protocol/kernel_proc_offsets.masm | 33 +++++----- crates/miden-protocol/asm/protocol/note.masm | 32 ++++++++++ .../asm/protocol/output_note.masm | 63 +++++++++++++++++++ 5 files changed, 174 insertions(+), 16 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index f85b373b21..6468e0d020 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -1214,6 +1214,33 @@ pub proc output_note_get_assets_info # => [ASSETS_COMMITMENT, num_assets, pad(11)] end +#! Returns the commitment over all attachments. +#! +#! Inputs: [note_index, pad(15)] +#! Outputs: [ATTACHMENTS_COMMITMENT, pad(12)] +#! +#! Where: +#! - note_index is the index of the output note whose attachments commitment should be returned. +#! - ATTACHMENTS_COMMITMENT is the commitment to all attachments of the note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of output notes. +#! +#! Invocation: dynexec +pub proc output_note_get_attachments_commitment + # assert that the provided note index is less than the total number of output notes + exec.output_note::assert_note_index_in_bounds + # => [note_index, pad(15)] + + # get the attachments commitment + exec.output_note::get_attachments_commitment + # => [ATTACHMENTS_COMMITMENT, pad(16)] + + # truncate the stack + swapw dropw + # => [ATTACHMENTS_COMMITMENT, pad(12)] +end + #! Returns the recipient of the output note with the specified index. #! #! Inputs: [note_index, pad(15)] diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm index 6ae57ad334..38301dd85f 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm @@ -177,6 +177,41 @@ pub proc get_assets_info # => [ASSETS_COMMITMENT, num_assets] end +#! Returns the commitment over all attachments in the note with the provided index. +#! +#! Inputs: [note_index] +#! Outputs: [ATTACHMENTS_COMMITMENT] +#! +#! Where: +#! - note_index is the index of the output note whose attachments commitment should be returned. +#! - ATTACHMENTS_COMMITMENT is the commitment to all attachments of the note. +pub proc get_attachments_commitment + # get the note data pointer based on the index of the requested note + exec.memory::get_output_note_ptr + # => [note_ptr] + + dup exec.memory::get_output_note_num_attachments + # => [num_attachments, note_ptr] + + dup.1 exec.memory::get_output_note_attachment_commitment_ptr + # => [start_ptr, num_attachments, note_ptr] + + # compute start_ptr and end_ptr for the attachment commitments in memory + dup movup.2 mul.WORD_SIZE add swap + # => [start_ptr, end_ptr, note_ptr] + + movup.2 exec.note::compute_attachments_commitment + # => [ATTACHMENTS_COMMITMENT, start_ptr, end_ptr] + + # store the attachment commitments to the advice map using ATTACHMENTS_COMMITMENT as a key + adv.insert_mem + # => [ATTACHMENTS_COMMITMENT, start_ptr, end_ptr] + + # remove pointers from the stack + movup.4 drop movup.4 drop + # => [ATTACHMENTS_COMMITMENT] +end + #! Adds the asset to the note specified by the index. #! #! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx] diff --git a/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm b/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm index 61493fd343..176972f288 100644 --- a/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm +++ b/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm @@ -62,33 +62,34 @@ pub const INPUT_NOTE_GET_RECIPIENT_OFFSET=33 # output notes pub const OUTPUT_NOTE_CREATE_OFFSET=34 pub const OUTPUT_NOTE_GET_METADATA_OFFSET=35 -pub const OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=36 -pub const OUTPUT_NOTE_GET_RECIPIENT_OFFSET=37 -pub const OUTPUT_NOTE_ADD_ASSET_OFFSET=38 -pub const OUTPUT_NOTE_ADD_ATTACHMENT_OFFSET=39 +pub const OUTPUT_NOTE_GET_RECIPIENT_OFFSET=36 +pub const OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=37 +pub const OUTPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET=38 +pub const OUTPUT_NOTE_ADD_ASSET_OFFSET=39 +pub const OUTPUT_NOTE_ADD_ATTACHMENT_OFFSET=40 ### Tx ########################################## # input notes -pub const TX_GET_NUM_INPUT_NOTES_OFFSET=40 -pub const TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=41 +pub const TX_GET_NUM_INPUT_NOTES_OFFSET=41 +pub const TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=42 # output notes -pub const TX_GET_NUM_OUTPUT_NOTES_OFFSET=42 -pub const TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=43 +pub const TX_GET_NUM_OUTPUT_NOTES_OFFSET=43 +pub const TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=44 # block info -pub const TX_GET_BLOCK_COMMITMENT_OFFSET=44 -pub const TX_GET_BLOCK_NUMBER_OFFSET=45 -pub const TX_GET_BLOCK_TIMESTAMP_OFFSET=46 +pub const TX_GET_BLOCK_COMMITMENT_OFFSET=45 +pub const TX_GET_BLOCK_NUMBER_OFFSET=46 +pub const TX_GET_BLOCK_TIMESTAMP_OFFSET=47 # foreign context -pub const TX_PREPARE_FPI_OFFSET = 47 -pub const TX_EXEC_FOREIGN_PROC_OFFSET = 48 +pub const TX_PREPARE_FPI_OFFSET = 48 +pub const TX_EXEC_FOREIGN_PROC_OFFSET = 49 # expiration data -pub const TX_GET_EXPIRATION_DELTA_OFFSET=49 # accessor -pub const TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=50 # mutator +pub const TX_GET_EXPIRATION_DELTA_OFFSET=50 # accessor +pub const TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=51 # mutator # tx script -pub const TX_GET_TX_SCRIPT_ROOT_OFFSET=51 +pub const TX_GET_TX_SCRIPT_ROOT_OFFSET=52 diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index f0df01ff63..1b30326ab2 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -79,6 +79,38 @@ pub proc write_assets_to_memory # AS => [] end +#! Writes the attachment commitments stored in the advice map to memory specified by the provided +#! destination pointer. +#! +#! Inputs: +#! Operand stack: [ATTACHMENTS_COMMITMENT, dest_ptr] +#! Advice map: { +#! ATTACHMENTS_COMMITMENT: [[ATTACHMENT_COMMITMENT]] +#! } +#! Outputs: +#! Operand stack: [num_attachments] +pub proc write_attachments_to_memory + # push the individual ATTACHMENT commitments from the advice map onto the advice stack + adv.push_mapvaln + # OS => [ATTACHMENTS_COMMITMENT, dest_ptr] + # AS => [num_elements, [ATTACHMENT_COMMITMENT]] + + # SAFETY: kernel should ensure num_elements is a multiple of WORD_SIZE = 4 and is in valid + # range + adv_push.1 u32div.4 + # OS => [num_words, ATTACHMENTS_COMMITMENT, dest_ptr] + # AS => [[ATTACHMENT_COMMITMENT]] + + # store the number of words as the number of attachments for return + swap.5 dup.5 + # OS => [num_words, dest_ptr, ATTACHMENTS_COMMITMENT, num_attachments] + # AS => [[ATTACHMENT_COMMITMENT]] + + # pipe attachment commitments to memory and validate they match the ATTACHMENTS_COMMITMENT + exec.mem::pipe_preimage_to_memory drop + # => [num_attachments] +end + #! Builds the recipient hash from note storage, script root, and serial number. #! #! This procedure computes the commitment of the note storage and then uses it to calculate the note diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index c922f3172c..ca135f9a63 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -4,6 +4,7 @@ use miden::protocol::kernel_proc_offsets::OUTPUT_NOTE_ADD_ASSET_OFFSET use miden::protocol::kernel_proc_offsets::OUTPUT_NOTE_ADD_ATTACHMENT_OFFSET use miden::protocol::kernel_proc_offsets::OUTPUT_NOTE_GET_RECIPIENT_OFFSET use miden::protocol::kernel_proc_offsets::OUTPUT_NOTE_GET_METADATA_OFFSET +use miden::protocol::kernel_proc_offsets::OUTPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET use miden::protocol::note use miden::core::crypto::hashes::poseidon2 @@ -123,6 +124,39 @@ pub proc get_assets # => [num_assets] end +#! Returns the commitment over all attachments of the output note with the specified index. +#! +#! Inputs: [note_index] +#! Outputs: [ATTACHMENTS_COMMITMENT] +#! +#! Where: +#! - note_index is the index of the output note whose attachments commitment should be returned. +#! - ATTACHMENTS_COMMITMENT is the commitment to all attachments of the note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of output notes. +#! +#! Invocation: exec +pub proc get_attachments_commitment + # start padding the stack + push.0.0 movup.2 + # => [note_index, 0, 0] + + push.OUTPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET + # => [offset, note_index, 0, 0] + + # pad the stack + padw swapw padw padw swapdw + # => [offset, note_index, pad(14)] + + syscall.exec_kernel_proc + # => [ATTACHMENTS_COMMITMENT, pad(12)] + + # clean the stack + swapdw dropw dropw swapw dropw + # => [ATTACHMENTS_COMMITMENT] +end + #! Adds the asset to the note specified by the index. #! #! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx] @@ -343,3 +377,32 @@ pub proc get_metadata swapdw dropw dropw # => [NOTE_ATTACHMENT, METADATA_HEADER] end + +#! Writes the attachment commitments from the note with note_idx to memory and returns the pointer +#! to it. +#! +#! Inputs: [note_index] +#! Outputs: [num_attachments, attachments_ptr] +#! +#! Where: +#! - note_index is the index of the output note whose metadata should be returned. +#! - METADATA_HEADER is the metadata header of the specified output note. +#! - NOTE_ATTACHMENT is the first attachment of the specified output note. +#! +#! Pancis if: +#! - The sequential hash over the attachment commitments in the advice inputs does not match the +#! attachments commitment. +@locals(16) +pub proc get_attachment_commitments + exec.get_attachments_commitment + # => [ATTACHMENTS_COMMITMENT] + + locaddr.0 movdn.4 + # => [ATTACHMENTS_COMMITMENT, dest_ptr] + + exec.note::write_attachments_to_memory + # => [num_attachments] + + locaddr.0 swap + # => [num_attachments, dest_ptr] +end From 3a97ed8e8bf7eeabcc7e24568eb546597123a30c Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 28 Apr 2026 15:47:53 +0200 Subject: [PATCH 02/23] chore: add test for get_attachments_commitemnt API --- .../src/kernel_tests/tx/test_output_note.rs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index a332f963cb..9a9509dd5e 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1468,6 +1468,109 @@ async fn test_network_note() -> anyhow::Result<()> { Ok(()) } +/// Test that `output_note::get_attachment_commitments` returns the correct number of attachments +/// and writes the individual attachment commitments to memory at the returned pointer. +#[tokio::test] +async fn test_get_attachment_commitments() -> anyhow::Result<()> { + let account = Account::mock(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, Auth::IncrNonce); + let rng = RandomCoin::new(Word::from([1, 2, 3, 4u32])); + + let attachment_0 = + NoteAttachment::new_word(NoteAttachmentScheme::new(1)?, Word::from([3, 4, 5, 6u32])); + let attachment_1 = + NoteAttachment::new_word(NoteAttachmentScheme::new(2)?, Word::from([7, 8, 9, 10u32])); + + let output_note = RawOutputNote::Full( + NoteBuilder::new(account.id(), rng) + .attachment(attachment_0.clone()) + .attachment(attachment_1.clone()) + .build()?, + ); + + let _attachments_commitment = output_note.attachments().commitment(); + let commitment_0 = attachment_0.to_commitment(); + let commitment_1 = attachment_1.to_commitment(); + + let tx_script = format!( + " + use miden::protocol::output_note + use miden::core::sys + + begin + push.{RECIPIENT} + push.{note_type} + push.{tag} + exec.output_note::create + # => [note_idx] + + # add first word attachment (note_idx = 0) + push.{ATTACHMENT_WORD_0} + push.{attachment_scheme_0} + # => [attachment_scheme, ATTACHMENT, note_idx] + exec.output_note::add_word_attachment + # => [] + + # add second word attachment + push.0 + push.{ATTACHMENT_WORD_1} + push.{attachment_scheme_1} + # => [attachment_scheme, ATTACHMENT, note_idx=0] + exec.output_note::add_word_attachment + # => [] + + # get attachment commitments for note at index 0 + push.0 + exec.output_note::get_attachment_commitments + # => [num_attachments, attachments_ptr] + + # assert num_attachments == 2 + eq.2 assert.err=\"expected 2 attachments\" + # => [attachments_ptr] + + # read commitment 0 from memory at ptr and assert + padw dup.4 mem_loadw_le + # => [COMMITMENT_0, attachments_ptr] + push.{EXPECTED_COMMITMENT_0} + assert_eqw.err=\"attachment commitment 0 mismatch\" + # => [attachments_ptr] + + # advance pointer to next word (WORD_SIZE=4) and read commitment 1 + padw movup.4 add.4 mem_loadw_le + # => [COMMITMENT_1] + push.{EXPECTED_COMMITMENT_1} + assert_eqw.err=\"attachment commitment 1 mismatch\" + # => [] + + # truncate the stack + exec.sys::truncate_stack + end + ", + RECIPIENT = output_note.recipient().unwrap().digest(), + note_type = output_note.metadata().note_type() as u8, + tag = output_note.metadata().tag().as_u32(), + attachment_scheme_0 = attachment_0.attachment_scheme().as_u16(), + ATTACHMENT_WORD_0 = Word::from([3, 4, 5, 6u32]), + attachment_scheme_1 = attachment_1.attachment_scheme().as_u16(), + ATTACHMENT_WORD_1 = Word::from([7, 8, 9, 10u32]), + EXPECTED_COMMITMENT_0 = commitment_0, + EXPECTED_COMMITMENT_1 = commitment_1, + ); + + let tx_script = CodeBuilder::new().compile_tx_script(tx_script)?; + + let tx = TransactionContextBuilder::new(account) + .extend_expected_output_notes(vec![output_note.clone()]) + .tx_script(tx_script) + .build()? + .execute() + .await?; + + let actual_note = tx.output_notes().get_note(0); + assert_eq!(actual_note.header(), output_note.header()); + + Ok(()) +} + /// Test that output_note procedures abort when given an out-of-bounds note index (equal to /// num_output_notes). /// @@ -1483,6 +1586,8 @@ async fn test_network_note() -> anyhow::Result<()> { #[case::add_attachment(5, "add_attachment")] #[case::add_word_attachment(5, "add_word_attachment")] #[case::add_array_attachment(5, "add_array_attachment")] +#[case::get_attachment_commitments(0, "get_attachment_commitments")] +#[case::get_attachments_commitment(0, "get_attachments_commitment")] #[tokio::test] async fn test_output_note_index_out_of_bounds( #[case] params_above: usize, From e504b7c02eeae8d613349efcf175cc39bf387e52 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 28 Apr 2026 18:03:37 +0200 Subject: [PATCH 03/23] feat: add get_attachment API --- crates/miden-protocol/asm/protocol/note.masm | 87 ++++++++++++++++++- .../asm/protocol/output_note.masm | 29 +++++++ .../src/kernel_tests/tx/test_output_note.rs | 81 +++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletion(-) diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 1b30326ab2..bee778d3f3 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -14,6 +14,14 @@ pub use miden::protocol::util::note::MAX_ATTACHMENT_WORDS const ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT="number of note storage exceeded the maximum limit of 1024" +const ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS = "attachment index out of bounds" + +# CONSTANTS +# ================================================================================================= + +#! The number of elements in a word. +const WORD_SIZE = 4 + # NOTE UTILITY PROCEDURES # ================================================================================================= @@ -97,7 +105,7 @@ pub proc write_attachments_to_memory # SAFETY: kernel should ensure num_elements is a multiple of WORD_SIZE = 4 and is in valid # range - adv_push.1 u32div.4 + adv_push.1 u32div.WORD_SIZE # OS => [num_words, ATTACHMENTS_COMMITMENT, dest_ptr] # AS => [[ATTACHMENT_COMMITMENT]] @@ -111,6 +119,83 @@ pub proc write_attachments_to_memory # => [num_attachments] end +#! Writes a single attachment's data stored in the advice map to the memory specified by the +#! provided destination pointer. +#! +#! Inputs: +#! Operand stack: [ATTACHMENT_COMMITMENT, dest_ptr] +#! Advice map: { +#! ATTACHMENT_COMMITMENT: [[ATTACHMENT_ELEMENTS]], +#! } +#! Outputs: +#! Operand stack: [num_words] +#! +#! Where: +#! - ATTACHMENT_COMMITMENT is the hash commitment to the attachment elements. +#! - dest_ptr is the memory address to write the attachment data. +#! - num_words is the number of words in the attachment. +pub proc write_attachment_to_memory + # push the number of attachment elements from the advice map onto the advice stack + adv.push_mapvaln + # OS => [ATTACHMENT_COMMITMENT, dest_ptr] + # AS => [num_elements, [ATTACHMENT_ELEMENTS]] + + # read num_elements and compute num_words + adv_push.1 u32div.WORD_SIZE + # OS => [num_words, ATTACHMENT_COMMITMENT, dest_ptr] + # AS => [[ATTACHMENT_ELEMENTS]] + + swap.5 dup.5 + # OS => [num_words, dest_ptr, ATTACHMENT_COMMITMENT, num_words] + # AS => [[ATTACHMENT_ELEMENTS]] + + # pipe the attachment data into memory, validating against ATTACHMENT_COMMITMENT + exec.mem::pipe_preimage_to_memory drop + # => [num_words] +end + +#! Writes the attachment with the provided index from the provided attachments to memory and +#! returns the pointer to the attachment elements. +#! +#! Inputs: [num_attachments, attachments_ptr, attachment_idx] +#! Outputs: [num_words, attachment_ptr] +#! +#! Where: +#! - attachment_idx is the index of the attachment to retrieve. +#! - attachment_ptr is a pointer to the attachment data written to memory. +#! - attachments_ptr is a pointer to the attachments written to memory. +#! - num_words is the number of attachments. +#! - num_words is the number of words in the attachment. +#! +#! Panics if: +#! - the attachment index is greater or equal to the number of attachments. +#! - the sequential hash over the attachment data in the advice inputs does not match the +#! attachment commitment. +#! +#! Invocation: exec +@locals(1024) +pub proc get_attachment + # assert attachment_idx < num_attachments + dup.2 swap u32assert2.err=ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS + u32lt assert.err=ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS + # => [attachments_ptr, attachment_idx] + + # compute the memory address of the attachment commitment: + # commitment_ptr = attachments_ptr + attachment_idx * WORD_SIZE + swap mul.WORD_SIZE add + # => [commitment_ptr] + + # load the ATTACHMENT_COMMITMENT from memory + locaddr.0 padw movup.5 mem_loadw_le + # => [ATTACHMENT_COMMITMENT, dest_ptr] + + exec.write_attachment_to_memory + # => [num_words] + + locaddr.0 swap + # => [num_words, attachment_ptr] +end + #! Builds the recipient hash from note storage, script root, and serial number. #! #! This procedure computes the commitment of the note storage and then uses it to calculate the note diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index ca135f9a63..30a9b42c1f 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -406,3 +406,32 @@ pub proc get_attachment_commitments locaddr.0 swap # => [num_attachments, dest_ptr] end + +#! Writes the attachment with the provided index from the note with note_idx to memory and returns +#! the pointer to the data. +#! +#! Inputs: [attachment_idx, note_index] +#! Outputs: [num_words, attachment_ptr] +#! +#! Where: +#! - attachment_idx is the index of the attachment to retrieve. +#! - note_index is the index of the output note. +#! - attachment_ptr is a pointer to the attachment data written to local memory. +#! - num_words is the number of words in the attachment. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of output notes. +#! - the attachment index is greater or equal to the number of attachments. +#! - the sequential hash over the attachment data in the advice inputs does not match the +#! attachment commitment. +#! +#! Invocation: exec +@locals(1024) +pub proc get_attachment + # get the attachment commitments for the note + swap exec.get_attachment_commitments + # => [num_attachments, attachments_ptr, attachment_idx] + + exec.note::get_attachment + # => [num_words, attachment_ptr] +end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 9a9509dd5e..24a34b6174 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1571,6 +1571,87 @@ async fn test_get_attachment_commitments() -> anyhow::Result<()> { Ok(()) } +/// Test that `output_note::get_attachment` retrieves the correct attachment data from local memory +/// after piping its preimage from the advice map. +#[tokio::test] +async fn test_get_attachment() -> anyhow::Result<()> { + let account = Account::mock(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, Auth::IncrNonce); + let rng = RandomCoin::new(Word::from([1, 2, 3, 4u32])); + + let word_0 = Word::from([3, 4, 5, 6u32]); + let word_1 = Word::from([7, 8, 9, 10u32]); + let attachment_0 = NoteAttachment::new_word(NoteAttachmentScheme::new(1)?, word_0); + let attachment_1 = NoteAttachment::new_word(NoteAttachmentScheme::new(2)?, word_1); + + let output_note = RawOutputNote::Full( + NoteBuilder::new(account.id(), rng) + .attachment(attachment_0.clone()) + .attachment(attachment_1.clone()) + .build()?, + ); + + let tx_script = format!( + " + use miden::protocol::output_note + use miden::core::sys + + begin + push.{RECIPIENT} + push.{note_type} + push.{tag} + exec.output_note::create + # => [note_idx] + + # add first word attachment (note_idx = 0) + push.{ATTACHMENT_WORD_0} + push.{attachment_scheme_0} + # => [attachment_scheme, ATTACHMENT, note_idx] + exec.output_note::add_word_attachment + # => [] + + # add second word attachment + push.0 + push.{ATTACHMENT_WORD_1} + push.{attachment_scheme_1} + # => [attachment_scheme, ATTACHMENT, note_idx=0] + exec.output_note::add_word_attachment + # => [] + + # --- get attachment 1 first (to debug with non-zero idx) --- + push.0 push.1 + # => [attachment_idx=1, note_idx=0] + exec.output_note::get_attachment + # => [attachment_ptr, num_words] + drop drop + + # truncate the stack + exec.sys::truncate_stack + end + ", + RECIPIENT = output_note.recipient().unwrap().digest(), + note_type = output_note.metadata().note_type() as u8, + tag = output_note.metadata().tag().as_u32(), + attachment_scheme_0 = attachment_0.attachment_scheme().as_u16(), + ATTACHMENT_WORD_0 = word_0, + attachment_scheme_1 = attachment_1.attachment_scheme().as_u16(), + ATTACHMENT_WORD_1 = word_1, + ); + + let tx_script = CodeBuilder::new().compile_tx_script(tx_script)?; + + let tx = TransactionContextBuilder::new(account) + .extend_expected_output_notes(vec![output_note.clone()]) + .tx_script(tx_script) + .build()? + .execute() + .await?; + + let actual_note = tx.output_notes().get_note(0); + assert_eq!(actual_note.header(), output_note.header()); + + Ok(()) +} + /// Test that output_note procedures abort when given an out-of-bounds note index (equal to /// num_output_notes). /// From 90fbee9bc67f603c5d90e4b9045df625541998a6 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 09:52:26 +0200 Subject: [PATCH 04/23] chore: implement metadata_into_attachment_schemes --- crates/miden-protocol/asm/protocol/note.masm | 35 ++++++++++ .../src/kernel_tests/tx/test_note.rs | 67 +++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index bee778d3f3..362fffdcff 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -337,6 +337,41 @@ pub proc metadata_into_attachment_header # [attachment_0_scheme] end +#! Extracts the attachment's schemes from the provided metadata header. +#! +#! Inputs: [METADATA_HEADER] +#! Outputs: [attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme] +#! +#! Where: +#! - METADATA_HEADER is the metadata word of a note. +#! - attachment_n_scheme is the scheme of the nth attachment (0 if absent). +#! +#! Invocation: exec +pub proc metadata_into_attachment_schemes + # => [sender_id_suffix_type_version, sender_id_prefix, tag, schemes] + + drop drop drop + # => [schemes] + + u32split swap + # => [schemes_hi, schemes_lo] + + # extract attachment scheme 3 from bits 48..64 + dup u32and.0xffff0000 u32shr.16 + # => [attachment_3_scheme, schemes_hi, schemes_lo] + + # extract attachment scheme 2 from bits 32..48 + swap u32and.0xffff + # => [attachment_2_scheme, attachment_3_scheme, schemes_lo] + + # extract attachment scheme 1 from bits 16..32 + dup.2 u32and.0xffff0000 u32shr.16 + # => [attachment_1_scheme, attachment_2_scheme, attachment_3_scheme, schemes_lo] + + movup.3 u32and.0xffff + # => [attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme] +end + #! Extracts the note type from the provided metadata header. #! #! The note type is encoded as a single bit at the 4th position from the right side (LSB) of the diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 19eeca7078..818930d8b0 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -13,6 +13,8 @@ use miden_protocol::errors::MasmError; use miden_protocol::note::{ Note, NoteAssets, + NoteAttachmentHeader, + NoteAttachmentScheme, NoteAttachments, NoteMetadata, NoteMetadataHeader, @@ -35,6 +37,7 @@ use miden_standards::testing::note::NoteBuilder; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; +use crate::executor::CodeExecutor; use crate::kernel_tests::tx::{ExecutionOutputExt, input_note_data_ptr}; use crate::{ Auth, @@ -537,3 +540,67 @@ async fn test_public_key_as_note_input() -> anyhow::Result<()> { tx_context.execute().await?; Ok(()) } + +#[rstest::rstest] +#[case::all_present( + [ + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(2)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(10000)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(0xfffe)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(42)), + ] +)] +#[case::first_only( + [ + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(0x0fff)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(0xfffe)), + NoteAttachmentHeader::absent(), + NoteAttachmentHeader::absent(), + ] +)] +#[case::all_absent( + [ + NoteAttachmentHeader::absent(), + NoteAttachmentHeader::absent(), + NoteAttachmentHeader::absent(), + NoteAttachmentHeader::absent(), + ] +)] +#[tokio::test] +async fn test_metadata_into_attachment_schemes( + #[case] attachment_headers: [NoteAttachmentHeader; 4], +) -> anyhow::Result<()> { + let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); + let metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(NoteTag::new(0)); + let metadata_header = + NoteMetadataHeader::from_parts(metadata, attachment_headers, Word::default()); + let metadata_word = metadata_header.to_metadata_word(); + + let code = format!( + " + use miden::protocol::note + + begin + push.{metadata_word} + exec.note::metadata_into_attachment_schemes + # => [scheme0, scheme1, scheme2, scheme3, pad(16)] + + # truncate the stack + swapw dropw + end + ", + ); + + let exec_output = CodeExecutor::with_default_host().run(&code).await?; + + for (i, header) in attachment_headers.iter().enumerate() { + let expected = header.scheme().map_or(0u64, |s| s.as_u16() as u64); + assert_eq!( + exec_output.get_stack_element(i), + Felt::new(expected), + "attachment scheme mismatch at index {i}" + ); + } + + Ok(()) +} From d662faadb7da78db4ee885c3c43cbb80e38b0b6e Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 10:12:53 +0200 Subject: [PATCH 05/23] feat: implement find_attachment_idx --- crates/miden-protocol/asm/protocol/note.masm | 48 ++++++++++ .../src/kernel_tests/tx/test_active_note.rs | 1 - .../src/kernel_tests/tx/test_input_note.rs | 1 - .../src/kernel_tests/tx/test_note.rs | 92 +++++++++++++++++++ 4 files changed, 140 insertions(+), 2 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 362fffdcff..4a30e37809 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -401,3 +401,51 @@ pub proc metadata_into_note_type u32and.1 # => [note_type] end + +#! Searches the metadata header for the specified attachment scheme and returns the index of the +#! first matching slot. +#! +#! Inputs: [attachment_scheme, METADATA_HEADER] +#! Outputs: [is_found, attachment_idx] +#! +#! Where: +#! - attachment_scheme is the scheme to search for. +#! - METADATA_HEADER is the metadata header word of a note. +#! - is_found is 1 if the scheme was found, 0 otherwise. +#! - attachment_idx is the index (0-3) of the first matching slot, or undefined if not found. +#! +#! Invocation: exec +pub proc find_attachment_idx + movdn.4 exec.metadata_into_attachment_schemes + # => [scheme_0, scheme_1, scheme_2, scheme_3, attachment_scheme] + + # initialize is_found = 0 and attachment_idx = 0 + movup.4 push.0 push.0 + # => [attachment_idx = 0, is_found = 0, attachment_scheme, scheme_0, scheme_1, scheme_2, scheme_3] + + repeat.4 + # => [attachment_idx, is_found, attachment_scheme, scheme_n, ...] + + # check if scheme_n is the scheme we're trying to find + dup.2 movup.4 eq + # => [is_scheme_n, attachment_idx, is_found, attachment_scheme, scheme_n+1, ...] + + # set is_found = is_found || is_scheme_n + movup.2 or swap + # => [attachment_idx, is_found', attachment_scheme, scheme_n+1, ...] + + # create prospective attachment idx by incrementing the current one + dup add.1 swap dup.2 + # => [is_found', attachment_idx, attachment_idx+1, is_found', attachment_scheme, scheme_n+1, ...] + + # if is_found' attachment_idx remains. + # if !is_found' attachment_idx+1 remains. + # this essentially increments the attachment idx as long as no match was found. + cdrop + # => [attachment_idx', is_found', attachment_scheme, scheme_n+1, ...] + end + # => [attachment_idx', is_found', attachment_scheme] + + movup.2 drop swap + # => [is_found', attachment_idx'] +end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index c26496ce85..791b08b8f9 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -4,7 +4,6 @@ use anyhow::Context; use miden_protocol::account::Account; use miden_protocol::account::auth::AuthScheme; use miden_protocol::asset::FungibleAsset; -use miden_protocol::crypto::SequentialCommit; use miden_protocol::crypto::rand::{FeltRng, RandomCoin}; use miden_protocol::errors::tx_kernel::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED; use miden_protocol::note::{ diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index 500a724e6d..52052f50da 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -1,7 +1,6 @@ use alloc::string::String; use miden_protocol::Word; -use miden_protocol::crypto::SequentialCommit; use miden_protocol::note::Note; use miden_protocol::transaction::memory::{ASSET_SIZE, ASSET_VALUE_OFFSET}; use miden_standards::code_builder::CodeBuilder; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 818930d8b0..574ff8d724 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -604,3 +604,95 @@ async fn test_metadata_into_attachment_schemes( Ok(()) } + +/// Tests the `find_attachment_idx` procedure which searches for a given scheme in the +/// metadata header and returns `[is_found, attachment_idx]`. +#[rstest::rstest] +#[case::found_at_index_0( + [ + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(42)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(20)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(30)), + // The scheme exists again at a higher index, but the first match should be returned. + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(42)), + ], + 42, + true, + 0, +)] +#[case::found_at_index_2( + [ + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(10)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(20)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(42)), + NoteAttachmentHeader::absent() + ], + 42, + true, + 2, +)] +#[case::found_at_index_3( + [ + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(10)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(20)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(30)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(42)), + ], + 42, + true, + 3, +)] +#[case::not_found( + [ + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(10)), + NoteAttachmentHeader::new(NoteAttachmentScheme::new_const(20)), + NoteAttachmentHeader::absent(), + NoteAttachmentHeader::absent(), + ], + 42, + false, + 0, // attachment_idx is undefined when not found; we don't assert it +)] +#[tokio::test] +async fn test_find_attachment_idx( + #[case] attachment_headers: [NoteAttachmentHeader; 4], + #[case] search_scheme: u16, + #[case] expected_found: bool, + #[case] expected_idx: u8, +) -> anyhow::Result<()> { + let sender = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); + let metadata = NoteMetadata::new(sender, NoteType::Public).with_tag(NoteTag::new(0)); + let metadata_header = + NoteMetadataHeader::from_parts(metadata, attachment_headers, Word::default()); + let metadata_word = metadata_header.to_metadata_word(); + + let code = format!( + " + use miden::protocol::note + + begin + push.{metadata_word} + push.{search_scheme} + exec.note::find_attachment_idx + # => [is_found, attachment_idx, pad(16)] + + # truncate the stack + movup.2 drop movup.2 drop + # => [is_found, attachment_idx, pad(14)] + end + ", + ); + + let exec_output = CodeExecutor::with_default_host().run(&code).await?; + + let is_found = exec_output.get_stack_element(0); + assert_eq!(is_found, Felt::from(expected_found as u8), "is_found mismatch"); + + // attachment_idx is undefined when not found so we only assert when found + if expected_found { + let attachment_idx = exec_output.get_stack_element(1); + assert_eq!(attachment_idx, Felt::from(expected_idx), "attachment_idx mismatch"); + } + + Ok(()) +} From 6fd3c6458c29cba99a3feb78b89e1b41e338404b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 10:45:14 +0200 Subject: [PATCH 06/23] feat: implement output_note::find_attachment --- .../asm/protocol/output_note.masm | 47 ++++++++- .../src/kernel_tests/tx/test_output_note.rs | 97 +++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index 30a9b42c1f..92748ef74a 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -378,6 +378,51 @@ pub proc get_metadata # => [NOTE_ATTACHMENT, METADATA_HEADER] end +#! Searches the metadata header for the specified attachment scheme and if found, pipes the +#! attachment's underlying data from the advice inputs to memory and returns the ptr to it. +#! +#! Pointer and num_words are zero if is_found = 0. +#! +#! Inputs: [attachment_scheme, note_idx] +#! Outputs: [is_found, num_words, attachment_ptr] +#! +#! Where: +#! - note_index is the index of the output note whose attachment should be returned. +#! - attachment_scheme is the scheme of the attachment to find. +#! - is_found is 1 if the attachment with the provided scheme was found, 0 otherwise. +#! - num_words is the number of words in the attachment. +#! - attachment_ptr is the pointer to the attachment elements. +#! +#! Pancis if: +#! - the sequential hash over the attachment commitments in the advice inputs does not match the +#! attachments commitment. +pub proc find_attachment + dup.1 + # => [note_idx, attachment_scheme, note_idx] + + exec.get_metadata + dropw + # => [METADATA_HEADER, attachment_scheme, note_idx] + + movup.4 + # => [attachment_scheme, METADATA_HEADER, note_idx] + + exec.note::find_attachment_idx + # => [is_found, attachment_idx, note_idx] + + if.true + exec.get_attachment + # => [num_words, attachment_ptr] + + push.1 + # => [is_found = 1, num_words, attachment_ptr] + else + drop drop push.0.0.0 + # => [is_found = 0, num_words = 0, attachment_ptr = 0] + end + # => [is_found, attachment_ptr, num_words] +end + #! Writes the attachment commitments from the note with note_idx to memory and returns the pointer #! to it. #! @@ -390,7 +435,7 @@ end #! - NOTE_ATTACHMENT is the first attachment of the specified output note. #! #! Pancis if: -#! - The sequential hash over the attachment commitments in the advice inputs does not match the +#! - the sequential hash over the attachment commitments in the advice inputs does not match the #! attachments commitment. @locals(16) pub proc get_attachment_commitments diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 24a34b6174..2d148688a2 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1652,6 +1652,102 @@ async fn test_get_attachment() -> anyhow::Result<()> { Ok(()) } +/// Tests `output_note::find_attachment` for both the found and not-found cases. +/// +/// Setup: a SPAWN note creates an output note with two word attachments (schemes 10 and 20). +/// The tx_script then calls `find_attachment` on the created output note. +/// +/// - `found`: search for scheme 10 → is_found=1, attachment data is returned. +/// - `not_found`: search for scheme 99 → is_found=0, num_words=0, attachment_ptr=0. +#[rstest] +#[case::found(10, true)] +#[case::not_found(99, false)] +#[tokio::test] +async fn test_find_attachment( + #[case] search_scheme: u16, + #[case] expected_found: bool, +) -> anyhow::Result<()> { + let mut builder = MockChain::builder(); + + let account = builder.add_existing_wallet(Auth::IncrNonce)?; + + let word_0 = Word::from([3, 4, 5, 6u32]); + let word_1 = Word::from([7, 8, 9, 10u32]); + let scheme_0 = NoteAttachmentScheme::new(10)?; + let scheme_1 = NoteAttachmentScheme::new(20)?; + + let output_note = NoteBuilder::new(account.id(), RandomCoin::new(Word::from([1, 2, 3, 4u32]))) + .note_type(NoteType::Public) + .attachment(NoteAttachment::new_word(scheme_0, word_0)) + .attachment(NoteAttachment::new_word(scheme_1, word_1)) + .build()?; + + let spawn_note = builder.add_spawn_note([&output_note])?; + let mut mock_chain = builder.build()?; + mock_chain.prove_next_block()?; + + let tx_script = format!( + r#" + use miden::protocol::output_note + use miden::core::sys + + begin + # the spawn note creates output note at index 0; + # search for the target scheme on that note + push.0 + push.{search_scheme} + # => [attachment_scheme, note_idx=0] + exec.output_note::find_attachment + # => [is_found, num_words, attachment_ptr] + + # assert is_found matches expectation + push.{expected_found} assert_eq.err="is_found mismatch" + # => [num_words, attachment_ptr] + + push.{expected_found} + if.true + # found path: verify num_words == 1 (word attachment) + eq.1 assert.err="expected num_words=1" + # => [attachment_ptr] + + # read the word from memory and assert it matches + padw movup.4 mem_loadw_le + # => [ATTACHMENT_WORD] + + push.{EXPECTED_WORD} + assert_eqw.err="attachment data mismatch" + # => [] + else + # not-found path: verify num_words=0 and ptr=0 + eq.0 assert.err="expected num_words=0" + eq.0 assert.err="expected attachment_ptr=0" + # => [] + end + + # truncate the stack + exec.sys::truncate_stack + end + "#, + expected_found = expected_found as u8, + EXPECTED_WORD = word_0, + ); + + let tx_script = CodeBuilder::new().compile_tx_script(tx_script)?; + + let tx = mock_chain + .build_tx_context(account.id(), &[spawn_note.id()], &[])? + .extend_expected_output_notes(vec![RawOutputNote::Full(output_note.clone())]) + .tx_script(tx_script) + .build()? + .execute() + .await?; + + let actual_note = tx.output_notes().get_note(0); + assert_eq!(actual_note.header(), output_note.header()); + + Ok(()) +} + /// Test that output_note procedures abort when given an out-of-bounds note index (equal to /// num_output_notes). /// @@ -1667,6 +1763,7 @@ async fn test_get_attachment() -> anyhow::Result<()> { #[case::add_attachment(5, "add_attachment")] #[case::add_word_attachment(5, "add_word_attachment")] #[case::add_array_attachment(5, "add_array_attachment")] +#[case::find_attachment(1, "find_attachment")] #[case::get_attachment_commitments(0, "get_attachment_commitments")] #[case::get_attachments_commitment(0, "get_attachments_commitment")] #[tokio::test] From d9b2d7383016510f93e450aa271ca36ab93e723d Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 10:54:37 +0200 Subject: [PATCH 07/23] chore: add docs for num allocated locals --- crates/miden-protocol/asm/protocol/output_note.masm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index 92748ef74a..3b2e7bbbbc 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -426,6 +426,9 @@ end #! Writes the attachment commitments from the note with note_idx to memory and returns the pointer #! to it. #! +#! This procedure allocates 16 elements of memory (max num attachments * WORD_SIZE) to store all +#! potential four attachment commitments. +#! #! Inputs: [note_index] #! Outputs: [num_attachments, attachments_ptr] #! @@ -455,6 +458,9 @@ end #! Writes the attachment with the provided index from the note with note_idx to memory and returns #! the pointer to the data. #! +#! This procedure allocates 1024 elements of memory (MAX_ATTACHMENT_WORDS * WORD_SIZE) to store +#! all potential attachment elements. +#! #! Inputs: [attachment_idx, note_index] #! Outputs: [num_words, attachment_ptr] #! From bee88a5c0afec20796367c3046ca298f561b889f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 14:56:54 +0200 Subject: [PATCH 08/23] feat: implement attachment APIs for input/active note --- .../asm/kernels/transaction/api.masm | 42 +++++ .../asm/protocol/active_note.masm | 130 +++++++++++++ .../asm/protocol/input_note.masm | 172 ++++++++++++++++++ .../asm/protocol/kernel_proc_offsets.masm | 39 ++-- crates/miden-protocol/asm/protocol/note.masm | 3 + .../asm/protocol/output_note.masm | 7 +- .../src/kernel_tests/tx/test_active_note.rs | 117 ++++++++++++ 7 files changed, 486 insertions(+), 24 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 6468e0d020..408d537b19 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -42,6 +42,8 @@ const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SCRIPT_ROOT_WHILE_NO_NOTE_BEING_PROCESSED= const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_SERIAL_NUMBER_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note serial number of active note because no note is currently being processed" +const ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ATTACHMENTS_WHILE_NO_NOTE_BEING_PROCESSED="failed to access note attachments of active note because no note is currently being processed" + const ERR_FOREIGN_ACCOUNT_PROCEDURE_ROOT_IS_ZERO="root of the provided foreign procedure equals zero indicating that tx_prepare_fpi was not called" const ERR_FOREIGN_ACCOUNT_ID_IS_ZERO="ID of the provided foreign account equals zero indicating that tx_prepare_fpi was not called" @@ -1102,6 +1104,46 @@ pub proc input_note_get_script_root # => [SCRIPT_ROOT, pad(12)] end +#! Returns the attachments commitment of the specified input note. +#! +#! Inputs: [is_active_note, note_index, pad(14)] +#! Outputs: [ATTACHMENTS_COMMITMENT, pad(12)] +#! +#! Where: +#! - is_active_note is the boolean flag indicating whether we should return the attachments +#! commitment from the active note or from the note with the specified index. +#! - note_index is the index of the input note whose attachments commitment should be returned. +#! Notice that if is_active_note is 1, note_index is ignored. +#! - ATTACHMENTS_COMMITMENT is the commitment to all attachments of the specified input note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! - is_active_note is 1 and no input note is not being processed (attempted to access note +#! attachments from incorrect context). +#! +#! Invocation: dynexec +pub proc input_note_get_attachments_commitment + # get the input note pointer depending on whether the requested note is current or it was + # requested by index. + exec.get_requested_note_ptr + # => [input_note_ptr, pad(15)] + + # assert the pointer is not zero - this would suggest the procedure has been called from an + # incorrect context + dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_ATTACHMENTS_WHILE_NO_NOTE_BEING_PROCESSED + # => [input_note_ptr, pad(15)] + + # get the attachments commitment + exec.memory::get_input_note_attachments_commitment + # => [ATTACHMENTS_COMMITMENT, pad(15)] + + # truncate the stack + repeat.3 + movup.4 drop + end + # => [ATTACHMENTS_COMMITMENT, pad(12)] +end + # OUTPUT NOTE # ------------------------------------------------------------------------------------------------- diff --git a/crates/miden-protocol/asm/protocol/active_note.masm b/crates/miden-protocol/asm/protocol/active_note.masm index ce1cf9c104..9616371a17 100644 --- a/crates/miden-protocol/asm/protocol/active_note.masm +++ b/crates/miden-protocol/asm/protocol/active_note.masm @@ -8,6 +8,7 @@ use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_METADATA_OFFSET use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET use miden::protocol::note +use miden::protocol::input_note # ERRORS # ================================================================================================= @@ -344,3 +345,132 @@ proc write_storage_to_memory assert_eqw.err=ERR_NOTE_DATA_DOES_NOT_MATCH_COMMITMENT # => [] end + +# ATTACHMENTS +# ================================================================================================= + +#! Returns the commitment over all attachments of the active note. +#! +#! Inputs: [] +#! Outputs: [ATTACHMENTS_COMMITMENT] +#! +#! Where: +#! - ATTACHMENTS_COMMITMENT is the commitment to all attachments of the active note. +#! +#! Panics if: +#! - no note is currently active. +#! +#! Invocation: exec +pub proc get_attachments_commitment + push.1 + # => [is_active_note = 1, note_index] + + exec.input_note::get_attachments_commitment_raw + # => [ATTACHMENTS_COMMITMENT] +end + +#! Writes the attachment commitments of the active note with the specified index to memory and +#! returns the pointer to the data. +#! +#! This procedure allocates 16 elements of memory (max num attachments * WORD_SIZE) to store all +#! potential four attachment commitments. +#! +#! Inputs: [] +#! Outputs: [num_attachments, attachments_ptr] +#! +#! Where: +#! - num_attachments is the number of attachments in the note. +#! - attachments_ptr is a pointer to the attachment commitments written to local memory. +#! +#! Panics if: +#! - no note is currently active. +#! - the note index is greater or equal to the total number of input notes. +#! - the sequential hash over the attachment commitments in the advice inputs does not match the +#! attachments commitment. +#! +#! Invocation: exec +@locals(16) +pub proc get_attachment_commitments + exec.get_attachments_commitment + # => [ATTACHMENTS_COMMITMENT] + + locaddr.0 movdn.4 + # => [ATTACHMENTS_COMMITMENT, dest_ptr] + + exec.note::write_attachments_to_memory + # => [num_attachments] + + locaddr.0 swap + # => [num_attachments, dest_ptr] +end + +#! Writes the attachment with the provided index from the active note to memory and returns the +#! pointer to the data. +#! +#! Inputs: [attachment_idx] +#! Outputs: [num_words, attachment_ptr] +#! +#! Where: +#! - attachment_idx is the index of the attachment to retrieve. +#! - attachment_ptr is a pointer to the attachment data written to local memory. +#! - num_words is the number of words in the attachment. +#! +#! Panics if: +#! - no note is currently active. +#! - the attachment index is greater or equal to the number of attachments. +#! - the sequential hash over the attachment data in the advice inputs does not match the +#! attachment commitment. +#! +#! Invocation: exec +pub proc get_attachment + # get the attachment commitments for the active note + swap exec.get_attachment_commitments + # => [num_attachments, attachments_ptr, attachment_idx] + + exec.note::get_attachment + # => [num_words, attachment_ptr] +end + +#! Searches the metadata header of the active note for the specified attachment scheme and if +#! found, pipes the attachment's underlying data from the advice inputs to memory and returns +#! the ptr to it. +#! +#! Inputs: [attachment_scheme] +#! Outputs: [is_found, num_words, attachment_ptr] +#! +#! Where: +#! - attachment_scheme is the scheme of the attachment to find. +#! - note_index is the index of the input note. +#! - attachment_ptr is the pointer to the attachment data written to memory. +#! - num_words is the number of words in the attachment. +#! - is_found is 1 if the attachment with the provided scheme was found, 0 otherwise. +#! +#! Panics if: +#! - no note is currently active. +#! - the sequential hash over the attachment commitments in the advice inputs does not match the +#! attachments commitment. +#! +#! Invocation: exec +pub proc find_attachment + exec.get_metadata + dropw + # => [METADATA_HEADER, attachment_scheme] + + movup.4 + # => [attachment_scheme, METADATA_HEADER] + + exec.note::find_attachment_idx + # => [is_found, attachment_idx] + + if.true + exec.get_attachment + # => [num_words, attachment_ptr] + + push.1 + # => [is_found = 1, num_words, attachment_ptr] + else + drop push.0.0.0 + # => [is_found = 0, num_words = 0, attachment_ptr = 0] + end + # => [is_found, num_words, attachment_ptr] +end diff --git a/crates/miden-protocol/asm/protocol/input_note.masm b/crates/miden-protocol/asm/protocol/input_note.masm index f687471bec..eee587ecdb 100644 --- a/crates/miden-protocol/asm/protocol/input_note.masm +++ b/crates/miden-protocol/asm/protocol/input_note.masm @@ -4,6 +4,7 @@ use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_METADATA_OFFSET use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_STORAGE_INFO_OFFSET use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET +use miden::protocol::kernel_proc_offsets::INPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET use miden::protocol::note # PROCEDURES @@ -307,3 +308,174 @@ pub proc get_serial_number swapdw dropw dropw swapw dropw # => [SERIAL_NUMBER] end + +# ATTACHMENTS +# ================================================================================================= + +#! Returns the commitment over all attachments of the input note with the specified index. +#! +#! Inputs: [note_index] +#! Outputs: [ATTACHMENTS_COMMITMENT] +#! +#! Where: +#! - note_index is the index of the input note whose attachments commitment should be returned. +#! - ATTACHMENTS_COMMITMENT is the commitment to all attachments of the note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! +#! Invocation: exec +pub proc get_attachments_commitment + push.0 + # => [is_active_note = 0, note_index] + + exec.get_attachments_commitment_raw + # => [ATTACHMENTS_COMMITMENT] +end + +#! Writes the attachment commitments of the note identified by the is_active_note flag to memory +#! and returns the pointer to the data. +#! +#! This is the shared implementation used by both `input_note::get_attachment_commitments` and +#! `active_note::get_attachment_commitments`. +#! +#! Inputs: [is_active_note, note_index] +#! Outputs: [ATTACHMENTS_COMMITMENT] +#! +#! Where: +#! - is_active_note is 0 for indexed access, 1 for the currently active note. +#! - note_index is the index of the input note (ignored when is_active_note = 1). +#! - ATTACHMENTS_COMMITMENT is the commitment to all attachments of the note. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! +#! Invocation: exec +pub proc get_attachments_commitment_raw + push.0 movdn.2 + # => [is_active_note, note_index, 0] + + push.INPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET + # => [offset, is_active_note, note_index, 0] + + padw swapw padw padw swapdw + # => [offset, is_active_note, note_index, pad(13)] + + syscall.exec_kernel_proc + # => [ATTACHMENTS_COMMITMENT, pad(12)] + + # clean the stack, keeping only the commitment + swapdw dropw dropw swapw dropw + # => [ATTACHMENTS_COMMITMENT] +end + +#! Writes the attachment commitments of the input note with the specified index to memory and +#! returns the pointer to the data. +#! +#! This procedure allocates 16 elements of memory (max num attachments * WORD_SIZE) to store all +#! potential four attachment commitments. +#! +#! Inputs: [note_index] +#! Outputs: [num_attachments, attachments_ptr] +#! +#! Where: +#! - note_index is the index of the input note. +#! - num_attachments is the number of attachments in the note. +#! - attachments_ptr is a pointer to the attachment commitments written to local memory. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! - the sequential hash over the attachment commitments in the advice inputs does not match the +#! attachments commitment. +#! +#! Invocation: exec +@locals(16) +pub proc get_attachment_commitments + exec.get_attachments_commitment + # => [ATTACHMENTS_COMMITMENT] + + locaddr.0 movdn.4 + # => [ATTACHMENTS_COMMITMENT, dest_ptr] + + exec.note::write_attachments_to_memory + # => [num_attachments] + + locaddr.0 swap + # => [num_attachments, dest_ptr] +end + +#! Writes the attachment with the provided index from the input note with the specified index +#! to memory and returns the pointer to the data. +#! +#! Inputs: [attachment_idx, note_index] +#! Outputs: [num_words, attachment_ptr] +#! +#! Where: +#! - attachment_idx is the index of the attachment to retrieve. +#! - note_index is the index of the input note. +#! - attachment_ptr is a pointer to the attachment data written to local memory. +#! - num_words is the number of words in the attachment. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! - the attachment index is greater or equal to the number of attachments. +#! - the sequential hash over the attachment data in the advice inputs does not match the +#! attachment commitment. +#! +#! Invocation: exec +pub proc get_attachment + # get the attachment commitments for the note + swap exec.get_attachment_commitments + # => [num_attachments, attachments_ptr, attachment_idx] + + exec.note::get_attachment + # => [num_words, attachment_ptr] +end + +#! Searches the metadata header of the input note for the specified attachment scheme and if +#! found, pipes the attachment's underlying data from the advice inputs to memory and returns +#! the ptr to it. +#! +#! Inputs: [attachment_scheme, note_index] +#! Outputs: [is_found, num_words, attachment_ptr] +#! +#! Where: +#! - attachment_scheme is the scheme of the attachment to find. +#! - note_index is the index of the input note. +#! - attachment_ptr is the pointer to the attachment data written to memory. +#! - num_words is the number of words in the attachment. +#! - is_found is 1 if the attachment with the provided scheme was found, 0 otherwise. +#! +#! Panics if: +#! - the note index is greater or equal to the total number of input notes. +#! - the sequential hash over the attachment commitments in the advice inputs does not match the +#! attachments commitment. +#! +#! Invocation: exec +pub proc find_attachment + dup.1 + # => [note_index, attachment_scheme, note_index] + + exec.get_metadata + dropw + # => [METADATA_HEADER, attachment_scheme, note_index] + debug.stack.4 + + movup.4 + # => [attachment_scheme, METADATA_HEADER, note_index] + + exec.note::find_attachment_idx + # => [is_found, attachment_idx, note_index] + + if.true + exec.get_attachment + # => [num_words, attachment_ptr] + + push.1 + # => [is_found = 1, num_words, attachment_ptr] + else + drop drop push.0.0.0 + # => [is_found = 0, num_words = 0, attachment_ptr = 0] + end + # => [is_found, num_words, attachment_ptr] +end diff --git a/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm b/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm index 176972f288..32abdf47a7 100644 --- a/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm +++ b/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm @@ -58,38 +58,39 @@ pub const INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=30 pub const INPUT_NOTE_GET_STORAGE_INFO_OFFSET=31 pub const INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=32 pub const INPUT_NOTE_GET_RECIPIENT_OFFSET=33 +pub const INPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET=34 # output notes -pub const OUTPUT_NOTE_CREATE_OFFSET=34 -pub const OUTPUT_NOTE_GET_METADATA_OFFSET=35 -pub const OUTPUT_NOTE_GET_RECIPIENT_OFFSET=36 -pub const OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=37 -pub const OUTPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET=38 -pub const OUTPUT_NOTE_ADD_ASSET_OFFSET=39 -pub const OUTPUT_NOTE_ADD_ATTACHMENT_OFFSET=40 +pub const OUTPUT_NOTE_CREATE_OFFSET=35 +pub const OUTPUT_NOTE_GET_METADATA_OFFSET=36 +pub const OUTPUT_NOTE_GET_RECIPIENT_OFFSET=37 +pub const OUTPUT_NOTE_GET_ASSETS_INFO_OFFSET=38 +pub const OUTPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET=39 +pub const OUTPUT_NOTE_ADD_ASSET_OFFSET=40 +pub const OUTPUT_NOTE_ADD_ATTACHMENT_OFFSET=41 ### Tx ########################################## # input notes -pub const TX_GET_NUM_INPUT_NOTES_OFFSET=41 -pub const TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=42 +pub const TX_GET_NUM_INPUT_NOTES_OFFSET=42 +pub const TX_GET_INPUT_NOTES_COMMITMENT_OFFSET=43 # output notes -pub const TX_GET_NUM_OUTPUT_NOTES_OFFSET=43 -pub const TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=44 +pub const TX_GET_NUM_OUTPUT_NOTES_OFFSET=44 +pub const TX_GET_OUTPUT_NOTES_COMMITMENT_OFFSET=45 # block info -pub const TX_GET_BLOCK_COMMITMENT_OFFSET=45 -pub const TX_GET_BLOCK_NUMBER_OFFSET=46 -pub const TX_GET_BLOCK_TIMESTAMP_OFFSET=47 +pub const TX_GET_BLOCK_COMMITMENT_OFFSET=46 +pub const TX_GET_BLOCK_NUMBER_OFFSET=47 +pub const TX_GET_BLOCK_TIMESTAMP_OFFSET=48 # foreign context -pub const TX_PREPARE_FPI_OFFSET = 48 -pub const TX_EXEC_FOREIGN_PROC_OFFSET = 49 +pub const TX_PREPARE_FPI_OFFSET = 49 +pub const TX_EXEC_FOREIGN_PROC_OFFSET = 50 # expiration data -pub const TX_GET_EXPIRATION_DELTA_OFFSET=50 # accessor -pub const TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=51 # mutator +pub const TX_GET_EXPIRATION_DELTA_OFFSET=51 # accessor +pub const TX_UPDATE_EXPIRATION_BLOCK_DELTA_OFFSET=52 # mutator # tx script -pub const TX_GET_TX_SCRIPT_ROOT_OFFSET=52 +pub const TX_GET_TX_SCRIPT_ROOT_OFFSET=53 diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 4a30e37809..73906ec0b2 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -157,6 +157,9 @@ end #! Writes the attachment with the provided index from the provided attachments to memory and #! returns the pointer to the attachment elements. #! +#! This procedure allocates 1024 elements of memory (MAX_ATTACHMENT_WORDS * WORD_SIZE) to store +#! all potential attachment elements. +#! #! Inputs: [num_attachments, attachments_ptr, attachment_idx] #! Outputs: [num_words, attachment_ptr] #! diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index 3b2e7bbbbc..3adba275f9 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -394,6 +394,7 @@ end #! - attachment_ptr is the pointer to the attachment elements. #! #! Pancis if: +#! - the note index is greater or equal to the total number of output notes. #! - the sequential hash over the attachment commitments in the advice inputs does not match the #! attachments commitment. pub proc find_attachment @@ -458,16 +459,13 @@ end #! Writes the attachment with the provided index from the note with note_idx to memory and returns #! the pointer to the data. #! -#! This procedure allocates 1024 elements of memory (MAX_ATTACHMENT_WORDS * WORD_SIZE) to store -#! all potential attachment elements. -#! #! Inputs: [attachment_idx, note_index] #! Outputs: [num_words, attachment_ptr] #! #! Where: #! - attachment_idx is the index of the attachment to retrieve. #! - note_index is the index of the output note. -#! - attachment_ptr is a pointer to the attachment data written to local memory. +#! - attachment_ptr is a pointer to the attachment data written to memory. #! - num_words is the number of words in the attachment. #! #! Panics if: @@ -477,7 +475,6 @@ end #! attachment commitment. #! #! Invocation: exec -@locals(1024) pub proc get_attachment # get the attachment commitments for the note swap exec.get_attachment_commitments diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index 791b08b8f9..a4842787c2 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -9,6 +9,8 @@ use miden_protocol::errors::tx_kernel::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_ use miden_protocol::note::{ Note, NoteAssets, + NoteAttachment, + NoteAttachmentScheme, NoteMetadata, NoteRecipient, NoteStorage, @@ -25,6 +27,8 @@ use miden_protocol::transaction::memory::{ASSET_SIZE, ASSET_VALUE_OFFSET}; use miden_protocol::{EMPTY_WORD, Felt, ONE, WORD_SIZE, Word}; use miden_standards::code_builder::CodeBuilder; use miden_standards::testing::mock_account::MockAccountExt; +use miden_standards::testing::note::NoteBuilder; +use rstest::rstest; use crate::kernel_tests::tx::ExecutionOutputExt; use crate::utils::create_public_p2any_note; @@ -613,3 +617,116 @@ async fn test_active_note_get_script_root() -> anyhow::Result<()> { assert_eq!(exec_output.get_stack_word(0), script_root); Ok(()) } + +/// Tests `{input_note, active_note}::find_attachment` for both the found and not-found cases. +/// +/// Setup: create an input note with two word attachments (schemes 10 and 20), then call +/// `find_attachment` on the active/input note. +/// +/// - `found`: search for scheme 10 → is_found=1, attachment data is returned. +/// - `not_found`: search for scheme 99 → is_found=0, ptr=0, num_words=0. +#[rstest] +#[case::active_note_scheme_found(None, "active_note", 10, true)] +#[case::active_note_scheme_not_found(None, "active_note", 99, false)] +// uses note index 1 +#[case::input_note_scheme_found(Some(1), "input_note", 10, true)] +// uses note index 1 +#[case::input_note_scheme_not_found(Some(1), "input_note", 99, false)] +#[tokio::test] +async fn test_note_find_attachment( + #[case] note_idx: Option, + #[case] module_under_test: &str, + #[case] search_scheme: u16, + #[case] expected_found: bool, +) -> anyhow::Result<()> { + let tx_context = { + let account = + Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); + + let word_0 = Word::from([3, 4, 5, 6u32]); + let word_1 = Word::from([7, 8, 9, 10u32]); + let scheme_0 = NoteAttachmentScheme::new(10)?; + let scheme_1 = NoteAttachmentScheme::new(20)?; + + let mut rng = RandomCoin::new(Word::from([1, 2, 3, 4u32])); + // Add a random first note so we test with note_index != 0. + let input_note0 = NoteBuilder::new(account.id(), &mut rng).build()?; + let input_note1 = NoteBuilder::new(account.id(), &mut rng) + .note_type(NoteType::Public) + .attachment(NoteAttachment::new_word(scheme_0, word_0)) + .attachment(NoteAttachment::new_word(scheme_1, word_1)) + .build()?; + + TransactionContextBuilder::new(account) + .extend_input_notes(vec![input_note0, input_note1]) + .build()? + }; + assert_eq!(tx_context.tx_inputs().input_notes().num_notes(), 2); + + let word_0 = Word::from([3, 4, 5, 6u32]); + + let push_note_index = match note_idx { + Some(idx) => format!("push.{idx}"), + // for active_note module, we don't need to push anything + None => "".into(), + }; + + let code = format!( + r#" + use $kernel::prologue + use $kernel::note->note_internal + use miden::protocol::active_note + use miden::protocol::input_note + + begin + exec.prologue::prepare_transaction + exec.note_internal::increment_active_input_note_ptr drop + # prepare note 1 + exec.note_internal::prepare_note + dropw dropw dropw dropw + + # push note index, if any + {push_note_index} + # search for the target scheme on the active note + push.{search_scheme} + # => [attachment_scheme] + exec.{module_under_test}::find_attachment + # => [is_found, num_words, attachment_ptr] + + # assert is_found matches expectation + push.{expected_found} + assert_eq.err="is_found mismatch" + # => [num_words, attachment_ptr] + + push.{expected_found} + if.true + # found path: verify num_words == 1 (word attachment) + eq.1 assert.err="expected num_words=1" + # => [attachment_ptr] + + # read the word from memory and assert it matches + padw movup.4 mem_loadw_le + # => [ATTACHMENT_WORD] + + push.{EXPECTED_WORD} + assert_eqw.err="attachment data mismatch" + # => [] + else + # not-found path: verify num_words=0 and ptr=0 + eq.0 assert.err="expected num_words=0" + eq.0 assert.err="expected attachment_ptr=0" + # => [] + end + + # truncate the stack + swapw dropw + end + "#, + expected_found = expected_found as u8, + EXPECTED_WORD = word_0, + ); + + tx_context.execute_code(&code).await?; + + Ok(()) +} From 7cfde4a1fbd08de908e7066890e0c1daa461bbe0 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 15:02:02 +0200 Subject: [PATCH 09/23] fix: construct input notes in tx context in provided order --- crates/miden-testing/src/tx_context/builder.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/miden-testing/src/tx_context/builder.rs b/crates/miden-testing/src/tx_context/builder.rs index b61a5a5e98..71b8c5272c 100644 --- a/crates/miden-testing/src/tx_context/builder.rs +++ b/crates/miden-testing/src/tx_context/builder.rs @@ -29,7 +29,7 @@ use miden_tx::TransactionMastStore; use miden_tx::auth::BasicAuthenticator; use super::TransactionContext; -use crate::{MockChain, MockChainNote}; +use crate::MockChain; // TRANSACTION CONTEXT BUILDER // ================================================================================================ @@ -284,17 +284,18 @@ impl TransactionContextBuilder { // to generate valid block header/MMR data let mut builder = MockChain::builder(); - for i in self.input_notes { - builder.add_output_note(RawOutputNote::Full(i)); + + // Get the set of note IDs in the provided order. + let input_note_ids: Vec = self.input_notes.iter().map(Note::id).collect(); + + for input_note in self.input_notes { + builder.add_output_note(RawOutputNote::Full(input_note)); } let mut mock_chain = builder.build()?; mock_chain.prove_next_block().context("failed to prove first block")?; mock_chain.prove_next_block().context("failed to prove second block")?; - let input_note_ids: Vec = - mock_chain.committed_notes().values().map(MockChainNote::id).collect(); - mock_chain .get_transaction_inputs(&self.account, &input_note_ids, &[]) .context("failed to get transaction inputs from mock chain")? From 3b0cad155a1bd42ad46ffe9a1e57d00b322bd837 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 15:03:01 +0200 Subject: [PATCH 10/23] chore: adapt network account target MASM to multiple attachments --- .../attachments/network_account_target.masm | 47 ++++++++++++------- .../src/standards/network_account_target.rs | 6 +-- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/miden-standards/asm/standards/attachments/network_account_target.masm b/crates/miden-standards/asm/standards/attachments/network_account_target.masm index 0f00b42e5b..7e7b8e3522 100644 --- a/crates/miden-standards/asm/standards/attachments/network_account_target.masm +++ b/crates/miden-standards/asm/standards/attachments/network_account_target.masm @@ -5,7 +5,6 @@ use miden::protocol::account_id use miden::protocol::active_account use miden::protocol::active_note -use miden::protocol::note # CONSTANTS # ================================================================================================ @@ -14,9 +13,18 @@ use miden::protocol::note #! This is a valid u32 that can be compared against an extracted attachment scheme. pub const NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME = 2 +#! The number of words in the network account target attachment. +pub const NETWORK_ACCOUNT_TARGET_ATTACHMENT_NUM_WORDS = 1 + # ERRORS # ================================================================================================ -const ERR_NOT_NETWORK_ACCOUNT_TARGET = "attachment is not a valid network account target" + +const ERR_NETWORK_ACCOUNT_TARGET_MISSING = "network account target attachment is not present on active note" + +const ERR_NETWORK_ACCOUNT_TARGET_INCORRECT_NUMBER_OF_WORDS = "network account target attachment must consist of exactly one word" + +# PROCEDURES +# ================================================================================================ #! Returns a boolean indicating whether the attachment scheme matches the expected #! scheme for a NetworkAccountTarget attachment. @@ -41,15 +49,16 @@ end #! WARNING: This procedure does not validate that the returned account ID is well-formed. #! The caller should validate the account ID if needed using `account_id::validate`. #! -#! Inputs: [NOTE_ATTACHMENT] +#! Inputs: [NETWORK_ACCOUNT_TARGET_ATTACHMENT] #! Outputs: [account_id_suffix, account_id_prefix] #! #! Where: #! - account_id_{suffix,prefix} are the suffix and prefix felts of an account ID. #! #! Invocation: exec -pub proc get_id - # => [NOTE_ATTACHMENT] = [account_id_suffix, account_id_prefix, exec_hint_tag, 0] +pub proc into_target_id + # => [NETWORK_ACCOUNT_TARGET_ATTACHMENT] + # => [account_id_suffix, account_id_prefix, exec_hint_tag, 0] movup.2 drop movup.2 drop # => [account_id_suffix, account_id_prefix] @@ -85,25 +94,29 @@ end #! - is_equal is a boolean indicating whether the active account matches the target account. #! #! Panics if: -#! - the attachment is not a valid network account target. +#! - the attachment is missing from the active note or the number of words is invalid. #! #! Invocation: exec pub proc active_account_matches_target_account - # ensure note attachment targets the consuming bridge account - exec.active_note::get_metadata - # => [NOTE_ATTACHMENT, METADATA_HEADER] + # find the network account target attachment, if any + # if there are multiple, we consider the first one the canonical one + push.NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME + exec.active_note::find_attachment + # => [is_found, num_words, attachment_ptr] - swapw - # => [METADATA_HEADER, NOTE_ATTACHMENT] + # network account target attachment must be present + assert.err=ERR_NETWORK_ACCOUNT_TARGET_MISSING + # => [num_words, attachment_ptr] - exec.note::metadata_into_attachment_header - # => [attachment_0_scheme, NOTE_ATTACHMENT] + eq.NETWORK_ACCOUNT_TARGET_ATTACHMENT_NUM_WORDS + assert.err=ERR_NETWORK_ACCOUNT_TARGET_INCORRECT_NUMBER_OF_WORDS + # => [attachment_ptr] - # ensure the attachment is a network account target - exec.is_network_account_target assert.err=ERR_NOT_NETWORK_ACCOUNT_TARGET - # => [NOTE_ATTACHMENT] = [target_id_suffix, target_id_prefix, exec_hint_tag, 0] + padw movup.4 mem_loadw_le + # => [NETWORK_ACCOUNT_TARGET_ATTACHMENT] + # => [target_id_suffix, target_id_prefix, exec_hint_tag, 0] - exec.get_id + exec.into_target_id # => [target_id_suffix, target_id_prefix] exec.active_account::get_id diff --git a/crates/miden-testing/src/standards/network_account_target.rs b/crates/miden-testing/src/standards/network_account_target.rs index b463a0287e..3fe804a40e 100644 --- a/crates/miden-testing/src/standards/network_account_target.rs +++ b/crates/miden-testing/src/standards/network_account_target.rs @@ -17,7 +17,7 @@ use miden_standards::note::{NetworkAccountTarget, NoteExecutionHint}; use crate::executor::CodeExecutor; #[tokio::test] -async fn network_account_target_get_id() -> anyhow::Result<()> { +async fn network_account_target_into_target_id() -> anyhow::Result<()> { let target_id = AccountIdBuilder::new() .storage_mode(AccountStorageMode::Network) .build_with_rng(&mut rand::rng()); @@ -46,7 +46,7 @@ async fn network_account_target_get_id() -> anyhow::Result<()> { # => [is_valid, NOTE_ATTACHMENT] assert.err=ERR_NOT_NETWORK_ACCOUNT_TARGET # => [NOTE_ATTACHMENT] - exec.network_account_target::get_id + exec.network_account_target::into_target_id # => [account_id_suffix, account_id_prefix] # cleanup stack movup.2 drop movup.2 drop @@ -138,7 +138,7 @@ async fn network_account_target_attachment_round_trip() -> anyhow::Result<()> { # => [is_valid, NOTE_ATTACHMENT] assert.err=ERR_NOT_NETWORK_ACCOUNT_TARGET # => [NOTE_ATTACHMENT] - exec.network_account_target::get_id + exec.network_account_target::into_target_id # => [target_id_suffix, target_id_prefix] # cleanup stack movup.2 drop movup.2 drop From 47374b072ab2336b2f13f027528a06d7f60eeec4 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 15:09:20 +0200 Subject: [PATCH 11/23] chore: remove outdated metadata_into_attachment_header --- crates/miden-protocol/asm/protocol/note.masm | 24 -------------- .../src/standards/network_account_target.rs | 33 +++++-------------- 2 files changed, 9 insertions(+), 48 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 73906ec0b2..99a4baac6b 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -316,30 +316,6 @@ pub proc metadata_into_sender # => [sender_id_suffix, sender_id_prefix] end -#! Extracts the first attachment's num_words and scheme from the provided metadata header. -#! -#! TODO(multi_attachments): This API temporarily maintains compatibility with the previous approach -#! that supported just one attachment per note. -#! -#! Inputs: [METADATA_HEADER] -#! Outputs: [attachment_0_scheme] -#! -#! Where: -#! - METADATA_HEADER is the metadata word of a note. -#! - attachment_0_scheme is the scheme of the first attachment (0 if absent). -#! -#! Invocation: exec -pub proc metadata_into_attachment_header - # => [sender_id_suffix_type_version, sender_id_prefix, tag, schemes] - - drop drop drop - # => [schemes] - - # extract attachment 0 scheme by taking only the lower 16 bits - u32and.0xffff - # [attachment_0_scheme] -end - #! Extracts the attachment's schemes from the provided metadata header. #! #! Inputs: [METADATA_HEADER] diff --git a/crates/miden-testing/src/standards/network_account_target.rs b/crates/miden-testing/src/standards/network_account_target.rs index 3fe804a40e..4546bfc571 100644 --- a/crates/miden-testing/src/standards/network_account_target.rs +++ b/crates/miden-testing/src/standards/network_account_target.rs @@ -2,15 +2,7 @@ use miden_protocol::Felt; use miden_protocol::account::AccountStorageMode; -use miden_protocol::note::{ - NoteAttachment, - NoteAttachmentContent, - NoteAttachments, - NoteMetadata, - NoteMetadataHeader, - NoteTag, - NoteType, -}; +use miden_protocol::note::{NoteAttachment, NoteAttachmentContent}; use miden_protocol::testing::account_id::AccountIdBuilder; use miden_standards::note::{NetworkAccountTarget, NoteExecutionHint}; @@ -24,27 +16,20 @@ async fn network_account_target_into_target_id() -> anyhow::Result<()> { let exec_hint = NoteExecutionHint::Always; let attachment = NoteAttachment::from(NetworkAccountTarget::new(target_id, exec_hint)?); - let attachments = NoteAttachments::from(attachment.clone()); - let metadata = NoteMetadata::new(target_id, NoteType::Public) - .with_tag(NoteTag::with_account_target(target_id)); - let metadata_header = NoteMetadataHeader::new(metadata, &attachments); - let metadata_word = metadata_header.to_metadata_word(); let source = format!( r#" use miden::standards::attachments::network_account_target use miden::protocol::note - const ERR_NOT_NETWORK_ACCOUNT_TARGET = "attachment is not a valid network account target" - begin - push.{attachment_commitment} - push.{metadata_word} - exec.note::metadata_into_attachment_header - # => [attachment_0_scheme, NOTE_ATTACHMENT] + push.{attachment_scheme} + # => [attachment_scheme] exec.network_account_target::is_network_account_target - # => [is_valid, NOTE_ATTACHMENT] - assert.err=ERR_NOT_NETWORK_ACCOUNT_TARGET + # => [is_valid] + assert.err="expected scheme to be a valid network account target" + + push.{attachment_word} # => [NOTE_ATTACHMENT] exec.network_account_target::into_target_id # => [account_id_suffix, account_id_prefix] @@ -52,8 +37,8 @@ async fn network_account_target_into_target_id() -> anyhow::Result<()> { movup.2 drop movup.2 drop end "#, - metadata_word = metadata_word, - attachment_commitment = match attachment.content() { + attachment_scheme = attachment.attachment_scheme().as_u16(), + attachment_word = match attachment.content() { NoteAttachmentContent::Word(word) => *word, _ => unreachable!("expected word attachment"), }, From 58946f956dc78d8da20085c2a675fc37e2f96a92 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 16:32:05 +0200 Subject: [PATCH 12/23] chore: remove outdated attachment param from input_note_get_metadata --- .../asm/kernels/transaction/api.masm | 38 +++---------------- .../asm/protocol/active_note.masm | 15 +++----- .../asm/protocol/input_note.masm | 13 +++---- .../asm/standards/notes/pswap.masm | 3 +- .../src/kernel_tests/tx/test_active_note.rs | 8 ---- .../src/kernel_tests/tx/test_input_note.rs | 5 --- 6 files changed, 17 insertions(+), 65 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 408d537b19..0fd5b8642b 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -7,7 +7,6 @@ use $kernel::input_note use $kernel::memory use $kernel::output_note use $kernel::tx -use $kernel::constants::WORD_SIZE use $kernel::memory::UPCOMING_FOREIGN_PROCEDURE_PTR use $kernel::memory::UPCOMING_FOREIGN_PROC_INPUT_VALUE_15_PTR @@ -913,7 +912,7 @@ end #! Returns the metadata of the specified input note. #! #! Inputs: [is_active_note, note_index, pad(14)] -#! Outputs: [NOTE_ATTACHMENT_0, METADATA_HEADER, pad(8)] +#! Outputs: [METADATA_HEADER, pad(12)] #! #! Where: #! - is_active_note is the boolean flag indicating whether we should return the metadata from @@ -921,7 +920,6 @@ end #! - note_index is the index of the input note whose metadata should be returned. Notice that if #! is_active_note is 1, note_index is ignored. #! - METADATA_HEADER is the metadata header of the specified input note. -#! - NOTE_ATTACHMENT_0 is the first attachment of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. @@ -945,38 +943,12 @@ pub proc input_note_get_metadata # => [input_note_ptr, pad(16)] # get the metadata - dup exec.memory::get_input_note_metadata_header - # => [METADATA_HEADER, input_note_ptr, pad(16)] - - # get the attachment - movup.4 exec.memory::get_input_note_attachments_commitment - # => [NOTE_ATTACHMENTS_COMMITMENT, METADATA_HEADER, pad(16)] + exec.memory::get_input_note_metadata_header + # => [METADATA_HEADER, pad(16)] # truncate the stack - swapdw dropw dropw - # => [NOTE_ATTACHMENTS_COMMITMENT, METADATA_HEADER, pad(8)] - - # TODO(multi_attachments): Maintain temporary compatibility with the previous API by returning - # the first attachment. This will be refactored in a follow-up PR. - exec.word::testz not - # => [!is_attachments_commitment_empty, NOTE_ATTACHMENTS_COMMITMENT, METADATA_HEADER, pad(8)] - - # if the attachments commitment is the empty word, the first attachment is also the empty word, - # so we leave the empty word on the stack - # - # otherwise: - if.true - # fetch the first attachment from the advice stack and overwrite the attachments commitment - adv.push_mapval - adv_loadw - # => [ATTACHMENT_COMMITMENT_0, METADATA_HEADER, pad(8)] - - adv.push_mapvaln - adv_push.1 eq.WORD_SIZE assert.err="retrieved attachments must be temporarily word-sized" - adv_loadw - # => [ATTACHMENT_0, METADATA_HEADER, pad(8)] - end - # => [ATTACHMENT_0, METADATA_HEADER, pad(8)] + swapw dropw + # => [METADATA_HEADER, pad(12)] end #! Returns the serial number of the specified input note. diff --git a/crates/miden-protocol/asm/protocol/active_note.masm b/crates/miden-protocol/asm/protocol/active_note.masm index 9616371a17..4bfa15e2b3 100644 --- a/crates/miden-protocol/asm/protocol/active_note.masm +++ b/crates/miden-protocol/asm/protocol/active_note.masm @@ -145,11 +145,10 @@ end #! Returns the metadata of the active note. #! #! Inputs: [] -#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER] +#! Outputs: [METADATA_HEADER] #! #! Where: -#! - METADATA_HEADER is the metadata header of the specified input note. -#! - NOTE_ATTACHMENT is the attachment of the specified input note. +#! - METADATA_HEADER is the metadata header of the active note. #! #! Panics if: #! - no note is currently active. @@ -168,11 +167,11 @@ pub proc get_metadata # => [offset, is_active_note = 1, pad(14)] syscall.exec_kernel_proc - # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] + # => [METADATA_HEADER, pad(12)] # clean the stack - swapdw dropw dropw - # => [NOTE_ATTACHMENT, METADATA_HEADER] + swapdw dropw dropw swapw dropw + # => [METADATA_HEADER] end #! Returns the sender of the active note. @@ -188,8 +187,7 @@ end #! #! Invocation: exec pub proc get_sender - # get metadata and drop attachment - exec.get_metadata dropw + exec.get_metadata # => [METADATA_HEADER] # extract the sender ID from the metadata header @@ -453,7 +451,6 @@ end #! Invocation: exec pub proc find_attachment exec.get_metadata - dropw # => [METADATA_HEADER, attachment_scheme] movup.4 diff --git a/crates/miden-protocol/asm/protocol/input_note.masm b/crates/miden-protocol/asm/protocol/input_note.masm index eee587ecdb..87110f286e 100644 --- a/crates/miden-protocol/asm/protocol/input_note.masm +++ b/crates/miden-protocol/asm/protocol/input_note.masm @@ -132,12 +132,11 @@ end #! Returns the metadata of the input note with the specified index. #! #! Inputs: [note_index] -#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER] +#! Outputs: [METADATA_HEADER] #! #! Where: #! - note_index is the index of the input note whose metadata should be returned. #! - METADATA_HEADER is the metadata header of the specified input note. -#! - NOTE_ATTACHMENT is the attachment of the specified input note. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. @@ -161,11 +160,11 @@ pub proc get_metadata # => [offset, is_active_note = 0, note_index, pad(13)] syscall.exec_kernel_proc - # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] + # => [METADATA_HEADER, pad(12)] # clean the stack - swapdw dropw dropw - # => [NOTE_ATTACHMENT, METADATA_HEADER] + swapdw dropw dropw swapw dropw + # => [METADATA_HEADER] end #! Returns the sender of the input note with the specified index. @@ -182,8 +181,7 @@ end #! #! Invocation: exec pub proc get_sender - # get metadata and drop attachment - exec.get_metadata dropw + exec.get_metadata # => [METADATA_HEADER] # extract the sender ID from the metadata header @@ -457,7 +455,6 @@ pub proc find_attachment # => [note_index, attachment_scheme, note_index] exec.get_metadata - dropw # => [METADATA_HEADER, attachment_scheme, note_index] debug.stack.4 diff --git a/crates/miden-standards/asm/standards/notes/pswap.masm b/crates/miden-standards/asm/standards/notes/pswap.masm index 52196d6248..7a387e2aaa 100644 --- a/crates/miden-standards/asm/standards/notes/pswap.masm +++ b/crates/miden-standards/asm/standards/notes/pswap.masm @@ -616,9 +616,8 @@ proc execute_pswap # => [remaining_requested] exec.active_note::get_metadata - # => [NOTE_ATTACHMENT, METADATA_HEADER, remaining_requested] + # => [METADATA_HEADER, remaining_requested] # where METADATA_HEADER = [sid_suf_ver, sid_pre, tag, att_ks] - dropw # => [sid_suf_ver, sid_pre, tag, att_ks, remaining_requested] dup.2 movdn.4 diff --git a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs index a4842787c2..84ff83aa46 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_active_note.rs @@ -109,10 +109,6 @@ async fn test_active_note_get_metadata() -> anyhow::Result<()> { # get the metadata of the active note exec.active_note::get_metadata - # => [NOTE_ATTACHMENT, METADATA_HEADER] - - push.{NOTE_ATTACHMENT} - assert_eqw.err="note 0 has incorrect note attachment" # => [METADATA_HEADER] push.{METADATA_HEADER} @@ -125,7 +121,6 @@ async fn test_active_note_get_metadata() -> anyhow::Result<()> { "#, METADATA_HEADER = tx_context.input_notes().get_note(0).note().metadata_header().to_metadata_word(), - NOTE_ATTACHMENT = tx_context.input_notes().get_note(0).note().attachments().to_commitment() ); tx_context.execute_code(&code).await?; @@ -205,9 +200,6 @@ async fn test_active_note_get_note_type(#[case] note_type: NoteType) -> anyhow:: dropw dropw dropw dropw exec.active_note::get_metadata - # => [NOTE_ATTACHMENT, METADATA_HEADER] - - dropw # => [METADATA_HEADER] exec.note::metadata_into_note_type diff --git a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs index 52052f50da..21b3491eff 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_input_note.rs @@ -121,10 +121,6 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { # get the metadata from the requested input note push.0 exec.input_note::get_metadata - # => [NOTE_ATTACHMENT, METADATA_HEADER] - - push.{NOTE_ATTACHMENT} - assert_eqw.err="note 0 has incorrect note attachment" # => [METADATA_HEADER] push.{METADATA_HEADER} @@ -134,7 +130,6 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { "#, RECIPIENT = p2id_note_1_asset.recipient().digest(), METADATA_HEADER = p2id_note_1_asset.metadata_header().to_metadata_word(), - NOTE_ATTACHMENT = p2id_note_1_asset.attachments().to_commitment(), ); let tx_script = CodeBuilder::default().compile_tx_script(code)?; From 40c7cdaed578d63eb734a7418404a8905b1fa3ad Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 16:35:29 +0200 Subject: [PATCH 13/23] chore: remove outdated attachment param from output_note_get_metadata --- .../asm/kernels/transaction/api.masm | 20 +++++-------------- .../asm/protocol/output_note.masm | 12 +++++------ .../src/kernel_tests/tx/test_output_note.rs | 5 ----- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index 0fd5b8642b..a37cb85856 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -1289,12 +1289,11 @@ end #! Returns the metadata of the output note with the specified index. #! #! Inputs: [note_index, pad(15)] -#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] +#! Outputs: [METADATA_HEADER, pad(12)] #! #! Where: #! - note_index is the index of the output note whose metadata should be returned. #! - METADATA_HEADER is the metadata header of the specified output note. -#! - NOTE_ATTACHMENT is the attachment of the specified output note. #! #! Panics if: #! - the note index is greater or equal to the total number of output notes. @@ -1309,22 +1308,13 @@ pub proc output_note_get_metadata exec.memory::get_output_note_ptr # => [note_ptr, pad(15)] - # make stack truncation at the end of the procedure easier - push.0 swap - # => [note_ptr, pad(16)] - # get the metadata - dup exec.memory::get_output_note_metadata_header - # => [METADATA_HEADER, note_ptr, pad(16)] - - # TODO(multi_attachments): Temporarily maintain compatibility with the old API and return the - # first attachment. - movup.4 push.0 swap exec.memory::get_output_note_attachment_commitment - # => [ATTACHMENT_0, METADATA_HEADER, pad(16)] + exec.memory::get_output_note_metadata_header + # => [METADATA_HEADER, pad(15)] # truncate the stack - swapdw dropw dropw - # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] + swapw drop drop drop movdn.4 + # => [METADATA_HEADER, pad(12)] end # TRANSACTION diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index 3adba275f9..692d6a12d2 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -344,15 +344,14 @@ pub proc get_recipient # => [RECIPIENT] end -#! Returns the metadata and first attachment of the output note with the specified index. +#! Returns the metadata of the output note with the specified index. #! #! Inputs: [note_index] -#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER] +#! Outputs: [METADATA_HEADER] #! #! Where: #! - note_index is the index of the output note whose metadata should be returned. #! - METADATA_HEADER is the metadata header of the specified output note. -#! - NOTE_ATTACHMENT is the first attachment of the specified output note. #! #! Panics if: #! - the note index is greater or equal to the total number of output notes. @@ -371,11 +370,11 @@ pub proc get_metadata # => [offset, note_index, pad(14)] syscall.exec_kernel_proc - # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] + # => [METADATA_HEADER, pad(12)] # clean the stack - swapdw dropw dropw - # => [NOTE_ATTACHMENT, METADATA_HEADER] + swapdw dropw dropw swapw dropw + # => [METADATA_HEADER] end #! Searches the metadata header for the specified attachment scheme and if found, pipes the @@ -402,7 +401,6 @@ pub proc find_attachment # => [note_idx, attachment_scheme, note_idx] exec.get_metadata - dropw # => [METADATA_HEADER, attachment_scheme, note_idx] movup.4 diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 2d148688a2..82c75bef03 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1019,10 +1019,6 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { # get the metadata (the only existing note has 0'th index) push.0 exec.output_note::get_metadata - # => [NOTE_ATTACHMENT, METADATA_HEADER] - - push.{NOTE_ATTACHMENT} - assert_eqw.err="requested note has incorrect note attachment" # => [METADATA_HEADER] push.{METADATA_HEADER} @@ -1036,7 +1032,6 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { output_note = create_output_note(&output_note), RECIPIENT = output_note.recipient().digest(), METADATA_HEADER = output_note.metadata_header().to_metadata_word(), - NOTE_ATTACHMENT = output_note.attachments().to_commitment(), ); let tx_script = CodeBuilder::default().compile_tx_script(tx_script_src)?; From 75c979193dd35c94a49e79ce3657bc3a199d94b0 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 16:52:26 +0200 Subject: [PATCH 14/23] chore: update protocol library docs --- docs/src/protocol_library.md | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/docs/src/protocol_library.md b/docs/src/protocol_library.md index d79b4cbe18..7221dce318 100644 --- a/docs/src/protocol_library.md +++ b/docs/src/protocol_library.md @@ -79,10 +79,14 @@ Active note procedures can be used to fetch data from the note that is currently | `get_assets` | Writes the [assets](note.md#assets) of the active note into memory starting at the specified address.

**Inputs:** `[dest_ptr]`
**Outputs:** `[num_assets, dest_ptr]` | Note | | `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the active note.

**Inputs:** `[]`
**Outputs:** `[RECIPIENT]` | Note | | `get_storage` | Writes the note's [inputs](note.md#inputs) to the specified memory address.

**Inputs:** `[dest_ptr]`
**Outputs:** `[num_storage_items, dest_ptr]` | Note | -| `get_metadata` | Returns the [metadata](note.md#metadata) of the active note.

**Inputs:** `[]`
**Outputs:** `[METADATA]` | Note | +| `get_metadata` | Returns the [metadata](note.md#metadata) of the active note.

**Inputs:** `[]`
**Outputs:** `[METADATA_HEADER]` | Note | | `get_sender` | Returns the sender of the active note.

**Inputs:** `[]`
**Outputs:** `[sender_id_suffix, sender_id_prefix]` | Note | | `get_serial_number` | Returns the [serial number](note.md#serial-number) of the active note.

**Inputs:** `[]`
**Outputs:** `[SERIAL_NUMBER]` | Note | | `get_script_root` | Returns the [script root](note.md#script) of the active note.

**Inputs:** `[]`
**Outputs:** `[SCRIPT_ROOT]` | Note | +| `get_attachments_commitment` | Returns the commitment over all attachments of the active note.

**Inputs:** `[]`
**Outputs:** `[ATTACHMENTS_COMMITMENT]` | Note | +| `get_attachment_commitments` | Writes the attachment commitments of the active note to memory and returns the pointer to the data.

**Inputs:** `[]`
**Outputs:** `[num_attachments, attachments_ptr]` | Note | +| `get_attachment` | Writes the attachment with the provided index from the active note to memory and returns the pointer to the data.

**Inputs:** `[attachment_idx]`
**Outputs:** `[num_words, attachment_ptr]` | Note | +| `find_attachment` | Searches the metadata header of the active note for the specified attachment scheme and if found, writes the attachment data to memory.

**Inputs:** `[attachment_scheme]`
**Outputs:** `[is_found, num_words, attachment_ptr]` | Note | ## Input Note Procedures (`miden::protocol::input_note`) @@ -93,11 +97,15 @@ Input note procedures can be used to fetch data on input notes consumed by the t | `get_assets_info` | Returns the information about [assets](note.md#assets) in the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ASSETS_COMMITMENT, num_assets]` | Any | | `get_assets` | Writes the [assets](note.md#assets) of the input note with the specified index into memory starting at the specified address.

**Inputs:** `[dest_ptr, note_index]`
**Outputs:** `[num_assets, dest_ptr, note_index]` | Any | | `get_recipient` | Returns the [recipient](note.md#note-recipient-restricting-consumption) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[RECIPIENT]` | Any | -| `get_metadata` | Returns the [metadata](note.md#metadata) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA]` | Any | +| `get_metadata` | Returns the [metadata](note.md#metadata) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA_HEADER]` | Any | | `get_sender` | Returns the sender of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[sender_id_suffix, sender_id_prefix]` | Any | | `get_storage_info` | Returns the [inputs](note.md#inputs) commitment and length of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[NOTE_STORAGE_COMMITMENT, num_storage_items]` | Any | | `get_script_root` | Returns the [script root](note.md#script) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[SCRIPT_ROOT]` | Any | | `get_serial_number` | Returns the [serial number](note.md#serial-number) of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[SERIAL_NUMBER]` | Any | +| `get_attachments_commitment` | Returns the commitment over all attachments of the input note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ATTACHMENTS_COMMITMENT]` | Any | +| `get_attachment_commitments` | Writes the attachment commitments of the input note with the specified index to memory and returns the pointer to the data.

**Inputs:** `[note_index]`
**Outputs:** `[num_attachments, attachments_ptr]` | Any | +| `get_attachment` | Writes the attachment with the provided index from the input note with the specified index to memory and returns the pointer to the data.

**Inputs:** `[attachment_idx, note_index]`
**Outputs:** `[num_words, attachment_ptr]` | Any | +| `find_attachment` | Searches the metadata header of the input note for the specified attachment scheme and if found, writes the attachment data to memory.

**Inputs:** `[attachment_scheme, note_index]`
**Outputs:** `[is_found, num_words, attachment_ptr]` | Any | ## Output Note Procedures (`miden::protocol::output_note`) @@ -109,11 +117,15 @@ Output note procedures can be used to fetch data on output notes created by the | `get_assets_info` | Returns the information about assets in the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ASSETS_COMMITMENT, num_assets]` | Any | | `get_assets` | Writes the assets of the output note with the specified index into memory starting at the specified address.

**Inputs:** `[dest_ptr, note_index]`
**Outputs:** `[num_assets, dest_ptr, note_index]` | Any | | `add_asset` | Adds the asset to the output note specified by the index.

**Inputs:** `[ASSET_KEY, ASSET_VALUE, note_idx]`
**Outputs:** `[]` | Native | -| `set_attachment` | Sets the attachment of the note specified by the index.

If attachment_kind == Array, there must be an advice map entry for ATTACHMENT.

**Inputs:**
`Operand Stack: [note_idx, attachment_scheme, attachment_kind, ATTACHMENT]`
`Advice map: { ATTACHMENT?: [[ATTACHMENT_ELEMENTS]] }`
**Outputs:** `[]` | Native | -| `set_array_attachment` | Sets the attachment of the note specified by the note index to the provided ATTACHMENT which commits to an array of felts.

**Inputs:**
`Operand Stack: [note_idx, attachment_scheme, ATTACHMENT]`
`Advice map: { ATTACHMENT: [[ATTACHMENT_ELEMENTS]] }`
**Outputs:** `[]` | Native | -| `set_word_attachment` | Sets the attachment of the note specified by the note index to the provided word.

**Inputs:** `[note_idx, attachment_scheme, ATTACHMENT]`
**Outputs:** `[]` | +| `add_attachment` | Adds an attachment to the note specified by the index. There must be an advice map entry for ATTACHMENT_COMMITMENT that maps to the raw attachment elements.

**Inputs:**
`Operand Stack: [attachment_scheme, ATTACHMENT_COMMITMENT, note_idx]`
`Advice map: { ATTACHMENT_COMMITMENT: [[ATTACHMENT_ELEMENTS]] }`
**Outputs:** `[]` | Native | +| `add_word_attachment` | Adds a single-word attachment to the note specified by the note index. Hashes the raw attachment word to produce the commitment, inserts the raw elements into the advice map, then delegates to `add_attachment`.

**Inputs:** `[attachment_scheme, ATTACHMENT, note_idx]`
**Outputs:** `[]` | Native | +| `add_array_attachment` | Adds an array attachment to the note specified by the note index. The ATTACHMENT_COMMITMENT is the hash commitment to a set of elements.

**Inputs:**
`Operand Stack: [attachment_scheme, ATTACHMENT_COMMITMENT, note_idx]`
`Advice map: { ATTACHMENT_COMMITMENT: [[ATTACHMENT_ELEMENTS]] }`
**Outputs:** `[]` | Native | +| `get_attachments_commitment` | Returns the commitment over all attachments of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[ATTACHMENTS_COMMITMENT]` | Any | +| `get_attachment_commitments` | Writes the attachment commitments of the output note with the specified index to memory and returns the pointer to the data.

**Inputs:** `[note_index]`
**Outputs:** `[num_attachments, attachments_ptr]` | Any | +| `get_attachment` | Writes the attachment with the provided index from the output note with the specified index to memory and returns the pointer to the data.

**Inputs:** `[attachment_idx, note_index]`
**Outputs:** `[num_words, attachment_ptr]` | Any | +| `find_attachment` | Searches the metadata header of the output note for the specified attachment scheme and if found, writes the attachment data to memory.

**Inputs:** `[attachment_scheme, note_idx]`
**Outputs:** `[is_found, num_words, attachment_ptr]` | Any | | `get_recipient` | Returns the [recipient](note#note-recipient-restricting-consumption) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[RECIPIENT]` | Any | -| `get_metadata` | Returns the [metadata](note#metadata) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA]` | Any | +| `get_metadata` | Returns the [metadata](note#metadata) of the output note with the specified index.

**Inputs:** `[note_index]`
**Outputs:** `[METADATA_HEADER]` | Any | ## Note Utility Procedures (`miden::protocol::note`) @@ -123,11 +135,15 @@ Note utility procedures can be used to compute the required utility data or writ | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `compute_storage_commitment` | Computes the commitment to the output note storage starting at the specified memory address.

**Inputs:** `[storage_ptr, num_storage_items]`
**Outputs:** `[STORAGE_COMMITMENT]` | Any | | `write_assets_to_memory` | Writes the assets data stored in the advice map to the memory specified by the provided destination pointer.

**Inputs:** `[ASSETS_COMMITMENT, num_assets, dest_ptr]`
**Outputs:** `[num_assets, dest_ptr]` | Any | +| `write_attachments_to_memory` | Writes the attachment commitments stored in the advice map to the memory specified by the provided destination pointer.

**Inputs:**
`Operand Stack: [ATTACHMENTS_COMMITMENT, dest_ptr]`
`Advice map: { ATTACHMENTS_COMMITMENT: [[ATTACHMENT_COMMITMENT]] }`
**Outputs:** `[num_attachments]` | Any | +| `write_attachment_to_memory` | Writes a single attachment's data stored in the advice map to the memory specified by the provided destination pointer.

**Inputs:**
`Operand Stack: [ATTACHMENT_COMMITMENT, dest_ptr]`
`Advice map: { ATTACHMENT_COMMITMENT: [[ATTACHMENT_ELEMENTS]] }`
**Outputs:** `[num_words]` | Any | +| `get_attachment` | Writes the attachment with the provided index from the provided attachments to memory and returns the pointer to the attachment elements.

**Inputs:** `[num_attachments, attachments_ptr, attachment_idx]`
**Outputs:** `[num_words, attachment_ptr]` | Any | | `build_recipient_hash` | Returns the `RECIPIENT` for a specified `SERIAL_NUM`, `SCRIPT_ROOT`, and storage commitment.

**Inputs:** `[SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT]`
**Outputs:** `[RECIPIENT]` | Any | | `build_recipient` | Builds the recipient hash from note storage, script root, and serial number.

**Inputs:** `[storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT]`
**Outputs:** `[RECIPIENT]` | Any | -| `metadata_into_sender` | Extracts the sender ID from the provided metadata word.

**Inputs:** `[METADATA]`
**Outputs:** `[sender_id_suffix, sender_id_prefix]` | Any | -| `metadata_into_attachment_info` | Extracts the attachment kind and scheme from the provided metadata header.

**Inputs:** `[METADATA_HEADER]`
**Outputs:** `[attachment_kind, attachment_scheme]` | Any | +| `metadata_into_sender` | Extracts the sender ID from the provided metadata word.

**Inputs:** `[METADATA_HEADER]`
**Outputs:** `[sender_id_suffix, sender_id_prefix]` | Any | +| `metadata_into_attachment_schemes` | Extracts the attachment schemes from the provided metadata header.

**Inputs:** `[METADATA_HEADER]`
**Outputs:** `[attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme]` | Any | | `metadata_into_note_type` | Extracts the note type from the provided metadata header. The note type is encoded as a single bit (0 = Private, 1 = Public).

**Inputs:** `[METADATA_HEADER]`
**Outputs:** `[note_type]` | Any | +| `find_attachment_idx` | Searches the metadata header for the specified attachment scheme and returns the index of the first matching slot.

**Inputs:** `[attachment_scheme, METADATA_HEADER]`
**Outputs:** `[is_found, attachment_idx]` | Any | ## Transaction Procedures (`miden::protocol::tx`) From 4ad8e60f63ebe7d8517055b799b284104e7d7cb2 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 16:57:08 +0200 Subject: [PATCH 15/23] fixup! feat: implement attachment APIs for input/active note --- crates/miden-protocol/asm/protocol/input_note.masm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/input_note.masm b/crates/miden-protocol/asm/protocol/input_note.masm index 87110f286e..ad35830346 100644 --- a/crates/miden-protocol/asm/protocol/input_note.masm +++ b/crates/miden-protocol/asm/protocol/input_note.masm @@ -334,8 +334,8 @@ end #! Writes the attachment commitments of the note identified by the is_active_note flag to memory #! and returns the pointer to the data. #! -#! This is the shared implementation used by both `input_note::get_attachment_commitments` and -#! `active_note::get_attachment_commitments`. +#! This is the shared implementation used by both `input_note::get_attachments_commitment` and +#! `active_note::get_attachments_commitment`. #! #! Inputs: [is_active_note, note_index] #! Outputs: [ATTACHMENTS_COMMITMENT] From 7605e211c5df59f06a8414a2c3dc7204a6e75044 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 16:59:30 +0200 Subject: [PATCH 16/23] fixup! feat: implement attachment APIs for input/active note --- .../asm/protocol/kernel_proc_offsets.masm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm b/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm index 32abdf47a7..b5b8f305f3 100644 --- a/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm +++ b/crates/miden-protocol/asm/protocol/kernel_proc_offsets.masm @@ -53,12 +53,12 @@ pub const FAUCET_HAS_CALLBACKS_OFFSET=27 # input notes pub const INPUT_NOTE_GET_METADATA_OFFSET=28 -pub const INPUT_NOTE_GET_ASSETS_INFO_OFFSET=29 -pub const INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=30 -pub const INPUT_NOTE_GET_STORAGE_INFO_OFFSET=31 -pub const INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=32 -pub const INPUT_NOTE_GET_RECIPIENT_OFFSET=33 -pub const INPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET=34 +pub const INPUT_NOTE_GET_RECIPIENT_OFFSET=29 +pub const INPUT_NOTE_GET_ASSETS_INFO_OFFSET=30 +pub const INPUT_NOTE_GET_ATTACHMENTS_COMMITMENT_OFFSET=31 +pub const INPUT_NOTE_GET_SCRIPT_ROOT_OFFSET=32 +pub const INPUT_NOTE_GET_STORAGE_INFO_OFFSET=33 +pub const INPUT_NOTE_GET_SERIAL_NUMBER_OFFSET=34 # output notes pub const OUTPUT_NOTE_CREATE_OFFSET=35 From 15df4094c69f098e0d1319b1c4b848f1e6ebe4c7 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 17:12:42 +0200 Subject: [PATCH 17/23] fixup! chore: implement metadata_into_attachment_schemes --- crates/miden-testing/src/kernel_tests/tx/test_note.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 574ff8d724..205ef0ec80 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -594,10 +594,10 @@ async fn test_metadata_into_attachment_schemes( let exec_output = CodeExecutor::with_default_host().run(&code).await?; for (i, header) in attachment_headers.iter().enumerate() { - let expected = header.scheme().map_or(0u64, |s| s.as_u16() as u64); + let expected_scheme = header.scheme().as_ref().map_or(0, NoteAttachmentScheme::as_u16); assert_eq!( - exec_output.get_stack_element(i), - Felt::new(expected), + exec_output.get_stack_element(i).as_canonical_u64(), + u64::from(expected_scheme), "attachment scheme mismatch at index {i}" ); } From 5b158be31f679b77942fd802ae0c81c927dda1ae Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 17:12:56 +0200 Subject: [PATCH 18/23] chore: update safety comments in attachment piping procs --- crates/miden-protocol/asm/protocol/note.masm | 16 +++++++++++----- .../miden-protocol/asm/protocol/output_note.masm | 2 -- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 99a4baac6b..33afc58e0f 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -103,9 +103,11 @@ pub proc write_attachments_to_memory # OS => [ATTACHMENTS_COMMITMENT, dest_ptr] # AS => [num_elements, [ATTACHMENT_COMMITMENT]] - # SAFETY: kernel should ensure num_elements is a multiple of WORD_SIZE = 4 and is in valid - # range - adv_push.1 u32div.WORD_SIZE + # SAFETY: if the provided num_elements is invalid, the commitment check would fail in + # pipe_preimage_to_memory so we assume validity and only do a basic u32 assertion to protect + # against invalid advice inputs. + adv_push.1 u32assert.err="invalid attachment num_elements advice input" + u32div.WORD_SIZE # OS => [num_words, ATTACHMENTS_COMMITMENT, dest_ptr] # AS => [[ATTACHMENT_COMMITMENT]] @@ -140,8 +142,11 @@ pub proc write_attachment_to_memory # OS => [ATTACHMENT_COMMITMENT, dest_ptr] # AS => [num_elements, [ATTACHMENT_ELEMENTS]] - # read num_elements and compute num_words - adv_push.1 u32div.WORD_SIZE + # SAFETY: if the provided num_elements is invalid, the commitment check would fail in + # pipe_preimage_to_memory so we assume validity and only do a basic u32 assertion to protect + # against invalid advice inputs. + adv_push.1 u32assert.err="invalid attachment num_elements advice input" + u32div.WORD_SIZE # OS => [num_words, ATTACHMENT_COMMITMENT, dest_ptr] # AS => [[ATTACHMENT_ELEMENTS]] @@ -402,6 +407,7 @@ pub proc find_attachment_idx movup.4 push.0 push.0 # => [attachment_idx = 0, is_found = 0, attachment_scheme, scheme_0, scheme_1, scheme_2, scheme_3] + # iterate over the four schemes repeat.4 # => [attachment_idx, is_found, attachment_scheme, scheme_n, ...] diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index 692d6a12d2..c3c70b113f 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -380,8 +380,6 @@ end #! Searches the metadata header for the specified attachment scheme and if found, pipes the #! attachment's underlying data from the advice inputs to memory and returns the ptr to it. #! -#! Pointer and num_words are zero if is_found = 0. -#! #! Inputs: [attachment_scheme, note_idx] #! Outputs: [is_found, num_words, attachment_ptr] #! From 0d65ba6355c06cb5f2a712cf7f1a18c4c9cc627d Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 29 Apr 2026 17:18:15 +0200 Subject: [PATCH 19/23] chore: add changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d74a2f67cf..bef2bc30f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ - [BREAKING] Removed redundant outputs from kernel procedures: `note::write_assets_to_memory`, `active_note::get_assets`, `input_note::get_assets`, `output_note::get_assets`, `active_note::get_storage`, and `faucet::mint` no longer return values identical to their inputs ([#2523](https://github.com/0xMiden/protocol/issues/2523)). - Added PSWAP (partial swap) note for decentralized partial-fill asset exchange with remainder note re-creation ([#2636](https://github.com/0xMiden/protocol/pull/2636)). -- [BREAKING] Add support for multiple attachments per note ([#2795](https://github.com/0xMiden/protocol/pull/2795)). +- [BREAKING] Add support for multiple attachments per note ([#2795](https://github.com/0xMiden/protocol/pull/2795), [#2849](https://github.com/0xMiden/protocol/pull/2849)): +- [BREAKING] Renamed `set_attachment` to `add_attachment`, `set_word_attachment` to `add_word_attachment`, and `set_array_attachment` to `add_array_attachment` in `miden::protocol::output_note` ([#2795](https://github.com/0xMiden/protocol/pull/2795), [#2849](https://github.com/0xMiden/protocol/pull/2849)). +- [BREAKING] Replaced `metadata_into_attachment_info` with `metadata_into_attachment_schemes` in `miden::protocol::note` ([#2795](https://github.com/0xMiden/protocol/pull/2795), [#2849](https://github.com/0xMiden/protocol/pull/2849)). +- [BREAKING] All `get_metadata` procedures (`active_note`, `input_note`, `output_note`) no longer return attachments ([#2795](https://github.com/0xMiden/protocol/pull/2795), [#2849](https://github.com/0xMiden/protocol/pull/2849)). ### Changes From a093a3bfbb1c547ef42c0e8f8ac9eef35021216c Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 5 May 2026 15:09:33 +0200 Subject: [PATCH 20/23] chore: add `_ptr` suffix to get_attachment* APIs --- .../miden-protocol/asm/protocol/active_note.masm | 10 +++++----- crates/miden-protocol/asm/protocol/input_note.masm | 10 +++++----- crates/miden-protocol/asm/protocol/note.masm | 2 +- .../miden-protocol/asm/protocol/output_note.masm | 10 +++++----- .../src/kernel_tests/tx/test_output_note.rs | 14 +++++++------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/active_note.masm b/crates/miden-protocol/asm/protocol/active_note.masm index 4bfa15e2b3..bd068cb08f 100644 --- a/crates/miden-protocol/asm/protocol/active_note.masm +++ b/crates/miden-protocol/asm/protocol/active_note.masm @@ -388,7 +388,7 @@ end #! #! Invocation: exec @locals(16) -pub proc get_attachment_commitments +pub proc get_attachment_commitments_ptr exec.get_attachments_commitment # => [ATTACHMENTS_COMMITMENT] @@ -420,12 +420,12 @@ end #! attachment commitment. #! #! Invocation: exec -pub proc get_attachment +pub proc get_attachment_ptr # get the attachment commitments for the active note - swap exec.get_attachment_commitments + swap exec.get_attachment_commitments_ptr # => [num_attachments, attachments_ptr, attachment_idx] - exec.note::get_attachment + exec.note::get_attachment_ptr # => [num_words, attachment_ptr] end @@ -460,7 +460,7 @@ pub proc find_attachment # => [is_found, attachment_idx] if.true - exec.get_attachment + exec.get_attachment_ptr # => [num_words, attachment_ptr] push.1 diff --git a/crates/miden-protocol/asm/protocol/input_note.masm b/crates/miden-protocol/asm/protocol/input_note.masm index ad35830346..2ee8c1dc39 100644 --- a/crates/miden-protocol/asm/protocol/input_note.masm +++ b/crates/miden-protocol/asm/protocol/input_note.masm @@ -388,7 +388,7 @@ end #! #! Invocation: exec @locals(16) -pub proc get_attachment_commitments +pub proc get_attachment_commitments_ptr exec.get_attachments_commitment # => [ATTACHMENTS_COMMITMENT] @@ -421,12 +421,12 @@ end #! attachment commitment. #! #! Invocation: exec -pub proc get_attachment +pub proc get_attachment_ptr # get the attachment commitments for the note - swap exec.get_attachment_commitments + swap exec.get_attachment_commitments_ptr # => [num_attachments, attachments_ptr, attachment_idx] - exec.note::get_attachment + exec.note::get_attachment_ptr # => [num_words, attachment_ptr] end @@ -465,7 +465,7 @@ pub proc find_attachment # => [is_found, attachment_idx, note_index] if.true - exec.get_attachment + exec.get_attachment_ptr # => [num_words, attachment_ptr] push.1 diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 2d25fa00cf..d7f6c52602 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -183,7 +183,7 @@ end #! #! Invocation: exec @locals(1024) -pub proc get_attachment +pub proc get_attachment_ptr # assert attachment_idx < num_attachments dup.2 swap u32assert2.err=ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS u32lt assert.err=ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index c3c70b113f..ca0f271f07 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -408,7 +408,7 @@ pub proc find_attachment # => [is_found, attachment_idx, note_idx] if.true - exec.get_attachment + exec.get_attachment_ptr # => [num_words, attachment_ptr] push.1 @@ -438,7 +438,7 @@ end #! - the sequential hash over the attachment commitments in the advice inputs does not match the #! attachments commitment. @locals(16) -pub proc get_attachment_commitments +pub proc get_attachment_commitments_ptr exec.get_attachments_commitment # => [ATTACHMENTS_COMMITMENT] @@ -471,11 +471,11 @@ end #! attachment commitment. #! #! Invocation: exec -pub proc get_attachment +pub proc get_attachment_ptr # get the attachment commitments for the note - swap exec.get_attachment_commitments + swap exec.get_attachment_commitments_ptr # => [num_attachments, attachments_ptr, attachment_idx] - exec.note::get_attachment + exec.note::get_attachment_ptr # => [num_words, attachment_ptr] end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 82c75bef03..abfe28215c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1463,10 +1463,10 @@ async fn test_network_note() -> anyhow::Result<()> { Ok(()) } -/// Test that `output_note::get_attachment_commitments` returns the correct number of attachments +/// Test that `output_note::get_attachment_commitments_ptr` returns the correct number of attachments /// and writes the individual attachment commitments to memory at the returned pointer. #[tokio::test] -async fn test_get_attachment_commitments() -> anyhow::Result<()> { +async fn test_get_attachment_commitments_ptr() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, Auth::IncrNonce); let rng = RandomCoin::new(Word::from([1, 2, 3, 4u32])); @@ -1515,7 +1515,7 @@ async fn test_get_attachment_commitments() -> anyhow::Result<()> { # get attachment commitments for note at index 0 push.0 - exec.output_note::get_attachment_commitments + exec.output_note::get_attachment_commitments_ptr # => [num_attachments, attachments_ptr] # assert num_attachments == 2 @@ -1566,10 +1566,10 @@ async fn test_get_attachment_commitments() -> anyhow::Result<()> { Ok(()) } -/// Test that `output_note::get_attachment` retrieves the correct attachment data from local memory +/// Test that `output_note::get_attachment_ptr` retrieves the correct attachment data from local memory /// after piping its preimage from the advice map. #[tokio::test] -async fn test_get_attachment() -> anyhow::Result<()> { +async fn test_get_attachment_ptr() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, Auth::IncrNonce); let rng = RandomCoin::new(Word::from([1, 2, 3, 4u32])); @@ -1615,7 +1615,7 @@ async fn test_get_attachment() -> anyhow::Result<()> { # --- get attachment 1 first (to debug with non-zero idx) --- push.0 push.1 # => [attachment_idx=1, note_idx=0] - exec.output_note::get_attachment + exec.output_note::get_attachment_ptr # => [attachment_ptr, num_words] drop drop @@ -1759,7 +1759,7 @@ async fn test_find_attachment( #[case::add_word_attachment(5, "add_word_attachment")] #[case::add_array_attachment(5, "add_array_attachment")] #[case::find_attachment(1, "find_attachment")] -#[case::get_attachment_commitments(0, "get_attachment_commitments")] +#[case::get_attachment_commitments_ptr(0, "get_attachment_commitments_ptr")] #[case::get_attachments_commitment(0, "get_attachments_commitment")] #[tokio::test] async fn test_output_note_index_out_of_bounds( From 1254f8250a9644c95615c0e33f2335a27e00f4ec Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Tue, 5 May 2026 15:50:32 +0200 Subject: [PATCH 21/23] fix: make format --- .../miden-testing/src/kernel_tests/tx/test_output_note.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index abfe28215c..8fb00cac3b 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1463,8 +1463,8 @@ async fn test_network_note() -> anyhow::Result<()> { Ok(()) } -/// Test that `output_note::get_attachment_commitments_ptr` returns the correct number of attachments -/// and writes the individual attachment commitments to memory at the returned pointer. +/// Test that `output_note::get_attachment_commitments_ptr` returns the correct number of +/// attachments and writes the individual attachment commitments to memory at the returned pointer. #[tokio::test] async fn test_get_attachment_commitments_ptr() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, Auth::IncrNonce); @@ -1566,8 +1566,8 @@ async fn test_get_attachment_commitments_ptr() -> anyhow::Result<()> { Ok(()) } -/// Test that `output_note::get_attachment_ptr` retrieves the correct attachment data from local memory -/// after piping its preimage from the advice map. +/// Test that `output_note::get_attachment_ptr` retrieves the correct attachment data from local +/// memory after piping its preimage from the advice map. #[tokio::test] async fn test_get_attachment_ptr() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET, Auth::IncrNonce); From 60bd303c9882dc7c3f21fb89ff40052441a13b9b Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 6 May 2026 07:44:13 +0200 Subject: [PATCH 22/23] chore: rename to write_attachment_commitments_to_memory --- .../miden-protocol/asm/protocol/active_note.masm | 8 ++++---- crates/miden-protocol/asm/protocol/input_note.masm | 8 ++++---- crates/miden-protocol/asm/protocol/note.masm | 14 +++++++------- .../miden-protocol/asm/protocol/output_note.masm | 12 ++++++------ .../src/kernel_tests/tx/test_output_note.rs | 8 ++++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/active_note.masm b/crates/miden-protocol/asm/protocol/active_note.masm index bd068cb08f..94e3914844 100644 --- a/crates/miden-protocol/asm/protocol/active_note.masm +++ b/crates/miden-protocol/asm/protocol/active_note.masm @@ -374,11 +374,11 @@ end #! potential four attachment commitments. #! #! Inputs: [] -#! Outputs: [num_attachments, attachments_ptr] +#! Outputs: [num_attachments, attachment_commitments_ptr] #! #! Where: #! - num_attachments is the number of attachments in the note. -#! - attachments_ptr is a pointer to the attachment commitments written to local memory. +#! - attachment_commitments_ptr is a pointer to the attachment commitments written to local memory. #! #! Panics if: #! - no note is currently active. @@ -395,7 +395,7 @@ pub proc get_attachment_commitments_ptr locaddr.0 movdn.4 # => [ATTACHMENTS_COMMITMENT, dest_ptr] - exec.note::write_attachments_to_memory + exec.note::write_attachment_commitments_to_memory # => [num_attachments] locaddr.0 swap @@ -423,7 +423,7 @@ end pub proc get_attachment_ptr # get the attachment commitments for the active note swap exec.get_attachment_commitments_ptr - # => [num_attachments, attachments_ptr, attachment_idx] + # => [num_attachments, attachment_commitments_ptr, attachment_idx] exec.note::get_attachment_ptr # => [num_words, attachment_ptr] diff --git a/crates/miden-protocol/asm/protocol/input_note.masm b/crates/miden-protocol/asm/protocol/input_note.masm index 2ee8c1dc39..5c8cdac10d 100644 --- a/crates/miden-protocol/asm/protocol/input_note.masm +++ b/crates/miden-protocol/asm/protocol/input_note.masm @@ -374,12 +374,12 @@ end #! potential four attachment commitments. #! #! Inputs: [note_index] -#! Outputs: [num_attachments, attachments_ptr] +#! Outputs: [num_attachments, attachment_commitments_ptr] #! #! Where: #! - note_index is the index of the input note. #! - num_attachments is the number of attachments in the note. -#! - attachments_ptr is a pointer to the attachment commitments written to local memory. +#! - attachment_commitments_ptr is a pointer to the attachment commitments written to local memory. #! #! Panics if: #! - the note index is greater or equal to the total number of input notes. @@ -395,7 +395,7 @@ pub proc get_attachment_commitments_ptr locaddr.0 movdn.4 # => [ATTACHMENTS_COMMITMENT, dest_ptr] - exec.note::write_attachments_to_memory + exec.note::write_attachment_commitments_to_memory # => [num_attachments] locaddr.0 swap @@ -424,7 +424,7 @@ end pub proc get_attachment_ptr # get the attachment commitments for the note swap exec.get_attachment_commitments_ptr - # => [num_attachments, attachments_ptr, attachment_idx] + # => [num_attachments, attachment_commitments_ptr, attachment_idx] exec.note::get_attachment_ptr # => [num_words, attachment_ptr] diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index d7f6c52602..36d8098956 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -98,7 +98,7 @@ end #! } #! Outputs: #! Operand stack: [num_attachments] -pub proc write_attachments_to_memory +pub proc write_attachment_commitments_to_memory # push the individual ATTACHMENT commitments from the advice map onto the advice stack adv.push_mapvaln # OS => [ATTACHMENTS_COMMITMENT, dest_ptr] @@ -160,19 +160,19 @@ pub proc write_attachment_to_memory # => [num_words] end -#! Writes the attachment with the provided index from the provided attachments to memory and -#! returns the pointer to the attachment elements. +#! Writes the attachment with the provided index from to memory and returns the pointer to the +#! attachment elements. #! #! This procedure allocates 1024 elements of memory (MAX_ATTACHMENT_WORDS * WORD_SIZE) to store #! all potential attachment elements. #! -#! Inputs: [num_attachments, attachments_ptr, attachment_idx] +#! Inputs: [num_attachments, attachment_commitments_ptr, attachment_idx] #! Outputs: [num_words, attachment_ptr] #! #! Where: #! - attachment_idx is the index of the attachment to retrieve. #! - attachment_ptr is a pointer to the attachment data written to memory. -#! - attachments_ptr is a pointer to the attachments written to memory. +#! - attachment_commitments_ptr is a pointer to the attachment commitments written to memory. #! - num_words is the number of attachments. #! - num_words is the number of words in the attachment. #! @@ -187,10 +187,10 @@ pub proc get_attachment_ptr # assert attachment_idx < num_attachments dup.2 swap u32assert2.err=ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS u32lt assert.err=ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS - # => [attachments_ptr, attachment_idx] + # => [attachment_commitments_ptr, attachment_idx] # compute the memory address of the attachment commitment: - # commitment_ptr = attachments_ptr + attachment_idx * WORD_SIZE + # commitment_ptr = attachment_commitments_ptr + attachment_idx * WORD_SIZE swap mul.WORD_SIZE add # => [commitment_ptr] diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index ca0f271f07..822f834e23 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -427,12 +427,12 @@ end #! potential four attachment commitments. #! #! Inputs: [note_index] -#! Outputs: [num_attachments, attachments_ptr] +#! Outputs: [num_attachments, attachment_commitments_ptr] #! #! Where: -#! - note_index is the index of the output note whose metadata should be returned. -#! - METADATA_HEADER is the metadata header of the specified output note. -#! - NOTE_ATTACHMENT is the first attachment of the specified output note. +#! - note_index is the index of the output note. +#! - num_attachments is the number of attachments in the note. +#! - attachment_commitments_ptr is a pointer to the attachment commitments written to local memory. #! #! Pancis if: #! - the sequential hash over the attachment commitments in the advice inputs does not match the @@ -445,7 +445,7 @@ pub proc get_attachment_commitments_ptr locaddr.0 movdn.4 # => [ATTACHMENTS_COMMITMENT, dest_ptr] - exec.note::write_attachments_to_memory + exec.note::write_attachment_commitments_to_memory # => [num_attachments] locaddr.0 swap @@ -474,7 +474,7 @@ end pub proc get_attachment_ptr # get the attachment commitments for the note swap exec.get_attachment_commitments_ptr - # => [num_attachments, attachments_ptr, attachment_idx] + # => [num_attachments, attachment_commitments_ptr, attachment_idx] exec.note::get_attachment_ptr # => [num_words, attachment_ptr] diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index 8fb00cac3b..d9166f961c 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1516,18 +1516,18 @@ async fn test_get_attachment_commitments_ptr() -> anyhow::Result<()> { # get attachment commitments for note at index 0 push.0 exec.output_note::get_attachment_commitments_ptr - # => [num_attachments, attachments_ptr] + # => [num_attachments, attachment_commitments_ptr] # assert num_attachments == 2 eq.2 assert.err=\"expected 2 attachments\" - # => [attachments_ptr] + # => [attachment_commitments_ptr] # read commitment 0 from memory at ptr and assert padw dup.4 mem_loadw_le - # => [COMMITMENT_0, attachments_ptr] + # => [COMMITMENT_0, attachment_commitments_ptr] push.{EXPECTED_COMMITMENT_0} assert_eqw.err=\"attachment commitment 0 mismatch\" - # => [attachments_ptr] + # => [attachment_commitments_ptr] # advance pointer to next word (WORD_SIZE=4) and read commitment 1 padw movup.4 add.4 mem_loadw_le From d7051e1e883ff0d8ad091c5190c81fda95216980 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Thu, 7 May 2026 15:30:02 +0200 Subject: [PATCH 23/23] fix: remove unused, outdated line --- crates/miden-testing/src/kernel_tests/tx/test_output_note.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs index ce2828335b..219680b657 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_output_note.rs @@ -1481,7 +1481,6 @@ async fn test_get_attachment_commitments_ptr() -> anyhow::Result<()> { .build()?, ); - let _attachments_commitment = output_note.attachments().commitment(); let commitment_0 = attachment_0.to_commitment(); let commitment_1 = attachment_1.to_commitment();