diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c2f6d9d20..505965b8ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Add `From<&ExecutedTransaction> for TransactionHeader` implementation ([#2178](https://github.com/0xMiden/miden-base/pull/2178)). - Add `AccountId::parse()` helper function to parse both hex and bech32 formats ([#2223](https://github.com/0xMiden/miden-base/pull/2223)). - Add `read_foreign_account_inputs()`, `read_vault_asset_witnesses()`, and `read_storage_map_witness()` for `TransactionInputs` ([#2246](https://github.com/0xMiden/miden-base/pull/2246)). +- [BREAKING] Introduce `NoteAttachment` as part of `NoteMetadata` and remove `aux` and `execution_hint` ([#2249](https://github.com/0xMiden/miden-base/pull/2249)). ### Changes diff --git a/crates/miden-protocol/asm/kernels/transaction/api.masm b/crates/miden-protocol/asm/kernels/transaction/api.masm index d5f7891a4f..8d12410b65 100644 --- a/crates/miden-protocol/asm/kernels/transaction/api.masm +++ b/crates/miden-protocol/asm/kernels/transaction/api.masm @@ -972,14 +972,15 @@ end #! Returns the metadata of the specified input note. #! #! Inputs: [is_active_note, note_index, pad(14)] -#! Outputs: [METADATA, pad(12)] +#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] #! #! Where: #! - is_active_note is the boolean flag indicating whether we should return the metadata from #! the active note or from the note with the specified index. #! - 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 is the metadata of the specified input note. +#! - 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. @@ -998,13 +999,21 @@ pub proc input_note_get_metadata dup neq.0 assert.err=ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_WHILE_NO_NOTE_BEING_PROCESSED # => [input_note_ptr, pad(15)] + # make stack truncation at the end of the procedure easier + push.0 swap + # => [input_note_ptr, pad(16)] + # get the metadata - exec.memory::get_input_note_metadata - # => [METADATA, pad(15)] + 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_attachment + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(16)] # truncate the stack - swapw drop drop drop movdn.4 - # => [METADATA, pad(12)] + swapdw dropw dropw + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] end #! Returns the serial number of the specified input note. @@ -1244,11 +1253,12 @@ end #! Returns the metadata of the output note with the specified index. #! #! Inputs: [note_index, pad(15)] -#! Outputs: [METADATA, pad(12)] +#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] #! #! Where: #! - note_index is the index of the output note whose metadata should be returned. -#! - METADATA is the metadata of the output note. +#! - 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. @@ -1258,18 +1268,26 @@ pub proc output_note_get_metadata # 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 note data pointer by the provided index 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 - exec.memory::get_output_note_metadata - # => [METADATA, pad(15)] + dup exec.memory::get_output_note_metadata_header + # => [METADATA_HEADER, note_ptr, pad(16)] + + # get the attachment + movup.4 exec.memory::get_output_note_attachment + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(16)] # truncate the stack - swapw drop drop drop movdn.4 - # => [METADATA, pad(12)] + swapdw dropw dropw + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] end # TRANSACTION diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm b/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm index 5d0ce1d1b0..b1ccb974e3 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/memory.masm @@ -224,11 +224,12 @@ const INPUT_NOTE_SCRIPT_ROOT_OFFSET=8 const INPUT_NOTE_INPUTS_COMMITMENT_OFFSET=12 const INPUT_NOTE_ASSETS_COMMITMENT_OFFSET=16 const INPUT_NOTE_RECIPIENT_OFFSET=20 -const INPUT_NOTE_METADATA_OFFSET=24 -const INPUT_NOTE_ARGS_OFFSET=28 -const INPUT_NOTE_NUM_INPUTS_OFFSET=32 -const INPUT_NOTE_NUM_ASSETS_OFFSET=36 -const INPUT_NOTE_ASSETS_OFFSET=40 +const INPUT_NOTE_METADATA_HEADER_OFFSET=24 +const INPUT_NOTE_ATTACHMENT_OFFSET=28 +const INPUT_NOTE_ARGS_OFFSET=32 +const INPUT_NOTE_NUM_INPUTS_OFFSET=36 +const INPUT_NOTE_NUM_ASSETS_OFFSET=40 +const INPUT_NOTE_ASSETS_OFFSET=44 # OUTPUT NOTES # ------------------------------------------------------------------------------------------------- @@ -238,12 +239,13 @@ const OUTPUT_NOTE_SECTION_OFFSET=16777216 # The offsets at which data of an output note is stored relative to the start of its data segment. const OUTPUT_NOTE_ID_OFFSET=0 -const OUTPUT_NOTE_METADATA_OFFSET=4 -const OUTPUT_NOTE_RECIPIENT_OFFSET=8 -const OUTPUT_NOTE_ASSETS_COMMITMENT_OFFSET=12 -const OUTPUT_NOTE_NUM_ASSETS_OFFSET=16 -const OUTPUT_NOTE_DIRTY_FLAG_OFFSET=17 -const OUTPUT_NOTE_ASSETS_OFFSET=20 +const OUTPUT_NOTE_METADATA_HEADER_OFFSET=4 +const OUTPUT_NOTE_ATTACHMENT_OFFSET=8 +const OUTPUT_NOTE_RECIPIENT_OFFSET=12 +const OUTPUT_NOTE_ASSETS_COMMITMENT_OFFSET=16 +const OUTPUT_NOTE_NUM_ASSETS_OFFSET=20 +const OUTPUT_NOTE_DIRTY_FLAG_OFFSET=21 +const OUTPUT_NOTE_ASSETS_OFFSET=24 # LINK MAP MEMORY # ------------------------------------------------------------------------------------------------- @@ -1607,27 +1609,54 @@ end #! Returns the metadata of an input note located at the specified memory address. #! #! Inputs: [note_ptr] -#! Outputs: [METADATA] +#! Outputs: [NOTE_METADATA_HEADER] #! #! Where: #! - note_ptr is the memory address at which the input note data begins. -#! - METADATA is the metadata of the input note. -pub proc get_input_note_metadata +#! - NOTE_METADATA_HEADER is the metadata header of the input note. +pub proc get_input_note_metadata_header padw - movup.4 add.INPUT_NOTE_METADATA_OFFSET + movup.4 add.INPUT_NOTE_METADATA_HEADER_OFFSET mem_loadw_be end #! Sets the metadata for an input note located at the specified memory address. #! -#! Inputs: [note_ptr, NOTE_METADATA] -#! Outputs: [NOTE_METADATA] +#! Inputs: [note_ptr, NOTE_METADATA_HEADER] +#! Outputs: [NOTE_METADATA_HEADER] #! #! Where: #! - note_ptr is the memory address at which the input note data begins. -#! - NOTE_METADATA is the metadata of the input note. -pub proc set_input_note_metadata - add.INPUT_NOTE_METADATA_OFFSET +#! - NOTE_METADATA_HEADER is the metadata header of the input note. +pub proc set_input_note_metadata_header + add.INPUT_NOTE_METADATA_HEADER_OFFSET + mem_storew_be +end + +#! Returns the attachment of an input note located at the specified memory address. +#! +#! Inputs: [note_ptr] +#! Outputs: [NOTE_ATTACHMENT] +#! +#! Where: +#! - note_ptr is the memory address at which the input note data begins. +#! - NOTE_ATTACHMENT is the attachment of the input note. +pub proc get_input_note_attachment + padw + movup.4 add.INPUT_NOTE_ATTACHMENT_OFFSET + mem_loadw_be +end + +#! Sets the attachment for an input note located at the specified memory address. +#! +#! Inputs: [note_ptr, NOTE_ATTACHMENT] +#! Outputs: [NOTE_ATTACHMENT] +#! +#! Where: +#! - note_ptr is the memory address at which the input note data begins. +#! - NOTE_ATTACHMENT is the attachment of the input note. +pub proc set_input_note_attachment + add.INPUT_NOTE_ATTACHMENT_OFFSET mem_storew_be end @@ -1778,36 +1807,6 @@ pub proc get_input_note_serial_num mem_loadw_be end -#! Returns the sender for the input note located at the specified memory address. -#! -#! Inputs: [note_ptr] -#! Outputs: [sender_id_prefix, sender_id_suffix] -#! -#! Where: -#! - note_ptr is the memory address at which the input note data begins. -#! - sender is the sender for the input note. -pub proc get_input_note_sender - padw - movup.4 add.INPUT_NOTE_METADATA_OFFSET - mem_loadw_be - # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] - - drop drop - # => [merged_sender_id_type_hint_tag, sender_id_prefix] - - # extract suffix of sender from merged layout, which means clearing the least significant byte - u32split swap - # => [merged_lo, merged_hi, sender_id_prefix] - - # clear least significant byte - u32and.0xffffff00 swap - # => [sender_id_suffix_hi, sender_id_suffix_lo, sender_id_prefix] - - # reassemble the suffix by multiplying the high part with 2^32 and adding the lo part - mul.0x0100000000 add swap - # => [sender_id_prefix, sender_id_suffix] -end - # OUTPUT NOTES # ------------------------------------------------------------------------------------------------- @@ -1865,31 +1864,60 @@ end #! Returns the output note's metadata. #! #! Inputs: [note_ptr] -#! Outputs: [METADATA] +#! Outputs: [METADATA_HEADER] #! #! Where: -#! - METADATA is the note metadata. +#! - METADATA_HEADER is the note metadata header. #! - note_ptr is the memory address at which the output note data begins. -pub proc get_output_note_metadata +pub proc get_output_note_metadata_header padw # => [0, 0, 0, 0, note_ptr] - movup.4 add.OUTPUT_NOTE_METADATA_OFFSET + movup.4 add.OUTPUT_NOTE_METADATA_HEADER_OFFSET # => [(note_ptr + offset), 0, 0, 0, 0] mem_loadw_be - # => [METADATA] + # => [METADATA_HEADER] +end + +#! Sets the output note's metadata header. +#! +#! Inputs: [note_ptr, METADATA_HEADER] +#! Outputs: [METADATA_HEADER] +#! +#! Where: +#! - METADATA_HEADER is the note metadata header. +#! - note_ptr is the memory address at which the output note data begins. +pub proc set_output_note_metadata_header + add.OUTPUT_NOTE_METADATA_HEADER_OFFSET + mem_storew_be +end + +#! Returns the output note's attachment. +#! +#! Inputs: [note_ptr] +#! Outputs: [ATTACHMENT] +#! +#! Where: +#! - ATTACHMENT is the note attachment. +#! - note_ptr is the memory address at which the output note data begins. +pub proc get_output_note_attachment + padw + movup.4 add.OUTPUT_NOTE_ATTACHMENT_OFFSET + mem_loadw_be + # => [ATTACHMENT] end -#! Sets the output note's metadata. +#! Sets the output note's attachment. #! -#! Inputs: [note_ptr, METADATA] -#! Outputs: [METADATA] +#! Inputs: [note_ptr, ATTACHMENT] +#! Outputs: [] #! #! Where: -#! - METADATA is the note metadata. +#! - ATTACHMENT is the note attachment. #! - note_ptr is the memory address at which the output note data begins. -pub proc set_output_note_metadata - add.OUTPUT_NOTE_METADATA_OFFSET +pub proc set_output_note_attachment + add.OUTPUT_NOTE_ATTACHMENT_OFFSET mem_storew_be + dropw end #! Returns the number of assets in the output note. diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/note.masm b/crates/miden-protocol/asm/kernels/transaction/lib/note.masm index 5a3faf962d..baf51d9a5f 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/note.masm @@ -209,53 +209,63 @@ end pub proc compute_output_notes_commitment # get the number of output notes from memory exec.memory::get_num_output_notes - # => [num_notes, ...] + # => [num_notes] - # calculate the address at which we should stop looping - exec.memory::get_output_note_ptr - # => [end_ptr, ...] - - # compute pointer for first address - push.0 exec.memory::get_output_note_ptr - # => [first_note_ptr, end_ptr, ...] + # initialize the output note index at which to start the commitment computation + push.0 + # => [current_index = 0, num_notes] # prepare stack for hashing padw padw padw - # => [PERM, PERM, PERM, first_note_ptr, end_ptr, ...] + # => [PERM, PERM, PERM, current_index, num_notes] - # check if the number of output notes is greater then 0. Conditional for the while loop. - dup.13 dup.13 neq - # => [PERM, PERM, PERM, first_note_ptr, end_ptr, ...] + # starting looping if num_notes != 0 + dup.13 neq.0 + # => [should_loop, PERM, PERM, PERM, current_index, num_notes] # loop and hash output notes while.true + dup.12 exec.memory::get_output_note_ptr + # => [current_note_ptr, PERM, PERM, PERM, current_index, num_notes] + # compute and save output note ID to memory (this also computes the note's asset commitment) - dup.12 exec.compute_output_note_id - # => [NOTE_ID, PERM, PERM, PERM, note_ptr, end_ptr, ...] + dup exec.compute_output_note_id + # => [NOTE_ID, current_note_ptr, PERM, PERM, PERM, current_index, num_notes] + + dup.4 exec.memory::get_output_note_metadata_header + # => [NOTE_METADATA_HEADER, NOTE_ID, current_note_ptr, PERM, PERM, PERM, current_index, num_notes] + + movup.8 exec.memory::get_output_note_attachment + # => [NOTE_ATTACHMENT, NOTE_METADATA_HEADER, NOTE_ID, current_note_ptr, PERM, PERM, PERM, current_index, num_notes] + + # compute hash(NOTE_METADATA_HEADER || NOTE_ATTACHMENT) + exec.rpo256::merge + # => [NOTE_METADATA_COMMITMENT, NOTE_ID, current_note_ptr, PERM, PERM, PERM, current_index, num_notes] - # drop output note ID from stack (it will be read from memory by the next instruction) - dropw - # => [PERM, PERM, PERM, note_ptr, end_ptr, ...] + # replace rate words with note ID and metadata commitment + swapdw dropw dropw + # => [NOTE_METADATA_COMMITMENT, NOTE_ID, PERM, current_index, num_notes] - # permute over (note_id, note_metadata) - mem_stream hperm - # => [PERM, PERM, PERM, note_ptr + 8, end_ptr, ...] + # permute over (note_id, note_metadata_commitment) + exec.rpo256::permute + # => [PERM, PERM, PERM, current_index, num_notes] - # increment output note pointer - movup.12 add.OUTPUT_NOTE_HASHING_MEM_DIFF - # => [note_ptr + 2048, PERM, PERM, PERM, end_ptr, ...] + # increment current_index + movup.12 add.1 movdn.12 + # => [PERM, PERM, PERM, current_index + 1, num_notes] - # check if we should loop again - dup movdn.13 dup.14 neq - # => [should_loop, PERM, PERM, PERM, note_ptr + 512, end_ptr, ...] + # continue looping if current_index != num_notes + dup.13 dup.13 neq + # => [should_loop, PERM, PERM, PERM, current_index + 1, num_notes] end + # => [PERM, PERM, PERM, current_index + 1, num_notes] # extract digest exec.rpo256::squeeze_digest - # => [OUTPUT_NOTES_COMMITMENT, end_ptr, end_ptr, ...] + # => [OUTPUT_NOTES_COMMITMENT, current_index + 1, num_notes] # drop accessory variables from stack movup.4 drop movup.4 drop - # => [OUTPUT_NOTES_COMMITMENT, ...] + # => [OUTPUT_NOTES_COMMITMENT] end 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 7e4bb51916..f122f079b7 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm @@ -13,6 +13,16 @@ const PUBLIC_NOTE=1 # 0b01 const PRIVATE_NOTE=2 # 0b10 const ENCRYPTED_NOTE=3 # 0b11 +# Constants for note attachment content types +const ATTACHMENT_CONTENT_TYPE_NONE=0 +const ATTACHMENT_CONTENT_TYPE_WORD=1 +const ATTACHMENT_CONTENT_TYPE_ARRAY=2 + +# The default value of the felt at index 3 in the note metadata header when a new note is created. +# All zeros sets the attachment content type to None and the user-defined attachment type to +# "untyped". +const ATTACHMENT_DEFAULT_TYPE_INFO=0 + # ERRORS # ================================================================================================= @@ -46,6 +56,7 @@ const NOTE_AFTER_ADD_ASSET_EVENT=event("miden::note::after_add_asset") # OUTPUT NOTE PROCEDURES # ================================================================================================= +# TODO(note_attachment): Remove aux and execution hint parameters. #! Creates a new note and returns the index of the note. #! #! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT] @@ -53,10 +64,8 @@ const NOTE_AFTER_ADD_ASSET_EVENT=event("miden::note::after_add_asset") #! #! Where: #! - tag is the note tag which can be used by the recipient(s) to identify notes intended for them. -#! - aux is the arbitrary user-defined value. #! - note_type is the type of the note, which defines how the note is to be stored (e.g., on-chain #! or off-chain). -#! - execution_hint is the hint which specifies when a note is ready to be consumed. #! - RECIPIENT defines spend conditions for the note. #! - note_idx is the index of the created note. #! @@ -65,29 +74,47 @@ const NOTE_AFTER_ADD_ASSET_EVENT=event("miden::note::after_add_asset") #! - the note tag is not a u32. #! - the number of output notes exceeds the maximum limit of 1024. pub proc create + # temporary: drop aux + swap drop + # => [tag, note_type, execution_hint, RECIPIENT] + + # temporary: drop execution_hint + movup.2 drop + # => [tag, note_type, RECIPIENT] + emit.NOTE_BEFORE_CREATED_EVENT + # => [tag, note_type, RECIPIENT] - exec.build_metadata - # => [NOTE_METADATA, RECIPIENT] + exec.build_metadata_header + # => [NOTE_METADATA_HEADER, RECIPIENT] # get the index for the next note to be created and increment counter exec.increment_num_output_notes dup movdn.9 - # => [note_idx, NOTE_METADATA, RECIPIENT, note_idx] + # => [note_idx, NOTE_METADATA_HEADER, RECIPIENT, note_idx] # get a pointer to the memory address at which the note will be stored exec.memory::get_output_note_ptr - # => [note_ptr, NOTE_METADATA, RECIPIENT, note_idx] + # => [note_ptr, NOTE_METADATA_HEADER, RECIPIENT, note_idx] movdn.4 - # => [NOTE_METADATA, note_ptr, RECIPIENT, note_idx] + # => [NOTE_METADATA_HEADER, note_ptr, RECIPIENT, note_idx] # emit event to signal that a new note is created emit.NOTE_AFTER_CREATED_EVENT # set the metadata for the output note dup.4 - # => [note_ptr, NOTE_METADATA, note_ptr, RECIPIENT, note_idx] - exec.memory::set_output_note_metadata dropw + # => [note_ptr, NOTE_METADATA_HEADER, note_ptr, RECIPIENT, note_idx] + + exec.memory::set_output_note_metadata_header dropw + # => [note_ptr, RECIPIENT, note_idx] + + # set the attachment value of a new note to an empty word + # note that the attachment content type is set to None by build_metadata_header + padw dup.4 + # => [note_ptr, EMPTY_WORD, note_ptr, RECIPIENT, note_idx] + + exec.memory::set_output_note_attachment # => [note_ptr, RECIPIENT, note_idx] # set the RECIPIENT for the output note @@ -229,99 +256,54 @@ end # HELPER PROCEDURES # ================================================================================================= -#! Builds the stack into the NOTE_METADATA word, encoding the note type and execution hint into a -#! single element. +#! Builds the provided inputs into the NOTE_METADATA_HEADER word. +#! +#! - The sender ID is set to the native account's ID. +#! - The attachment type is set to 0 (meaning untyped by convention) and the attachment content +#! type is set to None. +#! #! Note that this procedure is only exported so it can be tested. It should not be called from #! non-test code. #! -#! Inputs: [tag, aux, note_type, execution_hint] -#! Outputs: [NOTE_METADATA] +#! Inputs: [tag, note_type] +#! Outputs: [NOTE_METADATA_HEADER] #! #! Where: #! - tag is the note tag which can be used by the recipient(s) to identify notes intended for them. -#! - aux is the arbitrary user-defined value. #! - note_type is the type of the note, which defines how the note is to be stored (e.g., on-chain #! or off-chain). -#! - execution_hint is the hint which specifies when a note is ready to be consumed. -#! - NOTE_METADATA is the metadata associated with a note. -pub proc build_metadata - # Validate the note type. +#! - NOTE_METADATA_HEADER is the metadata associated with a note. +pub proc build_metadata_header + # Validate that note type is private or public. # -------------------------------------------------------------------------------------------- - # NOTE: encrypted notes are currently unsupported - dup.2 eq.PRIVATE_NOTE dup.3 eq.PUBLIC_NOTE or assert.err=ERR_NOTE_INVALID_TYPE - # => [tag, aux, note_type, execution_hint] - - u32assert.err=ERR_NOTE_TAG_MUST_BE_U32 - # => [tag, aux, note_type, execution_hint] - - # Split execution hint into its tag and payload parts as they are encoded in separate elements - # of the metadata. - # -------------------------------------------------------------------------------------------- + dup.1 eq.PRIVATE_NOTE dup.2 eq.PUBLIC_NOTE or assert.err=ERR_NOTE_INVALID_TYPE + # => [tag, note_type] - # the execution_hint is laid out like this: [26 zero bits | payload (32 bits) | tag (6 bits)] - movup.3 - # => [execution_hint, tag, aux, note_type] - dup u32split drop - # => [execution_hint_lo, execution_hint, tag, aux, note_type] - - # mask out the lower 6 execution hint tag bits. - u32and.0x3f - # => [execution_hint_tag, execution_hint, tag, aux, note_type] - - # compute the payload by subtracting the tag value so the lower 6 bits are zero - # note that this results in the following layout: [26 zero bits | payload (32 bits) | 6 zero bits] - swap - # => [execution_hint, execution_hint_tag, tag, aux, note_type] - dup.1 - # => [execution_hint_tag, execution_hint, execution_hint_tag, tag, aux, note_type] - sub - # => [execution_hint_payload, execution_hint_tag, tag, aux, note_type] - - # Merge execution hint payload and note tag. + # Validate the note tag fits into a u32. # -------------------------------------------------------------------------------------------- - # we need to move the payload to the upper 32 bits of the felt - # we only need to shift by 26 bits because the payload is already shifted left by 6 bits - # we shift the payload by multiplying with 2^26 - # this results in the lower 32 bits being zero which is where the note tag will be added - mul.0x04000000 - # => [execution_hint_payload, execution_hint_tag, tag, aux, note_type] - - # add the tag to the payload to produce the merged value - movup.2 add - # => [note_tag_hint_payload, execution_hint_tag, aux, note_type] + u32assert.err=ERR_NOTE_TAG_MUST_BE_U32 + # => [tag, note_type] - # Merge sender_id_suffix, note_type and execution_hint_tag. + # Merge note type and sender ID suffix. # -------------------------------------------------------------------------------------------- - exec.account::get_id - # => [sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux, note_type] - - movup.5 - # => [note_type, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux] - # multiply by 2^6 to shift the two note_type bits left by 6 bits. - mul.0x40 - # => [shifted_note_type, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux] - - # merge execution_hint_tag into the note_type - # this produces an 8-bit value with the layout: [note_type (2 bits) | execution_hint_tag (6 bits)] - movup.4 add - # => [merged_note_type_execution_hint_tag, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, aux] + exec.account::get_id swap + # => [sender_id_suffix, sender_id_prefix, tag, note_type] - # merge sender_id_suffix into this value - movup.2 add - # => [sender_id_suffix_type_and_hint_tag, sender_id_prefix, note_tag_hint_payload, aux] + # the lower bits of an account ID suffix are guaranteed to be zero, so we can safely use that + # space to encode the note type + movup.3 add swap + # => [sender_id_prefix, sender_id_suffix_and_note_type, tag] - # Rearrange elements to produce the final note metadata layout. + # Build metadata header. # -------------------------------------------------------------------------------------------- - swap movdn.3 - # => [sender_id_suffix_type_and_hint_tag, note_tag_hint_payload, aux, sender_id_prefix] - swap - # => [note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, aux, sender_id_prefix] movup.2 - # => [NOTE_METADATA = [aux, note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, sender_id_prefix]] + push.ATTACHMENT_DEFAULT_TYPE_INFO + # => [attachment_type_info, tag, sender_id_prefix, sender_id_suffix_and_note_type] + # => [NOTE_METADATA_HEADER] end #! Increments the number of output notes by one. Returns the index of the next note to be created. diff --git a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm index 615240d824..2da165d876 100644 --- a/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm +++ b/crates/miden-protocol/asm/kernels/transaction/lib/prologue.masm @@ -519,7 +519,7 @@ end #! Advice stack: [] #! #! Where: -#! - NOTE_COMMITMENT is the input note's commitment computed as `hash(NOTE_ID || NOTE_METADATA)`. +#! - NOTE_COMMITMENT is the input note's commitment computed as `hash(NOTE_ID || NOTE_METADATA_COMMITMENT)`. #! - block_num is the leaf position in the MMR chain of the block which created the input note. #! - BLOCK_SUB_COMMITMENT is the sub_commitment of the block which created the input note. #! - NOTE_ROOT is the merkle root of the notes tree containing the input note. @@ -614,33 +614,39 @@ proc process_input_note_details # => [NULLIFIER] end -#! Copies the note's metadata and args from the advice stack to memory. +#! Copies the note's metadata header and attachment as well as note args from the advice stack to +#! memory. #! #! Notes: #! - The note's ARGS are not authenticated, these are optional arguments the user can provide when #! consuming the note. #! - The note's metadata is authenticated, so the data is returned in the stack. The value is used -#! to compute the NOTE_COMMITMENT as `hash(NOTE_ID || NOTE_METADATA)`, which is the leaf value of the -#! note's tree in the contained in the block header. The NOTE_COMMITMENT is either verified by this -#! kernel, or delayed to be verified by another kernel (e.g. block or batch kernels). +#! to compute the NOTE_COMMITMENT as `hash(NOTE_ID || NOTE_METADATA_COMMITMENT)`, where +#! `NOTE_METADATA_COMMITMENT` is `hash(NOTE_METADATA_HEADER || NOTE_METADATA_ATTACHMENT)`. The +#! NOTE_COMMITMENT is the leaf value of the block note tree contained in the block header and is +#! either verified by this kernel, or delayed to be verified by another kernel (e.g. batch or +#! block kernels). #! #! Inputs: #! Operand stack: [note_ptr] -#! Advice stack: [NOTE_ARGS, NOTE_METADATA] +#! Advice stack: [NOTE_ARGS, NOTE_METADATA_HEADER, NOTE_ATTACHMENT] #! Outputs: -#! Operand stack: [NOTE_METADATA] +#! Operand stack: [NOTE_ATTACHMENT, NOTE_METADATA_HEADER] #! Advice stack: [] #! #! Where: #! - note_ptr is the memory location for the input note. #! - NOTE_ARGS are the user arguments passed to the note. -#! - NOTE_METADATA is the note's metadata. +#! - NOTE_METADATA_HEADER is the note's metadata. proc process_note_args_and_metadata padw adv_loadw dup.4 exec.memory::set_input_note_args dropw # => [note_ptr] - padw adv_loadw movup.4 exec.memory::set_input_note_metadata - # => [NOTE_METADATA] + padw adv_loadw dup.4 exec.memory::set_input_note_metadata_header + # => [NOTE_METADATA_HEADER, note_ptr] + + padw adv_loadw movup.8 exec.memory::set_input_note_attachment + # => [NOTE_ATTACHMENT, NOTE_METADATA_HEADER] end #! Checks that the number of note inputs is within limit and stores it to memory. @@ -849,8 +855,9 @@ end #! SCRIPT_ROOT, #! INPUTS_COMMITMENT, #! ASSETS_COMMITMENT, -#! ARGS, -#! NOTE_METADATA, +#! NOTE_ARGS, +#! NOTE_METADATA_HEADER, +#! NOTE_ATTACHMENT, #! assets_count, #! ASSET_0, ..., ASSET_N, #! is_authenticated, @@ -872,8 +879,9 @@ end #! - SCRIPT_ROOT is the note's script root. #! - INPUTS_COMMITMENT is the sequential hash of the padded note's inputs. #! - ASSETS_COMMITMENT is the sequential hash of the padded note's assets. -#! - NOTE_METADATA is the note's metadata. -#! - ARGS is the user arguments passed to the note. +#! - NOTE_METADATA_HEADER is the note's metadata header. +#! - NOTE_ATTACHMENT is the note's attachment. +#! - NOTE_ARGS are the user arguments passed to the note. #! - assets_count is the note's assets count. #! - ASSET_0, ..., ASSET_N are the padded note's assets. #! - is_authenticated is the boolean indicating if the note contains an authentication proof. @@ -902,38 +910,42 @@ proc process_input_note # => [note_ptr, NULLIFIER, HASHER_CAPACITY] dup exec.process_note_args_and_metadata - # => [NOTE_METADATA, note_ptr, NULLIFIER, HASHER_CAPACITY] + # => [NOTE_ATTACHMENT, NOTE_METADATA_HEADER, note_ptr, NULLIFIER, HASHER_CAPACITY] + + # compute hash(NOTE_METADATA_HEADER || NOTE_ATTACHMENT) + exec.rpo256::merge + # => [NOTE_METADATA_COMMITMENT, note_ptr, NULLIFIER, HASHER_CAPACITY] movup.4 - # => [note_ptr, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] + # => [note_ptr, NOTE_METADATA_COMMITMENT, NULLIFIER, HASHER_CAPACITY] # note inputs len # --------------------------------------------------------------------------------------------- exec.process_note_inputs_length - # => [note_ptr, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] + # => [note_ptr, NOTE_METADATA_COMMITMENT, NULLIFIER, HASHER_CAPACITY] # note assets # --------------------------------------------------------------------------------------------- dup exec.process_note_assets dup exec.add_input_note_assets_to_vault - # => [note_ptr, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] + # => [note_ptr, NOTE_METADATA_COMMITMENT, NULLIFIER, HASHER_CAPACITY] # note id # --------------------------------------------------------------------------------------------- dup exec.compute_input_note_id - # => [NOTE_ID, note_ptr, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] + # => [NOTE_ID, note_ptr, NOTE_METADATA_COMMITMENT, NULLIFIER, HASHER_CAPACITY] # save note id to memory movup.4 exec.memory::set_input_note_id - # => [NOTE_ID, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] + # => [NOTE_ID, NOTE_METADATA_COMMITMENT, NULLIFIER, HASHER_CAPACITY] # note authentication # --------------------------------------------------------------------------------------------- - # NOTE_COMMITMENT: `hash(NOTE_ID || NOTE_METADATA)` + # NOTE_COMMITMENT: `hash(NOTE_ID || NOTE_METADATA_COMMITMENT)` swapw hmerge # => [NOTE_COMMITMENT, NULLIFIER, HASHER_CAPACITY] diff --git a/crates/miden-protocol/asm/protocol/active_note.masm b/crates/miden-protocol/asm/protocol/active_note.masm index 9cda06a621..77865a19bc 100644 --- a/crates/miden-protocol/asm/protocol/active_note.masm +++ b/crates/miden-protocol/asm/protocol/active_note.masm @@ -131,10 +131,11 @@ end #! Returns the metadata of the active note. #! #! Inputs: [] -#! Outputs: [METADATA] +#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER] #! #! Where: -#! - METADATA is the metadata of the active note. +#! - METADATA_HEADER is the metadata header of the specified input note. +#! - NOTE_ATTACHMENT is the attachment of the specified input note. #! #! Panics if: #! - no note is currently active. @@ -153,11 +154,11 @@ pub proc get_metadata # => [offset, is_active_note = 1, pad(14)] syscall.exec_kernel_proc - # => [METADATA, pad(12)] + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] # clean the stack - swapdw dropw dropw swapw dropw - # => [METADATA] + swapdw dropw dropw + # => [NOTE_ATTACHMENT, METADATA_HEADER] end #! Returns the sender of the active note. @@ -173,26 +174,12 @@ end #! #! Invocation: exec pub proc get_sender - # pad the stack - padw padw padw push.0.0 - # => [pad(14)] - - # push the flag indicating that we want to request metadata from the active note - push.1 - # => [is_active_note = 1, pad(14)] - - exec.kernel_proc_offsets::input_note_get_metadata_offset - # => [offset, is_active_note = 1, pad(14)] + # get metadata and drop attachment + exec.get_metadata dropw + # => [METADATA_HEADER] - syscall.exec_kernel_proc - # => [METADATA, pad(12)] - - # extract the sender ID from the metadata word + # extract the sender ID from the metadata header exec.note::extract_sender_from_metadata - # => [sender_id_prefix, sender_id_suffix, pad(12)] - - # clean the stack - swapw dropw swapw dropw movdn.5 movdn.5 dropw # => [sender_id_prefix, sender_id_suffix] end diff --git a/crates/miden-protocol/asm/protocol/input_note.masm b/crates/miden-protocol/asm/protocol/input_note.masm index 9a88dc7636..a08d5a5dd2 100644 --- a/crates/miden-protocol/asm/protocol/input_note.masm +++ b/crates/miden-protocol/asm/protocol/input_note.masm @@ -122,11 +122,12 @@ end #! Returns the metadata of the input note with the specified index. #! #! Inputs: [note_index] -#! Outputs: [METADATA] +#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER] #! #! Where: #! - note_index is the index of the input note whose metadata should be returned. -#! - METADATA is the metadata of the input note. +#! - 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. @@ -150,11 +151,11 @@ pub proc get_metadata # => [offset, is_active_note = 0, note_index, pad(13)] syscall.exec_kernel_proc - # => [METADATA, pad(12)] + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] # clean the stack - swapdw dropw dropw swapw dropw - # => [METADATA] + swapdw dropw dropw + # => [NOTE_ATTACHMENT, METADATA_HEADER] end #! Returns the sender of the input note with the specified index. @@ -171,31 +172,12 @@ end #! #! Invocation: exec pub proc get_sender - # start padding the stack - push.0 swap - # => [note_index, 0] - - # push the flag indicating that we want to request metadata from the note with the specified - # index - push.0 - # => [is_active_note = 0, note_index, 0] - - exec.kernel_proc_offsets::input_note_get_metadata_offset - # => [offset, is_active_note = 0, note_index, 0] - - # pad the stack - padw swapw padw padw swapdw - # => [offset, is_active_note = 0, note_index, pad(13)] - - syscall.exec_kernel_proc - # => [METADATA, pad(12)] + # get metadata and drop attachment + exec.get_metadata dropw + # => [METADATA_HEADER] - # extract the sender ID from the metadata word + # extract the sender ID from the metadata header exec.note::extract_sender_from_metadata - # => [sender_id_prefix, sender_id_suffix, pad(12)] - - # clean the stack - swapw dropw swapw dropw movdn.5 movdn.5 dropw # => [sender_id_prefix, sender_id_suffix] end diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 85730c9528..13cb6f6ef3 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -191,21 +191,21 @@ pub proc build_recipient_hash # [RECIPIENT] end -#! Extracts the sender ID from the provided metadata word. +#! Extracts the sender ID from the provided metadata header. #! -#! Inputs: [METADATA] +#! Inputs: [METADATA_HEADER] #! Outputs: [sender_id_prefix, sender_id_suffix] #! #! Where: -#! - METADATA is the metadata of some note. +#! - METADATA_HEADER is the metadata of a note. #! - sender_{prefix,suffix} are the prefix and suffix felts of the sender ID of the note which #! metadata was provided. pub proc extract_sender_from_metadata - # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] + # => [attachment_type_info, tag, sender_id_prefix, sender_id_suffix_and_note_type] - # drop aux felt and the felt containing tag, execution hint and payload - drop drop - # => [merged_sender_id_type_hint_tag, sender_id_prefix] + # drop attachment type info and tag + drop drop swap + # => [sender_id_suffix_and_note_type, sender_id_prefix] # extract suffix of sender from merged layout, which means clearing the least significant byte exec.account_id::shape_suffix diff --git a/crates/miden-protocol/asm/protocol/output_note.masm b/crates/miden-protocol/asm/protocol/output_note.masm index 6b1093d0cb..36ec079f43 100644 --- a/crates/miden-protocol/asm/protocol/output_note.masm +++ b/crates/miden-protocol/asm/protocol/output_note.masm @@ -175,11 +175,12 @@ end #! Returns the metadata of the output note with the specified index. #! #! Inputs: [note_index] -#! Outputs: [METADATA] +#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER] #! #! Where: #! - note_index is the index of the output note whose metadata should be returned. -#! - METADATA is the metadata of the output note. +#! - 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. @@ -198,9 +199,9 @@ pub proc get_metadata # => [offset, note_index, pad(14)] syscall.exec_kernel_proc - # => [METADATA, pad(12)] + # => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)] # clean the stack - swapdw dropw dropw swapw dropw - # => [METADATA] + swapdw dropw dropw + # => [NOTE_ATTACHMENT, METADATA_HEADER] end diff --git a/crates/miden-protocol/src/batch/note_tree.rs b/crates/miden-protocol/src/batch/note_tree.rs index de473ee0f6..7897856389 100644 --- a/crates/miden-protocol/src/batch/note_tree.rs +++ b/crates/miden-protocol/src/batch/note_tree.rs @@ -8,7 +8,7 @@ use crate::{BATCH_NOTE_TREE_DEPTH, EMPTY_WORD, Word}; /// Wrapper over [SimpleSmt] for batch note tree. /// -/// Value of each leaf is computed as: `hash(note_id || note_metadata)`. +/// Value of each leaf is computed as: `hash(note_id || note_metadata_commitment)`. #[derive(Debug, Clone, PartialEq, Eq)] pub struct BatchNoteTree(SimpleSmt); diff --git a/crates/miden-protocol/src/block/block_body.rs b/crates/miden-protocol/src/block/block_body.rs index 8392a5ba7f..53b86741cb 100644 --- a/crates/miden-protocol/src/block/block_body.rs +++ b/crates/miden-protocol/src/block/block_body.rs @@ -114,7 +114,7 @@ impl BlockBody { pub fn compute_block_note_tree(&self) -> BlockNoteTree { let entries = self .output_notes() - .map(|(note_index, note)| (note_index, note.id(), *note.metadata())); + .map(|(note_index, note)| (note_index, note.id(), note.metadata())); // SAFETY: We only construct block bodies that: // - do not contain duplicates diff --git a/crates/miden-protocol/src/block/note_tree.rs b/crates/miden-protocol/src/block/note_tree.rs index 0e919073f1..497aab12ba 100644 --- a/crates/miden-protocol/src/block/note_tree.rs +++ b/crates/miden-protocol/src/block/note_tree.rs @@ -29,18 +29,18 @@ impl BlockNoteTree { /// /// Entry format: (note_index, note_id, note_metadata). /// - /// Value of each leaf is computed as: `hash(note_id || note_metadata)`. + /// Value of each leaf is computed as: `hash(note_id || note_metadata_commitment)`. /// All leaves omitted from the entries list are set to [crate::EMPTY_WORD]. /// /// # Errors /// Returns an error if: /// - The number of entries exceeds the maximum notes tree capacity, that is 2^16. /// - The provided entries contain multiple values for the same key. - pub fn with_entries( - entries: impl IntoIterator, + pub fn with_entries<'metadata>( + entries: impl IntoIterator, ) -> Result { let leaves = entries.into_iter().map(|(index, note_id, metadata)| { - (index.leaf_index_value() as u64, compute_note_commitment(note_id, &metadata)) + (index.leaf_index_value() as u64, compute_note_commitment(note_id, metadata)) }); SimpleSmt::with_leaves(leaves).map(Self) diff --git a/crates/miden-protocol/src/block/proposed_block.rs b/crates/miden-protocol/src/block/proposed_block.rs index 800c4239a3..129f94912b 100644 --- a/crates/miden-protocol/src/block/proposed_block.rs +++ b/crates/miden-protocol/src/block/proposed_block.rs @@ -423,7 +423,7 @@ impl ProposedBlock { "max batches in block and max notes in batches should be enforced", ), note.id(), - *note.metadata(), + note.metadata(), ) }) }); diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index e9939ba1aa..828f4c2430 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -34,7 +34,14 @@ use crate::address::AddressType; use crate::asset::AssetVaultKey; use crate::batch::BatchId; use crate::block::BlockNumber; -use crate::note::{NoteAssets, NoteExecutionHint, NoteTag, NoteType, Nullifier}; +use crate::note::{ + NoteAssets, + NoteAttachmentArray, + NoteExecutionHint, + NoteTag, + NoteType, + Nullifier, +}; use crate::transaction::{TransactionEventId, TransactionId}; use crate::{ ACCOUNT_UPDATE_MAX_SIZE, @@ -586,6 +593,13 @@ pub enum NoteError { TooManyInputs(usize), #[error("note tag requires a public note but the note is of type {0}")] PublicNoteRequired(NoteType), + #[error( + "note attachment cannot commit to more than {} elements", + NoteAttachmentArray::MAX_NUM_ELEMENTS + )] + NoteAttachmentArraySizeExceeded(usize), + #[error("unknown note attachment content type {0}")] + UnknownNoteAttachmentContentType(u8), #[error("{error_msg}")] Other { error_msg: Box, diff --git a/crates/miden-protocol/src/note/attachment.rs b/crates/miden-protocol/src/note/attachment.rs new file mode 100644 index 0000000000..9f67ce92b5 --- /dev/null +++ b/crates/miden-protocol/src/note/attachment.rs @@ -0,0 +1,491 @@ +use alloc::string::ToString; +use alloc::vec::Vec; + +use crate::crypto::SequentialCommit; +use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; +use crate::{Felt, Hasher, NoteError, Word}; + +// NOTE ATTACHMENT +// ================================================================================================ + +/// The optional attachment for a [`Note`](super::Note). +/// +/// An attachment is a _public_ extension to a note's [`NoteMetadata`](super::NoteMetadata). +/// +/// Example use cases: +/// - Communicate the [`NoteDetails`](super::NoteDetails) of a private note in encrypted form. +/// - In the context of network transactions, encode the ID of the network account that should +/// consume the note. +/// - Communicate details to the receiver of a _private_ note to allow deriving the +/// [`NoteDetails`](super::NoteDetails) of that note. For instance, the payback note of a partial +/// swap note can be private, but the receiver needs to know additional details to fully derive +/// the content of the payback note. They can neither fetch those details from the network, since +/// the note is private, nor is a side-channel available. The note attachment can encode those +/// details. +/// +/// These use cases require different amounts of data, e.g. an account ID takes up just two felts +/// while the details of an encrypted note require many felts. To accommodate these cases, both a +/// computationally efficient [`NoteAttachmentContent::Word`] as well as a more flexible +/// [`NoteAttachmentContent::Array`] variant are available. See the type's docs for more +/// details. +/// +/// Next to the content, a note attachment can optionally specify a [`NoteAttachmentType`]. This +/// allows a note attachment to describe itself. For example, a network account target attachment +/// can be identified by a standardized type. For cases when the attachment type is known from +/// content or typing is otherwise undesirable, [`NoteAttachmentType::untyped`] can be used. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct NoteAttachment { + attachment_type: NoteAttachmentType, + content: NoteAttachmentContent, +} + +impl NoteAttachment { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`NoteAttachment`] from a user-defined type and the provided content. + pub fn new(attachment_type: NoteAttachmentType, content: NoteAttachmentContent) -> Self { + Self { attachment_type, content } + } + + /// Creates a new note attachment with content [`NoteAttachmentContent::Word`] from the provided + /// word. + pub fn new_word(attachment_type: NoteAttachmentType, word: Word) -> Self { + Self::new(attachment_type, NoteAttachmentContent::new_word(word)) + } + + /// Creates a new note attachment with content [`NoteAttachmentContent::Array`] from the + /// provided set of elements. + /// + /// # Errors + /// + /// Returns an error if: + /// - The maximum number of elements exceeds [`NoteAttachmentArray::MAX_NUM_ELEMENTS`]. + pub fn new_array( + attachment_type: NoteAttachmentType, + elements: Vec, + ) -> Result { + NoteAttachmentContent::new_array(elements) + .map(|content| Self::new(attachment_type, content)) + } + + /// Creates a new [`NoteAttachment`] from the provided content and using + /// [`NoteAttachmentType::untyped`]. + pub fn new_untyped(content: NoteAttachmentContent) -> Self { + Self { + attachment_type: NoteAttachmentType::untyped(), + content, + } + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the attachment type. + pub fn attachment_type(&self) -> NoteAttachmentType { + self.attachment_type + } + + /// Returns a reference to the attachment content. + pub fn content(&self) -> &NoteAttachmentContent { + &self.content + } +} + +impl Serializable for NoteAttachment { + fn write_into(&self, target: &mut W) { + self.attachment_type().write_into(target); + self.content().write_into(target); + } +} + +impl Deserializable for NoteAttachment { + fn read_from(source: &mut R) -> Result { + let attachment_type = NoteAttachmentType::read_from(source)?; + let content = NoteAttachmentContent::read_from(source)?; + + Ok(Self::new(attachment_type, content)) + } +} + +/// The content of a [`NoteAttachment`]. +/// +/// If a note attachment is not required, [`NoteAttachmentContent::None`] should be used. +/// +/// When a single [`Word`] has sufficient space, [`NoteAttachmentContent::Word`] should be used, as +/// it does not require any hashing. The word itself is encoded into the +/// [`NoteMetadata`](super::NoteMetadata). +/// +/// If the space of a [`Word`] is insufficient, the more flexible +/// [`NoteAttachmentContent::Array`] variant can be used. It contains a set of field elements +/// where only their sequential hash is encoded into the [`NoteMetadata`](super::NoteMetadata). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum NoteAttachmentContent { + /// Signals the absence of a note attachment. + #[default] + None, + + /// A note attachment consisting of a single [`Word`]. + Word(Word), + + /// A note attachment consisting of the commitment to a set of felts. + Array(NoteAttachmentArray), +} + +impl NoteAttachmentContent { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`NoteAttachmentContent::Word`] containing an empty word. + pub fn empty_word() -> Self { + Self::Word(Word::empty()) + } + + /// Creates a new [`NoteAttachmentContent::Word`] from the provided word. + pub fn new_word(word: Word) -> Self { + Self::Word(word) + } + + /// Creates a new [`NoteAttachmentContent::Array`] from the provided elements. + /// + /// # Errors + /// + /// Returns an error if: + /// - The maximum number of elements exceeds [`NoteAttachmentArray::MAX_NUM_ELEMENTS`]. + pub fn new_array(elements: Vec) -> Result { + NoteAttachmentArray::new(elements).map(Self::from) + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the [`NoteAttachmentContentType`]. + pub fn content_type(&self) -> NoteAttachmentContentType { + match self { + NoteAttachmentContent::None => NoteAttachmentContentType::None, + NoteAttachmentContent::Word(_) => NoteAttachmentContentType::Word, + NoteAttachmentContent::Array(_) => NoteAttachmentContentType::Array, + } + } + + /// Returns the [`NoteAttachmentContent`] encoded to a [`Word`]. + /// + /// See the type-level documentation for more details. + pub fn to_word(&self) -> Word { + match self { + NoteAttachmentContent::None => Word::empty(), + NoteAttachmentContent::Word(word) => *word, + NoteAttachmentContent::Array(attachment_commitment) => { + attachment_commitment.commitment() + }, + } + } +} + +impl Serializable for NoteAttachmentContent { + fn write_into(&self, target: &mut W) { + self.content_type().write_into(target); + + match self { + NoteAttachmentContent::None => (), + NoteAttachmentContent::Word(word) => { + word.write_into(target); + }, + NoteAttachmentContent::Array(attachment_commitment) => { + attachment_commitment.num_elements().write_into(target); + target.write_many(&attachment_commitment.elements); + }, + } + } +} + +impl Deserializable for NoteAttachmentContent { + fn read_from(source: &mut R) -> Result { + let content_type = NoteAttachmentContentType::read_from(source)?; + + match content_type { + NoteAttachmentContentType::None => Ok(NoteAttachmentContent::None), + NoteAttachmentContentType::Word => { + let word = Word::read_from(source)?; + Ok(NoteAttachmentContent::Word(word)) + }, + NoteAttachmentContentType::Array => { + let num_elements = u16::read_from(source)?; + let elements = source.read_many(num_elements as usize)?; + Self::new_array(elements) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) + }, + } + } +} + +// NOTE ATTACHMENT COMMITMENT +// ================================================================================================ + +/// The type contained in [`NoteAttachmentContent::Array`] that commits to a set of field +/// elements. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NoteAttachmentArray { + elements: Vec, + commitment: Word, +} + +impl NoteAttachmentArray { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The maximum size of a note attachment that commits to a set of elements. + /// + /// Each element holds roughly 8 bytes of data and so this allows for a maximum of + /// 2048 * 8 = 2^14 = 16384 bytes. + pub const MAX_NUM_ELEMENTS: u16 = 2048; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`NoteAttachmentArray`] from the provided elements. + /// + /// # Errors + /// + /// Returns an error if: + /// - The maximum number of elements exceeds [`NoteAttachmentArray::MAX_NUM_ELEMENTS`]. + pub fn new(elements: Vec) -> Result { + if elements.len() > Self::MAX_NUM_ELEMENTS as usize { + return Err(NoteError::NoteAttachmentArraySizeExceeded(elements.len())); + } + + let commitment = Hasher::hash_elements(&elements); + Ok(Self { elements, commitment }) + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a reference to the elements this note attachment commits to. + pub fn as_slice(&self) -> &[Felt] { + &self.elements + } + + /// Returns the number of elements this note attachment commits to. + pub fn num_elements(&self) -> u16 { + u16::try_from(self.elements.len()).expect("type should enforce that size fits in u16") + } + + /// Returns the commitment over the contained field elements. + pub fn commitment(&self) -> Word { + self.commitment + } +} + +impl SequentialCommit for NoteAttachmentArray { + type Commitment = Word; + + fn to_elements(&self) -> Vec { + self.elements.clone() + } + + fn to_commitment(&self) -> Self::Commitment { + self.commitment + } +} + +impl From for NoteAttachmentContent { + fn from(attachment_commitment: NoteAttachmentArray) -> Self { + NoteAttachmentContent::Array(attachment_commitment) + } +} + +// NOTE ATTACHMENT TYPE +// ================================================================================================ + +/// The user-defined type of a [`NoteAttachment`]. +/// +/// A note attachment type is an arbitrary 32-bit unsigned integer. +/// +/// Value `0` is reserved to signal that an attachment is untyped. That is, no attempt should be +/// made to guess the type of the attachment. Whenever the type of attachment is not standardized or +/// interoperability is unimportant, this untyped value can be used. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct NoteAttachmentType(u32); + +impl NoteAttachmentType { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The reserved value to signal an untyped note attachment. + const UNTYPED: u32 = 0; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates a new [`NoteAttachmentType`] from a `u32`. + pub const fn new(attachment_type: u32) -> Self { + Self(attachment_type) + } + + /// Returns the [`NoteAttachmentType`] that signals an untyped note attachment. + pub const fn untyped() -> Self { + Self(Self::UNTYPED) + } + + /// Returns `true` if the attachment is untyped, `false` otherwise. + pub const fn is_untyped(&self) -> bool { + self.0 == Self::UNTYPED + } + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the note attachment type as a u32. + pub const fn as_u32(&self) -> u32 { + self.0 + } +} + +impl Default for NoteAttachmentType { + /// Returns [`NoteAttachmentType::untyped`]. + fn default() -> Self { + Self::untyped() + } +} + +impl core::fmt::Display for NoteAttachmentType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + +impl Serializable for NoteAttachmentType { + fn write_into(&self, target: &mut W) { + self.as_u32().write_into(target); + } +} + +impl Deserializable for NoteAttachmentType { + fn read_from(source: &mut R) -> Result { + let attachment_type = u32::read_from(source)?; + Ok(Self::new(attachment_type)) + } +} + +// NOTE ATTACHMENT CONTENT TYPE +// ================================================================================================ + +/// The type of [`NoteAttachmentContent`]. +/// +/// See its docs for more details on each type. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(u8)] +pub enum NoteAttachmentContentType { + /// Signals the absence of a note attachment. + #[default] + None = Self::NONE, + + /// A note attachment consisting of a single [`Word`]. + Word = Self::WORD, + + /// A note attachment consisting of the commitment to a set of felts. + Array = Self::ARRAY, +} + +impl NoteAttachmentContentType { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + const NONE: u8 = 0; + const WORD: u8 = 1; + const ARRAY: u8 = 2; + + // ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the content type as a u8. + pub const fn as_u8(&self) -> u8 { + *self as u8 + } + + /// Returns `true` if the content type is `None`, `false` otherwise. + pub const fn is_none(&self) -> bool { + matches!(self, Self::None) + } + + /// Returns `true` if the content type is `Word`, `false` otherwise. + pub const fn is_word(&self) -> bool { + matches!(self, Self::Word) + } + + /// Returns `true` if the content type is `Array`, `false` otherwise. + pub const fn is_array(&self) -> bool { + matches!(self, Self::Array) + } +} + +impl TryFrom for NoteAttachmentContentType { + type Error = NoteError; + + fn try_from(value: u8) -> Result { + match value { + Self::NONE => Ok(Self::None), + Self::WORD => Ok(Self::Word), + Self::ARRAY => Ok(Self::Array), + _ => Err(NoteError::UnknownNoteAttachmentContentType(value)), + } + } +} + +impl Serializable for NoteAttachmentContentType { + fn write_into(&self, target: &mut W) { + self.as_u8().write_into(target); + } +} + +impl Deserializable for NoteAttachmentContentType { + fn read_from(source: &mut R) -> Result { + let content_type = u8::read_from(source)?; + Self::try_from(content_type) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + + use super::*; + + #[rstest::rstest] + #[case::attachment_none(NoteAttachment::default())] + #[case::attachment_word(NoteAttachment::new_word(NoteAttachmentType::new(1), Word::from([3, 4, 5, 6u32])))] + #[case::attachment_array(NoteAttachment::new_array( + NoteAttachmentType::new(u32::MAX), + vec![Felt::new(5), Felt::new(6), Felt::new(7)], + )?)] + #[test] + fn note_attachment_serde(#[case] attachment: NoteAttachment) -> anyhow::Result<()> { + assert_eq!(attachment, NoteAttachment::read_from_bytes(&attachment.to_bytes())?); + Ok(()) + } + + #[test] + fn note_attachment_commitment_fails_on_too_many_elements() -> anyhow::Result<()> { + let too_many_elements = (NoteAttachmentArray::MAX_NUM_ELEMENTS as usize) + 1; + let elements = vec![Felt::from(1u32); too_many_elements]; + let err = NoteAttachmentArray::new(elements).unwrap_err(); + + assert_matches!(err, NoteError::NoteAttachmentArraySizeExceeded(len) => { + len == too_many_elements + }); + + Ok(()) + } + + #[test] + fn note_attachment_content_type_fails_on_unknown_variant() -> anyhow::Result<()> { + let err = NoteAttachmentContentType::try_from(3u8).unwrap_err(); + assert_matches!(err, NoteError::UnknownNoteAttachmentContentType(3u8)); + Ok(()) + } +} diff --git a/crates/miden-protocol/src/note/file.rs b/crates/miden-protocol/src/note/file.rs index c00317cc49..62cd7ba863 100644 --- a/crates/miden-protocol/src/note/file.rs +++ b/crates/miden-protocol/src/note/file.rs @@ -137,7 +137,6 @@ impl Deserializable for NoteFile { mod tests { use alloc::vec::Vec; - use miden_core::Felt; use miden_core::utils::{Deserializable, Serializable}; use crate::Word; @@ -172,13 +171,7 @@ mod tests { let recipient = NoteRecipient::new(serial_num, script, note_inputs); let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap()); - let metadata = NoteMetadata::new( - faucet, - NoteType::Public, - NoteTag::from(123), - crate::note::NoteExecutionHint::None, - Felt::new(0), - ); + let metadata = NoteMetadata::new(faucet, NoteType::Public, NoteTag::from(123)); Note::new(NoteAssets::new(vec![asset]).unwrap(), metadata, recipient) } diff --git a/crates/miden-protocol/src/note/header.rs b/crates/miden-protocol/src/note/header.rs index 93ec96df71..04aac21de4 100644 --- a/crates/miden-protocol/src/note/header.rs +++ b/crates/miden-protocol/src/note/header.rs @@ -1,11 +1,8 @@ -use alloc::vec::Vec; - use super::{ ByteReader, ByteWriter, Deserializable, DeserializationError, - Felt, NoteId, NoteMetadata, Serializable, @@ -19,7 +16,7 @@ use crate::Hasher; /// Holds the strictly required, public information of a note. /// /// See [NoteId] and [NoteMetadata] for additional details. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct NoteHeader { note_id: NoteId, note_metadata: NoteMetadata, @@ -43,9 +40,14 @@ impl NoteHeader { &self.note_metadata } + /// Consumes self and returns the note header's metadata. + pub fn into_metadata(self) -> NoteMetadata { + self.note_metadata + } + /// Returns a commitment to the note and its metadata. /// - /// > hash(NOTE_ID || NOTE_METADATA) + /// > hash(NOTE_ID || NOTE_METADATA_COMMITMENT) /// /// This value is used primarily for authenticating notes consumed when they are consumed /// in a transaction. @@ -59,64 +61,12 @@ impl NoteHeader { /// Returns a commitment to the note and its metadata. /// -/// > hash(NOTE_ID || NOTE_METADATA) +/// > hash(NOTE_ID || NOTE_METADATA_COMMITMENT) /// /// This value is used primarily for authenticating notes consumed when they are consumed /// in a transaction. pub fn compute_note_commitment(id: NoteId, metadata: &NoteMetadata) -> Word { - Hasher::merge(&[id.as_word(), Word::from(metadata)]) -} - -// CONVERSIONS FROM NOTE HEADER -// ================================================================================================ - -impl From for [Felt; 8] { - fn from(note_header: NoteHeader) -> Self { - (¬e_header).into() - } -} - -impl From for [Word; 2] { - fn from(note_header: NoteHeader) -> Self { - (¬e_header).into() - } -} - -impl From for [u8; 64] { - fn from(note_header: NoteHeader) -> Self { - (¬e_header).into() - } -} - -impl From<&NoteHeader> for [Felt; 8] { - fn from(note_header: &NoteHeader) -> Self { - let mut elements: [Felt; 8] = Default::default(); - elements[..4].copy_from_slice(note_header.note_id.as_elements()); - elements[4..].copy_from_slice(Word::from(note_header.metadata()).as_elements()); - elements - } -} - -impl From<&NoteHeader> for [Word; 2] { - fn from(note_header: &NoteHeader) -> Self { - let mut elements: [Word; 2] = Default::default(); - elements[0].copy_from_slice(note_header.note_id.as_elements()); - elements[1].copy_from_slice(Word::from(note_header.metadata()).as_elements()); - elements - } -} - -impl From<&NoteHeader> for [u8; 64] { - fn from(note_header: &NoteHeader) -> Self { - let mut elements: [u8; 64] = [0; 64]; - let note_metadata_bytes = Word::from(note_header.metadata()) - .iter() - .flat_map(|x| x.as_int().to_le_bytes()) - .collect::>(); - elements[..32].copy_from_slice(¬e_header.note_id.as_bytes()); - elements[32..].copy_from_slice(¬e_metadata_bytes); - elements - } + Hasher::merge(&[id.as_word(), metadata.to_commitment()]) } // SERIALIZATION diff --git a/crates/miden-protocol/src/note/metadata.rs b/crates/miden-protocol/src/note/metadata.rs index 3705201a3f..e641e7b150 100644 --- a/crates/miden-protocol/src/note/metadata.rs +++ b/crates/miden-protocol/src/note/metadata.rs @@ -1,6 +1,3 @@ -use alloc::string::ToString; - -use super::execution_hint::NoteExecutionHint; use super::{ AccountId, ByteReader, @@ -8,40 +5,62 @@ use super::{ Deserializable, DeserializationError, Felt, - NoteError, NoteTag, NoteType, Serializable, Word, }; +use crate::note::{ + NoteAttachment, + NoteAttachmentContentType, + NoteAttachmentType, + NoteExecutionHint, +}; +use crate::{Hasher, NoteError}; // NOTE METADATA // ================================================================================================ -/// Metadata associated with a note. +/// The metadata associated with a note. +/// +/// Note metadata consists of two parts: +/// - The header of the metadata, which consists of: +/// - the sender of the note +/// - the [`NoteType`] +/// - the [`NoteTag`] +/// - type information about the [`NoteAttachment`]. +/// - The optional [`NoteAttachment`]. /// /// # Word layout & validity /// -/// [`NoteMetadata`] can be encoded into a [`Word`] with the following layout: +/// [`NoteMetadata`] can be encoded into two words, a header and an attachment word. +/// +/// The header word has the following layout: /// /// ```text +/// 0th felt: [sender_id_suffix (56 bits) | 6 zero bits | note_type (2 bit)] /// 1st felt: [sender_id_prefix (64 bits)] -/// 2nd felt: [sender_id_suffix (56 bits) | note_type (2 bits) | note_execution_hint_tag (6 bits)] -/// 3rd felt: [note_execution_hint_payload (32 bits) | note_tag (32 bits)] -/// 4th felt: [aux (64 bits)] +/// 2nd felt: [32 zero bits | note_tag (32 bits)] +/// 3rd felt: [30 zero bits | attachment_content_type (2 bits) | attachment_type (32 bits)] /// ``` /// -/// The rationale for the above layout is to ensure the validity of each felt: -/// - 1st felt: Is equivalent to the prefix of the account ID so it inherits its validity. -/// - 2nd felt: The lower 8 bits of the account ID suffix are `0` by construction, so that they can -/// be overwritten with other data. The suffix is designed such that it retains its felt validity -/// even if all of its lower 8 bits are be set to `1`. This is because the most significant bit is -/// always zero. -/// - 3rd felt: The note execution hint payload must contain at least one `0` bit in its encoding, -/// so the upper 32 bits of the felt will contain at least one `0` bit making the entire felt -/// valid. -/// - 4th felt: The `aux` value must be a felt itself. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +/// The felt validity of each part of the layout is guaranteed: +/// - 1st felt: The lower 8 bits of the account ID suffix are `0` by construction, so that they can +/// be overwritten with other data. The suffix' most significant bit must be zero such that the +/// entire felt retains its validity even if all of its lower 8 bits are be set to `1`. So the +/// note type can be comfortably encoded. +/// - 2nd felt: Is equivalent to the prefix of the account ID so it inherits its validity. +/// - 3rd felt: The upper 32 bits are always zero. +/// - 4th felt: The upper 30 bits are always zero. +/// +/// The value of the attachment word depends on the +/// [`NoteAttachmentContentType`](crate::note::NoteAttachmentContentType): +/// - [`NoteAttachmentContentType::None`](crate::note::NoteAttachmentContentType::None): Empty word. +/// - [`NoteAttachmentContentType::Word`](crate::note::NoteAttachmentContentType::Word): The raw +/// word itself. +/// - [`NoteAttachmentContentType::Array`](crate::note::NoteAttachmentContentType::Array): The +/// commitment to the elements. +#[derive(Clone, Debug, Eq, PartialEq)] pub struct NoteMetadata { /// The ID of the account which created the note. sender: AccountId, @@ -52,31 +71,29 @@ pub struct NoteMetadata { /// A value which can be used by the recipient(s) to identify notes intended for them. tag: NoteTag, - /// An arbitrary user-defined value. - aux: Felt, - - /// Specifies when a note is ready to be consumed. - execution_hint: NoteExecutionHint, + /// The optional attachment of a note's metadata. + /// + /// Defaults to [`NoteAttachment::default`]. + attachment: NoteAttachment, } impl NoteMetadata { + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + /// Returns a new [`NoteMetadata`] instantiated with the specified parameters. - pub fn new( - sender: AccountId, - note_type: NoteType, - tag: NoteTag, - execution_hint: NoteExecutionHint, - aux: Felt, - ) -> Self { + pub fn new(sender: AccountId, note_type: NoteType, tag: NoteTag) -> Self { Self { sender, note_type, tag, - aux, - execution_hint, + attachment: NoteAttachment::default(), } } + // ACCESSORS + // -------------------------------------------------------------------------------------------- + /// Returns the account which created the note. pub fn sender(&self) -> AccountId { self.sender @@ -92,68 +109,78 @@ impl NoteMetadata { self.tag } + // TODO(note_attachment): Remove this method. + // Method left for temporary compatibility. /// Returns the execution hint associated with the note. pub fn execution_hint(&self) -> NoteExecutionHint { - self.execution_hint + NoteExecutionHint::always() } + // TODO(note_attachment): Remove this method. + // Method left for temporary compatibility. /// Returns the note's aux field. pub fn aux(&self) -> Felt { - self.aux + Felt::from(0u32) + } + + /// Returns the attachment of the note. + pub fn attachment(&self) -> &NoteAttachment { + &self.attachment } /// Returns `true` if the note is private. pub fn is_private(&self) -> bool { self.note_type == NoteType::Private } -} -impl From for Word { - /// Convert a [`NoteMetadata`] into a [`Word`]. + /// Returns the header of a [`NoteMetadata`] as a [`Word`]. /// - /// The produced layout of the word is documented on the [`NoteMetadata`] type. - fn from(metadata: NoteMetadata) -> Self { - (&metadata).into() + /// See [`NoteMetadata`] docs for more details. + fn to_header(&self) -> NoteMetadataHeader { + NoteMetadataHeader { + sender: self.sender, + note_type: self.note_type, + tag: self.tag, + attachment_content_type: self.attachment().content().content_type(), + attachment_type: self.attachment.attachment_type(), + } } -} -impl From<&NoteMetadata> for Word { - /// Convert a [`NoteMetadata`] into a [`Word`]. + /// Returns the [`Word`] that represents the header of a [`NoteMetadata`]. /// - /// The produced layout of the word is documented on the [`NoteMetadata`] type. - fn from(metadata: &NoteMetadata) -> Self { - let mut elements = Word::empty(); - elements[0] = metadata.sender.prefix().as_felt(); - elements[1] = merge_id_type_and_hint_tag( - metadata.sender.suffix(), - metadata.note_type, - metadata.execution_hint, - ); - elements[2] = merge_note_tag_and_hint_payload(metadata.execution_hint, metadata.tag); - elements[3] = metadata.aux; - elements + /// See [`NoteMetadata`] docs for more details. + pub fn to_header_word(&self) -> Word { + Word::from(self.to_header()) } -} - -impl TryFrom for NoteMetadata { - type Error = NoteError; - /// Tries to decode a [`Word`] into a [`NoteMetadata`]. + /// Returns the [`Word`] that represents the attachment of a [`NoteMetadata`]. /// - /// The expected layout of the word is documented on the [`NoteMetadata`] type. - fn try_from(elements: Word) -> Result { - let sender_id_prefix: Felt = elements[0]; + /// See [`NoteMetadata`] docs for more details. + pub fn to_attachment_word(&self) -> Word { + self.attachment.content().to_word() + } - let (sender_id_suffix, note_type, execution_hint_tag) = - unmerge_id_type_and_hint_tag(elements[1])?; + /// Returns the commitment to the note metadata, which is defined as: + /// + /// ```text + /// hash(NOTE_METADATA_HEADER || NOTE_METADATA_ATTACHMENT) + /// ``` + pub fn to_commitment(&self) -> Word { + Hasher::merge(&[self.to_header_word(), self.to_attachment_word()]) + } - let sender = AccountId::try_from([sender_id_prefix, sender_id_suffix]) - .map_err(NoteError::NoteSenderInvalidAccountId)?; + // MUTATORS + // -------------------------------------------------------------------------------------------- - let (execution_hint, note_tag) = - unmerge_note_tag_and_hint_payload(elements[2], execution_hint_tag)?; + /// Overwrites the note's attachment with the provided one. + pub fn set_attachment(&mut self, attachment: NoteAttachment) { + self.attachment = attachment; + } - Ok(Self::new(sender, note_type, note_tag, execution_hint, elements[3])) + /// Overwrites the note's attachment with the provided one. + pub fn with_attachment(mut self, attachment: NoteAttachment) -> Self { + self.attachment = attachment; + self } } @@ -162,125 +189,165 @@ impl TryFrom for NoteMetadata { impl Serializable for NoteMetadata { fn write_into(&self, target: &mut W) { - Word::from(self).write_into(target); + self.note_type().write_into(target); + self.sender().write_into(target); + self.tag().write_into(target); + self.attachment().write_into(target); } } impl Deserializable for NoteMetadata { fn read_from(source: &mut R) -> Result { - let word = Word::read_from(source)?; - Self::try_from(word).map_err(|err| DeserializationError::InvalidValue(err.to_string())) + let note_type = NoteType::read_from(source)?; + let sender = AccountId::read_from(source)?; + let tag = NoteTag::read_from(source)?; + let attachment = NoteAttachment::read_from(source)?; + + Ok(NoteMetadata::new(sender, note_type, tag).with_attachment(attachment)) + } +} + +// NOTE METADATA HEADER +// ================================================================================================ + +/// The header representation of [`NoteMetadata`]. +/// +/// See the metadata's type for details on this type's [`Word`] layout. +/// +/// This is intended to be a private type meant for encapsulating the conversion from and to words. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct NoteMetadataHeader { + sender: AccountId, + note_type: NoteType, + tag: NoteTag, + attachment_content_type: NoteAttachmentContentType, + attachment_type: NoteAttachmentType, +} + +impl From for Word { + fn from(header: NoteMetadataHeader) -> Self { + let mut metadata = Word::empty(); + + metadata[0] = merge_sender_suffix_and_note_type(header.sender.suffix(), header.note_type); + metadata[1] = header.sender.prefix().as_felt(); + metadata[2] = Felt::from(header.tag); + metadata[3] = + merge_attachment_type_info(header.attachment_content_type, header.attachment_type); + + metadata + } +} + +impl TryFrom for NoteMetadataHeader { + type Error = NoteError; + + /// Decodes a [`NoteMetadataHeader`] from a [`Word`]. + fn try_from(word: Word) -> Result { + let (sender_suffix, note_type) = unmerge_sender_suffix_and_note_type(word[0])?; + let sender_prefix = word[1]; + let tag = u32::try_from(word[2]).map(NoteTag::new).map_err(|_| { + NoteError::other("failed to convert note tag from metadata header to u32") + })?; + let (attachment_content_type, attachment_type) = unmerge_attachment_type_info(word[3])?; + + let sender = AccountId::try_from([sender_prefix, sender_suffix]).map_err(|source| { + NoteError::other_with_source("failed to decode account ID from metadata header", source) + })?; + + Ok(Self { + sender, + note_type, + tag, + attachment_content_type, + attachment_type, + }) } } // HELPER FUNCTIONS // ================================================================================================ -/// Merges the suffix of an [`AccountId`], a [`NoteType`] and the tag of a -/// [`NoteExecutionHint`] into a single [`Felt`]. +/// Merges the suffix of an [`AccountId`] and the [`NoteType`] into a single [`Felt`]. /// /// The layout is as follows: /// /// ```text -/// [sender_id_suffix (56 bits) | note_type (2 bits) | note_execution_hint_tag (6 bits)] +/// [sender_id_suffix (56 bits) | 6 zero bits | note_type (2 bits)] /// ``` /// -/// One of the upper 16 bits is guaranteed to be zero due to the guarantees of the epoch in the -/// account ID. +/// The most significant bit of the suffix is guaranteed to be zero, so the felt retains its +/// validity. /// -/// Note that `sender_id_suffix` is the suffix of the sender's account ID. -fn merge_id_type_and_hint_tag( - sender_id_suffix: Felt, - note_type: NoteType, - note_execution_hint: NoteExecutionHint, -) -> Felt { +/// The `sender_id_suffix` is the suffix of the sender's account ID. +fn merge_sender_suffix_and_note_type(sender_id_suffix: Felt, note_type: NoteType) -> Felt { let mut merged = sender_id_suffix.as_int(); - let type_bits = note_type as u8; - let (tag_bits, _) = note_execution_hint.into_parts(); + let note_type_byte = note_type as u8; + debug_assert!(note_type_byte < 4, "note type must not contain values >= 4"); + merged |= note_type_byte as u64; - debug_assert!(type_bits & 0b1111_1100 == 0, "note type must not contain values >= 4"); - debug_assert!( - tag_bits & 0b1100_0000 == 0, - "note execution hint tag must not contain values >= 64" - ); - - // Note: The least significant byte of the second AccountId felt is zero by construction so we - // can overwrite it. - merged |= (type_bits << 6) as u64; - merged |= tag_bits as u64; - - // SAFETY: The most significant bit of the suffix is zero by construction so the bytes will be a + // SAFETY: The most significant bit of the suffix is zero by construction so the u64 will be a // valid felt. Felt::try_from(merged).expect("encoded value should be a valid felt") } -/// Unmerges the given felt into the suffix of an [`AccountId`], a [`NoteType`] and the tag of -/// a [`NoteExecutionHint`]. -fn unmerge_id_type_and_hint_tag(element: Felt) -> Result<(Felt, NoteType, u8), NoteError> { - let element = element.as_int(); - - // Cut off the least significant byte. - let least_significant_byte = element as u8; - let note_type_bits = (least_significant_byte & 0b1100_0000) >> 6; - let tag_bits = least_significant_byte & 0b0011_1111; +/// Unmerges the sender ID suffix and note type. +fn unmerge_sender_suffix_and_note_type(element: Felt) -> Result<(Felt, NoteType), NoteError> { + const NOTE_TYPE_MASK: u8 = 0b11; + // Inverts the note type mask. + const SENDER_SUFFIX_MASK: u64 = !(NOTE_TYPE_MASK as u64); - let note_type = NoteType::try_from(note_type_bits)?; + let note_type_byte = element.as_int() as u8 & NOTE_TYPE_MASK; + let note_type = NoteType::try_from(note_type_byte).map_err(|source| { + NoteError::other_with_source("failed to decode note type from metadata header", source) + })?; - // Set least significant byte to zero. - let element = element & 0xffff_ffff_ffff_ff00; + // No bits were set so felt should still be valid. + let sender_suffix = + Felt::try_from(element.as_int() & SENDER_SUFFIX_MASK).expect("felt should still be valid"); - // SAFETY: The input was a valid felt and we cleared additional bits and did not set any - // bits, so it must still be a valid felt. - let sender_id_suffix = Felt::try_from(element).expect("element should still be valid"); - - Ok((sender_id_suffix, note_type, tag_bits)) + Ok((sender_suffix, note_type)) } -/// Merges the [`NoteExecutionHint`] payload and a [`NoteTag`] into a single [`Felt`]. +/// Merges the [`NoteAttachmentType`] and [`NoteAttachmentContentType`] into a single [`Felt`]. /// /// The layout is as follows: /// /// ```text -/// [note_execution_hint_payload (32 bits) | note_tag (32 bits)] +/// [30 zero bits | attachment_content_type (2 bits) | attachment_type (32 bits)] /// ``` -/// -/// One of the upper 32 bits is guaranteed to be zero. -fn merge_note_tag_and_hint_payload( - note_execution_hint: NoteExecutionHint, - note_tag: NoteTag, +fn merge_attachment_type_info( + attachment_content_type: NoteAttachmentContentType, + attachment_type: NoteAttachmentType, ) -> Felt { - let (_, payload) = note_execution_hint.into_parts(); - let note_tag: u32 = note_tag.into(); - - debug_assert_ne!( - payload, - u32::MAX, - "payload should never be u32::MAX as it would produce an invalid felt" + debug_assert!( + attachment_content_type.as_u8() < 4, + "attachment content type should fit into two bits" ); + let mut merged = (attachment_content_type.as_u8() as u64) << 32; + let attachment_type = attachment_type.as_u32(); + merged |= attachment_type as u64; - let felt_int = ((payload as u64) << 32) | (note_tag as u64); - - // SAFETY: The payload is guaranteed to never be u32::MAX so at least one of the upper 32 bits - // is zero, hence the felt is valid even if note_tag is u32::MAX. - Felt::try_from(felt_int).expect("bytes should be a valid felt") + Felt::try_from(merged).expect("the upper bit should be zero and the felt therefore valid") } -/// Unmerges the given felt into a [`NoteExecutionHint`] payload and a [`NoteTag`] and constructs a -/// [`NoteExecutionHint`] from the unmerged payload and the given `note_execution_hint_tag`. -fn unmerge_note_tag_and_hint_payload( +/// Unmerges the attachment content type and attachment type. +fn unmerge_attachment_type_info( element: Felt, - note_execution_hint_tag: u8, -) -> Result<(NoteExecutionHint, NoteTag), NoteError> { - let element = element.as_int(); - - let payload = (element >> 32) as u32; - let note_tag = (element & 0xffff_ffff) as u32; - - let execution_hint = NoteExecutionHint::from_parts(note_execution_hint_tag, payload)?; - let note_tag = NoteTag::from(note_tag); - - Ok((execution_hint, note_tag)) +) -> Result<(NoteAttachmentContentType, NoteAttachmentType), NoteError> { + let attachment_type = element.as_int() as u32; + let attachment_content_type = (element.as_int() >> 32) as u8; + + let attachment_type = NoteAttachmentType::new(attachment_type); + let attachment_content_type = NoteAttachmentContentType::try_from(attachment_content_type) + .map_err(|source| { + NoteError::other_with_source( + "failed to decode attachment content type from metadata header", + source, + ) + })?; + + Ok((attachment_content_type, attachment_type)) } // TESTS @@ -289,79 +356,34 @@ fn unmerge_note_tag_and_hint_payload( #[cfg(test)] mod tests { - use anyhow::Context; - use super::*; + use crate::note::NoteAttachmentType; use crate::testing::account_id::ACCOUNT_ID_MAX_ONES; + #[rstest::rstest] + #[case::attachment_none(NoteAttachment::default())] + #[case::attachment_raw(NoteAttachment::new_word(NoteAttachmentType::new(0), Word::from([3, 4, 5, 6u32])))] + #[case::attachment_commitment(NoteAttachment::new_array( + NoteAttachmentType::new(u32::MAX), + vec![Felt::new(5), Felt::new(6), Felt::new(7)], + )?)] #[test] - fn note_metadata_serde() -> anyhow::Result<()> { + fn note_metadata_serde(#[case] attachment: NoteAttachment) -> anyhow::Result<()> { // Use the Account ID with the maximum one bits to test if the merge function always // produces valid felts. let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap(); let note_type = NoteType::Public; - let tag = NoteTag::with_account_target(sender); - let aux = Felt::try_from(0xffff_ffff_0000_0000u64).unwrap(); - - for execution_hint in [ - NoteExecutionHint::always(), - NoteExecutionHint::none(), - NoteExecutionHint::on_block_slot(10, 11, 12), - NoteExecutionHint::after_block((u32::MAX - 1).into()).unwrap(), - ] { - let metadata = NoteMetadata::new(sender, note_type, tag, execution_hint, aux); - NoteMetadata::read_from_bytes(&metadata.to_bytes()) - .context(format!("failed for execution hint {execution_hint:?}"))?; - } + let tag = NoteTag::new(u32::MAX); + let metadata = NoteMetadata::new(sender, note_type, tag).with_attachment(attachment); - Ok(()) - } + // Serialization Roundtrip + let deserialized = NoteMetadata::read_from_bytes(&metadata.to_bytes())?; + assert_eq!(deserialized, metadata); - #[test] - fn merge_and_unmerge_id_type_and_hint() { - // Use the Account ID with the maximum one bits to test if the merge function always - // produces valid felts. - let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap(); - let sender_id_suffix = sender.suffix(); + // Metadata Header Roundtrip + let header = NoteMetadataHeader::try_from(metadata.to_header_word())?; + assert_eq!(header, metadata.to_header()); - let note_type = NoteType::Public; - let note_execution_hint = NoteExecutionHint::OnBlockSlot { - round_len: 10, - slot_len: 11, - slot_offset: 12, - }; - - let merged_value = - merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint); - let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) = - unmerge_id_type_and_hint_tag(merged_value).unwrap(); - - assert_eq!(note_type, extracted_note_type); - assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag); - assert_eq!(sender_id_suffix, extracted_suffix); - - let note_type = NoteType::Private; - let note_execution_hint = NoteExecutionHint::Always; - - let merged_value = - merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint); - let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) = - unmerge_id_type_and_hint_tag(merged_value).unwrap(); - - assert_eq!(note_type, extracted_note_type); - assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag); - assert_eq!(sender_id_suffix, extracted_suffix); - - let note_type = NoteType::Private; - let note_execution_hint = NoteExecutionHint::None; - - let merged_value = - merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint); - let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) = - unmerge_id_type_and_hint_tag(merged_value).unwrap(); - - assert_eq!(note_type, extracted_note_type); - assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag); - assert_eq!(sender_id_suffix, extracted_suffix); + Ok(()) } } diff --git a/crates/miden-protocol/src/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index a87252bcbf..7f5a3dcdca 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -20,6 +20,15 @@ pub use inputs::NoteInputs; mod metadata; pub use metadata::NoteMetadata; +mod attachment; +pub use attachment::{ + NoteAttachment, + NoteAttachmentArray, + NoteAttachmentContent, + NoteAttachmentContentType, + NoteAttachmentType, +}; + mod execution_hint; pub use execution_hint::{AfterBlockNumber, NoteExecutionHint}; @@ -149,7 +158,7 @@ impl Note { /// Returns a commitment to the note and its metadata. /// - /// > hash(NOTE_ID || NOTE_METADATA) + /// > hash(NOTE_ID || NOTE_METADATA_COMMITMENT) /// /// This value is used primarily for authenticating notes consumed when the are consumed /// in a transaction. @@ -170,12 +179,6 @@ impl AsRef for Note { // CONVERSIONS FROM NOTE // ================================================================================================ -impl From<&Note> for NoteHeader { - fn from(note: &Note) -> Self { - note.header - } -} - impl From for NoteHeader { fn from(note: Note) -> Self { note.header @@ -197,17 +200,7 @@ impl From for NoteDetails { impl From for PartialNote { fn from(note: Note) -> Self { let (assets, recipient, ..) = note.details.into_parts(); - PartialNote::new(*note.header.metadata(), recipient.digest(), assets) - } -} - -impl From<&Note> for PartialNote { - fn from(note: &Note) -> Self { - PartialNote::new( - *note.header.metadata(), - note.details.recipient().digest(), - note.details.assets().clone(), - ) + PartialNote::new(note.header.into_metadata(), recipient.digest(), assets) } } diff --git a/crates/miden-protocol/src/note/partial.rs b/crates/miden-protocol/src/note/partial.rs index 1b39af8a53..03553c3d2e 100644 --- a/crates/miden-protocol/src/note/partial.rs +++ b/crates/miden-protocol/src/note/partial.rs @@ -23,7 +23,7 @@ use crate::Word; /// and generally does not have enough info to execute the note. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PartialNote { - metadata: NoteMetadata, + header: NoteHeader, recipient_digest: Word, assets: NoteAssets, } @@ -31,7 +31,9 @@ pub struct PartialNote { impl PartialNote { /// Returns a new [PartialNote] instantiated from the provided parameters. pub fn new(metadata: NoteMetadata, recipient_digest: Word, assets: NoteAssets) -> Self { - Self { metadata, recipient_digest, assets } + let note_id = NoteId::new(recipient_digest, assets.commitment()); + let header = NoteHeader::new(note_id, metadata); + Self { header, recipient_digest, assets } } /// Returns the ID corresponding to this note. @@ -41,7 +43,7 @@ impl PartialNote { /// Returns the metadata associated with this note. pub fn metadata(&self) -> &NoteMetadata { - &self.metadata + self.header.metadata() } /// Returns the digest of the recipient associated with this note. @@ -55,17 +57,10 @@ impl PartialNote { pub fn assets(&self) -> &NoteAssets { &self.assets } -} - -impl From<&PartialNote> for NoteHeader { - fn from(note: &PartialNote) -> Self { - NoteHeader::new(note.id(), note.metadata) - } -} -impl From for NoteHeader { - fn from(note: PartialNote) -> Self { - NoteHeader::new(note.id(), note.metadata) + /// Returns the [`NoteHeader`] of this note. + pub fn header(&self) -> &NoteHeader { + &self.header } } @@ -74,7 +69,9 @@ impl From for NoteHeader { impl Serializable for PartialNote { fn write_into(&self, target: &mut W) { - self.metadata.write_into(target); + // Serialize only metadata since the note ID in the header can be recomputed from the + // remaining data. + self.header().metadata().write_into(target); self.recipient_digest.write_into(target); self.assets.write_into(target) } diff --git a/crates/miden-protocol/src/testing/block_note_tree.rs b/crates/miden-protocol/src/testing/block_note_tree.rs index 9779c06c57..3304527f9a 100644 --- a/crates/miden-protocol/src/testing/block_note_tree.rs +++ b/crates/miden-protocol/src/testing/block_note_tree.rs @@ -19,7 +19,7 @@ impl BlockNoteTree { // SAFETY: This is only called from test code. Reconsider if this changes. let block_note_index = BlockNoteIndex::new(batch_idx, *note_idx_in_batch) .expect("output note batch indices should fit into a block"); - (block_note_index, note.id(), *note.metadata()) + (block_note_index, note.id(), note.metadata()) }) }); diff --git a/crates/miden-protocol/src/testing/note.rs b/crates/miden-protocol/src/testing/note.rs index 13c8a9464e..56f2bedf84 100644 --- a/crates/miden-protocol/src/testing/note.rs +++ b/crates/miden-protocol/src/testing/note.rs @@ -1,11 +1,11 @@ use alloc::vec::Vec; +use crate::Word; use crate::assembly::Assembler; use crate::asset::FungibleAsset; use crate::note::{ Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -14,7 +14,6 @@ use crate::note::{ NoteType, }; use crate::testing::account_id::ACCOUNT_ID_SENDER; -use crate::{Word, ZERO}; pub const DEFAULT_NOTE_CODE: &str = "begin nop end"; @@ -29,8 +28,6 @@ impl Note { sender_id, NoteType::Private, NoteTag::with_account_target(sender_id), - NoteExecutionHint::Always, - ZERO, ); let inputs = NoteInputs::new(Vec::new()).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, inputs); diff --git a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs index 6f1423e37a..067626a143 100644 --- a/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs +++ b/crates/miden-protocol/src/transaction/kernel/advice_inputs.rs @@ -359,7 +359,8 @@ impl TransactionAdviceInputs { note_data.extend(*recipient.inputs().commitment()); note_data.extend(*assets.commitment()); note_data.extend(*note_arg); - note_data.extend(Word::from(note.metadata())); + note_data.extend(note.metadata().to_header_word()); + note_data.extend(note.metadata().to_attachment_word()); note_data.push(recipient.inputs().num_values().into()); note_data.push((assets.num_assets() as u32).into()); note_data.extend(assets.to_padded_assets()); diff --git a/crates/miden-protocol/src/transaction/kernel/memory.rs b/crates/miden-protocol/src/transaction/kernel/memory.rs index f5db5d4e1e..8b33b214ae 100644 --- a/crates/miden-protocol/src/transaction/kernel/memory.rs +++ b/crates/miden-protocol/src/transaction/kernel/memory.rs @@ -342,14 +342,14 @@ pub const NOTE_MEM_SIZE: MemoryAddress = 2048; // // Here `n` represents number of input notes. // -// Each nullifier occupies a single word. A data section for each note consists of exactly 512 -// words and is laid out like so: +// Each nullifier occupies a single word. A data section for each note consists of exactly 2048 +// elements and is laid out like so: // -// ┌──────┬────────┬────────┬────────┬────────────┬───────────┬──────┬───────┬────────┬────────┬───────┬─────┬───────┬─────────┬ -// │ NOTE │ SERIAL │ SCRIPT │ INPUTS │ ASSETS | RECIPIENT │ META │ NOTE │ NUM │ NUM │ ASSET │ ... │ ASSET │ PADDING │ -// │ ID │ NUM │ ROOT │ HASH │ COMMITMENT | │ DATA │ ARGS │ INPUTS │ ASSETS │ 0 │ │ n │ │ -// ├──────┼────────┼────────┼────────┼────────────┼───────────┼──────┼───────┼────────┼────────┼───────┼─────┼───────┼─────────┤ -// 0 4 8 12 16 20 24 28 32 36 40 + 4n +// ┌──────┬────────┬────────┬────────┬────────────┬───────────┬──────────┬────────────┬───────┬────────┬────────┬───────┬─────┬───────┬─────────┬ +// │ NOTE │ SERIAL │ SCRIPT │ INPUTS │ ASSETS | RECIPIENT │ METADATA │ ATTACHMENT │ NOTE │ NUM │ NUM │ ASSET │ ... │ ASSET │ PADDING │ +// │ ID │ NUM │ ROOT │ HASH │ COMMITMENT | │ HEADER │ │ ARGS │ INPUTS │ ASSETS │ 0 │ │ n │ │ +// ├──────┼────────┼────────┼────────┼────────────┼───────────┼──────────┼────────────┼───────┼────────┼────────┼───────┼─────┼───────┼─────────┤ +// 0 4 8 12 16 20 24 28 32 36 40 44 + 4n // // - NUM_INPUTS is encoded as [num_inputs, 0, 0, 0]. // - NUM_ASSETS is encoded as [num_assets, 0, 0, 0]. @@ -382,12 +382,15 @@ pub const INPUT_NOTE_SCRIPT_ROOT_OFFSET: MemoryOffset = 8; pub const INPUT_NOTE_INPUTS_COMMITMENT_OFFSET: MemoryOffset = 12; pub const INPUT_NOTE_ASSETS_COMMITMENT_OFFSET: MemoryOffset = 16; pub const INPUT_NOTE_RECIPIENT_OFFSET: MemoryOffset = 20; -pub const INPUT_NOTE_METADATA_OFFSET: MemoryOffset = 24; -pub const INPUT_NOTE_ARGS_OFFSET: MemoryOffset = 28; -pub const INPUT_NOTE_NUM_INPUTS_OFFSET: MemoryOffset = 32; -pub const INPUT_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 36; -pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 40; +pub const INPUT_NOTE_METADATA_HEADER_OFFSET: MemoryOffset = 24; +pub const INPUT_NOTE_ATTACHMENT_OFFSET: MemoryOffset = 28; +pub const INPUT_NOTE_ARGS_OFFSET: MemoryOffset = 32; +pub const INPUT_NOTE_NUM_INPUTS_OFFSET: MemoryOffset = 36; +pub const INPUT_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 40; +pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 44; +#[allow(clippy::empty_line_after_outer_attr)] +#[rustfmt::skip] // OUTPUT NOTES DATA // ------------------------------------------------------------------------------------------------ // Output notes section contains data of all notes produced by a transaction. The section starts at @@ -401,11 +404,11 @@ pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 40; // The total number of output notes for a transaction is stored in the bookkeeping section of the // memory. Data section of each note is laid out like so: // -// ┌──────┬──────────┬───────────┬────────────┬────────────────┬─────────┬─────┬─────────┬─────────┐ -// │ NOTE │ METADATA │ RECIPIENT │ ASSETS │ NUM ASSETS │ ASSET 0 │ ... │ ASSET n │ PADDING │ -// | ID | | | COMMITMENT | AND DIRTY FLAG | | | | | -// ├──────┼──────────┼───────────┼────────────┼────────────────┼─────────┼─────┼─────────┼─────────┤ -// 0 1 2 3 4 5 5 + n +// ┌──────┬──────────┬────────────┬───────────┬────────────┬────────────────┬─────────┬─────┬─────────┬─────────┐ +// │ NOTE │ METADATA │ METADATA │ RECIPIENT │ ASSETS │ NUM ASSETS │ ASSET 0 │ ... │ ASSET n │ PADDING │ +// | ID | HEADER | ATTACHMENT | | COMMITMENT | AND DIRTY FLAG | | | | | +// ├──────┼──────────┼────────────┼───────────┼────────────┼────────────────┼─────────┼─────┼─────────┼─────────┤ +// 0 1 2 3 4 5 6 6 + n // // The NUM_ASSETS_AND_DIRTY_FLAG word has the following layout: // `[num_assets, assets_commitment_dirty_flag, 0, 0]`, where: @@ -421,17 +424,15 @@ pub const INPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 40; /// The memory address at which the output notes section begins. pub const OUTPUT_NOTE_SECTION_OFFSET: MemoryOffset = 16_777_216; -/// The size of the core output note data segment. -pub const OUTPUT_NOTE_CORE_DATA_SIZE: MemSize = 16; - /// The offsets at which data of an output note is stored relative to the start of its data segment. pub const OUTPUT_NOTE_ID_OFFSET: MemoryOffset = 0; -pub const OUTPUT_NOTE_METADATA_OFFSET: MemoryOffset = 4; -pub const OUTPUT_NOTE_RECIPIENT_OFFSET: MemoryOffset = 8; -pub const OUTPUT_NOTE_ASSET_COMMITMENT_OFFSET: MemoryOffset = 12; -pub const OUTPUT_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 16; -pub const OUTPUT_NOTE_DIRTY_FLAG_OFFSET: MemoryOffset = 17; -pub const OUTPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 20; +pub const OUTPUT_NOTE_METADATA_HEADER_OFFSET: MemoryOffset = 4; +pub const OUTPUT_NOTE_ATTACHMENT_OFFSET: MemoryOffset = 8; +pub const OUTPUT_NOTE_RECIPIENT_OFFSET: MemoryOffset = 12; +pub const OUTPUT_NOTE_ASSET_COMMITMENT_OFFSET: MemoryOffset = 16; +pub const OUTPUT_NOTE_NUM_ASSETS_OFFSET: MemoryOffset = 20; +pub const OUTPUT_NOTE_DIRTY_FLAG_OFFSET: MemoryOffset = 21; +pub const OUTPUT_NOTE_ASSETS_OFFSET: MemoryOffset = 24; // LINK MAP // ------------------------------------------------------------------------------------------------ diff --git a/crates/miden-protocol/src/transaction/kernel/procedures.rs b/crates/miden-protocol/src/transaction/kernel/procedures.rs index e32f63b8dc..31cf42e8e8 100644 --- a/crates/miden-protocol/src/transaction/kernel/procedures.rs +++ b/crates/miden-protocol/src/transaction/kernel/procedures.rs @@ -68,27 +68,27 @@ pub const KERNEL_PROCEDURES: [Word; 52] = [ // faucet_is_non_fungible_asset_issued word!("0xd2131c592fd7565bbf5d555e73a022c4ec1b5bc70219724e26424ff662e02b1a"), // input_note_get_metadata - word!("0x7ad3e94585e7a397ee27443c98b376ed8d4ba762122af6413fde9314c00a6219"), + word!("0x447b342e38855a9402cde0ea52ecb5e4c1fe542b535a5364cb5caa8e94c82442"), // input_note_get_assets_info - word!("0x159439fe48dbc11e674c5d05830d0408dcfa033c26e85e01256002c6cbc07e9a"), + word!("0xe0817bed99fb61180e705b2c9e5ca8c8f0c62864953247a56acbc65b7d58c2d5"), // input_note_get_script_root word!("0x527036257e58c3a84cf0aa170fb3f219a4553db17d269279355ad164a2b90ac5"), // input_note_get_inputs_info - word!("0xdd8bbf4cdb48051da346bc89760b77fdf4c948904276a99d96409922a00bd322"), + word!("0xb7f45ec34f7708355551dcf1f82c9c40e2c19252f8d5c98dcf9ef1aa0a3eb878"), // input_note_get_serial_number word!("0x25815e02b7976d8e5c297dde60d372cc142c81f702f424ac0920190528c547ee"), // input_note_get_recipient word!("0xd3c255177f9243bb1a523a87615bbe76dd5a3605fcae87eb9d3a626d4ecce33c"), // output_note_create - word!("0xa40e074cd5bd330b0af04c55b839da1e60e72583136f45d3e1bbc8b847865c3a"), + word!("0x9689eb055efa7153b4498e433056d99cda07a8afd4dac39c9cc82ef8cf22b3fb"), // output_note_get_metadata - word!("0xde4a5b57f9d53692459383e6cf6302ef3602a348896ed6ab6fdf67e07fa483ff"), + word!("0x3db8427f20eb70603b72aa574a986506eb7216312004aeaf8b2a7e55d0049a48"), // output_note_get_assets_info - word!("0x7e5d726b5f25f6cfd533bd0294853f3fceea62c41e5f2fd68919d8d53a48b3f8"), + word!("0xd2078081e19843b0c2500b9483f9888fa0a2c33a5155064aa97819696d7ae157"), // output_note_get_recipient - word!("0xc824115ed79a2e1670daed8c18fba1bc15f54c5ec0ec6699de69a00b21d9df92"), + word!("0x1ce137f0c5be72832970e6c818968a789f65b97db34515bfebb767705f28db67"), // output_note_add_asset - word!("0x9b6929d1ce24b3a97c6fb098f2fa8d0958beb15f91e268b9c787194b0a977a0d"), + word!("0xaf22383e4390f4f15a429768f79aa445f8a535bb21b0807172b9ef2de063d9d1"), // tx_get_num_input_notes word!("0xfcc186d4b65c584f3126dda1460b01eef977efd76f9e36f972554af28e33c685"), // tx_get_input_notes_commitment @@ -96,7 +96,7 @@ pub const KERNEL_PROCEDURES: [Word; 52] = [ // tx_get_num_output_notes word!("0x2511fca9c078cd96e526fd488d1362cbfd597eb3db8452aedb00beffee9782b4"), // tx_get_output_notes_commitment - word!("0xd5b22dae48ec4b20ed479f2c43573d34930720886371ef6b484310a3bea4e818"), + word!("0xbb94a5220ae82a2b45ab0eca2fc653d1a8f0967507a675430bd4caedc022334c"), // tx_get_block_commitment word!("0xe474b491a64d222397fcf83ee5db7b048061988e5e83ce99b91bae6fd75a3522"), // tx_get_block_number diff --git a/crates/miden-protocol/src/transaction/outputs.rs b/crates/miden-protocol/src/transaction/outputs.rs index 410bbf5449..4be4b2b9d7 100644 --- a/crates/miden-protocol/src/transaction/outputs.rs +++ b/crates/miden-protocol/src/transaction/outputs.rs @@ -102,6 +102,7 @@ pub struct OutputNotes { impl OutputNotes { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- + /// Returns new [OutputNotes] instantiated from the provide vector of notes. /// /// # Errors @@ -120,7 +121,7 @@ impl OutputNotes { } } - let commitment = Self::compute_commitment(notes.iter().map(NoteHeader::from)); + let commitment = Self::compute_commitment(notes.iter().map(OutputNote::header)); Ok(Self { notes, commitment }) } @@ -163,9 +164,13 @@ impl OutputNotes { /// Computes a commitment to output notes. /// - /// For a non-empty list of notes, this is a sequential hash of (note_id, metadata) tuples for - /// the notes created in a transaction. For an empty list, [EMPTY_WORD] is returned. - pub(crate) fn compute_commitment(notes: impl ExactSizeIterator) -> Word { + /// - For an empty list, [`Word::empty`] is returned. + /// - For a non-empty list of notes, this is a sequential hash of (note_id, metadata_commitment) + /// tuples for the notes created in a transaction, where `metadata_commitment` is the return + /// value of [`NoteMetadata::to_commitment`]. + pub(crate) fn compute_commitment<'header>( + notes: impl ExactSizeIterator, + ) -> Word { if notes.len() == 0 { return Word::empty(); } @@ -173,7 +178,7 @@ impl OutputNotes { let mut elements: Vec = Vec::with_capacity(notes.len() * 8); for note_header in notes { elements.extend_from_slice(note_header.id().as_elements()); - elements.extend_from_slice(Word::from(note_header.metadata()).as_elements()); + elements.extend_from_slice(note_header.metadata().to_commitment().as_elements()); } Hasher::hash_elements(&elements) @@ -278,40 +283,30 @@ impl OutputNote { pub fn shrink(&self) -> Self { match self { OutputNote::Full(note) if note.metadata().is_private() => { - OutputNote::Header(*note.header()) + OutputNote::Header(note.header().clone()) }, - OutputNote::Partial(note) => OutputNote::Header(note.into()), + OutputNote::Partial(note) => OutputNote::Header(note.header().clone()), _ => self.clone(), } } + /// Returns a reference to the [`NoteHeader`] of this note. + pub fn header(&self) -> &NoteHeader { + match self { + OutputNote::Full(note) => note.header(), + OutputNote::Partial(note) => note.header(), + OutputNote::Header(header) => header, + } + } + /// Returns a commitment to the note and its metadata. /// - /// > hash(NOTE_ID || NOTE_METADATA) + /// > hash(NOTE_ID || NOTE_METADATA_COMMITMENT) pub fn commitment(&self) -> Word { compute_note_commitment(self.id(), self.metadata()) } } -// CONVERSIONS -// ------------------------------------------------------------------------------------------------ - -impl From for NoteHeader { - fn from(value: OutputNote) -> Self { - (&value).into() - } -} - -impl From<&OutputNote> for NoteHeader { - fn from(value: &OutputNote) -> Self { - match value { - OutputNote::Full(note) => note.into(), - OutputNote::Partial(note) => note.into(), - OutputNote::Header(note) => *note, - } - } -} - // SERIALIZATION // ------------------------------------------------------------------------------------------------ diff --git a/crates/miden-protocol/src/transaction/proven_tx.rs b/crates/miden-protocol/src/transaction/proven_tx.rs index 0e25ba6e5d..e0fde4d293 100644 --- a/crates/miden-protocol/src/transaction/proven_tx.rs +++ b/crates/miden-protocol/src/transaction/proven_tx.rs @@ -636,7 +636,7 @@ impl From<&InputNote> for InputNoteCommitment { }, InputNote::Unauthenticated { note } => Self { nullifier: note.nullifier(), - header: Some(*note.header()), + header: Some(note.header().clone()), }, } } @@ -654,7 +654,7 @@ impl ToInputNoteCommitments for InputNoteCommitment { } fn note_commitment(&self) -> Option { - self.header.map(|header| header.commitment()) + self.header.as_ref().map(NoteHeader::commitment) } } diff --git a/crates/miden-protocol/src/transaction/tx_header.rs b/crates/miden-protocol/src/transaction/tx_header.rs index 4b7adafad6..210d722e4b 100644 --- a/crates/miden-protocol/src/transaction/tx_header.rs +++ b/crates/miden-protocol/src/transaction/tx_header.rs @@ -10,6 +10,7 @@ use crate::transaction::{ ExecutedTransaction, InputNoteCommitment, InputNotes, + OutputNote, OutputNotes, ProvenTransaction, TransactionId, @@ -56,7 +57,7 @@ impl TransactionHeader { fee: FungibleAsset, ) -> Self { let input_notes_commitment = input_notes.commitment(); - let output_notes_commitment = OutputNotes::compute_commitment(output_notes.iter().copied()); + let output_notes_commitment = OutputNotes::compute_commitment(output_notes.iter()); let id = TransactionId::new( initial_state_commitment, @@ -166,7 +167,7 @@ impl From<&ProvenTransaction> for TransactionHeader { tx.account_update().initial_state_commitment(), tx.account_update().final_state_commitment(), tx.input_notes().clone(), - tx.output_notes().iter().map(NoteHeader::from).collect(), + tx.output_notes().iter().map(OutputNote::header).cloned().collect(), tx.fee(), ) } @@ -181,7 +182,7 @@ impl From<&ExecutedTransaction> for TransactionHeader { tx.initial_account().initial_commitment(), tx.final_account().commitment(), tx.input_notes().to_commitments(), - tx.output_notes().iter().map(NoteHeader::from).collect(), + tx.output_notes().iter().map(OutputNote::header).cloned().collect(), tx.fee(), ) } diff --git a/crates/miden-standards/src/account/interface/test.rs b/crates/miden-standards/src/account/interface/test.rs index 5b63a933ad..22aa38c005 100644 --- a/crates/miden-standards/src/account/interface/test.rs +++ b/crates/miden-standards/src/account/interface/test.rs @@ -6,7 +6,6 @@ use miden_protocol::crypto::rand::{FeltRng, RpoRandomCoin}; use miden_protocol::note::{ Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -252,13 +251,7 @@ fn test_basic_wallet_custom_notes() { let sender_account_id = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2.try_into().unwrap(); let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(wallet_account.id()); - let metadata = NoteMetadata::new( - sender_account_id, - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ); + let metadata = NoteMetadata::new(sender_account_id, NoteType::Public, tag); let vault = NoteAssets::new(vec![FungibleAsset::mock(100)]).unwrap(); let compatible_source_code = " @@ -285,7 +278,7 @@ fn test_basic_wallet_custom_notes() { "; let note_script = CodeBuilder::default().compile_note_script(compatible_source_code).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); - let compatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let compatible_custom_note = Note::new(vault.clone(), metadata.clone(), recipient); assert_eq!( NoteAccountCompatibility::Maybe, wallet_account_interface.is_compatible_with(&compatible_custom_note) @@ -341,13 +334,7 @@ fn test_basic_fungible_faucet_custom_notes() { let sender_account_id = ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2.try_into().unwrap(); let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(faucet_account.id()); - let metadata = NoteMetadata::new( - sender_account_id, - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ); + let metadata = NoteMetadata::new(sender_account_id, NoteType::Public, tag); let vault = NoteAssets::new(vec![FungibleAsset::mock(100)]).unwrap(); let compatible_source_code = " @@ -372,7 +359,7 @@ fn test_basic_fungible_faucet_custom_notes() { "; let note_script = CodeBuilder::default().compile_note_script(compatible_source_code).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); - let compatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let compatible_custom_note = Note::new(vault.clone(), metadata.clone(), recipient); assert_eq!( NoteAccountCompatibility::Maybe, faucet_account_interface.is_compatible_with(&compatible_custom_note) @@ -450,13 +437,7 @@ fn test_custom_account_custom_notes() { let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(target_account.id()); - let metadata = NoteMetadata::new( - sender_account.id(), - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ); + let metadata = NoteMetadata::new(sender_account.id(), NoteType::Public, tag); let vault = NoteAssets::new(vec![FungibleAsset::mock(100)]).unwrap(); let compatible_source_code = " @@ -484,7 +465,7 @@ fn test_custom_account_custom_notes() { .compile_note_script(compatible_source_code) .unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); - let compatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let compatible_custom_note = Note::new(vault.clone(), metadata.clone(), recipient); assert_eq!( NoteAccountCompatibility::Maybe, target_account_interface.is_compatible_with(&compatible_custom_note) @@ -560,13 +541,7 @@ fn test_custom_account_multiple_components_custom_notes() { let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(target_account.id()); - let metadata = NoteMetadata::new( - sender_account.id(), - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ); + let metadata = NoteMetadata::new(sender_account.id(), NoteType::Public, tag); let vault = NoteAssets::new(vec![FungibleAsset::mock(100)]).unwrap(); let compatible_source_code = " @@ -600,7 +575,7 @@ fn test_custom_account_multiple_components_custom_notes() { .compile_note_script(compatible_source_code) .unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, NoteInputs::default()); - let compatible_custom_note = Note::new(vault.clone(), metadata, recipient); + let compatible_custom_note = Note::new(vault.clone(), metadata.clone(), recipient); assert_eq!( NoteAccountCompatibility::Maybe, target_account_interface.is_compatible_with(&compatible_custom_note) diff --git a/crates/miden-standards/src/note/mod.rs b/crates/miden-standards/src/note/mod.rs index 1ad2c012ae..c31f6266c1 100644 --- a/crates/miden-standards/src/note/mod.rs +++ b/crates/miden-standards/src/note/mod.rs @@ -43,7 +43,8 @@ pub fn create_p2id_note( target: AccountId, assets: Vec, note_type: NoteType, - aux: Felt, + // TODO(note_attachment): Replace with note attachment. + _aux: Felt, rng: &mut R, ) -> Result { let serial_num = rng.draw_word(); @@ -51,7 +52,7 @@ pub fn create_p2id_note( let tag = NoteTag::with_account_target(target); - let metadata = NoteMetadata::new(sender, note_type, tag, NoteExecutionHint::always(), aux); + let metadata = NoteMetadata::new(sender, note_type, tag); let vault = NoteAssets::new(assets)?; Ok(Note::new(vault, metadata, recipient)) @@ -77,7 +78,8 @@ pub fn create_p2ide_note( reclaim_height: Option, timelock_height: Option, note_type: NoteType, - aux: Felt, + // TODO(note_attachment): Replace with note attachment. + _aux: Felt, rng: &mut R, ) -> Result { let serial_num = rng.draw_word(); @@ -85,12 +87,7 @@ pub fn create_p2ide_note( utils::build_p2ide_recipient(target, reclaim_height, timelock_height, serial_num)?; let tag = NoteTag::with_account_target(target); - let execution_hint = match timelock_height { - Some(height) => NoteExecutionHint::after_block(height)?, - None => NoteExecutionHint::always(), - }; - - let metadata = NoteMetadata::new(sender, note_type, tag, execution_hint, aux); + let metadata = NoteMetadata::new(sender, note_type, tag); let vault = NoteAssets::new(assets)?; Ok(Note::new(vault, metadata, recipient)) @@ -110,8 +107,10 @@ pub fn create_swap_note( offered_asset: Asset, requested_asset: Asset, swap_note_type: NoteType, - swap_note_aux: Felt, + // TODO(note_attachment): Replace with note attachment. + _swap_note_aux: Felt, payback_note_type: NoteType, + // TODO(note_attachment): Replace with note attachment. payback_note_aux: Felt, rng: &mut R, ) -> Result<(Note, NoteDetails), NoteError> { @@ -148,8 +147,7 @@ pub fn create_swap_note( let serial_num = rng.draw_word(); // build the outgoing note - let metadata = - NoteMetadata::new(sender, swap_note_type, tag, NoteExecutionHint::always(), swap_note_aux); + let metadata = NoteMetadata::new(sender, swap_note_type, tag); let assets = NoteAssets::new(vec![offered_asset])?; let recipient = NoteRecipient::new(serial_num, note_script, inputs); let note = Note::new(assets, metadata, recipient); @@ -187,7 +185,8 @@ pub fn create_mint_note( faucet_id: AccountId, sender: AccountId, mint_inputs: MintNoteInputs, - aux: Felt, + // TODO(note_attachment): Replace with note attachment. + _aux: Felt, rng: &mut R, ) -> Result { let note_script = WellKnownNote::MINT.script(); @@ -195,14 +194,13 @@ pub fn create_mint_note( // MINT notes are always public for network execution let note_type = NoteType::Public; - let execution_hint = NoteExecutionHint::always(); // Convert MintNoteInputs to NoteInputs let inputs = NoteInputs::from(mint_inputs); let tag = NoteTag::with_account_target(faucet_id); - let metadata = NoteMetadata::new(sender, note_type, tag, execution_hint, aux); + let metadata = NoteMetadata::new(sender, note_type, tag); let assets = NoteAssets::new(vec![])?; // MINT notes have no assets let recipient = NoteRecipient::new(serial_num, note_script, inputs); @@ -234,7 +232,8 @@ pub fn create_burn_note( sender: AccountId, faucet_id: AccountId, fungible_asset: Asset, - aux: Felt, + // TODO(note_attachment): Replace with note attachment. + _aux: Felt, rng: &mut R, ) -> Result { let note_script = WellKnownNote::BURN.script(); @@ -242,13 +241,11 @@ pub fn create_burn_note( // BURN notes are always public let note_type = NoteType::Public; - // Use always execution hint for BURN notes - let execution_hint = NoteExecutionHint::always(); let inputs = NoteInputs::new(vec![])?; let tag = NoteTag::with_account_target(faucet_id); - let metadata = NoteMetadata::new(sender, note_type, tag, execution_hint, aux); + let metadata = NoteMetadata::new(sender, note_type, tag); let assets = NoteAssets::new(vec![fungible_asset])?; // BURN notes contain the asset to burn let recipient = NoteRecipient::new(serial_num, note_script, inputs); diff --git a/crates/miden-standards/src/testing/note.rs b/crates/miden-standards/src/testing/note.rs index 5d6a508900..185fa9f762 100644 --- a/crates/miden-standards/src/testing/note.rs +++ b/crates/miden-standards/src/testing/note.rs @@ -31,10 +31,12 @@ pub struct NoteBuilder { inputs: Vec, assets: Vec, note_type: NoteType, + // TODO(note_attachment): Remove. note_execution_hint: NoteExecutionHint, serial_num: Word, tag: NoteTag, code: String, + // TODO(note_attachment): Remove. aux: Felt, dyn_libraries: Vec, source_manager: Arc, @@ -154,13 +156,7 @@ impl NoteBuilder { .compile_note_script(virtual_source_file) .expect("note script should compile"); let vault = NoteAssets::new(self.assets)?; - let metadata = NoteMetadata::new( - self.sender, - self.note_type, - self.tag, - self.note_execution_hint, - self.aux, - ); + let metadata = NoteMetadata::new(self.sender, self.note_type, self.tag); let inputs = NoteInputs::new(self.inputs)?; let recipient = NoteRecipient::new(self.serial_num, note_script, inputs); diff --git a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs index 6831529a7b..92ba3bb8b8 100644 --- a/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs +++ b/crates/miden-testing/src/kernel_tests/block/proven_block_success.rs @@ -116,7 +116,7 @@ async fn proven_block_success() -> anyhow::Result<()> { ( BlockNoteIndex::new(batch_idx, note_idx_in_batch).unwrap(), note.id(), - *note.metadata(), + note.metadata(), ) }, )) diff --git a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs index ef250d634c..dc4beb3519 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_account_interface.rs @@ -10,7 +10,6 @@ use miden_protocol::crypto::rand::FeltRng; use miden_protocol::note::{ Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -24,7 +23,7 @@ use miden_protocol::testing::account_id::{ ACCOUNT_ID_SENDER, }; use miden_protocol::transaction::{InputNote, OutputNote, TransactionKernel}; -use miden_protocol::{Felt, StarkField, Word, ZERO}; +use miden_protocol::{Felt, StarkField, Word}; use miden_standards::note::{ NoteConsumptionStatus, WellKnownNote, @@ -794,8 +793,7 @@ fn create_p2ide_note_with_inputs(inputs: impl IntoIterator, sender: ); let tag = NoteTag::with_account_target(sender); - let metadata = - NoteMetadata::new(sender, NoteType::Public, tag, NoteExecutionHint::always(), ZERO); + let metadata = NoteMetadata::new(sender, NoteType::Public, tag); Note::new(NoteAssets::default(), metadata, recipient) } 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 b23404dd27..3b2301338f 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 @@ -8,7 +8,6 @@ use miden_protocol::errors::tx_kernel::ERR_NOTE_ATTEMPT_TO_ACCESS_NOTE_METADATA_ use miden_protocol::note::{ Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -101,17 +100,23 @@ async fn test_active_note_get_metadata() -> anyhow::Result<()> { # get the metadata of the active note exec.active_note::get_metadata - # => [METADATA] + # => [NOTE_ATTACHMENT, METADATA_HEADER] - # assert this metadata - push.{METADATA} + push.{NOTE_ATTACHMENT} + assert_eqw.err="note 0 has incorrect note attachment" + # => [METADATA_HEADER] + + push.{METADATA_HEADER} assert_eqw.err="note 0 has incorrect metadata" + # => [] # truncate the stack swapw dropw end "#, - METADATA = Word::from(tx_context.input_notes().get_note(0).note().metadata()) + METADATA_HEADER = tx_context.input_notes().get_note(0).note().metadata().to_header_word(), + NOTE_ATTACHMENT = + tx_context.input_notes().get_note(0).note().metadata().to_attachment_word() ); tx_context.execute_code(&code).await?; @@ -403,13 +408,7 @@ async fn test_active_note_get_exactly_8_inputs() -> anyhow::Result<()> { // prepare note data let serial_num = RpoRandomCoin::new(Word::from([4u32; 4])).draw_word(); let tag = NoteTag::with_account_target(target_id); - let metadata = NoteMetadata::new( - sender_id, - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ); + let metadata = NoteMetadata::new(sender_id, NoteType::Public, tag); let vault = NoteAssets::new(vec![]).context("failed to create input note assets")?; let note_script = CodeBuilder::default() .compile_note_script("begin nop end") diff --git a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs index 79c7b608dc..78eeaa951f 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_epilogue.rs @@ -45,7 +45,7 @@ use crate::{ /// Tests that the return values from the tx kernel main.masm program match the expected values. #[tokio::test] -async fn test_epilogue() -> anyhow::Result<()> { +async fn test_transaction_epilogue() -> anyhow::Result<()> { let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce); let asset = FungibleAsset::mock(100); let output_note_1 = create_public_p2any_note(account.id(), [asset]); 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 d1c62d88bd..84a9d9322a 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 @@ -120,16 +120,20 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { # get the metadata from the requested input note push.0 exec.input_note::get_metadata - # => [METADATA] + # => [NOTE_ATTACHMENT, METADATA_HEADER] - # assert the correctness of the metadata - push.{METADATA} - assert_eqw.err="note 0 has incorrect metadata" + push.{NOTE_ATTACHMENT} + assert_eqw.err="note 0 has incorrect note attachment" + # => [METADATA_HEADER] + + push.{METADATA_HEADER} + assert_eqw.err="note 0 has incorrect metadata header" # => [] end "#, RECIPIENT = p2id_note_1_asset.recipient().digest(), - METADATA = Word::from(p2id_note_1_asset.metadata()), + METADATA_HEADER = p2id_note_1_asset.metadata().to_header_word(), + NOTE_ATTACHMENT = p2id_note_1_asset.metadata().to_attachment_word(), ); let tx_script = CodeBuilder::default().compile_tx_script(code)?; 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 f4ceaea6ac..678b041a19 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -14,7 +14,6 @@ use miden_protocol::errors::MasmError; use miden_protocol::note::{ Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -381,28 +380,16 @@ async fn test_compute_inputs_commitment() -> anyhow::Result<()> { } #[tokio::test] -async fn test_build_metadata() -> miette::Result<()> { +async fn test_build_metadata_header() -> miette::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build().unwrap(); let sender = tx_context.account().id(); let receiver = AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE) .map_err(|e| miette::miette!("Failed to convert account ID: {}", e))?; - let test_metadata1 = NoteMetadata::new( - sender, - NoteType::Private, - NoteTag::with_account_target(receiver), - NoteExecutionHint::after_block(500.into()) - .map_err(|e| miette::miette!("Failed to create execution hint: {}", e))?, - Felt::try_from(1u64 << 63).map_err(|e| miette::miette!("Failed to convert felt: {}", e))?, - ); - let test_metadata2 = NoteMetadata::new( - sender, - NoteType::Public, - NoteTag::new(u32::MAX), - NoteExecutionHint::on_block_slot(u8::MAX, u8::MAX, u8::MAX), - Felt::try_from(0u64).map_err(|e| miette::miette!("Failed to convert felt: {}", e))?, - ); + let test_metadata1 = + NoteMetadata::new(sender, NoteType::Private, NoteTag::with_account_target(receiver)); + let test_metadata2 = NoteMetadata::new(sender, NoteType::Public, NoteTag::new(u32::MAX)); for (iteration, test_metadata) in [test_metadata1, test_metadata2].into_iter().enumerate() { let code = format!( @@ -412,24 +399,27 @@ async fn test_build_metadata() -> miette::Result<()> { begin exec.prologue::prepare_transaction - push.{execution_hint} push.{note_type} push.{aux} push.{tag} - exec.output_note::build_metadata + push.{note_type} + push.{tag} + exec.output_note::build_metadata_header # truncate the stack swapw dropw end ", - execution_hint = Felt::from(test_metadata.execution_hint()), note_type = Felt::from(test_metadata.note_type()), - aux = test_metadata.aux(), tag = test_metadata.tag(), ); - let exec_output = tx_context.execute_code(&code).await.unwrap(); + let exec_output = tx_context.execute_code(&code).await?; let metadata_word = exec_output.get_stack_word_be(0); - assert_eq!(Word::from(test_metadata), metadata_word, "failed in iteration {iteration}"); + assert_eq!( + test_metadata.to_header_word(), + metadata_word, + "failed in iteration {iteration}" + ); } Ok(()) @@ -541,13 +531,7 @@ async fn test_public_key_as_note_input() -> anyhow::Result<()> { let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); let tag = NoteTag::with_account_target(target_account.id()); - let metadata = NoteMetadata::new( - sender_account.id(), - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ); + let metadata = NoteMetadata::new(sender_account.id(), NoteType::Public, tag); let vault = NoteAssets::new(vec![])?; let note_script = CodeBuilder::default().compile_note_script("begin nop end")?; let recipient = 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 a998663174..f9a430920c 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 @@ -35,7 +35,8 @@ use miden_protocol::transaction::memory::{ NOTE_MEM_SIZE, NUM_OUTPUT_NOTES_PTR, OUTPUT_NOTE_ASSETS_OFFSET, - OUTPUT_NOTE_METADATA_OFFSET, + OUTPUT_NOTE_ATTACHMENT_OFFSET, + OUTPUT_NOTE_METADATA_HEADER_OFFSET, OUTPUT_NOTE_RECIPIENT_OFFSET, OUTPUT_NOTE_SECTION_OFFSET, }; @@ -100,19 +101,21 @@ async fn test_create_note() -> anyhow::Result<()> { "recipient must be stored at the correct memory location", ); - let expected_note_metadata: Word = NoteMetadata::new( - account_id, - NoteType::Public, - tag, - NoteExecutionHint::after_block(23.into())?, - Felt::new(27), - ) - .into(); + let metadata = NoteMetadata::new(account_id, NoteType::Public, tag); + let expected_metadata_header = metadata.to_header_word(); + let expected_note_attachment = metadata.to_attachment_word(); + + assert_eq!( + exec_output + .get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_HEADER_OFFSET), + expected_metadata_header, + "metadata header must be stored at the correct memory location", + ); assert_eq!( - exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET), - expected_note_metadata, - "metadata must be stored at the correct memory location", + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ATTACHMENT_OFFSET), + expected_note_attachment, + "attachment must be stored at the correct memory location", ); assert_eq!( @@ -256,13 +259,8 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { let output_serial_no_1 = Word::from([8u32; 4]); let output_tag_1 = NoteTag::with_account_target(network_account); let assets = NoteAssets::new(vec![input_asset_1])?; - let metadata = NoteMetadata::new( - tx_context.tx_inputs().account().id(), - NoteType::Public, - output_tag_1, - NoteExecutionHint::Always, - ZERO, - ); + let metadata = + NoteMetadata::new(tx_context.tx_inputs().account().id(), NoteType::Public, output_tag_1); let inputs = NoteInputs::new(vec![])?; let recipient = NoteRecipient::new(output_serial_no_1, input_note_1.script().clone(), inputs); let output_note_1 = Note::new(assets, metadata, recipient); @@ -271,13 +269,8 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { let output_serial_no_2 = Word::from([11u32; 4]); let output_tag_2 = NoteTag::with_account_target(local_account); let assets = NoteAssets::new(vec![input_asset_2])?; - let metadata = NoteMetadata::new( - tx_context.tx_inputs().account().id(), - NoteType::Public, - output_tag_2, - NoteExecutionHint::after_block(123.into())?, - ZERO, - ); + let metadata = + NoteMetadata::new(tx_context.tx_inputs().account().id(), NoteType::Public, output_tag_2); let inputs = NoteInputs::new(vec![])?; let recipient = NoteRecipient::new(output_serial_no_2, input_note_2.script().clone(), inputs); let output_note_2 = Note::new(assets, metadata, recipient); @@ -363,21 +356,30 @@ async fn test_get_output_notes_commitment() -> anyhow::Result<()> { "The test creates two notes", ); assert_eq!( - NoteMetadata::try_from( - exec_output - .get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET) - ) - .unwrap(), - *output_note_1.metadata(), - "Validate the output note 1 metadata", + exec_output + .get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_HEADER_OFFSET), + output_note_1.metadata().to_header_word(), + "Validate the output note 1 metadata header", + ); + assert_eq!( + exec_output.get_kernel_mem_word(OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ATTACHMENT_OFFSET), + output_note_1.metadata().to_attachment_word(), + "Validate the output note 1 attachment", + ); + + assert_eq!( + exec_output.get_kernel_mem_word( + OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_HEADER_OFFSET + NOTE_MEM_SIZE + ), + output_note_2.metadata().to_header_word(), + "Validate the output note 2 metadata header", ); assert_eq!( - NoteMetadata::try_from(exec_output.get_kernel_mem_word( - OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_METADATA_OFFSET + NOTE_MEM_SIZE - )) - .unwrap(), - *output_note_2.metadata(), - "Validate the output note 1 metadata", + exec_output.get_kernel_mem_word( + OUTPUT_NOTE_SECTION_OFFSET + OUTPUT_NOTE_ATTACHMENT_OFFSET + NOTE_MEM_SIZE + ), + output_note_2.metadata().to_attachment_word(), + "Validate the output note 2 attachment", ); assert_eq!(exec_output.get_stack_word_be(0), expected_output_notes_commitment); @@ -890,11 +892,14 @@ 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 - # => [METADATA] + # => [NOTE_ATTACHMENT, METADATA_HEADER] + + push.{NOTE_ATTACHMENT} + assert_eqw.err="requested note has incorrect note attachment" + # => [METADATA_HEADER] - # assert the correctness of the metadata - push.{METADATA} - assert_eqw.err="requested note has incorrect metadata" + push.{METADATA_HEADER} + assert_eqw.err="requested note has incorrect metadata header" # => [] # truncate the stack @@ -903,7 +908,8 @@ async fn test_get_recipient_and_metadata() -> anyhow::Result<()> { "#, output_note = create_output_note(&output_note), RECIPIENT = output_note.recipient().digest(), - METADATA = Word::from(output_note.metadata()), + METADATA_HEADER = output_note.metadata().to_header_word(), + NOTE_ATTACHMENT = output_note.metadata().to_attachment_word(), ); let tx_script = CodeBuilder::default().compile_tx_script(tx_script_src)?; diff --git a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs index d04ba7b65f..07ad25bf3d 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_prologue.rs @@ -42,9 +42,10 @@ use miden_protocol::transaction::memory::{ INPUT_NOTE_ARGS_OFFSET, INPUT_NOTE_ASSETS_COMMITMENT_OFFSET, INPUT_NOTE_ASSETS_OFFSET, + INPUT_NOTE_ATTACHMENT_OFFSET, INPUT_NOTE_ID_OFFSET, INPUT_NOTE_INPUTS_COMMITMENT_OFFSET, - INPUT_NOTE_METADATA_OFFSET, + INPUT_NOTE_METADATA_HEADER_OFFSET, INPUT_NOTE_NULLIFIER_SECTION_PTR, INPUT_NOTE_NUM_ASSETS_OFFSET, INPUT_NOTE_RECIPIENT_OFFSET, @@ -501,9 +502,15 @@ fn input_notes_memory_assertions( ); assert_eq!( - exec_output.get_note_mem_word(note_idx, INPUT_NOTE_METADATA_OFFSET), - Word::from(note.metadata()), - "note metadata should be stored at the correct offset" + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_METADATA_HEADER_OFFSET), + note.metadata().to_header_word(), + "note metadata header should be stored at the correct offset" + ); + + assert_eq!( + exec_output.get_note_mem_word(note_idx, INPUT_NOTE_ATTACHMENT_OFFSET), + note.metadata().to_attachment_word(), + "note attachment should be stored at the correct offset" ); assert_eq!( diff --git a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs index d9641af5e5..8d3cfbe6e0 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_tx.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_tx.rs @@ -217,8 +217,7 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { let serial_num_2 = Word::from([1, 2, 3, 4u32]); let note_script_2 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_CODE)?; let inputs_2 = NoteInputs::new(vec![ONE])?; - let metadata_2 = - NoteMetadata::new(account_id, note_type2, tag2, NoteExecutionHint::none(), aux2); + let metadata_2 = NoteMetadata::new(account_id, note_type2, tag2); let vault_2 = NoteAssets::new(vec![removed_asset_3, removed_asset_4])?; let recipient_2 = NoteRecipient::new(serial_num_2, note_script_2, inputs_2); let expected_output_note_2 = Note::new(vault_2, metadata_2, recipient_2); @@ -227,13 +226,7 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { let serial_num_3 = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]); let note_script_3 = CodeBuilder::default().compile_note_script(DEFAULT_NOTE_CODE)?; let inputs_3 = NoteInputs::new(vec![ONE, Felt::new(2)])?; - let metadata_3 = NoteMetadata::new( - account_id, - note_type3, - tag3, - NoteExecutionHint::on_block_slot(1, 2, 3), - aux3, - ); + let metadata_3 = NoteMetadata::new(account_id, note_type3, tag3); let vault_3 = NoteAssets::new(vec![])?; let recipient_3 = NoteRecipient::new(serial_num_3, note_script_3, inputs_3); let expected_output_note_3 = Note::new(vault_3, metadata_3, recipient_3); @@ -374,10 +367,10 @@ async fn executed_transaction_output_notes() -> anyhow::Result<()> { let resulting_output_note_2 = executed_transaction.output_notes().get_note(1); let expected_note_id_2 = expected_output_note_2.id(); - let expected_note_metadata_2 = expected_output_note_2.metadata(); + let expected_note_metadata_2 = expected_output_note_2.metadata().clone(); assert_eq!( - NoteHeader::from(resulting_output_note_2), - NoteHeader::new(expected_note_id_2, *expected_note_metadata_2) + *resulting_output_note_2.header(), + NoteHeader::new(expected_note_id_2, expected_note_metadata_2) ); // assert that the expected output note 3 is present and has no assets diff --git a/crates/miden-testing/src/mock_chain/chain.rs b/crates/miden-testing/src/mock_chain/chain.rs index 5e94bc5df9..bfcaefd60a 100644 --- a/crates/miden-testing/src/mock_chain/chain.rs +++ b/crates/miden-testing/src/mock_chain/chain.rs @@ -908,7 +908,7 @@ impl MockChain { created_note.id(), MockChainNote::Private( created_note.id(), - *created_note.metadata(), + created_note.metadata().clone(), note_inclusion_proof, ), ); diff --git a/crates/miden-testing/tests/lib.rs b/crates/miden-testing/tests/lib.rs index 0c3c1d790f..8bda709733 100644 --- a/crates/miden-testing/tests/lib.rs +++ b/crates/miden-testing/tests/lib.rs @@ -5,13 +5,13 @@ mod scripts; mod wallet; use miden_processor::utils::Deserializable; +use miden_protocol::Word; use miden_protocol::account::AccountId; use miden_protocol::asset::FungibleAsset; use miden_protocol::crypto::utils::Serializable; use miden_protocol::note::{Note, NoteAssets, NoteInputs, NoteMetadata, NoteRecipient, NoteType}; use miden_protocol::testing::account_id::ACCOUNT_ID_SENDER; use miden_protocol::transaction::{ExecutedTransaction, ProvenTransaction}; -use miden_protocol::{Word, ZERO}; use miden_standards::code_builder::CodeBuilder; use miden_tx::{ LocalTransactionProver, @@ -56,15 +56,12 @@ pub fn get_note_with_fungible_asset_and_script( fungible_asset: FungibleAsset, note_script: &str, ) -> Note { - use miden_protocol::note::NoteExecutionHint; - let note_script = CodeBuilder::default().compile_note_script(note_script).unwrap(); let serial_num = Word::from([1, 2, 3, 4u32]); let sender_id = AccountId::try_from(ACCOUNT_ID_SENDER).unwrap(); let vault = NoteAssets::new(vec![fungible_asset.into()]).unwrap(); - let metadata = - NoteMetadata::new(sender_id, NoteType::Public, 1.into(), NoteExecutionHint::Always, ZERO); + let metadata = NoteMetadata::new(sender_id, NoteType::Public, 1.into()); let inputs = NoteInputs::new(vec![]).unwrap(); let recipient = NoteRecipient::new(serial_num, note_script, inputs); diff --git a/crates/miden-testing/tests/scripts/faucet.rs b/crates/miden-testing/tests/scripts/faucet.rs index d6edecea48..e842eff371 100644 --- a/crates/miden-testing/tests/scripts/faucet.rs +++ b/crates/miden-testing/tests/scripts/faucet.rs @@ -120,13 +120,7 @@ pub fn verify_minted_output_note( assert_eq!(output_note.id(), id); assert_eq!( output_note.metadata(), - &NoteMetadata::new( - faucet.id(), - params.note_type, - params.tag, - params.note_execution_hint, - params.aux - ) + &NoteMetadata::new(faucet.id(), params.note_type, params.tag) ); Ok(()) @@ -314,8 +308,8 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul let recipient_account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?; let amount = Felt::new(75); let tag = NoteTag::default(); - let aux = Felt::new(27); - let note_execution_hint = NoteExecutionHint::on_block_slot(5, 6, 7); + // TODO(note_attachment): Replace with attachment. + // let aux = Felt::new(27); let note_type = NoteType::Public; // Create a simple output note script @@ -346,7 +340,7 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul let output_script_root = note_recipient.script().root(); let asset = FungibleAsset::new(faucet.id(), amount.into())?; - let metadata = NoteMetadata::new(faucet.id(), note_type, tag, note_execution_hint, aux); + let metadata = NoteMetadata::new(faucet.id(), note_type, tag); let expected_note = Note::new(NoteAssets::new(vec![asset.into()])?, metadata, note_recipient); let trigger_note_script_code = format!( @@ -377,9 +371,9 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul # => [RECIPIENT] # Now call distribute with the computed recipient - push.{note_execution_hint} + push.0 # note_execution_hint push.{note_type} - push.{aux} + push.0 # aux push.{tag} push.{amount} # => [amount, tag, aux, note_type, execution_hint, RECIPIENT] @@ -401,9 +395,7 @@ async fn test_public_note_creation_with_script_from_datastore() -> anyhow::Resul input6 = note_inputs.values()[6], script_root = output_script_root, serial_num = serial_num, - aux = aux, tag = u32::from(tag), - note_execution_hint = Felt::from(note_execution_hint), amount = amount, ); diff --git a/crates/miden-testing/tests/scripts/send_note.rs b/crates/miden-testing/tests/scripts/send_note.rs index 84d8c02934..b272d1f963 100644 --- a/crates/miden-testing/tests/scripts/send_note.rs +++ b/crates/miden-testing/tests/scripts/send_note.rs @@ -7,7 +7,6 @@ use miden_protocol::crypto::rand::{FeltRng, RpoRandomCoin}; use miden_protocol::note::{ Note, NoteAssets, - NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, @@ -36,13 +35,7 @@ async fn test_send_note_script_basic_wallet() -> anyhow::Result<()> { let sender_account_interface = AccountInterface::from_account(&sender_basic_wallet_account); let tag = NoteTag::with_account_target(sender_basic_wallet_account.id()); - let metadata = NoteMetadata::new( - sender_basic_wallet_account.id(), - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ); + let metadata = NoteMetadata::new(sender_basic_wallet_account.id(), NoteType::Public, tag); let assets = NoteAssets::new(vec![sent_asset]).unwrap(); let note_script = CodeBuilder::default().compile_note_script("begin nop end").unwrap(); let serial_num = RpoRandomCoin::new(Word::from([1, 2, 3, 4u32])).draw_word(); @@ -96,13 +89,8 @@ async fn test_send_note_script_basic_fungible_faucet() -> anyhow::Result<()> { AccountInterface::from_account(&sender_basic_fungible_faucet_account); let tag = NoteTag::with_account_target(sender_basic_fungible_faucet_account.id()); - let metadata = NoteMetadata::new( - sender_basic_fungible_faucet_account.id(), - NoteType::Public, - tag, - NoteExecutionHint::always(), - Default::default(), - ); + let metadata = + NoteMetadata::new(sender_basic_fungible_faucet_account.id(), NoteType::Public, tag); let assets = NoteAssets::new(vec![Asset::Fungible( FungibleAsset::new(sender_basic_fungible_faucet_account.id(), 10).unwrap(), )])?; diff --git a/crates/miden-testing/tests/scripts/swap.rs b/crates/miden-testing/tests/scripts/swap.rs index 777adb0a69..157da3d039 100644 --- a/crates/miden-testing/tests/scripts/swap.rs +++ b/crates/miden-testing/tests/scripts/swap.rs @@ -1,15 +1,7 @@ use anyhow::Context; use miden_protocol::account::{Account, AccountId, AccountStorageMode, AccountType}; use miden_protocol::asset::{Asset, FungibleAsset, NonFungibleAsset}; -use miden_protocol::note::{ - Note, - NoteAssets, - NoteDetails, - NoteExecutionHint, - NoteMetadata, - NoteTag, - NoteType, -}; +use miden_protocol::note::{Note, NoteAssets, NoteDetails, NoteMetadata, NoteTag, NoteType}; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, @@ -138,7 +130,7 @@ async fn consume_swap_note_private_payback_note() -> anyhow::Result<()> { let full_payback_note = Note::new( payback_note.assets().clone(), - *output_payback_note.metadata(), + output_payback_note.metadata().clone(), payback_note.recipient().clone(), ); @@ -217,7 +209,7 @@ async fn consume_swap_note_public_payback_note() -> anyhow::Result<()> { let full_payback_note = Note::new( payback_note.assets().clone(), - *output_payback_note.metadata(), + output_payback_note.metadata().clone(), payback_note.recipient().clone(), ); @@ -353,14 +345,15 @@ pub fn create_p2id_note_exact( target: AccountId, assets: Vec, note_type: NoteType, - aux: Felt, + // TODO(note_attachment): Replace with attachment. + _aux: Felt, serial_num: Word, ) -> Result { let recipient = utils::build_p2id_recipient(target, serial_num)?; let tag = NoteTag::with_account_target(target); - let metadata = NoteMetadata::new(sender, note_type, tag, NoteExecutionHint::always(), aux); + let metadata = NoteMetadata::new(sender, note_type, tag); let vault = NoteAssets::new(assets)?; Ok(Note::new(vault, metadata, recipient)) diff --git a/crates/miden-tx/src/errors/mod.rs b/crates/miden-tx/src/errors/mod.rs index 5be724e9a2..2ea4c2f16a 100644 --- a/crates/miden-tx/src/errors/mod.rs +++ b/crates/miden-tx/src/errors/mod.rs @@ -226,8 +226,6 @@ pub enum TransactionKernelError { "note inputs data extracted from the advice map by the event handler is not well formed" )] MalformedNoteInputs(#[source] NoteError), - #[error("note metadata created by the event handler is not well formed")] - MalformedNoteMetadata(#[source] NoteError), #[error( "note script data `{data:?}` extracted from the advice map by the event handler is not well formed" )] diff --git a/crates/miden-tx/src/executor/exec_host.rs b/crates/miden-tx/src/executor/exec_host.rs index 7f4fa78114..3cd6412eb4 100644 --- a/crates/miden-tx/src/executor/exec_host.rs +++ b/crates/miden-tx/src/executor/exec_host.rs @@ -565,7 +565,7 @@ where self.base_host.on_account_push_procedure_index(code_commitment, procedure_root) }, - TransactionEvent::NoteAfterCreated { note_idx, metadata, recipient_data } => { + TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data } => { match recipient_data { RecipientData::Digest(recipient_digest) => { self.base_host.output_note_from_recipient_digest( diff --git a/crates/miden-tx/src/host/kernel_process.rs b/crates/miden-tx/src/host/kernel_process.rs index 50dbb83840..5d948de35a 100644 --- a/crates/miden-tx/src/host/kernel_process.rs +++ b/crates/miden-tx/src/host/kernel_process.rs @@ -10,6 +10,7 @@ use miden_protocol::transaction::memory::{ ACCT_STORAGE_SLOT_VALUE_OFFSET, ACTIVE_INPUT_NOTE_PTR, NATIVE_NUM_ACCT_STORAGE_SLOTS_PTR, + NUM_OUTPUT_NOTES_PTR, }; use miden_protocol::{Hasher, Word}; @@ -31,6 +32,9 @@ pub(super) trait TransactionKernelProcess { #[allow(dead_code)] fn get_num_storage_slots(&self) -> Result; + /// Returns the current number of output notes. + fn get_num_output_notes(&self) -> u64; + fn get_vault_root(&self, vault_root_ptr: Felt) -> Result; fn get_active_note_id(&self) -> Result, TransactionKernelError>; @@ -129,6 +133,14 @@ impl<'a> TransactionKernelProcess for ProcessState<'a> { Ok(num_storage_slots_felt.as_int()) } + fn get_num_output_notes(&self) -> u64 { + // Read the number from memory or default to 0 if the location hasn't been accessed + // previously (e.g. when no notes have been created yet). + self.get_mem_value(self.ctx(), NUM_OUTPUT_NOTES_PTR) + .map(|num_output_notes| num_output_notes.as_int()) + .unwrap_or(0) + } + /// Returns the ID of the active note, or None if the note execution hasn't started yet or has /// already ended. /// diff --git a/crates/miden-tx/src/host/tx_event.rs b/crates/miden-tx/src/host/tx_event.rs index ea7807ecf6..3515f92e32 100644 --- a/crates/miden-tx/src/host/tx_event.rs +++ b/crates/miden-tx/src/host/tx_event.rs @@ -3,7 +3,15 @@ use alloc::vec::Vec; use miden_processor::{AdviceMutation, ProcessState, RowIndex}; use miden_protocol::account::{AccountId, StorageMap, StorageSlotName, StorageSlotType}; use miden_protocol::asset::{Asset, AssetVault, AssetVaultKey, FungibleAsset}; -use miden_protocol::note::{NoteId, NoteInputs, NoteMetadata, NoteRecipient, NoteScript}; +use miden_protocol::note::{ + NoteId, + NoteInputs, + NoteMetadata, + NoteRecipient, + NoteScript, + NoteTag, + NoteType, +}; use miden_protocol::transaction::{TransactionEventId, TransactionSummary}; use miden_protocol::vm::EventId; use miden_protocol::{Felt, Hasher, Word}; @@ -97,7 +105,7 @@ pub(crate) enum TransactionEvent { procedure_root: Word, }, - NoteAfterCreated { + NoteBeforeCreated { /// The note index extracted from the stack. note_idx: usize, /// The note metadata extracted from the stack. @@ -325,16 +333,16 @@ impl TransactionEvent { }) }, - TransactionEventId::NoteBeforeCreated => None, + TransactionEventId::NoteBeforeCreated => { + // Expected stack state: [event, tag, note_type, RECIPIENT] + let tag = process.get_stack_item(1); + let note_type = process.get_stack_item(2); + let recipient_digest = process.get_stack_word_be(3); - TransactionEventId::NoteAfterCreated => { - // Expected stack state: [event, NOTE_METADATA, note_ptr, RECIPIENT, note_idx] - let metadata_word = process.get_stack_word_be(1); - let metadata = NoteMetadata::try_from(metadata_word) - .map_err(TransactionKernelError::MalformedNoteMetadata)?; + let sender = base_host.native_account_id(); + let metadata = build_note_metadata(sender, note_type, tag)?; - let recipient_digest = process.get_stack_word_be(6); - let note_idx = process.get_stack_item(10).as_int() as usize; + let note_idx = process.get_num_output_notes() as usize; // try to read the full recipient from the advice provider let recipient_data = if process.has_advice_map_entry(recipient_digest) { @@ -379,9 +387,11 @@ impl TransactionEvent { RecipientData::Digest(recipient_digest) }; - Some(TransactionEvent::NoteAfterCreated { note_idx, metadata, recipient_data }) + Some(TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data }) }, + TransactionEventId::NoteAfterCreated => None, + TransactionEventId::NoteBeforeAddAsset => { // Expected stack state: [event, ASSET, note_ptr, num_of_assets, note_idx] let note_idx = process.get_stack_item(7).as_int() as usize; @@ -658,6 +668,30 @@ fn extract_tx_summary<'store, STORE>( // HELPER FUNCTIONS // ================================================================================================ +/// Builds the note metadata from sender, note type and tag if all inputs are valid. +fn build_note_metadata( + sender: AccountId, + note_type: Felt, + tag: Felt, +) -> Result { + let note_type = u8::try_from(note_type) + .map_err(|_| TransactionKernelError::other("failed to decode note_type into u8")) + .and_then(|note_type_byte| { + NoteType::try_from(note_type_byte).map_err(|source| { + TransactionKernelError::other_with_source( + "failed to decode note_type from u8", + source, + ) + }) + })?; + + let tag = u32::try_from(tag) + .map_err(|_| TransactionKernelError::other("failed to decode note tag into u32")) + .map(NoteTag::new)?; + + Ok(NoteMetadata::new(sender, note_type, tag)) +} + /// Extracts a word from a slice of field elements. #[inline(always)] fn extract_word(commitments: &[Felt], start: usize) -> Word { diff --git a/crates/miden-tx/src/prover/prover_host.rs b/crates/miden-tx/src/prover/prover_host.rs index de31f51da4..470a1195cb 100644 --- a/crates/miden-tx/src/prover/prover_host.rs +++ b/crates/miden-tx/src/prover/prover_host.rs @@ -140,7 +140,7 @@ where self.base_host.on_account_push_procedure_index(code_commitment, procedure_root) }, - TransactionEvent::NoteAfterCreated { note_idx, metadata, recipient_data } => { + TransactionEvent::NoteBeforeCreated { note_idx, metadata, recipient_data } => { match recipient_data { RecipientData::Digest(recipient_digest) => self .base_host