use.$kernel::account
use.$kernel::memory
use.$kernel::note
use.$kernel::asset
use.$kernel::constants
use.std::word
# CONSTANTS
# =================================================================================================
# Constants for different note types
const.PUBLIC_NOTE=1 # 0b01
const.PRIVATE_NOTE=2 # 0b10
const.ENCRYPTED_NOTE=3 # 0b11
# The note type must be PUBLIC, unless the high bits are `0b11`. (See the table below.)
const.LOCAL_ANY_PREFIX=3 # 0b11
# ERRORS
# =================================================================================================
const.ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT="number of output notes in the transaction exceeds the maximum limit of 1024"
const.ERR_NOTE_INVALID_TYPE="invalid note type"
const.ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS="requested output note index should be less than the total number of created output notes"
const.ERR_NOTE_INVALID_INDEX="failed to find note at the given index; index must be within [0, num_of_notes]"
const.ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED="adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807"
const.ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS="non-fungible asset that already exists in the note cannot be added again"
# The 2 highest bits in the u32 tag have the following meaning:
#
# | Prefix | Name | [`NoteExecutionMode`] | Target | Allowed [`NoteType`] |
# | :----: | :--------------------: | :-------------------: | :----------------------: | :------------------: |
# | `0b00` | `NetworkAccount` | Network | Network Account | [`NoteType::Public`] |
# | `0b01` | `NetworkUseCase` | Network | Use case | [`NoteType::Public`] |
# | `0b10` | `LocalPublicAny` | Local | Any | [`NoteType::Public`] |
# | `0b11` | `LocalAny` | Local | Any | Any |
#
# Execution: Is a hint for the network, to check if the note can be consumed by a network controlled
# account
# Target: Is a hint for the type of target. Use case means the note may be consumed by anyone,
# specific means there is a specific target for the note (the target may be a public key, a user
# that knows some secret, or a specific account ID)
#
# Only the note type from the above list is enforced. The other values are only hints intended as a
# best effort optimization strategy. A badly formatted note may 1. not be consumed because honest
# users won't see the note 2. generate slightly more load as extra validation is performed for the
# invalid tags. None of these scenarios have any significant impact.
const.ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX="invalid note type for the given note tag prefix"
const.ERR_NOTE_TAG_MUST_BE_U32="the note's tag must fit into a u32 so the 32 most significant bits must be zero"
# EVENTS
# =================================================================================================
# Event emitted before a new note is created.
const.NOTE_BEFORE_CREATED_EVENT=event("miden::note::before_created")
# Event emitted after a new note is created.
const.NOTE_AFTER_CREATED_EVENT=event("miden::note::after_created")
# Event emitted before an ASSET is added to a note
const.NOTE_BEFORE_ADD_ASSET_EVENT=event("miden::note::before_add_asset")
# Event emitted after an ASSET is added to a note
const.NOTE_AFTER_ADD_ASSET_EVENT=event("miden::note::after_add_asset")
# OUTPUT NOTE PROCEDURES
# =================================================================================================
#! Creates a new note and returns the index of the note.
#!
#! Inputs: [tag, aux, note_type, execution_hint, RECIPIENT]
#! Outputs: [note_idx]
#!
#! 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.
#!
#! Panics if:
#! - the note_type is not valid.
#! - the note_tag is not an u32.
#! - the note_tag starts with anything but 0b11 and note_type is not public.
#! - the number of output notes exceeds the maximum limit of 1024.
export.create
emit.NOTE_BEFORE_CREATED_EVENT
exec.build_metadata
# => [NOTE_METADATA, 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]
# 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]
movdn.4
# => [NOTE_METADATA, 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, RECIPIENT, note_idx]
# set the RECIPIENT for the output note
exec.memory::set_output_note_recipient dropw
# => [note_idx]
end
#! Returns the information about assets in the output note with the specified index.
#!
#! The provided output note index is expected to be less than the total number of output notes.
#!
#! Inputs: [note_index]
#! Outputs: [ASSETS_COMMITMENT, num_assets]
#!
#! Where:
#! - note_index is the index of the output note whose assets info should be returned.
#! - num_assets is the number of assets in the specified note.
#! - ASSETS_COMMITMENT is a sequential hash of the assets in the specified note.
export.get_assets_info
# get the note data pointer based on the index of the requested note
exec.memory::get_output_note_ptr
# => [note_data_ptr]
# get the number of assets in the note
dup exec.memory::get_output_note_num_assets swap
# => [note_data_ptr, num_assets]
# get the assets commitment
dup exec.note::compute_output_note_assets_commitment
# => [ASSETS_COMMITMENT, note_data_ptr, num_assets]
# next we should store the assets in the advice map using the computed assets commitment to be
# able to get the assets later (in the `miden::output_note::get_assets` procedure)
# get the start and the end pointers of the asset data
#
# notice that if the number of assets is odd, the asset data end pointer will be shifted one
# word further to make the assets number even (the same way it is done in the
# `note::compute_output_note_assets_commitment` procedure)
movup.4 exec.memory::get_output_note_asset_data_ptr
# => [assets_data_ptr, ASSETS_COMMITMENT, num_assets]
dup dup.6 dup is_odd add
# => [padded_num_assets, assets_data_ptr, assets_data_ptr, ASSETS_COMMITMENT, num_assets]
mul.4 add
# => [assets_end_ptr, assets_start_ptr, ASSETS_COMMITMENT, num_assets]
movdn.5 movdn.4
# => [ASSETS_COMMITMENT, assets_start_ptr, assets_end_ptr, num_assets]
# store the assets data to the advice map using ASSETS_COMMITMENT as a key
adv.insert_mem
# => [ASSETS_COMMITMENT, assets_start_ptr, assets_end_ptr, num_assets]
# remove asset pointers from the stack
movup.4 drop movup.4 drop
# => [ASSETS_COMMITMENT, num_assets]
end
#! Adds the ASSET to the note specified by the index.
#!
#! Inputs: [note_idx, ASSET]
#! Outputs: []
#!
#! Where:
#! - note_idx is the index of the note to which the asset is added.
#! - ASSET can be a fungible or non-fungible asset.
#!
#! Panics if:
#! - the ASSET is malformed (e.g., invalid faucet ID).
#! - the max amount of fungible assets is exceeded.
#! - the non-fungible asset already exists in the note.
#! - the total number of ASSETs exceeds the maximum of 256.
export.add_asset
# check if the note exists, it must be within [0, num_of_notes]
dup exec.memory::get_num_output_notes lte assert.err=ERR_NOTE_INVALID_INDEX
# => [note_idx, ASSET]
# get a pointer to the memory address of the note at which the asset will be stored
dup movdn.5 exec.memory::get_output_note_ptr
# => [note_ptr, ASSET, note_idx]
# get current num of assets
dup exec.memory::get_output_note_num_assets movdn.5
# => [note_ptr, ASSET, num_of_assets, note_idx]
# validate the ASSET
movdn.4 exec.asset::validate_asset
# => [ASSET, note_ptr, num_of_assets, note_idx]
# emit event to signal that a new asset is going to be added to the note.
emit.NOTE_BEFORE_ADD_ASSET_EVENT
# => [ASSET, note_ptr, num_of_assets, note_idx]
# Check if ASSET to add is fungible
exec.asset::is_fungible_asset
# => [is_fungible_asset?, ASSET, note_ptr, num_of_assets, note_idx]
if.true
# ASSET to add is fungible
exec.add_fungible_asset
# => [note_ptr, note_idx]
else
# ASSET to add is non-fungible
exec.add_non_fungible_asset
# => [note_ptr, note_idx]
end
# => [note_ptr, note_idx]
# update the assets commitment dirty flag to signal that the current assets commitment is not
# valid anymore
push.1 swap exec.memory::set_output_note_dirty_flag
# => [note_idx]
# emit event to signal that a new asset was added to the note.
emit.NOTE_AFTER_ADD_ASSET_EVENT
# => [note_idx]
# drop the note index
drop
# => []
end
#! Assert that the provided note index is less than the total number of output notes.
#!
#! Inputs: [note_index]
#! Outputs: [note_index]
export.assert_note_index_in_bounds
# assert that the provided note index is less than the total number of notes
dup exec.memory::get_num_output_notes
# => [output_notes_num, note_index, note_index]
u32assert2.err=ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS
u32lt assert.err=ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS
# => [note_index]
end
# HELPER PROCEDURES
# =================================================================================================
#! Builds the stack into the NOTE_METADATA word, encoding the note type and execution hint into a
#! single element.
#! 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]
#!
#! 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.
export.build_metadata
# Validate the note type.
# --------------------------------------------------------------------------------------------
# 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]
# copy data to validate the tag
dup.2 push.PUBLIC_NOTE dup.1 dup.3
# => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint]
u32assert.err=ERR_NOTE_TAG_MUST_BE_U32
# => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint]
# enforce the note type depending on the tag' bits
u32shr.30 eq.LOCAL_ANY_PREFIX cdrop
assert_eq.err=ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX
# => [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.
# --------------------------------------------------------------------------------------------
# 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.
# --------------------------------------------------------------------------------------------
# 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]
# Merge sender_id_suffix, note_type and execution_hint_tag.
# --------------------------------------------------------------------------------------------
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]
# 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]
# Rearrange elements to produce the final note metadata layout.
# --------------------------------------------------------------------------------------------
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]]
end
#! Increments the number of output notes by one. Returns the index of the next note to be created.
#!
#! Inputs: []
#! Outputs: [note_idx]
#!
#! Where:
#! - note_idx is the index of the next note to be created.
proc.increment_num_output_notes
# get the current number of output notes
exec.memory::get_num_output_notes
# => [note_idx]
# assert that there is space for a new note
dup exec.constants::get_max_num_output_notes lt
assert.err=ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT
# => [note_idx]
# increment the number of output notes
dup add.1 exec.memory::set_num_output_notes
# => [note_idx]
end
#! Adds a fungible asset to a note. If the note already holds an asset issued by the same faucet id
#! the two quantities are summed up and the new quantity is stored at the old position in the note.
#! In the other case, the asset is stored at the next available position.
#! Returns the pointer to the note the asset was stored at.
#!
#! Inputs: [ASSET, note_ptr, num_of_assets, note_idx]
#! Outputs: [note_ptr]
#!
#! Where:
#! - ASSET is the fungible asset to be added to the note.
#! - note_ptr is the pointer to the note the asset will be added to.
#! - num_of_assets is the current number of assets.
#! - note_idx is the index of the note the asset will be added to.
#!
#! Panics if
#! - the summed amounts exceed the maximum amount of fungible assets.
proc.add_fungible_asset
dup.4 exec.memory::get_output_note_asset_data_ptr
# => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx]
# compute the pointer at which we should stop iterating
dup dup.7 mul.4 add
# => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx]
# reorganize and pad the stack, prepare for the loop
movdn.5 movdn.5 padw dup.9
# => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
# compute the loop latch
dup dup.10 neq
# => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets,
# note_idx]
while.true
mem_loadw_be
# => [STORED_ASSET, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
dup.4 eq
# => [are_equal, 0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr,
# num_of_assets, note_idx]
if.true
# add the asset quantity, we don't overflow here, bc both ASSETs are valid.
movup.2 movup.6 add
# => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr,
# num_of_assets, note_idx]
# check that we don't overflow bc we use lte
dup exec.asset::get_fungible_asset_max_amount lte
assert.err=ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED
# => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr,
# num_of_assets, note_idx]
# prepare stack to store the "updated" ASSET'' with the new quantity
movdn.5
# => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
# decrease num_of_assets by 1 to offset incrementing it later
movup.9 sub.1 movdn.9
# => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets - 1, note_idx]
# end the loop we add 0's to the stack to have the correct number of elements
push.0.0 dup.9 push.0
# => [0, asset_ptr, 0, 0, 0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr,
# num_of_assets - 1, note_idx]
else
# => [0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets,
# note_idx]
# drop ASSETs and increment the asset pointer
movup.2 drop push.0.0 movup.9 add.4 dup movdn.10
# => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr,
# num_of_assets, note_idx]
# check if we reached the end of the loop
dup dup.10 neq
end
end
# => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
# prepare stack for storing the ASSET
movdn.4 dropw
# => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
# Store the fungible asset, either the combined ASSET or the new ASSET
mem_storew_be dropw drop drop
# => [note_ptr, num_of_assets, note_idx]
# increase the number of assets in the note
swap add.1 dup.1 exec.memory::set_output_note_num_assets
# => [note_ptr, note_idx]
end
#! Adds a non-fungible asset to a note at the next available position.
#! Returns the pointer to the note the asset was stored at.
#!
#! Inputs: [ASSET, note_ptr, num_of_assets, note_idx]
#! Outputs: [note_ptr, note_idx]
#!
#! Where:
#! - ASSET is the non-fungible asset to be added to the note.
#! - note_ptr is the pointer to the note the asset will be added to.
#! - num_of_assets is the current number of assets.
#! - note_idx is the index of the note the asset will be added to.
#!
#! Panics if:
#! - the non-fungible asset already exists in the note.
proc.add_non_fungible_asset
dup.4 exec.memory::get_output_note_asset_data_ptr
# => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx]
# compute the pointer at which we should stop iterating
dup dup.7 mul.4 add
# => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx]
# reorganize and pad the stack, prepare for the loop
movdn.5 movdn.5 padw dup.9
# => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
# compute the loop latch
dup dup.10 neq
# => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets,
# note_idx]
while.true
# load the asset and compare
mem_loadw_be exec.word::test_eq
assertz.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS
# => [ASSET', ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
# drop ASSET' and increment the asset pointer
dropw movup.5 add.4 dup movdn.6 padw movup.4
# => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr,
# num_of_assets, note_idx]
# check if we reached the end of the loop
dup dup.10 neq
end
# => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
# prepare stack for storing the ASSET
movdn.4 dropw
# => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
# end of the loop reached, no error so we can store the non-fungible asset
mem_storew_be dropw drop drop
# => [note_ptr, num_of_assets, note_idx]
# increase the number of assets in the note
swap add.1 dup.1 exec.memory::set_output_note_num_assets
# => [note_ptr, note_idx]
end