use {
AccountId,
Bool,
NoteMetadata,
NoteRecipient,
NoteScriptRoot,
NoteSerialNumber,
NoteStorageCommitment,
NoteTag,
NoteType,
} from miden::protocol::types
use miden::protocol::account_id
use miden::core::crypto::hashes::poseidon2
pub use {ATTACHMENT_SCHEME_NONE, MAX_ATTACHMENT_SCHEME, MAX_ATTACHMENT_TOTAL_WORDS, MAX_ATTACHMENT_WORDS, MAX_NOTE_STORAGE_ITEMS, NOTE_TYPE_PRIVATE, NOTE_TYPE_PUBLIC}
from miden::protocol_utils::note
# ERRORS
# =================================================================================================
const ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT =
"number of note storage exceeded the maximum limit of 1024"
# NOTE UTILITY PROCEDURES
# =================================================================================================
#! Computes the commitment to the note storage starting at the specified memory address.
#!
#! This procedure checks that the provided number of note storage items is within limits and then computes
#! the commitment.
#!
#! If the number of note storage items is 0, procedure returns the empty word: [0, 0, 0, 0].
#!
#! Inputs: [storage_ptr, num_storage_items]
#! Outputs: [STORAGE_COMMITMENT]
#!
#! Cycles:
#! - If number of elements divides by 8: 56 cycles + 3 * words
#! - Else: 189 cycles + 3 * words
#!
#! Panics if:
#! - storage_ptr is not word-aligned (i.e., is not a multiple of 4).
#! - num_storage_items is greater than 1024.
#!
#! Invocation: exec
pub proc compute_storage_commitment(
storage_ptr: ptr<felt>,
num_storage_items: u16
) -> NoteStorageCommitment
# check that number of storage items is less than or equal to MAX_NOTE_STORAGE_ITEMS
dup.1 push.MAX_NOTE_STORAGE_ITEMS
u32assert2.err=ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT
u32lte assert.err=ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT
# => [storage_ptr, num_storage_items]
# compute the storage commitment (over the unpadded values)
exec.poseidon2::hash_elements
# => [STORAGE_COMMITMENT]
end
#! Computes the recipient hash from note storage, script root, and serial number.
#!
#! This procedure computes the commitment of the note storage and then uses it to calculate the note
#! recipient by hashing this commitment, the provided script root, and the serial number.
#!
#! Inputs:
#! Operand stack: [storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT]
#! Outputs:
#! Operand stack: [RECIPIENT]
#! Advice map: {
#! STORAGE_COMMITMENT: [INPUTS],
#! RECIPIENT: [SERIAL_SCRIPT_HASH, STORAGE_COMMITMENT],
#! SERIAL_SCRIPT_HASH: [SERIAL_HASH, SCRIPT_ROOT],
#! SERIAL_HASH: [SERIAL_NUM, EMPTY_WORD],
#! }
#!
#! Where:
#! - storage_ptr is the memory address where the note storage are stored.
#! - num_storage_items is the number of input values.
#! - SCRIPT_ROOT is the script root of the note.
#! - SERIAL_NUM is the serial number of the note.
#! - RECIPIENT is the commitment to the input note's script, storage, and the serial number.
#!
#! Locals:
#! - 0: storage_ptr
#! - 1: num_storage_items
#!
#! Panics if:
#! - storage_ptr is not word-aligned (i.e., is not a multiple of 4).
#! - num_storage_items is greater than 1024.
#!
#! Invocation: exec
pub proc compute_and_store_recipient(
storage_ptr: ptr<felt>,
num_storage_items: u16,
serial_num: NoteSerialNumber,
script_root: NoteScriptRoot
) -> NoteRecipient
dup.1 dup.1
# => [storage_ptr, num_storage_items, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT]
exec.compute_storage_commitment
# => [STORAGE_COMMITMENT, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT]
movup.5 movup.5 dup movdn.2
# => [storage_ptr, num_storage_items, storage_ptr, STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT]
add swap
# => [storage_ptr, end_ptr, STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT]
movdn.5 movdn.5
# => [STORAGE_COMMITMENT, storage_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT]
adv.insert_mem
# => [STORAGE_COMMITMENT, storage_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT]
movup.4 drop movup.4 drop
# => [STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT]
movdnw.2
# => [SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT]
padw swapw
# => [SERIAL_NUM, EMPTY_WORD, SCRIPT_ROOT, STORAGE_COMMITMENT]
adv.insert_hdword exec.poseidon2::merge
# => [SERIAL_COMMITMENT, SCRIPT_ROOT, STORAGE_COMMITMENT]
adv.insert_hdword exec.poseidon2::merge
# => [SERIAL_SCRIPT_COMMITMENT, STORAGE_COMMITMENT]
adv.insert_hdword exec.poseidon2::merge
# => [RECIPIENT]
end
#! Computes the RECIPIENT for a specified SERIAL_NUM, SCRIPT_ROOT and STORAGE_COMMITMENT.
#!
#! Inputs: [SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT]
#! Outputs: [RECIPIENT]
#!
#! Where:
#! - SERIAL_NUM is the serial number of the recipient.
#! - SCRIPT_ROOT is the commitment of the note script.
#! - STORAGE_COMMITMENT is the commitment of the note storage.
#! - RECIPIENT is the recipient of the note.
#!
#! Invocation: exec
pub proc compute_recipient(
serial_num: NoteSerialNumber,
script_root: NoteScriptRoot,
storage_commitment: NoteStorageCommitment
) -> NoteRecipient
padw swapw
# => [SERIAL_NUM, EMPTY_WORD, SCRIPT_ROOT, STORAGE_COMMITMENT]
exec.poseidon2::merge
# => [SERIAL_NUM_HASH, SCRIPT_ROOT, STORAGE_COMMITMENT]
exec.poseidon2::merge
# => [MERGE_SCRIPT, STORAGE_COMMITMENT]
exec.poseidon2::merge
# => [RECIPIENT]
end
#! Extracts the sender ID from the provided metadata.
#!
#! Inputs: [METADATA]
#! Outputs: [sender_id_suffix, sender_id_prefix]
#!
#! Where:
#! - METADATA is the metadata of a note.
#! - sender_{suffix,prefix} are the suffix and prefix felts of the sender ID of the note which
#! metadata was provided.
pub proc metadata_into_sender(metadata: NoteMetadata) -> AccountId
# => [sender_id_suffix_type_version, sender_id_prefix, tag, attachment_kind_scheme]
# drop tag and attachment_kind_scheme
movup.3 drop movup.2 drop
# => [sender_id_suffix_type_version, sender_id_prefix]
# extract suffix of sender from merged layout, which means clearing the least significant byte
exec.account_id::shape_suffix
# => [sender_id_suffix, sender_id_prefix]
end
#! Extracts the attachment's schemes from the provided metadata.
#!
#! Inputs: [METADATA]
#! Outputs: [attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme]
#!
#! Where:
#! - METADATA is the metadata word of a note.
#! - attachment_n_scheme is the scheme of the nth attachment (0 if absent).
#!
#! Invocation: exec
pub proc metadata_into_attachment_schemes(metadata: NoteMetadata) -> (u16, u16, u16, u16)
# => [sender_id_suffix_type_version, sender_id_prefix, tag, schemes]
drop drop drop
# => [schemes]
u32split swap
# => [schemes_hi, schemes_lo]
# extract attachment scheme 3 from bits 48..64
dup u32and.0xffff0000 u32shr.16
# => [attachment_3_scheme, schemes_hi, schemes_lo]
# extract attachment scheme 2 from bits 32..48
swap u32and.0xffff
# => [attachment_2_scheme, attachment_3_scheme, schemes_lo]
# extract attachment scheme 1 from bits 16..32
dup.2 u32and.0xffff0000 u32shr.16
# => [attachment_1_scheme, attachment_2_scheme, attachment_3_scheme, schemes_lo]
movup.3 u32and.0xffff
# => [attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme]
end
#! Extracts the note type from the provided metadata.
#!
#! The note type is encoded as a single bit at the 6th position from the right side (LSB) of the
#! first felt of the metadata, where 0 = Private and 1 = Public.
#!
#! Inputs: [METADATA]
#! Outputs: [note_type]
#!
#! Where:
#! - METADATA is the metadata of a note, laid out on the stack as
#! [sender_id_suffix_type_version, sender_id_prefix, tag, attachment_schemes].
#! The first felt (sender_id_suffix_type_version) has the following bit layout:
#! [sender_id_suffix (56 bits) | reserved (1 bit) | note_type (1 bit) | version (6 bits)]
#! - note_type is the type of the note (0 for private, 1 for public).
#!
#! Invocation: exec
pub proc metadata_into_note_type(metadata: NoteMetadata) -> NoteType
movdn.3 drop drop drop
# => [sender_id_suffix_type_version]
u32split swap drop
# => [lo32]
u32shr.6
# => [shifted]
u32and.1
# => [note_type]
end
#! Extracts the tag from the provided metadata.
#!
#! The tag is stored in the lower 32 bits of the tag element. The upper 32 bits are reserved.
#!
#! Inputs: [METADATA]
#! Outputs: [tag]
#!
#! Where:
#! - METADATA is the metadata word of a note.
#! - tag is the lower 32 bits of the tag element.
#!
#! Invocation: exec
pub proc metadata_into_tag(metadata: NoteMetadata) -> NoteTag
drop drop swap drop
# => [tag_element]
# extract the lower 32 bits as the tag
u32split swap drop
# => [tag]
end
#! Searches the metadata for the specified attachment scheme and returns the index of the
#! first matching slot.
#!
#! Inputs: [attachment_scheme, METADATA]
#! Outputs: [is_found, attachment_idx]
#!
#! Where:
#! - attachment_scheme is the scheme to search for.
#! - METADATA is the metadata word of a note.
#! - is_found is 1 if the scheme was found, 0 otherwise.
#! - attachment_idx is the index (0-3) of the first matching slot, or undefined if not found.
#!
#! Invocation: exec
pub proc find_attachment_idx(attachment_scheme: u16, metadata: NoteMetadata) -> (Bool, u8)
movdn.4 exec.metadata_into_attachment_schemes
# => [scheme_0, scheme_1, scheme_2, scheme_3, attachment_scheme]
# initialize is_found = 0 and attachment_idx = 0
movup.4 push.0 push.0
# => [attachment_idx = 0, is_found = 0, attachment_scheme, scheme_0, scheme_1, scheme_2, scheme_3]
# iterate over the four schemes
repeat.4
# => [attachment_idx, is_found, attachment_scheme, scheme_n, ...]
# check if scheme_n is the scheme we're trying to find
dup.2 movup.4 eq
# => [is_scheme_n, attachment_idx, is_found, attachment_scheme, scheme_n+1, ...]
# set is_found = is_found || is_scheme_n
movup.2 or swap
# => [attachment_idx, is_found', attachment_scheme, scheme_n+1, ...]
# create prospective attachment idx by incrementing the current one
dup add.1 swap dup.2
# => [is_found', attachment_idx, attachment_idx+1, is_found', attachment_scheme, scheme_n+1, ...]
# if is_found' attachment_idx remains.
# if !is_found' attachment_idx+1 remains.
# this essentially increments the attachment idx as long as no match was found.
cdrop
# => [attachment_idx', is_found', attachment_scheme, scheme_n+1, ...]
end
# => [attachment_idx', is_found', attachment_scheme]
movup.2 drop swap
# => [is_found', attachment_idx']
end