use.std::mem use.std::collections::mmr use.std::crypto::hashes::rpo use.kernel::account use.kernel::asset_vault use.kernel::constants use.kernel::memory # CONSTS # ================================================================================================= # Max U32 value, used for initializing the expiration block number const.MAX_BLOCK_NUM=0xFFFFFFFF # ERRORS # ================================================================================================= # The provided global inputs do not match the block hash commitment const.ERR_PROLOGUE_GLOBAL_INPUTS_PROVIDED_DO_NOT_MATCH_BLOCK_HASH_COMMITMENT=0x00020034 # New account must have an empty vault const.ERR_PROLOGUE_NEW_ACCOUNT_VAULT_MUST_BE_EMPTY=0x00020035 # Reserved slot for new fungible faucet is not empty const.ERR_PROLOGUE_NEW_FUNGIBLE_FAUCET_RESERVED_SLOT_MUST_BE_EMPTY=0x00020036 # Reserved slot for new fungible faucet has an invalid type const.ERR_PROLOGUE_NEW_FUNGIBLE_FAUCET_RESERVED_SLOT_INVALID_TYPE=0x00020037 # Reserved slot for non-fungible faucet is not a valid empty SMT const.ERR_PROLOGUE_NEW_NON_FUNGIBLE_FAUCET_RESERVED_SLOT_MUST_BE_VALID_EMPY_SMT=0x00020038 # Reserved slot for new non-fungible faucet has an invalid type const.ERR_PROLOGUE_NEW_NON_FUNGIBLE_FAUCET_RESERVED_SLOT_INVALID_TYPE=0x00020039 # Account data provided does not match the commitment recorded on-chain const.ERR_PROLOGUE_PROVIDED_ACCOUNT_DATA_DOES_NOT_MATCH_ON_CHAIN_COMMITMENT=0x0002003A # Existing accounts must have a non-zero nonce const.ERR_PROLOGUE_EXISTING_ACCOUNT_MUST_HAVE_NON_ZERO_NONCE=0x0002003B # Account IDs provided via global inputs and advice provider do not match const.ERR_PROLOGUE_MISMATCH_OF_ACCOUNT_IDS_FROM_GLOBAL_INPUTS_AND_ADVICE_PROVIDER=0x0002003C # Reference block MMR and note's authentication MMR must match const.ERR_PROLOGUE_MISMATCH_OF_REFERENCE_BLOCK_MMR_AND_NOTE_AUTHENTICATION_MMR=0x0002003D # Number of note assets exceeds the maximum limit of 256 const.ERR_PROLOGUE_NUMBER_OF_NOTE_ASSETS_EXCEEDS_LIMIT=0x0002003E # Provided info about assets of an input does not match its commitment const.ERR_PROLOGUE_PROVIDED_INPUT_ASSETS_INFO_DOES_NOT_MATCH_ITS_COMMITMENT=0x0002003F # Number of input notes exceeds the kernel's maximum limit of 1024 const.ERR_PROLOGUE_NUMBER_OF_INPUT_NOTES_EXCEEDS_LIMIT=0x00020040 # Note commitment computed from the input note data does not match given note commitment const.ERR_PROLOGUE_INPUT_NOTES_COMMITMENT_MISMATCH=0x00020041 # PUBLIC INPUTS # ================================================================================================= #! Saves global inputs to memory. #! #! Stack: [BLOCK_HASH, acct_id, INITIAL_ACCOUNT_HASH, INPUT_NOTES_COMMITMENT] #! Output: [] #! #! Where: #! - BLOCK_HASH, reference block for the transaction execution. #! - acct_id is the account id of the account that the transaction is being executed against. #! - INITIAL_ACCOUNT_HASH, account state prior to the transaction, EMPTY_WORD for new accounts. #! - INPUT_NOTES_COMMITMENT, see `transaction::api::get_input_notes_commitment`. proc.process_global_inputs exec.memory::set_block_hash dropw exec.memory::set_global_acct_id exec.memory::set_init_acct_hash dropw exec.memory::set_nullifier_commitment dropw end # KERNEL DATA # ================================================================================================= #! Saves the procedure hashes of the chosen kernel to memory. Verifies that kernel root and kernel #! hash match the sequential hash of all kernels and sequential hash of kernel procedures #! respectively. #! #! Inputs: #! Operand stack: [] #! Advice stack: [kernel_version] #! Advice map: { #! KERNEL_ROOT: [KERNEL_HASHES] #! KERNEL_HASH: [KERNEL_PROCEDURE_HASHES] #! } #! Outputs: #! Operand stack: [] #! Advice stack: [] #! #! Where: #! - kernel_version, index of the desired kernel in the array of all kernels available for the #! current transaction #! - KERNEL_ROOT, accumulative hash from all kernel hashes. #! - [KERNEL_HASHES], array of each kernel hash #! - [KERNEL_PROCEDURE_HASHES], array of procedure hashes of the current kernel proc.process_kernel_data # move the kernel offset to the operand stack adv_push.1 # OS => [kernel_version] # AS => [] # load the kernel root from the memory exec.memory::get_kernel_root # OS => [KERNEL_ROOT, kernel_version] # AS => [] # push the kernel hashes from the advice map to the advice stack adv.push_mapvaln # OS => [KERNEL_ROOT, kernel_version] # AS => [len_felts, [KERNEL_HASHES]] # move the number of felt elements in the [KERNEL_HASHES] array to the stack and get the # number of Words from it adv_push.1 div.4 # OS => [len_words, KERNEL_ROOT, kernel_version] # AS => [[KERNEL_HASHES]] # get the pointer to the memory where kernel hashes will be stored # Note: for now we use the same address for kernel hash and for kernel procedures since there is # only one kernel and its hash will be overwritten by the procedures anyway. exec.memory::get_kernel_procedures_ptr swap # OS => [len_words, kernel_mem_ptr, KERNEL_ROOT, kernel_version] # AS => [[KERNEL_HASHES]] # store the kernel hashes in memory exec.mem::pipe_words_to_memory # OS => [C, B, A, kernel_mem_ptr', KERNEL_ROOT, kernel_version] # AS => [] # extract the resulting hash exec.rpo::squeeze_digest # OS => [SEQ_HASH, kernel_mem_ptr', KERNEL_ROOT, kernel_version] # AS => [] # assert that sequential hash matches the precomputed kernel root movup.4 drop assert_eqw # OS => [kernel_version] # AS => [] # get the hash of the kernel which will be used in the current transaction exec.memory::get_kernel_procedures_ptr add # OS => [kernel_ptr] # AS => [] padw movup.4 mem_loadw # OS => [KERNEL_HASH] # AS => [] # push the procedure hashes of the chosen kernel from the advice map to the advice stack adv.push_mapvaln # OS => [KERNEL_HASH] # AS => [len_felts, [PROC_HASHES]] # move the number of felt elements in the [PROC_HASHES] array to the stack and get the # number of Words from it adv_push.1 div.4 # OS => [len_words, KERNEL_HASH] # AS => [[PROC_HASHES]] # store the number of the procedures of the chosen kernel to the memory dup exec.memory::set_num_kernel_procedures # OS => [len_words, KERNEL_HASH] # AS => [[PROC_HASHES]] # get the pointer to the memory where hashes of the kernel procedures will be stored exec.memory::get_kernel_procedures_ptr swap # OS => [len_words, kernel_procs_ptr, KERNEL_HASH] # AS => [[PROC_HASHES]] # store the kernel procedures to the memory exec.mem::pipe_words_to_memory # OS => [C, B, A, kernel_procs_ptr', KERNEL_HASH] # AS => [] # extract the resulting hash exec.rpo::squeeze_digest # OS => [SEQ_HASH, kernel_procs_ptr', KERNEL_HASH] # AS => [] # assert that the precomputed hash matches the computed one movup.4 drop assert_eqw # OS => [] # AS => [] end # BLOCK DATA # ================================================================================================= #! Saves block data to memory and verifies that it matches the BLOCK_HASH public input. #! #! Stack: [] #! Advice stack: [ #! PREVIOUS_BLOCK_HASH, #! CHAIN_MMR_HASH, #! ACCOUNT_ROOT, #! NULLIFIER_ROOT, #! TX_HASH, #! KERNEL_ROOT #! PROOF_HASH, #! [block_num, version, timestamp, 0], #! NOTE_ROOT, #! ] #! Output: [] #! #! Where: #! - PREVIOUS_BLOCK_HASH, hash of the previous block. #! - CHAIN_MMR_HASH, sequential hash of the reference MMR. #! - ACCOUNT_ROOT, root of the tree with latest account states for all accounts. #! - NULLIFIER_ROOT, root of the tree with nullifiers of all notes that have ever been consumed. #! - TX_HASH, commitment to a set of IDs of transactions which affected accounts in the block. #! - KERNEL_ROOT, accumulative hash from all kernel hashes. #! - PROOF_HASH, hash of the block's stark proof. #! - block_num, the reference block number. #! - version, current protocol version. #! - timestamp, current timestamp. #! - NOTE_ROOT, root of the tree with all notes created in the block. proc.process_block_data exec.memory::get_block_data_ptr # => [block_data_ptr] # read block data and compute its subhash. See `Advice stack` above for details. padw padw padw adv_pipe hperm adv_pipe hperm adv_pipe hperm adv_pipe hperm exec.rpo::squeeze_digest # => [DIG, block_data_ptr'] # store the note root in memory padw adv_loadw dupw exec.memory::set_note_root dropw # => [NOTE_ROOT, DIG, block_data_ptr'] # merge the note root with the block data digest hmerge # => [BLOCK_HASH, block_data_ptr'] # assert that the block hash matches the hash in global inputs exec.memory::get_block_hash assert_eqw.err=ERR_PROLOGUE_GLOBAL_INPUTS_PROVIDED_DO_NOT_MATCH_BLOCK_HASH_COMMITMENT # => [block_data_ptr'] drop # => [] end # CHAIN DATA # ================================================================================================= #! Saves the MMR peaks to memory and verifies that it matches the reference block's CHAIN_MMR_HASH. #! #! This procedure loads the MMR peaks from the advice provider, verifies their hash matches the #! reference block, and insert the reference block in the MMR. The reference block is added to the #! MMR so that notes created at this block can be consumed, since the MMR can't contain it and is #! always one block behind. The number MMR peaks is variable, from 16 up to 63, depending on #! `num_blocks`. #! #! Stack: [] #! Advice Map: {CHAIN_MMR_HASH: [[num_blocks, 0, 0, 0], PEAK_1, ..., PEAK_N]} #! Output: [] #! #! Where: #! - CHAIN_MMR_HASH, is the sequential hash of the padded MMR peaks. #! - num_blocks, is the number of blocks in the MMR. #! - PEAK_1 .. PEAK_N, are the MMR peaks. proc.process_chain_data exec.memory::get_chain_mmr_ptr dup # => [chain_mmr_ptr, chain_mmr_ptr] # save the MMR peaks to memory and verify it matches the block's CHAIN_ROOT exec.memory::get_chain_root exec.mmr::unpack # => [chain_mmr_ptr] # add the current block's hash to the MMR, enabling authentication of notes created in it exec.memory::get_block_hash exec.mmr::add # => [] end # ACCOUNT DATA # ================================================================================================= #! Validates that the account the transaction is being executed against satisfies the criteria #! for a new account. #! #! Stack: [] #! Output: [] #! #! #! Apply the following validation to the new account: #! - assert that the account id is valid. #! - assert that the account vault is empty. #! - assert that the account nonce is set to 0. #! - read the account seed from the advice provider and assert it satisfies seed requirements. proc.validate_new_account # Assert the account id of the account is valid exec.memory::get_account_id exec.account::validate_id # => [] # Assert the account nonce is 0 exec.memory::get_acct_nonce eq.0 assert # => [] # Assert the initial vault is empty # --------------------------------------------------------------------------------------------- # get the account vault root exec.memory::get_acct_vault_root # => [ACCT_VAULT_ROOT] # push empty vault root onto stack exec.constants::get_empty_smt_root # => [EMPTY_VAULT_ROOT, ACCT_VAULT_ROOT] assert_eqw.err=ERR_PROLOGUE_NEW_ACCOUNT_VAULT_MUST_BE_EMPTY # => [] # Assert faucet reserved slot is correctly initialized # --------------------------------------------------------------------------------------------- # check if the account is a faucet exec.account::get_id dup exec.account::is_faucet # => [is_faucet, acct_id] # process conditional logic depending on whether the account is a faucet if.true # get the faucet reserved slot exec.account::get_faucet_storage_data_slot exec.account::get_item # => [FAUCET_RESERVED_SLOT, acct_id] # check if the account is a fungible faucet movup.4 exec.account::is_fungible_faucet # => [is_fungible_faucet, FAUCET_RESERVED_SLOT] if.true # assert the fungible faucet reserved slot is initialized correctly (EMPTY_WORD) # TODO: Switch to standard library implementation when available (miden-vm/#1483) exec.is_empty_word_dropped not assertz.err=ERR_PROLOGUE_NEW_FUNGIBLE_FAUCET_RESERVED_SLOT_MUST_BE_EMPTY # => [] # get the faucet reserved storage data slot type exec.account::get_faucet_storage_data_slot exec.account::get_storage_slot_type # => [slot_type] # assert the fungible faucet reserved slot type == value exec.constants::get_storage_slot_type_value eq assert.err=ERR_PROLOGUE_NEW_FUNGIBLE_FAUCET_RESERVED_SLOT_INVALID_TYPE # => [] else # assert the non-fungible faucet reserved slot is initialized correctly (root of # empty SMT) exec.constants::get_empty_smt_root assert_eqw.err=ERR_PROLOGUE_NEW_NON_FUNGIBLE_FAUCET_RESERVED_SLOT_MUST_BE_VALID_EMPY_SMT # => [] push.1001 drop # TODO: remove line, see miden-vm/#1122 # get the faucet reserved storage data slot type exec.account::get_faucet_storage_data_slot exec.account::get_storage_slot_type # => [slot_type] # assert the non-fungible faucet reserved slot type == map exec.constants::get_storage_slot_type_map eq assert.err=ERR_PROLOGUE_NEW_NON_FUNGIBLE_FAUCET_RESERVED_SLOT_INVALID_TYPE # => [] end else # drop the account id drop # => [] end # Assert the provided account seed satisfies the seed requirements # --------------------------------------------------------------------------------------------- exec.account::validate_seed # => [] # Assert the provided procedures offsets and sizes satisfy storage requirements # --------------------------------------------------------------------------------------------- exec.account::validate_procedure_metadata # => [] end #! Returns whether the input word is equal to EMPTY_WORD as a boolean value. #! #! This procedure drops the input word. #! Stack: [INPUT_WORD] #! Output: [is_empty] #! #! Where: #! - INPUT_WORD is the word is compared with EMPTY_WORD. #! - is_empty is a boolean value that is 1 if INPUT_WORD is equal to EMPTY_WORD, and 0 otherwise. proc.is_empty_word_dropped eq.0 swap eq.0 and swap eq.0 and swap eq.0 and end #! Saves the account data to memory and validates it. #! #! This procedure will: #! #! - Read the account data from the advice stack #! - Save it to memory #! - For new accounts, signaled by having a INITIAL_ACCOUNT_HASH set to EMPTY_WORD as a global input, #! validate the account's id and initial state #! - For existing accounts, verify the INITIAL_ACCOUNT_HASH commitment matches the provided data, #! and the account nonce is not zero #! #! Stack: [] #! Advice stack: [account_id, 0, 0, account_nonce, ACCOUNT_VAULT_ROOT, ACCOUNT_STORAGE_COMMITMENT, ACCOUNT_CODE_COMMITMENT] #! Output: [] #! #! Where: #! - account_id, the account that the transaction is being executed against. #! - account_nonce, account's nonce. #! - ACCOUNT_VAULT_ROOT, account's vault root. #! - ACCOUNT_STORAGE_COMMITMENT, account's storage commitment. #! - ACCOUNT_CODE_COMMITMENT, account's code commitment. proc.process_account_data # Initialize the current account data pointer in the bookkeeping section with the native offset # (2048) exec.memory::set_current_account_data_ptr_to_native_account # Copy the account data from the advice provider to memory and hash it # --------------------------------------------------------------------------------------------- exec.memory::get_current_account_data_ptr # => [acct_data_ptr] # read account details and compute its digest. See `Advice stack` above for details. padw padw padw adv_pipe hperm adv_pipe hperm exec.rpo::squeeze_digest # => [ACCT_HASH, acct_data_ptr'] movup.4 drop # => [ACCT_HASH] # assert the account id matches the account id in global inputs exec.memory::get_global_acct_id exec.memory::get_account_id assert_eq.err=ERR_PROLOGUE_MISMATCH_OF_ACCOUNT_IDS_FROM_GLOBAL_INPUTS_AND_ADVICE_PROVIDER # => [ACCT_HASH] # store a copy of the initial nonce in global inputs exec.memory::get_acct_nonce exec.memory::set_init_nonce # => [ACCT_HASH] # validates and stores account storage slots in memory. exec.memory::get_acct_storage_commitment exec.account::save_account_storage_data # => [ACCT_HASH] # set the new account code commitment to the initial account code root # this is used for managing code commitment updates exec.memory::get_acct_code_commitment exec.memory::set_new_acct_code_commitment # => [ACCOUNT_CODE_COMMITMENT, ACCT_HASH] # validates and stores account procedures in memory. exec.account::save_account_procedure_data # => [ACCT_HASH] # copy the initial account vault hash to the input vault hash to support transaction asset # invariant checking exec.memory::get_acct_vault_root exec.memory::set_input_vault_root dropw # => [ACCT_HASH] # Validate the account # --------------------------------------------------------------------------------------------- # It is a new account if the global input INITIAL_ACCOUNT_HASH was set to EMPTY_WORD. padw exec.memory::get_init_acct_hash eqw # => [is_new, INITIAL_ACCOUNT_HASH, EMPTY_WORD, ACCT_HASH] # clean the stack movdn.8 dropw dropw # => [is_new, ACCT_HASH] # process conditional logic depending on whether the account is new or existing if.true # set the initial account hash exec.memory::set_init_acct_hash dropw # => [] # validate the new account exec.validate_new_account # => [] else # assert that the existing account hash matches the hash in global inputs exec.memory::get_init_acct_hash assert_eqw.err=ERR_PROLOGUE_PROVIDED_ACCOUNT_DATA_DOES_NOT_MATCH_ON_CHAIN_COMMITMENT # => [] # assert the nonce of an existing account is non-zero exec.memory::get_acct_nonce neq.0 assert.err=ERR_PROLOGUE_EXISTING_ACCOUNT_MUST_HAVE_NON_ZERO_NONCE # => [] end end # INPUT NOTES DATA # ================================================================================================= #! Authenticates the input note data. #! #! This procedure will: #! - authenticate the MMR leaf associated with the block the note was created in. #! - authenticate the note root associated with the block the note was created in. #! - authenticate the note and its metadata in the note Merkle tree from the block the note was #! created in. #! #! Inputs: #! Stack: [NOTE_HASH] #! Advice stack: [block_num, BLOCK_SUB_HASH, NOTE_ROOT, note_index] #! Outputs: #! Stack: [] #! Advice stack: [] #! #! Where: #! - NOTE_HASH, input note's commitment computed as `hash(NOTE_ID || NOTE_METADATA)`. #! - block_num, leaf position in the MMR chain of the block which created the input note. #! - BLOCK_SUB_HASH, sub_hash of the block which created the input note. #! - NOTE_ROOT, merkle root of the notes tree containing the input note. #! - note_index, input note's position in the notes tree. proc.authenticate_note.2 # Load the BLOCK_HASH from the CHAIN_MMR # --------------------------------------------------------------------------------------------- exec.memory::get_chain_mmr_ptr adv_push.1 # => [block_num, chain_mmr_ptr, NOTE_HASH] exec.mmr::get # => [BLOCK_HASH, NOTE_HASH] locaddr.0 # => [mem_ptr, BLOCK_HASH, NOTE_HASH] # Load and authenticate the NOTE_ROOT # --------------------------------------------------------------------------------------------- # read data from advice and compute hash(BLOCK_SUB_HASH || NOTE_ROOT) padw padw padw adv_pipe hperm # => [PERM, COMPUTED_BLOCK_HASH, PERM, mem_ptr', BLOCK_HASH, NOTE_HASH] dropw # => [COMPUTED_BLOCK_HASH, PERM, mem_ptr', BLOCK_HASH, NOTE_HASH] # assert the computed block hash matches movup.8 drop movupw.2 assert_eqw.err=ERR_PROLOGUE_MISMATCH_OF_REFERENCE_BLOCK_MMR_AND_NOTE_AUTHENTICATION_MMR # => [PERM, NOTE_HASH] # Authenticate the NOTE_HASH # --------------------------------------------------------------------------------------------- # load the note root from memory loc_loadw.1 swapw # => [NOTE_HASH, NOTE_ROOT] # load the index of the note adv_push.1 movdn.4 # => [NOTE_HASH, note_index, NOTE_ROOT] # get the depth of the note tree exec.constants::get_note_tree_depth movdn.4 # => [NOTE_HASH, depth, note_index, NOTE_ROOT] # verify the note hash mtree_verify # => [NOTE_HASH, depth, note_index, NOTE_ROOT] dropw drop drop dropw # => [] end #! Copies the input note's details from the advice stack to memory and computes its nullifier. #! #! Stack: [note_ptr] #! Advice stack: [ #! SERIAL_NUMBER, #! SCRIPT_ROOT, #! INPUTS_HASH, #! ASSETS_HASH, #! ] #! Output: [NULLIFIER] #! #! Where: #! - note_ptr, memory location for the input note. #! - SERIAL_NUMBER, note's serial. #! - SCRIPT_ROOT, note's script root. #! - INPUTS_HASH, sequential hash of the padded note's inputs. #! - ASSETS_HASH, sequential hash of the padded note's assets. #! - NULLIFIER result of `hash(SERIAL_NUMBER || SCRIPT_ROOT || INPUTS_HASH || ASSETS_HASH)`. proc.process_input_note_details exec.memory::get_input_note_core_ptr # => [note_data_ptr] # read input note's data and compute its digest. See `Advice stack` above for details. padw padw padw adv_pipe hperm adv_pipe hperm exec.rpo::squeeze_digest # => [NULLIFIER, note_data_ptr + 4] movup.4 drop # => [NULLIFIER] end #! Copies the note's metadata and 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_HASH 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_HASH is either verified by this kernel, or #! delayed to be verified by another kernel (e.g. block or batch kernels). #! #! Inputs: #! Stack: [note_ptr] #! Advice stack: [NOTE_ARGS, NOTE_METADATA] #! Outputs: #! Stack: [NOTE_METADATA] #! Advice stack: [] #! #! Where: #! - note_ptr, memory location for the input note. #! - NOTE_ARGS, user arguments passed to the note. #! - NOTE_METADATA, 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] end #! Copies the note's assets the advice stack to memory and verifies the commitment. #! #! Stack: [note_ptr] #! Advice stack: [ #! assets_count, #! ASSET_0, ..., ASSET_N, #! ] #! Output: [] #! #! Where: #! - note_ptr, memory location for the input note. #! - assets_count, note's assets count. #! - ASSET_0, ..., ASSET_N, padded note's assets. proc.process_note_assets # verify and save the assets count # --------------------------------------------------------------------------------------------- adv_push.1 # => [assets_count, note_ptr] dup exec.constants::get_max_assets_per_note lte assert.err=ERR_PROLOGUE_NUMBER_OF_NOTE_ASSETS_EXCEEDS_LIMIT # => [assets_count, note_ptr] dup dup.2 exec.memory::set_input_note_num_assets # => [assets_count, note_ptr] # round up the number of assets, to the its padded length dup push.1 u32and add # => [rounded_num_assets, note_ptr] # read the note's assets # --------------------------------------------------------------------------------------------- # Stack organization: # - Top of the stack contains the hash state. The complete state is needed to extract the final # hash. # - Followed by the assets_ptr, with the target address used to pipe data from the advice # provider. # - Followed by a copy of the note_ptr for later use. # - Followed by the loop variables, the current counter and rounded_num_assets, laid at this # depth because dup.15 is an efficient operation. push.0 movup.2 # => [note_ptr, counter, rounded_num_assets] dup exec.memory::get_input_note_assets_ptr # => [assets_ptr, note_ptr, counter, rounded_num_assets] padw padw padw # => [PERM, PERM, PERM, assets_ptr, note_ptr, counter, rounded_num_assets] # loop condition: counter != rounded_num_assets dup.15 dup.15 neq # => [should_loop, PERM, PERM, PERM, assets_ptr, note_ptr, counter, rounded_num_assets] # loop and read assets from the advice provider while.true # read data and compute its digest. See `Advice stack` above for details. adv_pipe hperm # => [PERM, PERM, PERM, assets_ptr+2, note_ptr, counter, rounded_num_assets] # update counter swapw.3 movup.2 add.2 movdn.2 swapw.3 # => [PERM, PERM, PERM, assets_ptr+2, note_ptr, counter+2, rounded_num_assets] # loop condition: counter != rounded_num_assets dup.15 dup.15 neq # => [should_loop, PERM, PERM, PERM, assets_ptr+2, note_ptr, counter+2, rounded_num_assets] end # => [PERM, PERM, PERM, assets_ptr+n, note_ptr, counter+n, rounded_num_assets] exec.rpo::squeeze_digest # => [ASSET_HASH_COMPUTED, assets_ptr+n, note_ptr, counter+n, rounded_num_assets] swapw drop movdn.2 drop drop # => [note_ptr, ASSET_HASH_COMPUTED] # VERIFY: computed ASSET_HASH matches the provided hash exec.memory::get_input_note_assets_hash assert_eqw.err=ERR_PROLOGUE_PROVIDED_INPUT_ASSETS_INFO_DOES_NOT_MATCH_ITS_COMMITMENT # => [] end #! Adds the assets of an input note to the input vault. #! #! Stack: [note_ptr] #! Output: [] #! #! Where: #! - note_ptr, memory location for the input note. proc.add_input_note_assets_to_vault # prepare the stack # --------------------------------------------------------------------------------------------- exec.memory::get_input_vault_root_ptr # => [input_vault_root_ptr, note_ptr] dup.1 exec.memory::get_input_note_assets_ptr # => [assets_start_ptr, input_vault_root_ptr, note_ptr] dup movup.3 exec.memory::get_input_note_num_assets add swap # => [assets_start_ptr, assets_end_ptr, input_vault_root_ptr] # add input note's assets to input vault # --------------------------------------------------------------------------------------------- # loop condition: assets_start_ptr != assets_end_ptr dup.1 dup.1 neq # => [should_loop, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] while.true dup.2 # => [input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] padw dup.5 mem_loadw # => [ASSET, input_vault_root_ptr, assets_start_ptr, assets_end_ptr, input_vault_root_ptr] exec.asset_vault::add_asset dropw # => [assets_start_ptr, assets_end_ptr, input_vault_root_ptr] add.1 # => [assets_start_ptr+1, assets_end_ptr, input_vault_root_ptr] # loop condition: assets_start_ptr != assets_end_ptr dup.1 dup.1 neq # => [should_loop, assets_start_ptr+1, assets_end_ptr, input_vault_root_ptr] end drop drop drop # => [] end #! Computes an input note's id. #! #! Stack: [note_ptr] #! Output: [NOTE_ID] #! #! Where: #! - note_ptr, memory location for the input note. #! - NOTE_ID, the note's id, i.e. `hash(RECIPIENT || ASSET_HASH)`. proc.compute_input_note_id # compute SERIAL_HASH: hash(SERIAL_NUMBER || EMPTY_WORD) dup exec.memory::get_input_note_serial_num padw hmerge # => [SERIAL_HASH, note_ptr] # compute MERGE_SCRIPT: hash(SERIAL_HASH || SCRIPT_HASH) dup.4 exec.memory::get_input_note_script_root hmerge # => [MERGE_SCRIPT, note_ptr] # compute RECIPIENT: hash(MERGE_SCRIPT || INPUT_HASH) dup.4 exec.memory::get_input_note_inputs_hash hmerge # => [RECIPIENT, note_ptr] # compute NOTE_ID: hash(RECIPIENT || ASSET_HASH) movup.4 exec.memory::get_input_note_assets_hash hmerge # => [NOTE_ID] end #! Reads data for the input note from the advice provider and stores it in memory at the appropriate #! memory address. #! #! This procedures will also compute the note's nullifier. Store the note's nullifier, metadata, #! args, assets, id, and hash to memory. And return the hasher state in the stack so that the #! commitment can be extracted. #! #! Stack: [idx, HASHER_CAPACITY] #! Advice stack: [ #! SERIAL_NUMBER, #! SCRIPT_ROOT, #! INPUTS_HASH, #! ASSETS_HASH, #! ARGS, #! NOTE_METADATA, #! assets_count, #! ASSET_0, ..., ASSET_N, #! is_authenticated, #! ( #! block_num, #! BLOCK_SUB_HASH, #! NOTE_ROOT, #! )? #! ] #! Output: [PERM, PERM, PERM] #! #! Where: #! - idx, the index of the input note. #! - HASHER_CAPACITY, state of the hasher capacity word, with the commitment to the previous notes. #! - SERIAL_NUMBER, note's serial. #! - SCRIPT_ROOT, note's script root. #! - INPUTS_HASH, sequential hash of the padded note's inputs. #! - ASSETS_HASH, sequential hash of the padded note's assets. #! - NOTE_METADATA, note's metadata. #! - ARGS, user arguments passed to the note. #! - assets_count, note's assets count. #! - ASSET_0, ..., ASSET_N, padded note's assets. #! - is_authenticated, boolean indicating if the note contains an authentication proof. #! #! Optional values, required if `is_authenticated` is true: #! #! - block_num, note's creation block number. #! - BLOCK_SUB_HASH, the block's sub_hash for which the note was created. #! - NOTE_ROOT, the merkle root of the note's tree. #! proc.process_input_note # note details # --------------------------------------------------------------------------------------------- dup exec.memory::get_input_note_ptr dup # => [note_ptr, note_ptr, idx, HASHER_CAPACITY] exec.process_input_note_details # => [NULLIFIER, note_ptr, idx, HASHER_CAPACITY] # save NULLIFIER to memory movup.5 exec.memory::get_input_note_nullifier_ptr mem_storew # => [NULLIFIER, note_ptr, HASHER_CAPACITY] # note metadata & args # --------------------------------------------------------------------------------------------- movup.4 # => [note_ptr, NULLIFIER, HASHER_CAPACITY] dup exec.process_note_args_and_metadata # => [NOTE_METADATA, note_ptr, NULLIFIER, HASHER_CAPACITY] movup.4 # => [note_ptr, NOTE_METADATA, 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 id # --------------------------------------------------------------------------------------------- dup exec.compute_input_note_id # => [NOTE_ID, note_ptr, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] # save note id to memory movup.4 exec.memory::set_input_note_id # => [NOTE_ID, NOTE_METADATA, NULLIFIER, HASHER_CAPACITY] # note authentication # --------------------------------------------------------------------------------------------- # NOTE_HASH: `hash(NOTE_ID || NOTE_METADATA)` swapw hmerge # => [NOTE_HASH, NULLIFIER, HASHER_CAPACITY] adv_push.1 # => [is_authenticated, NOTE_HASH, NULLIFIER, HASHER_CAPACITY] if.true # => [NOTE_HASH, NULLIFIER, HASHER_CAPACITY] exec.authenticate_note # => [NULLIFIER, HASHER_CAPACITY] padw # => [EMPTY_WORD, NULLIFIER, HASHER_CAPACITY] end # => [EMPTY_WORD_OR_NOTE_HASH, NULLIFIER, HASHER_CAPACITY] # update the input note commitment hperm # => [PERM, PERM, PERM] end #! Process the input notes data provided via the advice provider. This involves reading the data #! from the advice provider and storing it at the appropriate memory addresses. As each note is #! processed its hash and nullifier are computed. The transaction input notes commitment is #! computed via a sequential hash of all (NULLIFIER, EMPTY_WORD_NOTE_HASH) pairs for all input #! notes. #! #! Stack: [] #! Advice stack: [num_notes], #! Advice map: { INPUT_NOTES_COMMITMENT => NOTE_DATA } #! Output: [] #! #! Where: #! - num_notes is the number of input notes. #! - INPUT_NOTES_COMMITMENT, see `transaction::api::get_input_notes_commitment`. #! - NOTE_DATA, input notes' details, for format see `prologue::process_input_note`. proc.process_input_notes_data # get the number of input notes from the advice stack adv_push.1 # => [num_notes] # assert the number of input notes is within limits; since max number of input notes is # expected to be smaller than 2^32, we can use a more efficient u32 comparison dup exec.constants::get_max_num_input_notes u32assert2.err=ERR_PROLOGUE_NUMBER_OF_INPUT_NOTES_EXCEEDS_LIMIT u32lte assert.err=ERR_PROLOGUE_NUMBER_OF_INPUT_NOTES_EXCEEDS_LIMIT # => [num_notes] # if there are input notes, load input notes data from the advice map onto the advice stack dup neq.0 if.true exec.memory::get_input_notes_commitment adv.push_mapval push.15787 drop # TODO: remove line, see miden-vm/#1122 dropw end # => [num_notes] # store the number of input notes into kernel memory dup exec.memory::set_num_input_notes # => [num_notes] # loop over input notes and read data # --------------------------------------------------------------------------------------------- # Stack organization: # - On the top of the stack is the hasher state containing the input notes commitment. The # hasher state will be updated by `process_input_note`. After the loop the commitment is # extracted. # - Below the hasher state in the stack is the current note index. This number is used for two # purposes: # 1. Compute the input note's memory addresses, the index works as an offset. # 2. Determine the loop condition. The loop below runs until all input notes have been # processed. # - The num_notes is kept at position 13, because dup.13 is cheap. # - The [idx, num_notes] pair is kept in a word boundary, so that its word can be swapped with a # cheap swapw.3 instruction to update the `idx` counter. push.0 padw padw padw # => [PERM, PERM, PERM, idx, num_notes] # loop condition: idx != num_notes dup.13 dup.13 neq # => [has_more_notes, PERM, PERM, PERM, idx, num_notes] while.true # the hasher operates in overwrite mode, so discard the rate words, and keep the capacity dropw dropw # => [HASHER_CAPACITY, idx, num_notes] # process the note dup.4 exec.process_input_note # => [PERM, PERM, PERM, idx, num_notes] # update the idx counter swapw.3 add.1 swapw.3 # => [PERM, PERM, PERM, idx+1, num_notes] # loop condition: idx != num_notes dup.13 dup.13 neq # => [has_more_notes, PERM, PERM, PERM, idx+1, num_notes] end exec.rpo::squeeze_digest # => [INPUT_NOTES_COMMITMENT, idx+1, num_notes] # assert the input notes and the commitment matches exec.memory::get_input_notes_commitment assert_eqw.err=ERR_PROLOGUE_INPUT_NOTES_COMMITMENT_MISMATCH # => [idx+1, num_notes] # set the current input note ptr to the address of the first input note push.0 exec.memory::get_input_note_ptr exec.memory::set_current_input_note_ptr # => [idx+1, num_notes] drop drop # => [...] end # TRANSACTION SCRIPT # ================================================================================================= #! Saves the transaction script root to memory. #! #! Advice Stack: [TX_SCRIPT_ROOT] #! Stack: [] #! Output: [] #! #! Where: #! - TX_SCRIPT_ROOT, the transaction's script root. proc.process_tx_script_root # read the transaction script root from the advice stack adv_loadw # => [TX_SCRIPT_ROOT] # store the transaction script root in memory exec.memory::set_tx_script_root dropw # => [] end # TRANSACTION PROLOGUE # ================================================================================================= #! The transaction prologue is executed at the beginning of a transaction. Its responsibility is: #! 1. "Unhash" inputs, authenticate the data and store it in the root contexts memory. #! 2. Build a single vault containing assets of all inputs (input notes combined with current #! account vault). #! 3. Verify that all input notes are present in the note db. #! #! Errors: #! - If data provided by the advice provider does not match global inputs. #! - The account data is invalid. #! - Any of the input notes do note exist in the note db. #! #! Operand stack: [ #! BLOCK_HASH, #! account_id, #! INITIAL_ACCOUNT_HASH, #! INPUT_NOTES_COMMITMENT, #! ] #! Advice stack: [ #! PREVIOUS_BLOCK_HASH, #! CHAIN_MMR_HASH, #! ACCOUNT_ROOT, #! NULLIFIER_ROOT, #! TX_HASH, #! KERNEL_ROOT #! PROOF_HASH, #! [block_num, version, timestamp, 0], #! NOTE_ROOT, #! kernel_version #! [account_id, 0, 0, account_nonce], #! ACCOUNT_VAULT_ROOT, #! ACCOUNT_STORAGE_COMMITMENT, #! ACCOUNT_CODE_COMMITMENT, #! number_of_input_notes, #! TX_SCRIPT_ROOT, #! ] #! Advice map: { #! CHAIN_MMR_HASH: MMR_PEAKS, #! INPUT_NOTES_COMMITMENT => NOTE_DATA, #! KERNEL_ROOT => KERNEL_HASHES #! KERNEL_HASH => KERNEL_PROCEDURE_HASHES #! ACCOUNT_CODE_COMMITMENT => [ACCOUNT_PROCEDURE_DATA] #! ACCOUNT_STORAGE_COMMITMENT => [ACCOUNT_STORAGE_SLOT_DATA] #! } #! Output: [] #! #! Where: #! - BLOCK_HASH, reference block for the transaction execution. #! - account_id, the account that the transaction is being executed against. #! - INITIAL_ACCOUNT_HASH, account state prior to the transaction, EMPTY_WORD for new accounts. #! - INPUT_NOTES_COMMITMENT, see `transaction::api::get_input_notes_commitment`. #! - KERNEL_ROOT, accumulative hash from all kernel hashes. #! - PREVIOUS_BLOCK_HASH, hash of the previous block. #! - CHAIN_MMR_HASH, sequential hash of the reference MMR. #! - ACCOUNT_ROOT, root of the tree with latest account states for all accounts. #! - NULLIFIER_ROOT, root of the tree with nullifiers of all notes that have ever been consumed. #! - TX_HASH, commitment to a set of IDs of transactions which affected accounts in the block. #! - PROOF_HASH, hash of the block's stark proof. #! - block_num, the reference block number. #! - version, the current protocol version. #! - timestamp, the current timestamp. #! - NOTE_ROOT, root of the tree with all notes created in the block. #! - kernel_version, index of the desired kernel in the array of all kernels available for the #! current transaction. #! - account_nonce, account's nonce. #! - ACCOUNT_VAULT_ROOT, account's vault root. #! - ACCOUNT_STORAGE_COMMITMENT, account's storage commitment. #! - ACCOUNT_CODE_COMMITMENT, account's code commitment. #! - ACCOUNT_PROCEDURE_DATA, vector of the account's procedure data. #! - ACCOUNT_STORAGE_SLOT_DATA, vector of the account's storage slot data. #! - number_of_input_notes, number of input notes. #! - TX_SCRIPT_ROOT, the transaction's script root. #! - MMR_PEAKS, is the MMR peak data, see process_chain_data #! - NOTE_DATA, input notes' details, for format see prologue::process_input_note. export.prepare_transaction exec.process_global_inputs # => [] exec.process_block_data exec.process_kernel_data exec.process_chain_data exec.process_account_data exec.process_input_notes_data exec.process_tx_script_root # => [] push.MAX_BLOCK_NUM exec.memory::set_expiration_block_num end