miden-protocol 0.17.0-rc.5

Core components of the Miden protocol
Documentation
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