From df6151566da23027ffcd0bd9767c8b94250fdcdf Mon Sep 17 00:00:00 2001 From: "Augusto F. Hack" Date: Thu, 7 Mar 2024 18:55:02 +0100 Subject: [PATCH] note: implement note storage modes Notes can be stored offchain, on-chain, or encrypted. This is an orthogonal concept to how the notes are executed. This commit adds a new metadata field to encode the storage type. --- miden-lib/asm/kernels/transaction/api.masm | 9 ++- .../contracts/faucets/basic_fungible.masm | 7 ++ .../asm/miden/contracts/wallets/basic.masm | 8 +- miden-lib/asm/miden/kernels/tx/tx.masm | 27 +++++-- miden-lib/asm/miden/tx.masm | 8 +- miden-lib/src/notes/mod.rs | 23 +++++- miden-lib/src/tests/test_tx.rs | 58 ++++++++++++-- miden-tx/src/compiler/tests.rs | 4 +- miden-tx/src/tests.rs | 28 +++---- miden-tx/tests/integration/main.rs | 3 +- miden-tx/tests/integration/scripts/faucet.rs | 4 +- miden-tx/tests/integration/scripts/p2id.rs | 3 + miden-tx/tests/integration/scripts/p2idr.rs | 3 + miden-tx/tests/integration/scripts/swap.rs | 6 +- mock/src/builders/note.rs | 14 +++- mock/src/constants.rs | 5 -- mock/src/lib.rs | 3 +- mock/src/mock/account.rs | 64 ++++++++++++--- mock/src/mock/notes.rs | 57 +++++++++---- objects/src/errors.rs | 7 +- objects/src/notes/metadata.rs | 19 ++++- objects/src/notes/mod.rs | 27 ++----- objects/src/notes/note_type.rs | 80 +++++++++++++++++++ 23 files changed, 354 insertions(+), 113 deletions(-) create mode 100644 objects/src/notes/note_type.rs diff --git a/miden-lib/asm/kernels/transaction/api.masm b/miden-lib/asm/kernels/transaction/api.masm index b4aa5e1be..5919d29eb 100644 --- a/miden-lib/asm/kernels/transaction/api.masm +++ b/miden-lib/asm/kernels/transaction/api.masm @@ -421,21 +421,22 @@ end #! Creates a new note and returns a pointer to the memory address at which the note is stored. #! -#! Inputs: [ASSET, tag, RECIPIENT] +#! Inputs: [ASSET, tag, note_type, RECIPIENT] #! Outputs: [ptr, 0, 0, 0, 0, 0, 0, 0, 0] #! #! ASSET is the asset to be included in the note. #! tag is the tag to be included in the note. +#! note_type is the note storage type #! RECIPIENT is the recipient of the note. #! ptr is the pointer to the memory address at which the note is stored. export.create_note # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [ASSET, tag, RECIPIENT] + # => [ASSET, tag, note_type, RECIPIENT] - # create the note + # create the offchain note exec.tx::create_note - # => [ptr, 0, 0, 0, 0, 0, 0, 0, 0] + # => [ptr, 0, 0, 0, 0, 0, 0, 0, 0, 0] end #! Returns a commitment to the account vault the transaction is being executed against. diff --git a/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm b/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm index 31d2d1f3f..a22e29eec 100644 --- a/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm +++ b/miden-lib/asm/miden/contracts/faucets/basic_fungible.masm @@ -13,6 +13,10 @@ use.miden::faucet use.miden::tx use.miden::contracts::auth::basic +# CONSTANTS +# ================================================================================================= +const.PUBLIC_NOTE=2 + # ERRORS # ================================================================================================= @@ -68,6 +72,9 @@ export.distribute exec.faucet::mint # => [ASSET, tag, RECIPIENT, ...] + push.PUBLIC_NOTE movdn.5 + # => [ASSET, tag, note_type, RECIPIENT, ...] + # create a note containing the asset exec.tx::create_note # => [note_ptr, ZERO, ZERO, ...] diff --git a/miden-lib/asm/miden/contracts/wallets/basic.masm b/miden-lib/asm/miden/contracts/wallets/basic.masm index a4df22ffa..fafeb45a7 100644 --- a/miden-lib/asm/miden/contracts/wallets/basic.masm +++ b/miden-lib/asm/miden/contracts/wallets/basic.masm @@ -1,6 +1,10 @@ use.miden::account use.miden::tx +# CONSTANTS +# ================================================================================================= +const.PUBLIC_NOTE=2 + #! Adds the provided asset to the current account. #! #! Inputs: [ASSET] @@ -43,7 +47,9 @@ export.send_asset.1 push.0 swap loc_store.0 padw push.0.0.0 swapdw loc_load.0 # => [ASSET, tag, RECIPIENT, ZERO, ZERO, ...] - + push.PUBLIC_NOTE movdn.5 + # => [ASSET, tag, note_type, RECIPIENT, ...] + # exec.tx::create_note # => [note_ptr, ZERO, ZERO, ...] end diff --git a/miden-lib/asm/miden/kernels/tx/tx.masm b/miden-lib/asm/miden/kernels/tx/tx.masm index 2e4a0a1cb..9a17674a7 100644 --- a/miden-lib/asm/miden/kernels/tx/tx.masm +++ b/miden-lib/asm/miden/kernels/tx/tx.masm @@ -4,12 +4,23 @@ use.miden::kernels::tx::constants use.miden::kernels::tx::memory use.miden::kernels::tx::note +# CONSTANTS +# ================================================================================================= + +# Constants for different note types +const.OFFCHAIN_NOTE=0 +const.ENCRYPTED_NOTE=1 +const.PUBLIC_NOTE=2 + # ERRORS # ================================================================================================= # Output notes exceeded the maximum limit const.ERR_TX_OUTPUT_NOTES_OVERFLOW=0x00020020 +# Invalid note type +const.ERR_INVALID_NOTE_TYPE=0x00020044 + #! Returns the block hash of the last known block at the time of transaction execution. #! #! Inputs: [] @@ -64,7 +75,7 @@ end #! Creates a new note and returns a pointer to the memory address at which the note is stored. #! -#! Inputs: [ASSET, tag, RECIPIENT] +#! Inputs: [ASSET, tag, note_type, RECIPIENT] #! Outputs: [ptr, 0, 0, 0, 0, 0, 0, 0, 0] #! #! ASSET is the asset to be included in the note. @@ -74,19 +85,23 @@ end export.create_note # validate the asset exec.asset::validate_asset - # => [ASSET, tag, RECIPIENT] + # => [ASSET, tag, note_type, RECIPIENT] + + # validate the note type + dup.5 push.OFFCHAIN_NOTE eq dup.6 push.ENCRYPTED_NOTE eq or dup.6 push.PUBLIC_NOTE eq or assert.err=ERR_INVALID_NOTE_TYPE + # => [ASSET, tag, note_type, RECIPIENT] # get the index for the next note to be created and increment counter exec.increment_num_created_notes - # => [note_idx, ASSET, tag, RECIPIENT] + # => [note_idx, ASSET, tag, note_type, RECIPIENT] # get a pointer to the memory address at which the note will be stored exec.memory::get_created_note_ptr - # => [note_ptr, ASSET, tag, RECIPIENT] + # => [note_ptr, ASSET, tag, note_type, RECIPIENT] # populate the metadata - movup.5 exec.account::get_id push.0.0 - # => [0, 0, acct_id, tag, note_ptr, ASSET, RECIPIENT] + movup.5 exec.account::get_id movup.7 push.0 + # => [0, note_type, acct_id, tag, note_ptr, ASSET, RECIPIENT] # set the metadata for the new created note dup.4 exec.memory::set_created_note_metadata diff --git a/miden-lib/asm/miden/tx.masm b/miden-lib/asm/miden/tx.masm index e8e13fde1..188291e55 100644 --- a/miden-lib/asm/miden/tx.masm +++ b/miden-lib/asm/miden/tx.masm @@ -58,17 +58,19 @@ end #! Creates a new note and returns a pointer to the memory address at which the note is stored. #! -#! Inputs: [ASSET, tag, RECIPIENT] +#! Inputs: [ASSET, tag, note_type, RECIPIENT] #! Outputs: [ptr] #! #! ASSET is the asset to be included in the note. #! tag is the tag to be included in the note. +#! note_type is the storage type of the note #! RECIPIENT is the recipient of the note. #! ptr is the pointer to the memory address at which the note is stored. export.create_note syscall.create_note - # => [ptr, 0, 0, 0, 0, 0, 0, 0, 0] + # => [ptr, 0, 0, 0, 0, 0, 0, 0, 0, 0] - movdn.8 dropw dropw + # clear the padding from the kernel response + movdn.4 dropw movdn.4 dropw swap drop # => [ptr] end diff --git a/miden-lib/src/notes/mod.rs b/miden-lib/src/notes/mod.rs index 3ec84bfaf..5a8d5016a 100644 --- a/miden-lib/src/notes/mod.rs +++ b/miden-lib/src/notes/mod.rs @@ -1,7 +1,11 @@ use alloc::vec::Vec; use miden_objects::{ - accounts::AccountId, assets::Asset, crypto::rand::FeltRng, notes::Note, Felt, NoteError, Word, + accounts::AccountId, + assets::Asset, + crypto::rand::FeltRng, + notes::{Note, NoteType}, + Felt, NoteError, Word, }; use self::utils::build_note_script; @@ -25,6 +29,7 @@ pub fn create_p2id_note( sender: AccountId, target: AccountId, assets: Vec, + note_type: NoteType, mut rng: R, ) -> Result { let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/P2ID.masb")); @@ -34,7 +39,7 @@ pub fn create_p2id_note( let tag: Felt = target.into(); let serial_num = rng.draw_word(); - Note::new(note_script, &inputs, &assets, serial_num, sender, tag) + Note::new(note_script, &inputs, &assets, serial_num, sender, note_type, tag) } /// Generates a P2IDR note - pay to id with recall after a certain block height. @@ -53,6 +58,7 @@ pub fn create_p2idr_note( sender: AccountId, target: AccountId, assets: Vec, + note_type: NoteType, recall_height: u32, mut rng: R, ) -> Result { @@ -63,7 +69,7 @@ pub fn create_p2idr_note( let tag: Felt = target.into(); let serial_num = rng.draw_word(); - Note::new(note_script.clone(), &inputs, &assets, serial_num, sender, tag) + Note::new(note_script.clone(), &inputs, &assets, serial_num, sender, note_type, tag) } /// Generates a SWAP note - swap of assets between two accounts. @@ -78,6 +84,7 @@ pub fn create_swap_note( sender: AccountId, offered_asset: Asset, requested_asset: Asset, + note_type: NoteType, mut rng: R, ) -> Result<(Note, Word), NoteError> { let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/note_scripts/SWAP.masb")); @@ -102,7 +109,15 @@ pub fn create_swap_note( let tag: Felt = Felt::new(0); let serial_num = rng.draw_word(); - let note = Note::new(note_script.clone(), &inputs, &[offered_asset], serial_num, sender, tag)?; + let note = Note::new( + note_script.clone(), + &inputs, + &[offered_asset], + serial_num, + sender, + note_type, + tag, + )?; Ok((note, payback_serial_num)) } diff --git a/miden-lib/src/tests/test_tx.rs b/miden-lib/src/tests/test_tx.rs index 39f408629..04c18ffaf 100644 --- a/miden-lib/src/tests/test_tx.rs +++ b/miden-lib/src/tests/test_tx.rs @@ -1,8 +1,9 @@ use alloc::vec::Vec; use miden_objects::{ - notes::Note, + notes::{Note, NoteType}, transaction::{OutputNote, OutputNotes}, + FieldElement, }; use mock::{ mock::{ @@ -21,7 +22,8 @@ use super::{ }; use crate::transaction::memory::{ CREATED_NOTE_ASSETS_OFFSET, CREATED_NOTE_METADATA_OFFSET, CREATED_NOTE_NUM_ASSETS_OFFSET, - CREATED_NOTE_RECIPIENT_OFFSET, CREATED_NOTE_SECTION_OFFSET, NUM_CREATED_NOTES_PTR, + CREATED_NOTE_RECIPIENT_OFFSET, CREATED_NOTE_SECTION_OFFSET, NOTE_MEM_SIZE, + NUM_CREATED_NOTES_PTR, }; #[test] @@ -43,6 +45,7 @@ fn test_create_note() { exec.prologue::prepare_transaction push.{recipient} + push.{PUBLIC_NOTE} push.{tag} push.{asset} @@ -50,8 +53,9 @@ fn test_create_note() { end ", recipient = prepare_word(&recipient), + PUBLIC_NOTE = NoteType::Public as u8, tag = tag, - asset = prepare_word(&asset) + asset = prepare_word(&asset), ); let transaction = prepare_transaction(tx_inputs, None, &code, None); @@ -72,7 +76,7 @@ fn test_create_note() { // assert the metadata is stored at the correct memory location. assert_eq!( read_root_mem_value(&process, CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_METADATA_OFFSET), - [tag, Felt::from(account_id), ZERO, ZERO] + [tag, Felt::from(account_id), NoteType::Public.into(), ZERO] ); // assert the number of assets is stored at the correct memory location. @@ -109,6 +113,7 @@ fn test_create_note_too_many_notes() { exec.memory::set_num_created_notes push.{recipient} + push.{PUBLIC_NOTE} push.{tag} push.{asset} @@ -117,7 +122,8 @@ fn test_create_note_too_many_notes() { ", recipient = prepare_word(&recipient), tag = tag, - asset = prepare_word(&asset) + asset = prepare_word(&asset), + PUBLIC_NOTE = NoteType::Public as u8, ); let process = @@ -147,6 +153,7 @@ fn test_get_output_notes_hash() { &[input_asset_1], output_serial_no_1, tx_inputs.account().id(), + NoteType::Public, output_tag_1, ) .unwrap(); @@ -160,6 +167,7 @@ fn test_get_output_notes_hash() { &[input_asset_2], output_serial_no_2, tx_inputs.account().id(), + NoteType::Public, output_tag_2, ) .unwrap(); @@ -178,27 +186,38 @@ fn test_get_output_notes_hash() { use.miden::tx begin + # => [BH, acct_id, IAH, NC] exec.prologue::prepare_transaction + # => [] # create output note 1 push.{recipient_1} + push.{PUBLIC_NOTE} push.{tag_1} push.{asset_1} exec.tx::create_note + # => [note_ptr] + drop + # => [] # create output note 2 push.{recipient_2} + push.{PUBLIC_NOTE} push.{tag_2} push.{asset_2} exec.tx::create_note + # => [note_ptr] + drop + # => [] # compute the output notes hash exec.tx::get_output_notes_hash - push.{expected} assert_eqw + # => [COMM] end ", + PUBLIC_NOTE = NoteType::Public as u8, recipient_1 = prepare_word(&output_note_1.recipient()), tag_1 = output_note_1.metadata().tag(), asset_1 = prepare_word(&Word::from( @@ -209,11 +228,34 @@ fn test_get_output_notes_hash() { asset_2 = prepare_word(&Word::from( **output_note_2.assets().iter().take(1).collect::>().first().unwrap() )), - expected = prepare_word(&expected_output_notes_hash) ); let transaction = prepare_transaction(tx_inputs, None, &code, None); - let _process = run_tx(&transaction).unwrap(); + let process = run_tx(&transaction).unwrap(); + + assert_eq!( + process.get_mem_value(ContextId::root(), NUM_CREATED_NOTES_PTR), + Some([Felt::new(2), Felt::ZERO, Felt::ZERO, Felt::ZERO]), + "The test creates two notes", + ); + assert_eq!( + process.get_mem_value( + ContextId::root(), + CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_METADATA_OFFSET + ), + Some(output_note_1.metadata().into()), + "Validate the output note 1 metadata", + ); + assert_eq!( + process.get_mem_value( + ContextId::root(), + CREATED_NOTE_SECTION_OFFSET + CREATED_NOTE_METADATA_OFFSET + NOTE_MEM_SIZE + ), + Some(output_note_2.metadata().into()), + "Validate the output note 1 metadata", + ); + + assert_eq!(process.get_stack_word(0), *expected_output_notes_hash); } // HELPER FUNCTIONS diff --git a/miden-tx/src/compiler/tests.rs b/miden-tx/src/compiler/tests.rs index d429d0173..97bbe7278 100644 --- a/miden-tx/src/compiler/tests.rs +++ b/miden-tx/src/compiler/tests.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use miden_objects::{ accounts::ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, assets::{Asset, FungibleAsset}, - notes::{Note, NoteInclusionProof}, + notes::{Note, NoteInclusionProof, NoteType}, transaction::{InputNote, InputNotes}, Felt, FieldElement, Word, }; @@ -162,6 +162,7 @@ fn mock_consumed_notes( &[fungible_asset_1, fungible_asset_2, fungible_asset_3], SERIAL_NUM_1, sender, + NoteType::Public, Felt::ZERO, ) .unwrap(); @@ -173,6 +174,7 @@ fn mock_consumed_notes( &[fungible_asset_1, fungible_asset_2, fungible_asset_3], SERIAL_NUM_2, sender, + NoteType::Public, Felt::ZERO, ) .unwrap(); diff --git a/miden-tx/src/tests.rs b/miden-tx/src/tests.rs index bbdbd9990..9535f784f 100644 --- a/miden-tx/src/tests.rs +++ b/miden-tx/src/tests.rs @@ -14,15 +14,12 @@ use miden_objects::{ }; use miden_prover::ProvingOptions; use mock::{ - constants::{ - non_fungible_asset, ACCOUNT_PROCEDURE_INCR_NONCE_PROC_IDX, - ACCOUNT_PROCEDURE_SET_CODE_PROC_IDX, ACCOUNT_PROCEDURE_SET_ITEM_PROC_IDX, - FUNGIBLE_ASSET_AMOUNT, MIN_PROOF_SECURITY_LEVEL, - }, + constants::{non_fungible_asset, FUNGIBLE_ASSET_AMOUNT, MIN_PROOF_SECURITY_LEVEL}, mock::{ account::{ MockAccountType, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, + ACCOUNT_INCR_NONCE_MAST_ROOT, ACCOUNT_SET_CODE_MAST_ROOT, ACCOUNT_SET_ITEM_MAST_ROOT, STORAGE_INDEX_0, }, notes::AssetPreservationStatus, @@ -113,13 +110,6 @@ fn executed_transaction_account_delta() { let removed_asset_3 = non_fungible_asset(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN); let removed_assets = [removed_asset_1, removed_asset_2, removed_asset_3]; - let account_procedure_incr_nonce_mast_root = - &data_store.account.code().procedures()[ACCOUNT_PROCEDURE_INCR_NONCE_PROC_IDX].to_hex(); - let account_procedure_set_code_mast_root = - &data_store.account.code().procedures()[ACCOUNT_PROCEDURE_SET_CODE_PROC_IDX].to_hex(); - let account_procedure_set_item_mast_root = - &data_store.account.code().procedures()[ACCOUNT_PROCEDURE_SET_ITEM_PROC_IDX].to_hex(); - let tx_script = format!( "\ use.miden::account @@ -132,12 +122,12 @@ fn executed_transaction_account_delta() { push.0 movdn.5 push.0 movdn.5 push.0 movdn.5 # => [index, V', 0, 0, 0] - call.{account_procedure_set_item_mast_root} + call.{ACCOUNT_SET_ITEM_MAST_ROOT} # => [R', V] end proc.set_code - call.{account_procedure_set_code_mast_root} + call.{ACCOUNT_SET_CODE_MAST_ROOT} # => [0, 0, 0, 0] dropw @@ -145,7 +135,7 @@ fn executed_transaction_account_delta() { end proc.incr_nonce - call.{account_procedure_incr_nonce_mast_root} + call.{ACCOUNT_INCR_NONCE_MAST_ROOT} # => [0] drop @@ -342,12 +332,14 @@ fn test_tx_script() { .unwrap(); let tx_args = TransactionArgs::with_tx_script(tx_script); - // execute the transaction let executed_transaction = executor.execute_transaction(account_id, block_ref, ¬e_ids, Some(tx_args)); - // assert the transaction executed successfully - assert!(executed_transaction.is_ok()); + assert!( + executed_transaction.is_ok(), + "Transaction execution failed {:?}", + executed_transaction, + ); } // MOCK DATA STORE diff --git a/miden-tx/tests/integration/main.rs b/miden-tx/tests/integration/main.rs index cbaa0b81a..608092705 100644 --- a/miden-tx/tests/integration/main.rs +++ b/miden-tx/tests/integration/main.rs @@ -7,7 +7,7 @@ use miden_objects::{ assembly::{ModuleAst, ProgramAst}, assets::{Asset, AssetVault, FungibleAsset}, crypto::{dsa::rpo_falcon512::KeyPair, utils::Serializable}, - notes::{Note, NoteId, NoteScript}, + notes::{Note, NoteId, NoteScript, NoteType}, transaction::{ ChainMmr, ExecutedTransaction, InputNote, InputNotes, ProvenTransaction, TransactionInputs, }, @@ -183,6 +183,7 @@ pub fn get_note_with_fungible_asset_and_script( &[fungible_asset.into()], SERIAL_NUM, sender_id, + NoteType::Public, Felt::new(1), ) .unwrap() diff --git a/miden-tx/tests/integration/scripts/faucet.rs b/miden-tx/tests/integration/scripts/faucet.rs index d95a29f8a..fd3476014 100644 --- a/miden-tx/tests/integration/scripts/faucet.rs +++ b/miden-tx/tests/integration/scripts/faucet.rs @@ -8,7 +8,7 @@ use miden_objects::{ assembly::{ModuleAst, ProgramAst}, assets::{Asset, AssetVault, FungibleAsset, TokenSymbol}, crypto::dsa::rpo_falcon512::{KeyPair, PublicKey}, - notes::{NoteAssets, NoteMetadata}, + notes::{NoteAssets, NoteMetadata, NoteType}, transaction::{OutputNote, TransactionArgs}, Felt, Word, ZERO, }; @@ -86,7 +86,7 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() { let expected_note = OutputNote::new( recipient.into(), NoteAssets::new(&[fungible_asset]).unwrap(), - NoteMetadata::new(faucet_account.id(), tag), + NoteMetadata::new(faucet_account.id(), NoteType::Public, tag), ); let created_note = executed_transaction.output_notes().get_note(0).clone(); diff --git a/miden-tx/tests/integration/scripts/p2id.rs b/miden-tx/tests/integration/scripts/p2id.rs index 804a436ab..f390a5d59 100644 --- a/miden-tx/tests/integration/scripts/p2id.rs +++ b/miden-tx/tests/integration/scripts/p2id.rs @@ -4,6 +4,7 @@ use miden_objects::{ assembly::ProgramAst, assets::{Asset, AssetVault, FungibleAsset}, crypto::rand::RpoRandomCoin, + notes::NoteType, transaction::TransactionArgs, utils::collections::*, Felt, @@ -44,6 +45,7 @@ fn prove_p2id_script() { sender_account_id, target_account_id, vec![fungible_asset], + NoteType::Public, RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), ) .unwrap(); @@ -156,6 +158,7 @@ fn p2id_script_multiple_assets() { sender_account_id, target_account_id, vec![fungible_asset_1, fungible_asset_2], + NoteType::Public, RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), ) .unwrap(); diff --git a/miden-tx/tests/integration/scripts/p2idr.rs b/miden-tx/tests/integration/scripts/p2idr.rs index fd8a1ac1f..0217c3164 100644 --- a/miden-tx/tests/integration/scripts/p2idr.rs +++ b/miden-tx/tests/integration/scripts/p2idr.rs @@ -4,6 +4,7 @@ use miden_objects::{ assembly::ProgramAst, assets::{Asset, AssetVault, FungibleAsset}, crypto::rand::RpoRandomCoin, + notes::NoteType, transaction::TransactionArgs, utils::collections::*, Felt, @@ -62,6 +63,7 @@ fn p2idr_script() { sender_account_id, target_account_id, vec![fungible_asset], + NoteType::Public, reclaim_block_height_in_time, RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), ) @@ -72,6 +74,7 @@ fn p2idr_script() { sender_account_id, target_account_id, vec![fungible_asset], + NoteType::Public, reclaim_block_height_reclaimable, RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), ) diff --git a/miden-tx/tests/integration/scripts/swap.rs b/miden-tx/tests/integration/scripts/swap.rs index 9e8696d89..940b1b8d9 100644 --- a/miden-tx/tests/integration/scripts/swap.rs +++ b/miden-tx/tests/integration/scripts/swap.rs @@ -4,7 +4,7 @@ use miden_objects::{ assembly::ProgramAst, assets::{Asset, AssetVault, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}, crypto::rand::RpoRandomCoin, - notes::{NoteAssets, NoteMetadata}, + notes::{NoteAssets, NoteMetadata, NoteType}, transaction::{OutputNote, TransactionArgs}, Felt, }; @@ -49,6 +49,7 @@ fn prove_swap_script() { sender_account_id, fungible_asset, non_fungible_asset, + NoteType::Public, RpoRandomCoin::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), ) .unwrap(); @@ -96,7 +97,8 @@ fn prove_swap_script() { // Check if the created `Note` is what we expect let recipient = build_p2id_recipient(sender_account_id, repay_serial_num).unwrap(); - let note_metadata = NoteMetadata::new(target_account_id, sender_account_id.into()); + let note_metadata = + NoteMetadata::new(target_account_id, NoteType::Public, sender_account_id.into()); let note_assets = NoteAssets::new(&[non_fungible_asset]).unwrap(); diff --git a/mock/src/builders/note.rs b/mock/src/builders/note.rs index c7f5651b2..8fdc3fe46 100644 --- a/mock/src/builders/note.rs +++ b/mock/src/builders/note.rs @@ -7,7 +7,7 @@ use miden_objects::{ accounts::AccountId, assembly::ProgramAst, assets::Asset, - notes::{Note, NoteInclusionProof, NoteInputs, NoteScript}, + notes::{Note, NoteInclusionProof, NoteInputs, NoteScript, NoteType}, Felt, NoteError, Word, }; use rand::Rng; @@ -25,6 +25,7 @@ pub struct NoteBuilder { sender: AccountId, inputs: Vec, assets: Vec, + note_type: NoteType, serial_num: Word, tag: Felt, code: String, @@ -44,6 +45,7 @@ impl NoteBuilder { sender, inputs: vec![], assets: vec![], + note_type: NoteType::Public, serial_num, tag: Felt::default(), code: DEFAULT_NOTE_CODE.to_string(), @@ -81,6 +83,14 @@ impl NoteBuilder { let assembler = TransactionKernel::assembler(); let note_ast = ProgramAst::parse(&self.code).unwrap(); let (note_script, _) = NoteScript::new(note_ast, &assembler)?; - Note::new(note_script, &self.inputs, &self.assets, self.serial_num, self.sender, self.tag) + Note::new( + note_script, + &self.inputs, + &self.assets, + self.serial_num, + self.sender, + self.note_type, + self.tag, + ) } } diff --git a/mock/src/constants.rs b/mock/src/constants.rs index 4746b9daa..9429e0697 100644 --- a/mock/src/constants.rs +++ b/mock/src/constants.rs @@ -4,11 +4,6 @@ use miden_objects::{ Felt, }; -pub use super::mock::account::{ - ACCOUNT_PROCEDURE_INCR_NONCE_PROC_IDX, ACCOUNT_PROCEDURE_SET_CODE_PROC_IDX, - ACCOUNT_PROCEDURE_SET_ITEM_PROC_IDX, -}; - pub const FUNGIBLE_ASSET_AMOUNT: u64 = 100; pub const FUNGIBLE_FAUCET_INITIAL_BALANCE: u64 = 50000; diff --git a/mock/src/lib.rs b/mock/src/lib.rs index 58c196315..23d6262a8 100644 --- a/mock/src/lib.rs +++ b/mock/src/lib.rs @@ -62,8 +62,7 @@ pub fn run_tx_with_inputs( let (stack_inputs, mut advice_inputs) = tx.get_kernel_inputs(); advice_inputs.extend(inputs); let host = MockHost::new(tx.account().into(), advice_inputs); - let exec_options = ExecutionOptions::default().with_tracing(); - let mut process = Process::new(program.kernel().clone(), stack_inputs, host, exec_options); + let mut process = Process::new_debug(program.kernel().clone(), stack_inputs, host); process.execute(&program)?; Ok(process) } diff --git a/mock/src/mock/account.rs b/mock/src/mock/account.rs index c38455853..0e4f7eb60 100644 --- a/mock/src/mock/account.rs +++ b/mock/src/mock/account.rs @@ -87,10 +87,24 @@ pub fn mock_account_storage() -> AccountStorage { AccountStorage::new(vec![storage_item_0(), storage_item_1()]).unwrap() } -// Constants that define the indexes of the account procedures of interest -pub const ACCOUNT_PROCEDURE_INCR_NONCE_PROC_IDX: usize = 2; -pub const ACCOUNT_PROCEDURE_SET_ITEM_PROC_IDX: usize = 3; -pub const ACCOUNT_PROCEDURE_SET_CODE_PROC_IDX: usize = 4; +// The MAST root of the default account's interface. Use these constants to interact with the +// account's procedures. +pub const ACCOUNT_RECEIVE_ASSET_MAST_ROOT: &str = + "0x5e2981e93f961ddc63f5f65332554cafabe642d1eccff58a9279be4a13075f15"; +pub const ACCOUNT_SEND_ASSET_MAST_ROOT: &str = + "0xfc9bc2880f9ea985a278863867e03b68bed1673f876ee4426a225c97dafb90fc"; +pub const ACCOUNT_INCR_NONCE_MAST_ROOT: &str = + "0xd765111e22479256e87a57eaf3a27479d19cc876c9a715ee6c262e0a0d47a2ac"; +pub const ACCOUNT_SET_ITEM_MAST_ROOT: &str = + "0xf7f1a1facd65a56bda0471acaf12c62b99d9f9c8e33c9fa02a64b167fb7669db"; +pub const ACCOUNT_SET_CODE_MAST_ROOT: &str = + "0x9d221abcc386973775499406d126764cdf4530ccf8084e27091f7e9f28177bbe"; +pub const ACCOUNT_CREATE_NOTE_MAST_ROOT: &str = + "0x2657be3de82b6dbb9cfa6805badcecc5891d2b0e8dd0848a60a9efc662cd70b9"; +pub const ACCOUNT_ACCOUNT_PROCEDURE_1_MAST_ROOT: &str = + "0xff06b90f849c4b262cbfbea67042c4ea017ea0e9c558848a951d44b23370bec5"; +pub const ACCOUNT_ACCOUNT_PROCEDURE_2_MAST_ROOT: &str = + "0x8ef0092134469a1330e3c468f57c7f085ce611645d09cc7516c786fefc71d794"; // ACCOUNT ASSEMBLY CODE // ================================================================================================ @@ -152,14 +166,8 @@ pub fn mock_account_code(assembler: &Assembler) -> AccountCode { # acct proc 5 export.create_note - # apply padding - repeat.8 - push.0 movdn.9 - end - - # create note exec.tx::create_note - # => [ptr, 0, 0, 0, 0, 0, 0, 0, 0] + # => [ptr, 0, 0, 0, 0, 0, 0, 0, 0, 0] end # acct proc 6 @@ -175,7 +183,39 @@ pub fn mock_account_code(assembler: &Assembler) -> AccountCode { end "; let account_module_ast = ModuleAst::parse(account_code).unwrap(); - AccountCode::new(account_module_ast, assembler).unwrap() + let code = AccountCode::new(account_module_ast, assembler).unwrap(); + + // Ensures the mast root constants match the latest version of the code. + // + // The constants will change if the library code changes, and need to be updated so that the + // tests will work properly. If these asserts fail, copy the value of the code (the left + // value), into the constants. + // + // Comparing all the values together, in case multiple of them change, a single test run will + // detect it. + let current = [ + code.procedures()[0].to_hex(), + code.procedures()[1].to_hex(), + code.procedures()[2].to_hex(), + code.procedures()[3].to_hex(), + code.procedures()[4].to_hex(), + code.procedures()[5].to_hex(), + code.procedures()[6].to_hex(), + code.procedures()[7].to_hex(), + ]; + let expected = [ + ACCOUNT_RECEIVE_ASSET_MAST_ROOT, + ACCOUNT_SEND_ASSET_MAST_ROOT, + ACCOUNT_INCR_NONCE_MAST_ROOT, + ACCOUNT_SET_ITEM_MAST_ROOT, + ACCOUNT_SET_CODE_MAST_ROOT, + ACCOUNT_CREATE_NOTE_MAST_ROOT, + ACCOUNT_ACCOUNT_PROCEDURE_1_MAST_ROOT, + ACCOUNT_ACCOUNT_PROCEDURE_2_MAST_ROOT, + ]; + assert_eq!(current, expected); + + code } // MOCK ACCOUNT diff --git a/mock/src/mock/notes.rs b/mock/src/mock/notes.rs index 04535458d..b7575f232 100644 --- a/mock/src/mock/notes.rs +++ b/mock/src/mock/notes.rs @@ -4,7 +4,7 @@ use miden_objects::{ accounts::AccountId, assembly::{Assembler, ProgramAst}, assets::{Asset, FungibleAsset}, - notes::{Note, NoteScript}, + notes::{Note, NoteScript, NoteType}, Felt, Word, ZERO, }; @@ -14,9 +14,9 @@ use crate::{ CONSUMED_ASSET_3_AMOUNT, }, mock::account::{ - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, - ACCOUNT_ID_SENDER, + ACCOUNT_CREATE_NOTE_MAST_ROOT, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3, + ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_SENDER, }, utils::{prepare_assets, prepare_word}, }; @@ -61,6 +61,7 @@ pub fn mock_notes( &[fungible_asset_1], SERIAL_NUM_4, sender, + NoteType::Public, ZERO, ) .unwrap(); @@ -72,14 +73,22 @@ pub fn mock_notes( &[fungible_asset_2], SERIAL_NUM_5, sender, + NoteType::Public, ZERO, ) .unwrap(); const SERIAL_NUM_6: Word = [Felt::new(21), Felt::new(22), Felt::new(23), Felt::new(24)]; - let created_note_3 = - Note::new(note_script, &[Felt::new(2)], &[fungible_asset_3], SERIAL_NUM_6, sender, ZERO) - .unwrap(); + let created_note_3 = Note::new( + note_script, + &[Felt::new(2)], + &[fungible_asset_3], + SERIAL_NUM_6, + sender, + NoteType::Public, + ZERO, + ) + .unwrap(); let created_notes = vec![created_note_1, created_note_2, created_note_3]; @@ -92,21 +101,24 @@ pub fn mock_notes( begin # create note 0 push.{created_note_0_recipient} + push.{PUBLIC_NOTE} push.{created_note_0_tag} push.{created_note_0_asset} # MAST root of the `create_note` mock account procedure - call.0xacb46cadec8d1721934827ed161b851f282f1f4b88b72391a67fed668b1a00ba - drop dropw dropw + call.{ACCOUNT_CREATE_NOTE_MAST_ROOT} + drop drop dropw dropw # create note 1 push.{created_note_1_recipient} + push.{PUBLIC_NOTE} push.{created_note_1_tag} push.{created_note_1_asset} # MAST root of the `create_note` mock account procedure - call.0xacb46cadec8d1721934827ed161b851f282f1f4b88b72391a67fed668b1a00ba - drop dropw dropw + call.{ACCOUNT_CREATE_NOTE_MAST_ROOT} + drop drop dropw dropw end ", + PUBLIC_NOTE = NoteType::Public as u8, created_note_0_recipient = prepare_word(&created_notes[0].recipient()), created_note_0_tag = created_notes[0].metadata().tag(), created_note_0_asset = prepare_assets(created_notes[0].assets())[0], @@ -123,13 +135,15 @@ pub fn mock_notes( begin # create note 2 push.{created_note_2_recipient} + push.{PUBLIC_NOTE} push.{created_note_2_tag} push.{created_note_2_asset} # MAST root of the `create_note` mock account procedure - call.0xacb46cadec8d1721934827ed161b851f282f1f4b88b72391a67fed668b1a00ba - drop dropw dropw + call.{ACCOUNT_CREATE_NOTE_MAST_ROOT} + drop drop dropw dropw end ", + PUBLIC_NOTE = NoteType::Public as u8, created_note_2_recipient = prepare_word(&created_notes[2].recipient()), created_note_2_tag = created_notes[2].metadata().tag(), created_note_2_asset = prepare_assets(created_notes[2].assets())[0], @@ -139,9 +153,16 @@ pub fn mock_notes( // Consumed Notes const SERIAL_NUM_1: Word = [Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]; - let consumed_note_1 = - Note::new(note_1_script, &[Felt::new(1)], &[fungible_asset_1], SERIAL_NUM_1, sender, ZERO) - .unwrap(); + let consumed_note_1 = Note::new( + note_1_script, + &[Felt::new(1)], + &[fungible_asset_1], + SERIAL_NUM_1, + sender, + NoteType::Public, + ZERO, + ) + .unwrap(); const SERIAL_NUM_2: Word = [Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]; let consumed_note_2 = Note::new( @@ -150,6 +171,7 @@ pub fn mock_notes( &[fungible_asset_2, fungible_asset_3], SERIAL_NUM_2, sender, + NoteType::Public, ZERO, ) .unwrap(); @@ -164,6 +186,7 @@ pub fn mock_notes( &[fungible_asset_2, fungible_asset_3], SERIAL_NUM_3, sender, + NoteType::Public, ZERO, ) .unwrap(); @@ -178,6 +201,7 @@ pub fn mock_notes( &[non_fungible_asset_2(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN)], SERIAL_NUM_7, sender, + NoteType::Public, ZERO, ) .unwrap(); @@ -224,6 +248,7 @@ pub fn mock_notes( ], SERIAL_NUM_8, sender, + NoteType::Public, ZERO, ) .unwrap(); diff --git a/objects/src/errors.rs b/objects/src/errors.rs index b0c0ceecb..8f1e48c78 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -204,13 +204,14 @@ pub enum NoteError { DuplicateFungibleAsset(AccountId), DuplicateNonFungibleAsset(NonFungibleAsset), EmptyAssetList, - InconsistentStubId(NoteId, NoteId), InconsistentStubAssetHash(Digest, Digest), - InvalidStubDataLen(usize), - InvalidOriginIndex(String), + InconsistentStubId(NoteId, NoteId), InvalidAssetData(AssetError), + InvalidOriginIndex(String), + InvalidStubDataLen(usize), NoteDeserializationError(DeserializationError), NoteMetadataSenderInvalid(AccountError), + NoteTypeInvalid(u64), ScriptCompilationError(AssemblyError), TooManyAssets(usize), TooManyInputs(usize), diff --git a/objects/src/notes/metadata.rs b/objects/src/notes/metadata.rs index b9bb61515..9faa24c61 100644 --- a/objects/src/notes/metadata.rs +++ b/objects/src/notes/metadata.rs @@ -1,7 +1,8 @@ use vm_processor::DeserializationError; use super::{ - AccountId, ByteReader, ByteWriter, Deserializable, Felt, NoteError, Serializable, Word, + AccountId, ByteReader, ByteWriter, Deserializable, Felt, NoteError, NoteType, Serializable, + Word, }; // NOTE METADATA @@ -17,12 +18,13 @@ use super::{ pub struct NoteMetadata { sender: AccountId, tag: Felt, + note_type: NoteType, } impl NoteMetadata { /// Returns a new [NoteMetadata] instantiated with the specified parameters. - pub fn new(sender: AccountId, tag: Felt) -> Self { - Self { sender, tag } + pub fn new(sender: AccountId, note_type: NoteType, tag: Felt) -> Self { + Self { sender, tag, note_type } } /// Returns the account which created the note. @@ -34,6 +36,11 @@ impl NoteMetadata { pub fn tag(&self) -> Felt { self.tag } + + /// Returns the note's type. + pub fn note_type(&self) -> NoteType { + self.note_type + } } impl From for Word { @@ -47,6 +54,7 @@ impl From<&NoteMetadata> for Word { let mut elements = Word::default(); elements[0] = metadata.tag; elements[1] = metadata.sender.into(); + elements[2] = metadata.note_type().into(); elements } } @@ -58,6 +66,7 @@ impl TryFrom for NoteMetadata { Ok(Self { sender: elements[1].try_into().map_err(NoteError::NoteMetadataSenderInvalid)?, tag: elements[0], + note_type: elements[2].try_into()?, }) } } @@ -69,6 +78,7 @@ impl Serializable for NoteMetadata { fn write_into(&self, target: &mut W) { self.sender.write_into(target); self.tag.write_into(target); + self.note_type.write_into(target); } } @@ -76,7 +86,8 @@ impl Deserializable for NoteMetadata { fn read_from(source: &mut R) -> Result { let sender = AccountId::read_from(source)?; let tag = Felt::read_from(source)?; + let note_type = NoteType::read_from(source)?; - Ok(Self { sender, tag }) + Ok(Self { sender, tag, note_type }) } } diff --git a/objects/src/notes/mod.rs b/objects/src/notes/mod.rs index a8c35c7cb..e389cc0bb 100644 --- a/objects/src/notes/mod.rs +++ b/objects/src/notes/mod.rs @@ -7,6 +7,9 @@ use crate::{ Digest, Felt, Hasher, NoteError, Word, NOTE_TREE_DEPTH, WORD_SIZE, ZERO, }; +mod assets; +pub use assets::NoteAssets; + mod envelope; pub use envelope::NoteEnvelope; @@ -19,6 +22,9 @@ pub use metadata::NoteMetadata; mod note_id; pub use note_id::NoteId; +mod note_type; +pub use note_type::NoteType; + mod nullifier; pub use nullifier::Nullifier; @@ -28,9 +34,6 @@ pub use origin::{NoteInclusionProof, NoteOrigin}; mod script; pub use script::NoteScript; -mod assets; -pub use assets::NoteAssets; - // CONSTANTS // ================================================================================================ @@ -42,21 +45,6 @@ pub const NOTE_LEAF_DEPTH: u8 = NOTE_TREE_DEPTH + 1; // NOTE // ================================================================================================ -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum NoteType { - /// Notes with this type have only their hash published to the network. - OffChain, - - /// Notes with type are shared with the network encrypted. - Encrypted, - - /// Notes with this type are known by the network, intended to be used in local transactions. - Local, - - /// Notes with this type are known by the network, intended for network transactions. - Network, -} - /// A note which can be used to transfer assets between accounts. /// /// This struct is a full description of a note which is needed to execute a note in a transaction. @@ -107,11 +95,12 @@ impl Note { assets: &[Asset], serial_num: Word, sender: AccountId, + note_type: NoteType, tag: Felt, ) -> Result { let inputs = NoteInputs::new(inputs.to_vec())?; let assets = NoteAssets::new(assets)?; - let metadata = NoteMetadata::new(sender, tag); + let metadata = NoteMetadata::new(sender, note_type, tag); Ok(Self::from_parts(script, inputs, assets, serial_num, metadata)) } diff --git a/objects/src/notes/note_type.rs b/objects/src/notes/note_type.rs new file mode 100644 index 000000000..954b5859d --- /dev/null +++ b/objects/src/notes/note_type.rs @@ -0,0 +1,80 @@ +use crate::{ + utils::{ + format, + serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, + }, + Felt, NoteError, +}; + +// CONSTANTS +// ================================================================================================ +const OFF_CHAIN: u8 = 0; +const ENCRYPTED: u8 = 1; +const PUBLIC: u8 = 2; + +// NOTE TYPE +// ================================================================================================ + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[repr(u8)] +pub enum NoteType { + /// Notes with this type have only their hash published to the network. + OffChain = OFF_CHAIN, + + /// Notes with type are shared with the network encrypted. + Encrypted = ENCRYPTED, + + /// Notes with this type are fully shared with the network. + Public = PUBLIC, +} + +impl From for Felt { + fn from(id: NoteType) -> Self { + Felt::new(id as u64) + } +} + +impl TryFrom for NoteType { + type Error = NoteError; + + fn try_from(value: Felt) -> Result { + let value = value.as_int(); + let note_type: u8 = value.try_into().map_err(|_| NoteError::NoteTypeInvalid(value))?; + match note_type { + OFF_CHAIN => Ok(NoteType::OffChain), + ENCRYPTED => Ok(NoteType::Encrypted), + PUBLIC => Ok(NoteType::Public), + _ => Err(NoteError::NoteTypeInvalid(value)), + } + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for NoteType { + fn write_into(&self, target: &mut W) { + (*self as u8).write_into(target) + } +} + +impl Deserializable for NoteType { + fn read_from(source: &mut R) -> Result { + let discriminat = u8::read_from(source)?; + + let note_type = match discriminat { + OFF_CHAIN => NoteType::OffChain, + ENCRYPTED => NoteType::Encrypted, + PUBLIC => NoteType::Public, + v => { + return Err(DeserializationError::InvalidValue(format!( + "Value {} is not a valid NoteType", + v + ))) + }, + }; + + Ok(note_type) + } +}