diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6ee67f128..ce62ab9e3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,6 +30,22 @@ jobs: rustup component add clippy make clippy + clippy-no-std: + name: clippy no_std + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - uses: Swatinem/rust-cache@v2 + with: + # Only update the cache on push onto the next branch. This strikes a nice balance between + # cache hits and cache evictions (github has a 10GB cache limit). + save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} + - name: Clippy no_std + run: | + rustup update --no-self-update + rustup component add clippy + make clippy-no-std + rustfmt: name: rustfmt runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1090408bf..309febcb8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,3 +31,16 @@ jobs: run: make test-default - name: test-prove run: make test-prove + + doc-tests: + name: doc-tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/next' }} + - name: Install rust + run: rustup update --no-self-update + - name: Run doc-tests + run: make test-docs diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f559128..ecc85473d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,12 @@ - Implemented `to_hex` for `AccountIdPrefix` and `epoch_block_num` for `BlockHeader` (#1039). - Introduce `AccountIdBuilder` to simplify `AccountId` generation in tests (#1045). - Introduced `AccountComponentTemplate` with TOML serialization and templating (#1015, #1027). +<<<<<<< HEAD - [BREAKING] Updated the names and values of the kernel procedure offsets and corresponding kernel procedures (#1037). +======= +- Introduce `AccountIdError` and make account ID byte representations (`u128`, `[u8; 15]`) consistent (#1055). +- Refactor `AccountId` and `AccountIdPrefix` into version wrappers (#1058). +>>>>>>> next ## 0.6.2 (2024-11-20) diff --git a/Makefile b/Makefile index 1ab0dec4c..e571634ed 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,11 @@ clippy: ## Runs Clippy with configs cargo clippy --workspace --all-targets $(ALL_FEATURES_BUT_ASYNC) -- -D warnings +.PHONY: clippy-no-std +clippy-no-std: ## Runs Clippy with configs + cargo clippy --no-default-features --target wasm32-unknown-unknown --workspace --lib -- -D warnings + + .PHONY: fix fix: ## Runs Fix with configs cargo fix --workspace --allow-staged --allow-dirty --all-targets $(ALL_FEATURES_BUT_ASYNC) @@ -37,7 +42,7 @@ format-check: ## Runs Format using nightly toolchain but only in check mode .PHONY: lint -lint: format fix clippy ## Runs all linting tasks at once (Clippy, fixing, formatting) +lint: format fix clippy clippy-no-std ## Runs all linting tasks at once (Clippy, fixing, formatting) # --- docs ---------------------------------------------------------------------------------------- @@ -62,6 +67,16 @@ test-default: ## Run default tests excluding `prove` $(DEBUG_ASSERTIONS) $(BACKTRACE) cargo nextest run --profile default --cargo-profile test-release --features concurrent,testing --filter-expr "not test(prove)" +.PHONY: test-dev +test-dev: ## Run default tests excluding slow tests (prove and ID anchor block tests) in debug mode intended to be run locally + $(DEBUG_ASSERTIONS) $(BACKTRACE) cargo nextest run --profile default --features concurrent,testing --filter-expr "not test(prove) & not test(create_accounts_with_non_zero_anchor_block)" + + +.PHONY: test-docs +test-docs: ## Run documentation tests + $(WARNINGS) $(DEBUG_ASSERTIONS) cargo test --doc $(ALL_FEATURES_BUT_ASYNC) + + .PHONY: test-prove test-prove: ## Run `prove` tests (tests which use the Miden prover) $(DEBUG_ASSERTIONS) $(BACKTRACE) cargo nextest run --profile prove --cargo-profile test-release --features concurrent,testing --filter-expr "test(prove)" @@ -76,6 +91,11 @@ test: test-default test-prove ## Run all tests check: ## Check all targets and features for errors without code generation ${BUILD_KERNEL_ERRORS} cargo check --all-targets $(ALL_FEATURES_BUT_ASYNC) + +.PHONY: check-no-std +check-no-std: ## Check the no-std target without any features for errors without code generation + ${BUILD_KERNEL_ERRORS} cargo check --no-default-features --target wasm32-unknown-unknown --workspace --lib + # --- building ------------------------------------------------------------------------------------ .PHONY: build diff --git a/bin/bench-tx/src/utils.rs b/bin/bench-tx/src/utils.rs index 755a8446a..beeaf082b 100644 --- a/bin/bench-tx/src/utils.rs +++ b/bin/bench-tx/src/utils.rs @@ -70,8 +70,7 @@ pub fn get_account_with_basic_authenticated_wallet( public_key: Word, assets: Option, ) -> Account { - AccountBuilder::new() - .init_seed(init_seed) + AccountBuilder::new(init_seed) .account_type(account_type) .storage_mode(storage_mode) .with_assets(assets) diff --git a/miden-lib/asm/kernels/transaction/api.masm b/miden-lib/asm/kernels/transaction/api.masm index 82df555ea..db93d7460 100644 --- a/miden-lib/asm/kernels/transaction/api.masm +++ b/miden-lib/asm/kernels/transaction/api.masm @@ -111,23 +111,23 @@ export.account_get_current_hash # => [ACCT_HASH, pad(12)] end -#! Returns the account id. +#! Returns the account ID. #! #! Inputs: [pad(16)] -#! Outputs: [acct_id_hi, acct_id_lo, pad(14)] +#! Outputs: [acct_id_prefix, acct_id_suffix, pad(14)] #! #! Where: -#! - acct_id is the account id. +#! - acct_id is the account ID. #! #! Invocation: dynexec export.account_get_id - # get the account id + # get the account ID exec.account::get_id - # => [acct_id_hi, acct_id_lo, pad(16)] + # => [acct_id_prefix, acct_id_suffix, pad(16)] # truncate the stack movup.2 drop movup.2 drop - # => [acct_id_hi, acct_id_lo, pad(14)] + # => [acct_id_prefix, acct_id_suffix, pad(14)] end #! Returns the account nonce. @@ -503,11 +503,11 @@ end #! Returns the balance of a fungible asset associated with a faucet_id. #! -#! Inputs: [faucet_id_hi, faucet_id_lo, pad(14)] +#! Inputs: [faucet_id_prefix, faucet_id_suffix, pad(14)] #! Outputs: [balance, pad(15)] #! #! Where: -#! - faucet_id_{hi,lo} are the first and second felt of the faucet id of the fungible asset +#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet id of the fungible asset #! of interest. #! - balance is the vault balance of the fungible asset. #! @@ -518,7 +518,7 @@ end export.account_get_balance # get the vault root exec.memory::get_acct_vault_root_ptr movdn.2 - # => [faucet_id_hi, faucet_id_lo, acct_vault_root_ptr, pad(14)] + # => [faucet_id_prefix, faucet_id_suffix, acct_vault_root_ptr, pad(14)] # get the asset balance exec.asset_vault::get_balance @@ -765,10 +765,10 @@ end #! Returns the sender of the note currently being processed. #! #! Inputs: [pad(16)] -#! Outputs: [sender_hi, sender_lo, pad(14)] +#! Outputs: [sender_id_prefix, sender_id_suffix, pad(14)] #! #! Where: -#! - sender_{hi,lo} are the first and second felt of the sender account id of the note currently +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender account ID of the note currently #! being processed. #! #! Panics if: @@ -777,11 +777,11 @@ end #! Invocation: dynexec export.note_get_sender exec.note::get_sender - # => [sender_hi, sender_lo, pad(16)] + # => [sender_id_prefix, sender_id_suffix, pad(16)] # truncate the stack movup.2 drop movup.2 drop - # => [sender_hi, sender_lo, pad(14)] + # => [sender_id_prefix, sender_id_suffix, pad(14)] end #! Returns the script hash of the note currently being processed. @@ -925,9 +925,9 @@ end #! Moves the account pointer to the currently accessing foreign account. #! #! Inputs: -#! Operand stack: [foreign_account_id_hi, foreign_account_id_lo, pad(14)] +#! Operand stack: [foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] #! Advice map: { -#! FOREIGN_ACCOUNT_ID: [[foreign_account_lo, foreign_account_id_hi, 0, account_nonce], +#! FOREIGN_ACCOUNT_ID: [[foreign_account_id_suffix, foreign_account_id_prefix, 0, account_nonce], #! VAULT_ROOT, STORAGE_ROOT, CODE_ROOT], #! STORAGE_ROOT: [[STORAGE_SLOT_DATA]], #! CODE_ROOT: [num_procs, [ACCOUNT_PROCEDURE_DATA]] @@ -936,10 +936,10 @@ end #! Operand stack: [pad(16)] #! #! Where: -#! - foreign_account_id_{hi,lo} are the first and second felt of the ID of the foreign account +#! - foreign_account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the foreign account #! whose procedure is going to be executed. #! - FOREIGN_ACCOUNT_ID is the word constructed from the foreign_account_id as follows: -#! [foreign_account_lo, foreign_account_id_hi, 0, 0]. +#! [foreign_account_id_suffix, foreign_account_id_prefix, 0, 0]. #! - account_nonce is the nonce of the foreign account. #! - VAULT_ROOT is the commitment of the foreign account's vault. #! - STORAGE_ROOT is the commitment of the foreign account's storage. @@ -956,27 +956,27 @@ end export.tx_start_foreign_context # check that this procedure was executed against the native account exec.memory::assert_native_account - # OS => [foreign_account_id_hi, foreign_account_id_lo, pad(14)] + # OS => [foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] # get the memory address and a flag whether this account was already loaded. exec.account::get_foreign_account_ptr - # OS => [was_loaded, ptr, foreign_account_id_hi, foreign_account_id_lo, pad(14)] + # OS => [was_loaded, ptr, foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] if.true exec.memory::set_current_account_data_ptr drop drop # OS => [pad(16)] else exec.memory::set_current_account_data_ptr - # OS => [foreign_account_id_hi, foreign_account_id_lo, pad(14)] + # OS => [foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] # construct the word with account ID to load the core account data from the advice map push.0.0 - # OS => [0, 0, foreign_account_id_hi, foreign_account_id_lo, pad(14)] + # OS => [0, 0, foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] # move the core account data to the advice stack adv.push_mapval - # OS => [0, 0, foreign_account_id_hi, foreign_account_id_lo, pad(14)] - # AS => [[foreign_account_id_hi, foreign_account_lo, 0, account_nonce], VAULT_ROOT, STORAGE_ROOT, CODE_ROOT] + # OS => [0, 0, foreign_account_id_prefix, foreign_account_id_suffix, pad(14)] + # AS => [[foreign_account_id_prefix, foreign_account_id_suffix, 0, account_nonce], VAULT_ROOT, STORAGE_ROOT, CODE_ROOT] # store the id and nonce of the foreign account to the memory dropw adv_loadw diff --git a/miden-lib/asm/kernels/transaction/lib/account.masm b/miden-lib/asm/kernels/transaction/lib/account.masm index 1b85ee181..b9ecfba74 100644 --- a/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/miden-lib/asm/kernels/transaction/lib/account.masm @@ -12,7 +12,7 @@ use.kernel::memory # Account nonce cannot be increased by a greater than u32 value const.ERR_ACCOUNT_NONCE_INCREASE_MUST_BE_U32=0x00020004 -# Least significant byte of second felt of the account id must be zero. +# Least significant byte of the account ID suffix must be zero. const.ERR_ACCOUNT_ID_LEAST_SIGNIFICANT_BYTE_MUST_BE_ZERO=0x00020005 # Account code must be updatable for it to be possible to set new code @@ -69,23 +69,38 @@ const.ERR_FOREIGN_ACCOUNT_ID_EQUALS_NATIVE_ACCT_ID=0x00020016 # State of the current foreign account is invalid. const.ERR_FOREIGN_ACCOUNT_INVALID=0x00020017 -# Unknown version in account id. +# Unknown version in account ID. const.ERR_ACCOUNT_ID_UNKNOWN_VERSION=0x00020057 # Epoch must be less than u16::MAX (0xffff). const.ERR_ACCOUNT_ID_EPOCH_MUST_BE_LESS_THAN_U16_MAX=0x00020058 +# Unknown account storage mode in account ID. +const.ERR_ACCOUNT_ID_UNKNOWN_STORAGE_MODE=0x00020059 + # CONSTANTS # ================================================================================================= -# Given the least significant 32 bits of an account id's first felt, this mask defines the bits used +# Given the least significant 32 bits of an account ID's prefix, this mask defines the bits used # to determine the account version. const.ACCOUNT_VERSION_MASK_U32=0x0f # 0b1111 -# Given the least significant 32 bits of an account id's first felt, this mask defines the bits used +# Given the least significant 32 bits of an account ID's prefix, this mask defines the bits used # to determine the account type. const.ACCOUNT_ID_TYPE_MASK_U32=0x30 # 0b11_0000 +# Given the least significant 32 bits of an account ID's first felt, this mask defines the bits used +# to determine the account storage mode. +const.ACCOUNT_ID_STORAGE_MODE_MASK_U32=0xC0 # 0b1100_0000 + +# Given the least significant 32 bits of an account ID's first felt with the storage mode mask +# applied, this value defines the public storage mode. +const.ACCOUNT_ID_STORAGE_MODE_PUBLIC_U32=0 # 0b0000_0000 + +# Given the least significant 32 bits of an account ID's first felt with the storage mode mask +# applied, this value defines the private storage mode. +const.ACCOUNT_ID_STORAGE_MODE_PRIVATE_U32=0x80 # 0b1000_0000 + # Bit pattern for an account w/ immutable code, after the account type mask has been applied. const.REGULAR_ACCOUNT_IMMUTABLE_CODE=0 # 0b00_0000 @@ -232,13 +247,13 @@ export.incr_nonce emit.ACCOUNT_AFTER_INCREMENT_NONCE_EVENT end -#! Returns the account id. +#! Returns the account ID. #! #! Inputs: [] #! Outputs: [acct_id] #! #! Where: -#! - acct_id is the account id. +#! - acct_id is the account ID. export.memory::get_account_id->get_id #! Returns the account nonce. @@ -261,11 +276,11 @@ export.memory::get_init_acct_hash->get_initial_hash #! Returns a boolean indicating whether the account is a fungible faucet. #! -#! Inputs: [acct_id_hi] +#! Inputs: [acct_id_prefix] #! Outputs: [is_fungible_faucet] #! #! Where: -#! - acct_id_hi is the first felt of the account id. +#! - acct_id_prefix is the prefix of the account ID. #! - is_fungible_faucet is a boolean indicating whether the account is a fungible faucet. export.is_fungible_faucet exec.type push.FUNGIBLE_FAUCET_ACCOUNT eq @@ -274,11 +289,11 @@ end #! Returns a boolean indicating whether the account is a non-fungible faucet. #! -#! Inputs: [acct_id_hi] +#! Inputs: [acct_id_prefix] #! Outputs: [is_non_fungible_faucet] #! #! Where: -#! - acct_id_hi is the first felt of the account id. +#! - acct_id_prefix is the prefix of the account ID. #! - is_non_fungible_faucet is a boolean indicating whether the account is a non-fungible faucet. export.is_non_fungible_faucet exec.type push.NON_FUNGIBLE_FAUCET_ACCOUNT eq @@ -287,11 +302,11 @@ end #! Returns a boolean indicating whether the account is a faucet. #! -#! Inputs: [acct_id_hi] +#! Inputs: [acct_id_prefix] #! Outputs: [is_faucet] #! #! Where: -#! - acct_id_hi is the first felt of the account id. +#! - acct_id_prefix is the prefix of the account ID. #! - is_faucet is a boolean indicating whether the account is a faucet. export.is_faucet u32split drop push.FAUCET_ACCOUNT u32and eq.0 not @@ -300,11 +315,11 @@ end #! Returns a boolean indicating whether the account is a regular updatable account. #! -#! Inputs: [acct_id_hi] +#! Inputs: [acct_id_prefix] #! Outputs: [is_updatable_account] #! #! Where: -#! - acct_id_hi is the first felt of the account id. +#! - acct_id_prefix is the prefix of the account ID. #! - is_updatable_account is a boolean indicating whether the account is a regular updatable #! account. export.is_updatable_account @@ -314,11 +329,11 @@ end #! Returns a boolean indicating whether the account is a regular immutable account. #! -#! Inputs: [acct_id_hi] +#! Inputs: [acct_id_prefix] #! Outputs: [is_immutable_account] #! #! Where: -#! - acct_id_hi is the first felt of the account id. +#! - acct_id_prefix is the prefix of the account ID. #! - is_immutable_account is a boolean indicating whether the account is a regular immutable #! account. export.is_immutable_account @@ -328,52 +343,66 @@ end #! Returns a boolean indicating whether the given account_ids are equal. #! -#! Inputs: [acct_id_hi, acct_id_lo, other_acct_id_hi, other_acct_id_lo] +#! Inputs: [acct_id_prefix, acct_id_suffix, other_acct_id_prefix, other_acct_id_suffix] #! Outputs: [is_id_equal] #! #! Where: -#! - acct_id_{hi,lo} are the first and second felt of an account id. -#! - other_acct_id_{hi,lo} are the first and second felt of the other account id to compare against. -#! - is_id_equal is a boolean indicating whether the account ids are equal. +#! - acct_id_{prefix,suffix} are the prefix and suffix felts of an account ID. +#! - other_acct_id_{prefix,suffix} are the prefix and suffix felts of the other account ID to compare against. +#! - is_id_equal is a boolean indicating whether the account IDs are equal. export.is_id_eq movup.2 eq - # => [is_hi_equal, acct_id_lo, other_acct_id_lo] + # => [is_prefix_equal, acct_id_suffix, other_acct_id_suffix] movdn.2 eq - # => [is_lo_equal, is_hi_equal] + # => [is_suffix_equal, is_prefix_equal] and # => [is_id_equal] end -#! Validates an account id. +#! Validates an account ID. Note that this does not validate anything about the account type, +#! since any bit pattern is a valid account type. #! -#! Inputs: [account_id_hi, account_id_lo] +#! Inputs: [account_id_prefix, account_id_suffix] #! Outputs: [] #! #! Where: -#! - account_id_{hi,lo} are the first and second felt of the account id. +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID. #! #! Panics if: -#! - account_id_hi does not contain version zero. -#! - account_id_lo contains an anchor epoch that is greater or equal to 2^16. -#! - account_id_lo does not have its lower 8 bits set to zero. +#! - account_id_prefix does not contain version zero. +#! - account_id_prefix does not contain either the public or private storage mode. +#! - account_id_suffix contains an anchor epoch that is greater or equal to 2^16. +#! - account_id_suffix does not have its lower 8 bits set to zero. export.validate_id - # Validate version in first felt. For now only version 0 is supported. + # Validate version in prefix. For now only version 0 is supported. # --------------------------------------------------------------------------------------------- - exec.id_version - # => [id_version, account_id_lo] + dup exec.id_version + # => [id_version, account_id_prefix, account_id_suffix] assertz.err=ERR_ACCOUNT_ID_UNKNOWN_VERSION - # => [account_id_lo] + # => [account_id_prefix, account_id_suffix] + + # Validate storage mode in prefix. + # --------------------------------------------------------------------------------------------- + + u32split drop + # => [account_id_prefix_lo, account_id_suffix] + u32and.ACCOUNT_ID_STORAGE_MODE_MASK_U32 dup eq.ACCOUNT_ID_STORAGE_MODE_PRIVATE_U32 + # => [is_private_storage_mode, id_storage_mode_masked, account_id_suffix] + swap eq.ACCOUNT_ID_STORAGE_MODE_PUBLIC_U32 + # => [is_public_storage_mode, is_private_storage_mode, account_id_suffix] + or assert.err=ERR_ACCOUNT_ID_UNKNOWN_STORAGE_MODE + # => [account_id_suffix] - # Validate anchor epoch is less than u16::MAX (0xffff) in second felt. + # Validate anchor epoch is less than u16::MAX (0xffff) in suffix. # --------------------------------------------------------------------------------------------- dup exec.id_anchor_epoch - # => [anchor_epoch, account_id_lo] + # => [anchor_epoch, account_id_suffix] lt.0xffff assert.err=ERR_ACCOUNT_ID_EPOCH_MUST_BE_LESS_THAN_U16_MAX - # => [account_id_lo] + # => [account_id_suffix] - # Validate lower 8 bits of second felt are zero. + # Validate lower 8 bits of suffix are zero. # --------------------------------------------------------------------------------------------- u32split drop u32and.0xff eq.0 @@ -741,7 +770,7 @@ end #! 1. Retrieve the anchor block hash by computing the block number of the anchor block and #! retrieving it from the chain mmr. #! 2. Compute the hash of (SEED, CODE_COMMITMENT, STORAGE_COMMITMENT, ANCHOR_BLOCK_HASH). -#! 3. Assert the two least significant elements of the digest are equal to the account id of the +#! 3. Assert the two least significant elements of the digest are equal to the account ID of the #! account the transaction is being executed against. #! #! Inputs: [] @@ -753,30 +782,30 @@ export.validate_seed # prepare the advice map key for the seed for later exec.memory::get_account_id push.0.0 - # => [0, 0, account_id_hi, account_id_lo] + # => [0, 0, account_id_prefix, account_id_suffix] # get the anchor block's number dup.3 exec.id_anchor_block_num - # => [anchor_block_num, 0, 0, account_id_hi, account_id_lo] + # => [anchor_block_num, 0, 0, account_id_prefix, account_id_suffix] exec.memory::get_chain_mmr_ptr swap - # => [anchor_block_num, chain_mmr_ptr, 0, 0, account_id_hi, account_id_lo] + # => [anchor_block_num, chain_mmr_ptr, 0, 0, account_id_prefix, account_id_suffix] exec.mmr::get - # => [ANCHOR_BLOCK_HASH, 0, 0, account_id_hi, account_id_lo] + # => [ANCHOR_BLOCK_HASH, 0, 0, account_id_prefix, account_id_suffix] # assert that the anchor block hash is not the empty word padw eqw not assert.err=ERR_ACCOUNT_ANCHOR_BLOCK_HASH_MUST_NOT_BE_EMPTY dropw - # => [ANCHOR_BLOCK_HASH, 0, 0, account_id_hi, account_id_lo] + # => [ANCHOR_BLOCK_HASH, 0, 0, account_id_prefix, account_id_suffix] # Compute the hash of (SEED, CODE_COMMITMENT, STORAGE_COMMITMENT, ANCHOR_BLOCK_HASH). # --------------------------------------------------------------------------------------------- # prepare advice push_mapval swapw - # => [0, 0, account_id_hi, account_id_lo, ANCHOR_BLOCK_HASH] + # => [0, 0, account_id_prefix, account_id_suffix, ANCHOR_BLOCK_HASH] - # populate first four elements of the rate with the account id seed + # populate first four elements of the rate with the account ID seed adv.push_mapval push.15263 drop # TODO: remove line, see miden-vm/#1122 adv_loadw # => [SEED, ANCHOR_BLOCK_HASH] @@ -809,117 +838,117 @@ export.validate_seed exec.rpo::squeeze_digest # => [DIGEST] - # Shape second felt to add the anchor epoch and compare computed and provided ID. + # Shape suffix to add the anchor epoch and compare computed and provided ID. # --------------------------------------------------------------------------------------------- - # extract account id from digest + # extract account ID from digest drop drop swap - # => [hashed_account_id_hi, hashed_account_id_lo] + # => [hashed_account_id_prefix, hashed_account_id_suffix] exec.memory::get_account_id movdn.3 - # => [account_id_lo, hashed_account_id_hi, hashed_account_id_lo, account_id_hi] + # => [account_id_suffix, hashed_account_id_prefix, hashed_account_id_suffix, account_id_prefix] # extract anchor epoch from ID of the new account dup movdn.4 exec.id_anchor_epoch - # => [anchor_epoch, hashed_account_id_hi, hashed_account_id_lo, account_id_hi, account_id_lo] + # => [anchor_epoch, hashed_account_id_prefix, hashed_account_id_suffix, account_id_prefix, account_id_suffix] - # shape second felt of hashed id, adding the anchor epoch and setting the lower 8 bits to zero - movup.2 exec.shape_second_felt swap - # => [hashed_account_id_hi, hashed_account_id_lo, account_id_hi, account_id_lo] + # shape suffix of hashed id, adding the anchor epoch and setting the lower 8 bits to zero + movup.2 exec.shape_suffix swap + # => [hashed_account_id_prefix, hashed_account_id_suffix, account_id_prefix, account_id_suffix] - # assert the account id matches the account id of the new account + # assert the account ID matches the account ID of the new account exec.is_id_eq assert.err=ERR_ACCOUNT_SEED_ANCHOR_BLOCK_HASH_DIGEST_MISMATCH # => [] end -#! Shapes the second felt so it meets the requirements of the account ID, by overwriting the +#! Shapes the suffix so it meets the requirements of the account ID, by overwriting the #! upper 16 bits with the anchor epoch and setting the lower 8 bits to zero. #! -#! Inputs: [seed_digest_lo, anchor_epoch] -#! Outputs: [account_id_lo] +#! Inputs: [seed_digest_suffix, anchor_epoch] +#! Outputs: [account_id_suffix] #! #! Where: -#! - seed_digest_lo is the second felt of the digest that should be shaped into the second felt +#! - seed_digest_suffix is the suffix of the digest that should be shaped into the suffix #! of an account ID. -#! - account_id_lo is the second felt of an account ID. +#! - account_id_suffix is the suffix of an account ID. #! - anchor_epoch is the epoch number to which this account ID is anchored. -proc.shape_second_felt +proc.shape_suffix u32split - # => [seed_digest_lo_hi, seed_digest_lo_lo, anchor_epoch] + # => [seed_digest_suffix_hi, seed_digest_suffix_lo, anchor_epoch] # clear epoch bits in hi part so we can set them later u32and.0x0000ffff swap - # => [seed_digest_lo_lo, seed_digest_lo_hi', anchor_epoch] + # => [seed_digest_suffix_lo, seed_digest_suffix_hi', anchor_epoch] # clear lower 8 bits of the lo part u32and.0xffffff00 swap.2 - # => [anchor_epoch, seed_digest_lo_hi', seed_digest_lo_lo'] + # => [anchor_epoch, seed_digest_suffix_hi', seed_digest_suffix_lo'] # assert epoch is not 2^16 # this is technically optional as we will compare this id with the provided one for which # this property was already checked, but since this check is cheap we include it anyway dup eq.0xffff assertz.err=ERR_ACCOUNT_ID_EPOCH_MUST_BE_LESS_THAN_U16_MAX - # => [anchor_epoch, seed_digest_lo_hi', seed_digest_lo_lo'] + # => [anchor_epoch, seed_digest_suffix_hi', seed_digest_suffix_lo'] # shift epoch left by 16 bits and set epoch bits on hi part u32shl.16 u32or - # => [seed_digest_lo_hi'', seed_digest_lo_lo'] + # => [seed_digest_suffix_hi'', seed_digest_suffix_lo'] - # reassemble the second felt by multiplying the hi part with 2^32 and adding the lo part + # reassemble the suffix by multiplying the hi part with 2^32 and adding the lo part mul.0x0100000000 add - # => [account_id_lo] + # => [account_id_suffix] end -#! Extracts the block number of the anchor block from the second felt of an account ID. +#! Extracts the block number of the anchor block from the suffix of an account ID. #! -#! Inputs: [account_id_lo] +#! Inputs: [account_id_suffix] #! Outputs: [anchor_block_num] #! #! Where: -#! - account_id_lo is the second felt of an account ID. +#! - account_id_suffix is the suffix of an account ID. #! - anchor_block_num is the number of the block to which this account ID is anchored. proc.id_anchor_block_num # extract the upper 32 bits u32split swap drop - # => [account_id_lo_hi] + # => [account_id_suffix_hi] - # to get the epoch's block number we would have to multiply the epoch in the account id by 2^16 + # to get the epoch's block number we would have to multiply the epoch in the account ID by 2^16 # since the epoch is already in the upper 16 bits of the u32, we can simply zero out the # lower 16 bits to achieve the same result. u32and.0xffff0000 # => [anchor_block_num] end -#! Extracts the epoch from the second felt of an account ID. +#! Extracts the epoch from the suffix of an account ID. #! -#! Inputs: [account_id_lo] +#! Inputs: [account_id_suffix] #! Outputs: [anchor_epoch] #! #! Where: -#! - account_id_lo is the second felt of an account ID. +#! - account_id_suffix is the suffix of an account ID. #! - anchor_epoch is the epoch number to which this account ID is anchored. proc.id_anchor_epoch # extract the upper 32 bits u32split swap drop - # => [account_id_lo_hi] + # => [account_id_suffix_hi] # shift the upper 16 bits to the right to produce the epoch u32shr.16 # => [anchor_epoch] end -#! Extracts the account ID version from the first felt of an account ID. +#! Extracts the account ID version from the prefix of an account ID. #! -#! Inputs: [account_id_hi] +#! Inputs: [account_id_prefix] #! Outputs: [id_version] #! #! Where: -#! - account_id_hi is the first felt of an account ID. +#! - account_id_prefix is the prefix of an account ID. #! - id_version is the version number of the ID. proc.id_version # extract the lower 32 bits u32split drop - # => [account_id_hi_lo] + # => [account_id_prefix_lo] # mask out the version u32and.ACCOUNT_VERSION_MASK_U32 @@ -1070,19 +1099,19 @@ end # HELPER PROCEDURES # ================================================================================================= -#! Returns the most significant half with the account type bits masked out. +#! Returns the least significant half of an account ID prefix with the account type bits masked out. #! -#! The account type can be defined by comparing this value with the following constants: +#! The account type can be obtained by comparing this value with the following constants: #! - REGULAR_ACCOUNT_UPDATABLE_CODE #! - REGULAR_ACCOUNT_IMMUTABLE_CODE #! - FUNGIBLE_FAUCET_ACCOUNT #! - NON_FUNGIBLE_FAUCET_ACCOUNT #! -#! Inputs: [acct_id_hi] +#! Inputs: [acct_id_prefix] #! Outputs: [acct_type] #! #! Where: -#! - acct_id_hi is the first felt of the account id. +#! - acct_id_prefix is the prefix of the account ID. #! - acct_type is the account type. proc.type u32split drop push.ACCOUNT_ID_TYPE_MASK_U32 u32and @@ -1144,11 +1173,11 @@ end #! Returns the pointer to the next vacant memory slot if the account was not loaded before, and the #! pointer to the account data otherwise. #! -#! Inputs: [foreign_account_id_hi, foreign_account_id_lo] -#! Outputs: [was_loaded, ptr, foreign_account_id_hi, foreign_account_id_lo] +#! Inputs: [foreign_account_id_prefix, foreign_account_id_suffix] +#! Outputs: [was_loaded, ptr, foreign_account_id_prefix, foreign_account_id_suffix] #! #! Where: -#! - foreign_account_id_{hi,lo} are the first and second felt of the ID of the foreign account +#! - foreign_account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the foreign account #! whose procedure is going to be executed. #! - was_loaded is the binary flag indicating whether the foreign account was already loaded to the #! memory. @@ -1156,20 +1185,20 @@ end #! data, depending on the value of the was_loaded flag. #! #! Panics if: -#! - the first or second felt of the provided foreign account ID equal zero. +#! - the prefix or suffix of the provided foreign account ID equal zero. #! - the maximum allowed number of foreign account to be loaded (64) was exceeded. export.get_foreign_account_ptr - # check that foreign account id is not equal zero + # check that foreign account ID is not equal zero dup.1 eq.0 dup.1 eq.0 and not assert.err=ERR_FOREIGN_ACCOUNT_ID_IS_ZERO - # => [foreign_account_id_hi, foreign_account_id_lo] + # => [foreign_account_id_prefix, foreign_account_id_suffix] - # check that foreign account id is not equal to the native account id + # check that foreign account ID is not equal to the native account ID dup.1 dup.1 exec.memory::get_native_account_id exec.is_id_eq not assert.err=ERR_FOREIGN_ACCOUNT_ID_EQUALS_NATIVE_ACCT_ID # get the initial account data pointer exec.memory::get_native_account_data_ptr - # => [curr_account_ptr, foreign_account_id_hi, foreign_account_id_lo] + # => [curr_account_ptr, foreign_account_id_prefix, foreign_account_id_suffix] # push the flag to enter the loop push.1 @@ -1178,41 +1207,41 @@ export.get_foreign_account_ptr # drop the flag left from the previous loop # in the first iteration this will be a pad element movup.3 drop - # => [curr_account_ptr, foreign_account_id_hi, foreign_account_id_lo] + # => [curr_account_ptr, foreign_account_id_prefix, foreign_account_id_suffix] # move the current account pointer to the next account data block exec.memory::get_account_data_length add - # => [curr_account_ptr', foreign_account_id_hi, foreign_account_id_lo] + # => [curr_account_ptr', foreign_account_id_prefix, foreign_account_id_suffix] # load the first data word at the current account pointer padw dup.4 mem_loadw - # => [FIRST_DATA_WORD, curr_account_ptr', foreign_account_id_hi, foreign_account_id_lo] + # => [FIRST_DATA_WORD, curr_account_ptr', foreign_account_id_prefix, foreign_account_id_suffix] # check whether the last value in the word equals zero # if so it means this memory block was not yet initialized drop drop dup.1 eq.0 - # => [is_empty_block, maybe_account_id_hi, maybe_account_id_lo, curr_account_ptr', foreign_account_id_hi, foreign_account_id_lo] + # => [is_empty_block, maybe_account_id_prefix, maybe_account_id_suffix, curr_account_ptr', foreign_account_id_prefix, foreign_account_id_suffix] # check whether the current id matches the foreign id movdn.2 dup.5 dup.5 exec.is_id_eq - # => [is_equal_id, is_empty_word, curr_account_ptr', foreign_account_id_hi, foreign_account_id_lo] + # => [is_equal_id, is_empty_word, curr_account_ptr', foreign_account_id_prefix, foreign_account_id_suffix] # get the loop flag # it equals 1 if both `is_equal_id` and `is_empty_block` flags are equal to 0, so we should # continue iterating dup movdn.5 or not - # => [loop_flag, curr_account_ptr', foreign_account_id_hi, foreign_account_id_lo, is_equal_id] + # => [loop_flag, curr_account_ptr', foreign_account_id_prefix, foreign_account_id_suffix, is_equal_id] end # check that the loading of one more account won't exceed the maximum number of the foreign # accounts which can be loaded. dup exec.memory::get_max_foreign_account_ptr lte assert.err=ERR_FOREIGN_ACCOUNT_MAX_NUMBER_EXCEEDED - # => [curr_account_ptr, foreign_account_id_hi, foreign_account_id_lo, is_equal_id] + # => [curr_account_ptr, foreign_account_id_prefix, foreign_account_id_suffix, is_equal_id] # the resulting `was_loaded` flag is essentially equal to the `is_equal_id` flag movup.3 - # => [was_loaded, curr_account_ptr, foreign_account_id_hi, foreign_account_id_lo] + # => [was_loaded, curr_account_ptr, foreign_account_id_prefix, foreign_account_id_suffix] end #! Checks that the state of the current foreign account is valid. @@ -1227,22 +1256,21 @@ export.validate_current_foreign_account exec.memory::get_acct_db_root # => [ACCOUNT_DB_ROOT] - # TODO: Update when account tree was updated. # get the current account ID exec.memory::get_account_id swap drop - # => [account_id_hi, ACCOUNT_DB_ROOT] + # => [account_id_prefix, ACCOUNT_DB_ROOT] # push the depth of the account database tree push.ACCOUNT_TREE_DEPTH - # => [depth, account_id_hi, ACCOUNT_DB_ROOT] + # => [depth, account_id_prefix, ACCOUNT_DB_ROOT] # get the foreign account hash exec.get_current_hash - # => [FOREIGN_ACCOUNT_HASH, depth, account_id_hi, ACCOUNT_DB_ROOT] + # => [FOREIGN_ACCOUNT_HASH, depth, account_id_prefix, ACCOUNT_DB_ROOT] # verify that the account database has the hash of the current foreign account mtree_verify.err=ERR_FOREIGN_ACCOUNT_INVALID - # => [FOREIGN_ACCOUNT_HASH, depth, account_id_hi, ACCOUNT_DB_ROOT] + # => [FOREIGN_ACCOUNT_HASH, depth, account_id_prefix, ACCOUNT_DB_ROOT] # clean the stack dropw drop drop dropw diff --git a/miden-lib/asm/kernels/transaction/lib/asset.masm b/miden-lib/asm/kernels/transaction/lib/asset.masm index 6d6add3a3..d38b257fd 100644 --- a/miden-lib/asm/kernels/transaction/lib/asset.masm +++ b/miden-lib/asm/kernels/transaction/lib/asset.masm @@ -59,11 +59,11 @@ export.validate_fungible_asset dup.2 not assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_ONE_MUST_BE_ZERO # => [ASSET] - # assert that the tuple (ASSET[3], ASSET[2]) forms a valid account id + # assert that the tuple (ASSET[3], ASSET[2]) forms a valid account ID dup.1 dup.1 exec.account::validate_id # => [ASSET] - # assert that the first felt (ASSET[3]) of the account id is of type fungible faucet + # assert that the prefix (ASSET[3]) of the account ID is of type fungible faucet dup exec.account::is_fungible_faucet assert.err=ERR_FUNGIBLE_ASSET_FORMAT_ELEMENT_TWO_AND_THREE_MUST_BE_FUNGIBLE_FAUCET_ID # => [ASSET] @@ -101,8 +101,8 @@ end #! Panics if: #! - the asset is not well formed. export.validate_non_fungible_asset - # assert that ASSET[3] is a valid account id prefix - # hack: because we only have the first felt we add a 0 as the second felt which is always valid + # assert that ASSET[3] is a valid account ID prefix + # hack: because we only have the prefix we add a 0 as the suffix which is always valid push.0 dup.1 exec.account::validate_id # => [ASSET] @@ -155,16 +155,16 @@ end #! Validates that a fungible asset is associated with the provided faucet_id. #! -#! Inputs: [faucet_id_hi, faucet_id_lo, ASSET] +#! Inputs: [faucet_id_prefix, faucet_id_suffix, ASSET] #! Outputs: [ASSET] #! #! Where: -#! - faucet_id_hi is the first felt of the faucet's account id. +#! - faucet_id_prefix is the prefix of the faucet's account ID. #! - ASSET is the asset to validate. export.validate_fungible_asset_origin # assert the origin of the asset is the faucet_id provided via the stack dup.3 dup.3 - # => [asset_id_hi, asset_id_lo, faucet_id_hi, faucet_id_lo, ASSET] + # => [asset_id_prefix, asset_id_suffix, faucet_id_prefix, faucet_id_suffix, ASSET] exec.account::is_id_eq assert.err=ERR_FUNGIBLE_ASSET_FAUCET_IS_NOT_ORIGIN # => [ASSET] @@ -176,11 +176,11 @@ end #! Validates that a non-fungible asset is associated with the provided faucet_id. #! -#! Inputs: [faucet_id_hi, ASSET] +#! Inputs: [faucet_id_prefix, ASSET] #! Outputs: [ASSET] #! #! Where: -#! - faucet_id_hi is the first felt of the faucet's account id. +#! - faucet_id_prefix is the prefix of the faucet's account ID. #! - ASSET is the asset to validate. export.validate_non_fungible_asset_origin # assert the origin of the asset is the faucet_id prefix provided via the stack diff --git a/miden-lib/asm/kernels/transaction/lib/asset_vault.masm b/miden-lib/asm/kernels/transaction/lib/asset_vault.masm index cbba6e040..0f63f7f0e 100644 --- a/miden-lib/asm/kernels/transaction/lib/asset_vault.masm +++ b/miden-lib/asm/kernels/transaction/lib/asset_vault.masm @@ -42,12 +42,12 @@ const.INVERSE_FUNGIBLE_BITMASK_U32=0xffffffdf # last byte: 0b1101_1111 #! Returns the balance of a fungible asset associated with a faucet_id. #! -#! Inputs: [faucet_id_hi, faucet_id_lo, vault_root_ptr] +#! Inputs: [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] #! Outputs: [balance] #! #! Where: #! - vault_root_ptr is a pointer to the memory location at which the vault root is stored. -#! - faucet_id_hi is the first felt of the faucet id of the fungible asset of interest. +#! - faucet_id_prefix is the prefix of the faucet id of the fungible asset of interest. #! - balance is the vault balance of the fungible asset. #! #! Panics if: @@ -56,15 +56,15 @@ export.get_balance # assert that the faucet id is a fungible faucet dup exec.account::is_fungible_faucet assert.err=ERR_VAULT_GET_BALANCE_PROC_CAN_ONLY_BE_CALLED_ON_FUNGIBLE_FAUCET - # => [faucet_id_hi, faucet_id_lo, vault_root_ptr] + # => [faucet_id_prefix, faucet_id_suffix, vault_root_ptr] # get the asset vault root padw movup.6 mem_loadw - # => [ASSET_VAULT_ROOT, faucet_id_hi, faucet_id_lo] + # => [ASSET_VAULT_ROOT, faucet_id_prefix, faucet_id_suffix] # prepare the key for fungible asset lookup (pad least significant elements with zeros) push.0.0 movup.7 movup.7 - # => [faucet_id_hi, faucet_id_lo, 0, 0, ASSET_VAULT_ROOT] + # => [faucet_id_prefix, faucet_id_suffix, 0, 0, ASSET_VAULT_ROOT] # lookup asset exec.smt::get swapw dropw @@ -134,15 +134,15 @@ export.add_fungible_asset # Create the asset key from the asset. # --------------------------------------------------------------------------------------------- - # => [faucet_id_hi, faucet_id_lo, 0, amount, vault_root_ptr] + # => [faucet_id_prefix, faucet_id_suffix, 0, amount, vault_root_ptr] dupw - # => [faucet_id_hi, faucet_id_lo, 0, amount, faucet_id_hi, faucet_id_lo, 0, amount, vault_root_ptr] + # => [faucet_id_prefix, faucet_id_suffix, 0, amount, faucet_id_prefix, faucet_id_suffix, 0, amount, vault_root_ptr] push.0 swap.4 drop - # => [[faucet_id_hi, faucet_id_lo, 0, 0], faucet_id_hi, faucet_id_lo, 0, amount, vault_root_ptr] + # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], faucet_id_prefix, faucet_id_suffix, 0, amount, vault_root_ptr] movup.6 drop - # => [[faucet_id_hi, faucet_id_lo, 0, 0], faucet_id_hi, faucet_id_lo, amount, vault_root_ptr] + # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] padw dup.11 - # => [vault_root_ptr, pad(4), ASSET_KEY, faucet_id_hi, faucet_id_lo, amount, vault_root_ptr] + # => [vault_root_ptr, pad(4), ASSET_KEY, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] # Get the asset vault root and read the current asset using the `push_smtpeek` decorator. # --------------------------------------------------------------------------------------------- @@ -150,18 +150,18 @@ export.add_fungible_asset # the current asset may be the empty word if it does not exist and so its faucet id would be zeroes # we therefore overwrite the faucet id with the faucet id from ASSET to account for this edge case mem_loadw swapw - # => [ASSET_KEY, VAULT_ROOT, faucet_id_hi, faucet_id_lo, amount, vault_root_ptr] + # => [ASSET_KEY, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] adv.push_smtpeek push.15329 drop # TODO: remove line, see miden-vm/#1122 adv_loadw - # => [CUR_VAULT_VALUE, VAULT_ROOT, faucet_id_hi, faucet_id_lo, amount, vault_root_ptr] + # => [CUR_VAULT_VALUE, VAULT_ROOT, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] swapw - # => [VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_hi, faucet_id_lo, amount, vault_root_ptr] + # => [VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] dupw.1 - # => [CUR_VAULT_VALUE, VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_hi, faucet_id_lo, amount, vault_root_ptr] + # => [CUR_VAULT_VALUE, VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] drop drop - # => [[0, cur_amount], VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_hi, faucet_id_lo, amount, vault_root_ptr] + # => [[0, cur_amount], VAULT_ROOT, CUR_VAULT_VALUE, faucet_id_prefix, faucet_id_suffix, amount, vault_root_ptr] movup.11 movup.11 - # => [[faucet_id_hi, faucet_id_lo, 0, cur_amount], VAULT_ROOT, CUR_VAULT_VALUE, amount, vault_root_ptr] + # => [[faucet_id_prefix, faucet_id_suffix, 0, cur_amount], VAULT_ROOT, CUR_VAULT_VALUE, amount, vault_root_ptr] # Check the new amount does not exceed the maximum allowed amount and add the two # fungible assets together. @@ -169,16 +169,16 @@ export.add_fungible_asset # arrange amounts movup.3 movup.12 dup - # => [amount, amount, cur_amount, faucet_id_hi, faucet_id_lo, 0, VAULT_ROOT, CUR_VAULT_VALUE, vault_root_ptr] + # => [amount, amount, cur_amount, faucet_id_prefix, faucet_id_suffix, 0, VAULT_ROOT, CUR_VAULT_VALUE, vault_root_ptr] # compute max_amount - cur_amount exec.asset::get_fungible_asset_max_amount dup.3 sub - # => [(max_amount - cur_amount), amount, amount, cur_amount, faucet_id_hi, faucet_id_lo, 0, VAULT_ROOT, + # => [(max_amount - cur_amount), amount, amount, cur_amount, faucet_id_prefix, faucet_id_suffix, 0, VAULT_ROOT, # CUR_VAULT_VALUE, vault_root_ptr] # assert amount + cur_amount < max_amount lte assert.err=ERR_VAULT_FUNGIBLE_MAX_AMOUNT_EXCEEDED - # => [amount, cur_amount, faucet_id_hi, faucet_id_lo, 0, VAULT_ROOT, CUR_VAULT_VALUE, vault_root_ptr] + # => [amount, cur_amount, faucet_id_prefix, faucet_id_suffix, 0, VAULT_ROOT, CUR_VAULT_VALUE, vault_root_ptr] # add asset amounts add movdn.3 @@ -193,7 +193,7 @@ export.add_fungible_asset dupw # => [ASSET', ASSET', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] push.0 swap.4 drop - # => [[faucet_id_hi, faucet_id_lo, 0, 0], ASSET', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] + # => [[faucet_id_prefix, faucet_id_suffix, 0, 0], ASSET', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] swapw # => [ASSET', ASSET_KEY', VAULT_ROOT, CUR_VAULT_VALUE, ASSET', vault_root_ptr] @@ -433,17 +433,17 @@ end #! - ASSET_KEY is the vault key of the non-fungible asset. proc.build_non_fungible_asset_vault_key # create the asset key from the non-fungible asset by swapping hash0 with the faucet id - # => [faucet_id_hi, hash2, hash1, hash0] + # => [faucet_id_prefix, hash2, hash1, hash0] swap.3 - # => [hash0, hash2, hash1 faucet_id_hi] + # => [hash0, hash2, hash1 faucet_id_prefix] # disassemble hash0 into u32 limbs u32split swap - # => [hash0_lo, hash0_hi, hash2, hash1 faucet_id_hi] + # => [hash0_lo, hash0_hi, hash2, hash1 faucet_id_prefix] # set the fungible bit to 0 u32and.INVERSE_FUNGIBLE_BITMASK_U32 - # => [hash0_lo', hash0_hi, hash2, hash1 faucet_id_hi] + # => [hash0_lo', hash0_hi, hash2, hash1 faucet_id_prefix] # reassemble hash0 felt by multiplying the high part with 2^32 and adding the lo part swap push.0x0100000000 mul add diff --git a/miden-lib/asm/kernels/transaction/lib/constants.masm b/miden-lib/asm/kernels/transaction/lib/constants.masm index a4fff64b3..6ee55fb5d 100644 --- a/miden-lib/asm/kernels/transaction/lib/constants.masm +++ b/miden-lib/asm/kernels/transaction/lib/constants.masm @@ -22,14 +22,6 @@ const.NOTE_TREE_DEPTH=16 # The maximum number of notes that can be created in a single transaction. const.MAX_OUTPUT_NOTES_PER_TX=1024 -# Specifies a modulus used to assess if an account seed digest has the required number of trailing -# zeros for a regular account (2^23). -const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS=8388608 - -# Specifies a modulus used to assess if an account seed digest has the required number of trailing -# zeros for a faucet account (2^31). -const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS=2147483648 - # TYPES # ================================================================================================= @@ -118,32 +110,6 @@ export.get_max_num_output_notes push.MAX_OUTPUT_NOTES_PER_TX end -#! Returns a modulus used to assess if an account seed digest has the required number of trailing -#! zeros for a regular account (2^23). -#! -#! Inputs: [] -#! Outputs: [REGULAR_ACCOUNT_SEED_DIGEST_MODULUS] -#! -#! Where: -#! - REGULAR_ACCOUNT_SEED_DIGEST_MODULUS is a modulus used to assess if a seed digest has the -#! required number of trailing zeros for a regular account. -export.get_regular_account_seed_digest_modulus - push.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS -end - -#! Returns a modulus used to assess if an account seed digest has the required number of trailing -#! zeros for a faucet account (2^31). -#! -#! Inputs: [] -#! Outputs: [FAUCET_ACCOUNT_SEED_DIGEST_MODULUS] -#! -#! Where: -#! - FAUCET_ACCOUNT_SEED_DIGEST_MODULUS is a modulus used to assess if a seed digest has the -#! required number of trailing zeros for a faucet account. -export.get_faucet_seed_digest_modulus - push.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS -end - #! Returns the root of an empty Sparse Merkle Tree. #! #! Inputs: [] diff --git a/miden-lib/asm/kernels/transaction/lib/memory.masm b/miden-lib/asm/kernels/transaction/lib/memory.masm index 87e5450df..e7804eb40 100644 --- a/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -49,7 +49,7 @@ const.GLOBAL_INPUTS_SECTION_OFFSET=100 # The memory address at which the latest known block hash is stored const.BLK_HASH_PTR=100 -# The memory address at which the account id felts are stored. +# The memory address at which the account ID felts are stored. const.ACCT_ID_PTR=101 # The memory address at which the initial account hash is stored @@ -328,31 +328,31 @@ export.get_block_hash padw push.BLK_HASH_PTR mem_loadw end -#! Sets the account id. +#! Sets the account ID. #! -#! Inputs: [acct_id_hi, acct_id_lo] +#! Inputs: [acct_id_prefix, acct_id_suffix] #! Outputs: [] #! #! Where: -#! - acct_id_{hi,lo} are the first and second felt of the account id. +#! - acct_id_{prefix,suffix} are the prefix and suffix felts of the account ID. export.set_global_acct_id push.0.0 - # => [0, 0, acct_id_hi, acct_id_lo] + # => [0, 0, acct_id_prefix, acct_id_suffix] mem_storew.ACCT_ID_PTR dropw # => [] end -#! Returns the global account id. +#! Returns the global account ID. #! #! Inputs: [] -#! Outputs: [acct_id_hi, acct_id_lo] +#! Outputs: [acct_id_prefix, acct_id_suffix] #! #! Where: -#! - acct_id_{hi,lo} are the first and second felt of the account id. +#! - acct_id_{prefix,suffix} are the prefix and suffix felts of the account ID. export.get_global_acct_id padw mem_loadw.ACCT_ID_PTR - # => [0, 0, acct_id_hi, acct_id_lo] + # => [0, 0, acct_id_prefix, acct_id_suffix] drop drop end @@ -731,25 +731,25 @@ end #! Returns the id of the current account. #! #! Inputs: [] -#! Outputs: [curr_acct_id_hi, curr_acct_id_lo] +#! Outputs: [curr_acct_id_prefix, curr_acct_id_suffix] #! #! Where: -#! - curr_acct_id_{hi,lo} are the first and second felt of the account id of the currently +#! - curr_acct_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the currently #! accessing account. export.get_account_id padw exec.get_current_account_data_ptr push.ACCT_ID_AND_NONCE_OFFSET add mem_loadw - # => [nonce, 0, curr_acct_id_hi, curr_acct_id_lo] + # => [nonce, 0, curr_acct_id_prefix, curr_acct_id_suffix] drop drop - # => [curr_acct_id_hi, curr_acct_id_lo] + # => [curr_acct_id_prefix, curr_acct_id_suffix] end -#! Sets the account id and nonce. +#! Sets the account ID and nonce. #! -#! Inputs: [account_nonce, 0, account_id_hi, account_id_lo] -#! Outputs: [account_nonce, 0, account_id_hi, account_id_lo] +#! Inputs: [account_nonce, 0, account_id_prefix, account_id_suffix] +#! Outputs: [account_nonce, 0, account_id_prefix, account_id_suffix] #! #! Where: -#! - account_id_{hi,lo} are the first and second felt of the id of the currently accessing account. +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the id of the currently accessing account. #! - account_nonce is the nonce of the currently accessing account. export.set_acct_id_and_nonce exec.get_current_account_data_ptr push.ACCT_ID_AND_NONCE_OFFSET add @@ -759,15 +759,15 @@ end #! Returns the id of the native account. #! #! Inputs: [] -#! Outputs: [native_acct_id_hi, native_acct_id_lo] +#! Outputs: [native_acct_id_prefix, native_acct_id_suffix] #! #! Where: -#! - native_acct_id_{hi,lo} are the first and second felt of the id of the native account. +#! - native_acct_id_{prefix,suffix} are the prefix and suffix felts of the id of the native account. export.get_native_account_id padw push.NATIVE_ACCOUNT_DATA_PTR push.ACCT_ID_AND_NONCE_OFFSET add mem_loadw - # => [nonce, 0, native_acct_id_hi, native_acct_id_lo] + # => [nonce, 0, native_acct_id_prefix, native_acct_id_suffix] drop drop - # => [native_acct_id_hi, native_acct_id_lo] + # => [native_acct_id_prefix, native_acct_id_suffix] end #! Returns the account nonce. @@ -795,7 +795,7 @@ export.set_acct_nonce exec.get_current_account_data_ptr push.ACCT_ID_AND_NONCE_OFFSET add padw # => [0, 0, 0, 0, acct_id_and_nonce_ptr, new_nonce] dup.4 mem_loadw - # => [old_nonce, 0, old_id_hi, old_id_lo, acct_id_and_nonce_ptr, new_nonce] + # => [old_nonce, 0, old_id_prefix, old_id_suffix, acct_id_and_nonce_ptr, new_nonce] drop movup.4 movup.4 mem_storew dropw # => [] end @@ -1261,7 +1261,7 @@ end #! Returns the sender for the input note located at the specified memory address. #! #! Inputs: [note_ptr] -#! Outputs: [sender_hi, sender_lo] +#! Outputs: [sender_id_prefix, sender_id_suffix] #! #! Where: #! - note_ptr is the memory address at which the input note data begins. @@ -1270,22 +1270,22 @@ export.get_input_note_sender padw movup.4 push.INPUT_NOTE_METADATA_OFFSET add mem_loadw - # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_hi] + # => [aux, merged_tag_hint_payload, merged_sender_id_type_hint_tag, sender_id_prefix] drop drop - # => [merged_sender_id_type_hint_tag, sender_hi] + # => [merged_sender_id_type_hint_tag, sender_id_prefix] - # extract second felt of sender from merged layout, which means clearing the least significant byte + # extract suffix of sender from merged layout, which means clearing the least significant byte u32split swap - # => [merged_lo, merged_hi, sender_hi] + # => [merged_lo, merged_hi, sender_id_prefix] # clear least significant byte u32and.0xffffff00 swap - # => [sender_lo_hi, sender_lo_lo, sender_hi] + # => [sender_id_suffix_hi, sender_id_suffix_lo, sender_id_prefix] - # reassemble the second felt by multiplying the high part with 2^32 and adding the lo part + # reassemble the suffix by multiplying the high part with 2^32 and adding the lo part mul.0x0100000000 add swap - # => [sender_hi, sender_lo] + # => [sender_id_prefix, sender_id_suffix] end diff --git a/miden-lib/asm/kernels/transaction/lib/note.masm b/miden-lib/asm/kernels/transaction/lib/note.masm index c69b6b2df..64f92066a 100644 --- a/miden-lib/asm/kernels/transaction/lib/note.masm +++ b/miden-lib/asm/kernels/transaction/lib/note.masm @@ -31,10 +31,10 @@ const.OUTPUT_NOTE_HASHING_MEM_DIFF=510 #! Returns the sender of the note currently being processed. #! #! Inputs: [] -#! Outputs: [sender_hi, sender_lo] +#! Outputs: [sender_id_prefix, sender_id_suffix] #! #! Where: -#! - sender_{hi,lo} are the first and second felt of the sender of the note currently being processed. +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender of the note currently being processed. #! #! Panics if: #! - the note is not being processed. @@ -50,7 +50,7 @@ export.get_sender # get the sender from the note pointer exec.memory::get_input_note_sender - # => [sender_hi, sender_lo] + # => [sender_id_prefix, sender_id_suffix] end #! Returns the number of assets and the assets hash of the note currently being processed. diff --git a/miden-lib/asm/kernels/transaction/lib/prologue.masm b/miden-lib/asm/kernels/transaction/lib/prologue.masm index f72943eef..ce35a82f2 100644 --- a/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -66,12 +66,12 @@ const.ERR_PROLOGUE_NEW_ACCOUNT_NONCE_MUST_BE_ZERO=0x0002005B #! Saves global inputs to memory. #! -#! Inputs: [BLOCK_HASH, account_id_hi, account_id_lo, INITIAL_ACCOUNT_HASH, INPUT_NOTES_COMMITMENT] +#! Inputs: [BLOCK_HASH, account_id_prefix, account_id_suffix, INITIAL_ACCOUNT_HASH, INPUT_NOTES_COMMITMENT] #! Outputs: [] #! #! Where: #! - BLOCK_HASH is the reference block for the transaction execution. -#! - account_id_{hi,lo} are the first and second felt of the account id of the account that the +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the account that the #! transaction is being executed against. #! - INITIAL_ACCOUNT_HASH is the account state prior to the transaction, EMPTY_WORD for new #! accounts. @@ -308,7 +308,7 @@ end #! for a new account. #! #! Applies the following validation to the new account: -#! - assert that the account id is valid. +#! - 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. @@ -316,7 +316,7 @@ end #! Inputs: [] #! Outputs: [] proc.validate_new_account - # Assert the account id of the account is valid + # Assert the account ID of the account is valid exec.memory::get_account_id exec.account::validate_id # => [] @@ -341,13 +341,13 @@ proc.validate_new_account # --------------------------------------------------------------------------------------------- # check if the account is a faucet exec.account::get_id swap drop dup exec.account::is_faucet - # => [is_faucet, acct_id_hi] + # => [is_faucet, acct_id_prefix] # 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_hi] + # => [FAUCET_RESERVED_SLOT, acct_id_prefix] # check if the account is a fungible faucet movup.4 exec.account::is_fungible_faucet @@ -429,7 +429,7 @@ end #! Inputs: #! Operand stack: [] #! Advice stack: [ -#! account_id_lo, account_id_hi, 0, account_nonce, +#! account_id_suffix, account_id_prefix, 0, account_nonce, #! ACCOUNT_VAULT_ROOT, #! ACCOUNT_STORAGE_COMMITMENT, #! ACCOUNT_CODE_COMMITMENT @@ -439,7 +439,7 @@ end #! Advice stack: [] #! #! Where: -#! - account_id_{hi,lo} are the first and second felt of the ID of the account that the transaction +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the ID of the account that the transaction #! is being executed against. #! - account_nonce is the account's nonce. #! - ACCOUNT_VAULT_ROOT is the account's vault root. @@ -466,7 +466,7 @@ proc.process_account_data movup.4 drop # => [ACCT_HASH] - # assert the account id matches the account id in global inputs + # assert the account ID matches the account ID in global inputs exec.memory::get_global_acct_id exec.memory::get_account_id exec.account::is_id_eq assert.err=ERR_PROLOGUE_MISMATCH_OF_ACCOUNT_IDS_FROM_GLOBAL_INPUTS_AND_ADVICE_PROVIDER @@ -1086,7 +1086,7 @@ end #! Inputs: #! Operand stack: [ #! BLOCK_HASH, -#! account_id_hi, account_id_lo, +#! account_id_prefix, account_id_suffix, #! INITIAL_ACCOUNT_HASH, #! INPUT_NOTES_COMMITMENT, #! ] @@ -1101,7 +1101,7 @@ end #! [block_num, version, timestamp, 0], #! NOTE_ROOT, #! kernel_version -#! [account_id_lo, account_id_hi, 0, account_nonce], +#! [account_id_suffix, account_id_prefix, 0, account_nonce], #! ACCOUNT_VAULT_ROOT, #! ACCOUNT_STORAGE_COMMITMENT, #! ACCOUNT_CODE_COMMITMENT, @@ -1122,7 +1122,7 @@ end #! #! Where: #! - BLOCK_HASH is the reference block for the transaction execution. -#! - account_id_{hi,lo} are the first and second felt of the account that the transaction is being +#! - account_id_{prefix,suffix} are the prefix and suffix felts of the account that the transaction is being #! executed against. #! - INITIAL_ACCOUNT_HASH is the account state prior to the transaction, EMPTY_WORD for new #! accounts. diff --git a/miden-lib/asm/kernels/transaction/lib/tx.masm b/miden-lib/asm/kernels/transaction/lib/tx.masm index 0e0399463..dbaa41b09 100644 --- a/miden-lib/asm/kernels/transaction/lib/tx.masm +++ b/miden-lib/asm/kernels/transaction/lib/tx.masm @@ -46,7 +46,7 @@ const.ERR_NOTE_INVALID_TYPE=0x00020043 # 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) +# 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 @@ -354,6 +354,8 @@ end #! 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] @@ -365,7 +367,8 @@ end #! 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. -proc.build_note_metadata +export.build_note_metadata + # Validate the note type. # -------------------------------------------------------------------------------------------- @@ -422,36 +425,36 @@ proc.build_note_metadata movup.2 add # => [note_tag_hint_payload, execution_hint_tag, aux, note_type] - # Merge sender_lo, note_type and execution_hint_tag. + # Merge sender_id_suffix, note_type and execution_hint_tag. # -------------------------------------------------------------------------------------------- exec.account::get_id - # => [sender_hi, sender_lo, note_tag_hint_payload, execution_hint_tag, aux, note_type] + # => [sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux, note_type] movup.5 - # => [note_type, sender_hi, sender_lo, note_tag_hint_payload, execution_hint_tag, aux] + # => [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. push.0x40 mul - # => [shifted_note_type, sender_hi, sender_lo, note_tag_hint_payload, execution_hint_tag, aux] + # => [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_hi, sender_lo, note_tag_hint_payload, aux] + # => [merged_note_type_execution_hint_tag, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, aux] - # merge sender_lo into this value + # merge sender_id_suffix into this value movup.2 add - # => [sender_lo_type_and_hint_tag, sender_hi, note_tag_hint_payload, aux] + # => [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_lo_type_and_hint_tag, note_tag_hint_payload, aux, sender_hi] + # => [sender_id_suffix_type_and_hint_tag, note_tag_hint_payload, aux, sender_id_prefix] swap - # => [note_tag_hint_payload, sender_lo_type_and_hint_tag, aux, sender_hi] + # => [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_lo_type_and_hint_tag, sender_hi]] + # => [NOTE_METADATA = [aux, note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, sender_id_prefix]] end #! Creates a new note and returns the index of the note. diff --git a/miden-lib/asm/miden/account.masm b/miden-lib/asm/miden/account.masm index 2cb2b04a7..9eec06d2c 100644 --- a/miden-lib/asm/miden/account.masm +++ b/miden-lib/asm/miden/account.masm @@ -3,13 +3,13 @@ use.miden::kernel_proc_offsets # NATIVE ACCOUNT PROCEDURES # ================================================================================================= -#! Returns the account id. +#! Returns the account ID. #! #! Inputs: [] -#! Outputs: [acct_id_hi, acct_id_lo] +#! Outputs: [acct_id_prefix, acct_id_suffix] #! #! Where: -#! - acct_id_{hi,lo} are the first and second felt of the account id. +#! - acct_id_{prefix,suffix} are the prefix and suffix felts of the account ID. #! #! Invocation: exec export.get_id @@ -24,11 +24,11 @@ export.get_id # => [offset, pad(15)] syscall.exec_kernel_proc - # => [acct_id_hi, acct_id_lo, pad(14)] + # => [acct_id_prefix, acct_id_suffix, pad(14)] # clean the stack swapdw dropw dropw swapw dropw movdn.3 movdn.3 drop drop - # => [acct_id_hi, acct_id_lo] + # => [acct_id_prefix, acct_id_suffix] end #! Returns the account nonce. @@ -316,11 +316,11 @@ end #! Returns the balance of a fungible asset associated with a faucet_id. #! -#! Inputs: [faucet_id_hi, faucet_id_lo] +#! Inputs: [faucet_id_prefix, faucet_id_suffix] #! Outputs: [balance] #! #! Where: -#! - faucet_id_{hi,lo} are the first and second felt of the faucet id of the fungible asset +#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet id of the fungible asset #! of interest. #! - balance is the vault balance of the fungible asset. #! @@ -330,11 +330,11 @@ end #! Invocation: exec export.get_balance exec.kernel_proc_offsets::account_get_balance_offset - # => [offset, faucet_id_hi, faucet_id_lo] + # => [offset, faucet_id_prefix, faucet_id_suffix] # pad the stack push.0 movdn.3 padw swapw padw padw swapdw - # => [offset, faucet_id_hi, faucet_id_lo, pad(13)] + # => [offset, faucet_id_prefix, faucet_id_suffix, pad(13)] syscall.exec_kernel_proc # => [balance, pad(15)] @@ -464,7 +464,7 @@ end # PROCEDURES COPIED FROM KERNEL (TODO: get rid of this duplication) # ================================================================================================= -# Given the least significant 32 bits of an account id's first felt, this mask defines the bits used +# Given the least significant 32 bits of an account ID's prefix, this mask defines the bits used # to determine the account type. const.ACCOUNT_ID_TYPE_MASK_U32=0x30 # 0b11_0000 @@ -484,10 +484,10 @@ const.NON_FUNGIBLE_FAUCET_ACCOUNT=0x30 # 0b11_0000 #! - FUNGIBLE_FAUCET_ACCOUNT #! - NON_FUNGIBLE_FAUCET_ACCOUNT #! -#! Stack: [acct_id_hi] +#! Stack: [acct_id_prefix] #! Output: [acct_type] #! -#! - acct_id_hi is the first felt of the account id. +#! - acct_id_prefix is the prefix of the account ID. #! - acct_type is the account type. proc.type u32split drop push.ACCOUNT_ID_TYPE_MASK_U32 u32and @@ -499,7 +499,7 @@ end #! Stack: [acct_id] #! Output: [is_fungible_faucet] #! -#! - acct_id is the account id. +#! - acct_id is the account ID. #! - is_fungible_faucet is a boolean indicating whether the account is a fungible faucet. export.is_fungible_faucet exec.type push.FUNGIBLE_FAUCET_ACCOUNT eq @@ -511,7 +511,7 @@ end #! Stack: [acct_id] #! Output: [is_non_fungible_faucet] #! -#! - acct_id is the account id. +#! - acct_id is the account ID. #! - is_non_fungible_faucet is a boolean indicating whether the account is a non-fungible faucet. export.is_non_fungible_faucet exec.type push.NON_FUNGIBLE_FAUCET_ACCOUNT eq @@ -522,18 +522,18 @@ end #! #! Returns a boolean indicating whether the given account_ids are equal. #! -#! Inputs: [acct_id_hi, acct_id_lo, other_acct_id_hi, other_acct_id_lo] +#! Inputs: [acct_id_prefix, acct_id_suffix, other_acct_id_prefix, other_acct_id_suffix] #! Outputs: [is_id_equal] #! #! Where: -#! - acct_id_{hi,lo} are the first and second felt of an account id. -#! - other_acct_id_{hi,lo} are the first and second felt of the other account id to compare against. -#! - is_id_equal is a boolean indicating whether the account ids are equal. +#! - acct_id_{prefix,suffix} are the prefix and suffix felts of an account ID. +#! - other_acct_id_{prefix,suffix} are the prefix and suffix felts of the other account ID to compare against. +#! - is_id_equal is a boolean indicating whether the account IDs are equal. export.is_id_eq movup.2 eq - # => [is_hi_equal, acct_id_lo, other_acct_id_lo] - swap movup.2 eq - # => [is_lo_equal, is_hi_equal] + # => [is_prefix_equal, acct_id_suffix, other_acct_id_suffix] + movdn.2 eq + # => [is_suffix_equal, is_prefix_equal] and # => [is_id_equal] end diff --git a/miden-lib/asm/miden/asset.masm b/miden-lib/asm/miden/asset.masm index b421f1fec..d1b9d2dc2 100644 --- a/miden-lib/asm/miden/asset.masm +++ b/miden-lib/asm/miden/asset.masm @@ -17,11 +17,11 @@ const.ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID=0x0002004D #! Builds a fungible asset for the specified fungible faucet and amount. #! -#! Inputs: [faucet_id_hi, faucet_id_lo, amount] +#! Inputs: [faucet_id_prefix, faucet_id_suffix, amount] #! Outputs: [ASSET] #! #! Where: -#! - faucet_id_{hi,lo} are the first and second felt of the faucet to create the asset for. +#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet to create the asset for. #! - amount is the amount of the asset to create. #! - ASSET is the built fungible asset. #! @@ -29,12 +29,12 @@ const.ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID=0x0002004D export.build_fungible_asset # assert the faucet is a fungible faucet dup exec.account::is_fungible_faucet assert.err=ERR_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID - # => [faucet_id_hi, faucet_id_lo, amount] + # => [faucet_id_prefix, faucet_id_suffix, amount] # assert the amount is valid dup.2 exec.get_fungible_asset_max_amount lte assert.err=ERR_FUNGIBLE_ASSET_AMOUNT_EXCEEDS_MAX_ALLOWED_AMOUNT - # => [faucet_id_hi, faucet_id_lo, amount] + # => [faucet_id_prefix, faucet_id_suffix, amount] # create the asset push.0 movdn.2 @@ -54,7 +54,7 @@ end export.create_fungible_asset # fetch the id of the faucet the transaction is being executed against. exec.account::get_id - # => [id_hi, id_lo, amount] + # => [id_prefix, id_suffix, amount] # build the fungible asset exec.build_fungible_asset @@ -63,11 +63,11 @@ end #! Builds a non fungible asset for the specified non-fungible faucet and amount. #! -#! Inputs: [faucet_id_hi, DATA_HASH] +#! Inputs: [faucet_id_prefix, DATA_HASH] #! Outputs: [ASSET] #! #! Where: -#! - faucet_id_{hi,lo} are the first and second felt of the faucet to create the asset for. +#! - faucet_id_{prefix,suffix} are the prefix and suffix felts of the faucet to create the asset for. #! - DATA_HASH is the data hash of the non-fungible asset to build. #! - ASSET is the built non-fungible asset. #! @@ -76,11 +76,11 @@ export.build_non_fungible_asset # assert the faucet is a non-fungible faucet dup exec.account::is_non_fungible_faucet assert.err=ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID - # => [faucet_id_hi, hash3, hash2, hash1, hash0] + # => [faucet_id_prefix, hash3, hash2, hash1, hash0] # build the asset swap drop - # => [faucet_id_hi, hash2, hash1, hash0] + # => [faucet_id_prefix, hash2, hash1, hash0] # => [ASSET] end @@ -97,7 +97,7 @@ end export.create_non_fungible_asset # get the id of the faucet the transaction is being executed against exec.account::get_id swap drop - # => [faucet_id_hi, DATA_HASH] + # => [faucet_id_prefix, DATA_HASH] # build the non-fungible asset exec.build_non_fungible_asset diff --git a/miden-lib/asm/miden/contracts/auth/basic.masm b/miden-lib/asm/miden/contracts/auth/basic.masm index 36c205a7d..6e035b4d1 100644 --- a/miden-lib/asm/miden/contracts/auth/basic.masm +++ b/miden-lib/asm/miden/contracts/auth/basic.masm @@ -26,10 +26,10 @@ export.auth_tx_rpo_falcon512 # Get current AccountID and pad exec.account::get_id push.0.0 - # => [0, 0, account_id_hi, account_id_lo, 0, 0, 0, nonce, INPUT_NOTES_HASH, OUTPUT_NOTES_HASH, pad(16)] + # => [0, 0, account_id_prefix, account_id_suffix, 0, 0, 0, nonce, INPUT_NOTES_HASH, OUTPUT_NOTES_HASH, pad(16)] # Compute the message to be signed - # MESSAGE = h(OUTPUT_NOTES_HASH, h(INPUT_NOTES_HASH, h(0, 0, account_id_hi, account_id_lo, 0, 0, 0, nonce))) + # MESSAGE = h(OUTPUT_NOTES_HASH, h(INPUT_NOTES_HASH, h(0, 0, account_id_prefix, account_id_suffix, 0, 0, 0, nonce))) hmerge hmerge hmerge # => [MESSAGE, pad(16)] diff --git a/miden-lib/asm/miden/note.masm b/miden-lib/asm/miden/note.masm index 5cf776e0b..7f28e290a 100644 --- a/miden-lib/asm/miden/note.masm +++ b/miden-lib/asm/miden/note.masm @@ -144,10 +144,10 @@ end #! Returns the sender of the note currently being processed. #! #! Inputs: [] -#! Outputs: [sender_hi, sender_lo] +#! Outputs: [sender_id_prefix, sender_id_suffix] #! #! Where: -#! - sender_{hi,lo} are the first and second felt of the sender of the note currently being processed. +#! - sender_{prefix,suffix} are the prefix and suffix felts of the sender of the note currently being processed. #! #! Panics if: #! - no note is being processed. @@ -166,7 +166,7 @@ export.get_sender # clean the stack swapdw dropw dropw swapw dropw movdn.3 movdn.3 drop drop - # => [sender_hi, sender_lo] + # => [sender_id_prefix, sender_id_suffix] end #! Returns the serial number of the note currently being processed. diff --git a/miden-lib/asm/miden/tx.masm b/miden-lib/asm/miden/tx.masm index 42163f250..622a8e646 100644 --- a/miden-lib/asm/miden/tx.masm +++ b/miden-lib/asm/miden/tx.masm @@ -190,11 +190,11 @@ end #! than 15 elements back. Otherwise exceeding elements will not be provided to the procedure and #! will not be returned from it. #! -#! Inputs: [foreign_account_id_hi, foreign_account_id_lo, FOREIGN_PROC_ROOT, , pad(n)] +#! Inputs: [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, , pad(n)] #! Outputs: [] #! #! Where: -#! - foreign_account_id_{hi,lo} are the first and second felt of the account ID of the foreign +#! - foreign_account_id_{prefix,suffix} are the prefix and suffix felts of the account ID of the foreign #! account to execute the procedure on. #! - pad(n) is the exact number of pads needed to set the number of procedure inputs to 16 at the #! moment of the foreign procedure execution (n = 16 - mem_addr_size - foreign_inputs_len). @@ -203,11 +203,11 @@ end export.execute_foreign_procedure.1 # get the start_foreign_context procedure offset push.0 movup.2 movup.2 exec.kernel_proc_offsets::tx_start_foreign_context_offset - # => [offset, foreign_account_id_hi, foreign_account_id_lo, 0, FOREIGN_PROC_ROOT, , pad(n)] + # => [offset, foreign_account_id_prefix, foreign_account_id_suffix, 0, FOREIGN_PROC_ROOT, , pad(n)] # pad the stack before the syscall padw swapw padw padw swapdw - # => [offset, foreign_account_id_hi, foreign_account_id_lo, pad(13), FOREIGN_PROC_ROOT, , pad(n)] + # => [offset, foreign_account_id_prefix, foreign_account_id_suffix, pad(13), FOREIGN_PROC_ROOT, , pad(n)] # load the foreign account to the memory syscall.exec_kernel_proc diff --git a/miden-lib/asm/note_scripts/P2ID.masm b/miden-lib/asm/note_scripts/P2ID.masm index 6bfe759bd..aed3a5cb9 100644 --- a/miden-lib/asm/note_scripts/P2ID.masm +++ b/miden-lib/asm/note_scripts/P2ID.masm @@ -90,12 +90,12 @@ begin eq.2 assert.err=ERR_P2ID_WRONG_NUMBER_OF_INPUTS # => [inputs_ptr] - # read the target account id from the note inputs + # read the target account ID from the note inputs padw movup.4 mem_loadw drop drop - # => [target_account_id_hi, target_account_id_lo] + # => [target_account_id_prefix, target_account_id_suffix] exec.account::get_id - # => [account_id_hi, account_id_lo, target_account_id_hi, target_account_id_lo, ...] + # => [account_id_prefix, account_id_suffix, target_account_id_prefix, target_account_id_suffix, ...] # ensure account_id = target_account_id, fails otherwise exec.account::is_id_eq assert.err=ERR_P2ID_TARGET_ACCT_MISMATCH diff --git a/miden-lib/asm/note_scripts/P2IDR.masm b/miden-lib/asm/note_scripts/P2IDR.masm index 1828b307d..c0ff7a73e 100644 --- a/miden-lib/asm/note_scripts/P2IDR.masm +++ b/miden-lib/asm/note_scripts/P2IDR.masm @@ -99,16 +99,16 @@ begin eq.3 assert.err=ERR_P2IDR_WRONG_NUMBER_OF_INPUTS # => [inputs_ptr] - # read the reclaim block height and target account id from the note inputs + # read the reclaim block height and target account ID from the note inputs padw movup.4 mem_loadw drop - # => [reclaim_block_height, target_account_id_hi, target_account_id_lo] + # => [reclaim_block_height, target_account_id_prefix, target_account_id_suffix] exec.account::get_id dup.1 dup.1 - # => [account_id_hi, account_id_lo, account_id_hi, account_id_lo, reclaim_block_height, target_account_id_hi, target_account_id_lo, ...] + # => [account_id_prefix, account_id_suffix, account_id_prefix, account_id_suffix, reclaim_block_height, target_account_id_prefix, target_account_id_suffix, ...] # determine if the current account is the target account movup.6 movup.6 exec.account::is_id_eq - # => [is_target, account_id_hi, account_id_lo, reclaim_block_height] + # => [is_target, account_id_prefix, account_id_suffix, reclaim_block_height] if.true # if current account is the target, we don't need to check anything else @@ -118,9 +118,9 @@ begin else # if current account is not the target, we need to ensure it is the sender exec.note::get_sender - # => [sender_account_id_hi, sender_account_id_lo, account_id_hi, account_id_lo, reclaim_block_height] + # => [sender_account_id_prefix, sender_account_id_suffix, account_id_prefix, account_id_suffix, reclaim_block_height] - # ensure current account id = sender account id + # ensure current account ID = sender account ID exec.account::is_id_eq assert.err=ERR_P2IDR_RECLAIM_ACCT_IS_NOT_SENDER # => [reclaim_block_height] diff --git a/miden-lib/build.rs b/miden-lib/build.rs index d04b07159..e9234e590 100644 --- a/miden-lib/build.rs +++ b/miden-lib/build.rs @@ -2,8 +2,8 @@ use std::{ collections::{BTreeMap, BTreeSet}, env, fmt::Write, - fs::{self, File}, - io::{self, BufRead, BufReader}, + fs::{self}, + io::{self}, path::{Path, PathBuf}, sync::Arc, }; @@ -100,37 +100,9 @@ fn main() -> Result<()> { /// - {target_dir}/tx_kernel.masl -> contains kernel library compiled from api.masm. /// - {target_dir}/tx_kernel.masb -> contains the executable compiled from main.masm. /// - src/transaction/procedures/kernel_v0.rs -> contains the kernel procedures table. -/// -/// When the `testing` feature is enabled, the POW requirements for account ID generation are -/// adjusted by modifying the corresponding constants in {source_dir}/lib/constants.masm file. fn compile_tx_kernel(source_dir: &Path, target_dir: &Path) -> Result { let assembler = build_assembler(None)?; - // if this build has the testing flag set, modify the code and reduce the cost of proof-of-work - match env::var("CARGO_FEATURE_TESTING") { - Ok(ref s) if s == "1" => { - let constants = source_dir.join("lib/constants.masm"); - let patched = source_dir.join("lib/constants.masm.patched"); - - // scope for file handlers - { - let read = File::open(&constants).unwrap(); - let mut write = File::create(&patched).unwrap(); - let modified = BufReader::new(read).lines().map(decrease_pow); - - for line in modified { - io::Write::write_all(&mut write, line.unwrap().as_bytes()).unwrap(); - io::Write::write_all(&mut write, b"\n").unwrap(); - } - io::Write::flush(&mut write).unwrap(); - } - - fs::remove_file(&constants).unwrap(); - fs::rename(&patched, &constants).unwrap(); - }, - _ => (), - } - // assemble the kernel library and write it to the "tx_kernel.masl" file let kernel_lib = KernelLibrary::from_dir( source_dir.join("api.masm"), @@ -174,20 +146,6 @@ fn compile_tx_kernel(source_dir: &Path, target_dir: &Path) -> Result Ok(assembler) } -fn decrease_pow(line: io::Result) -> io::Result { - let mut line = line?; - if line.starts_with("const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS") { - line.clear(); - // 2**5 - line.push_str("const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS=32 # reduced via build.rs"); - } else if line.starts_with("const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS") { - line.clear(); - // 2**6 - line.push_str("const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS=64 # reduced via build.rs"); - } - Ok(line) -} - /// Generates `kernel_v0.rs` file based on the kernel library fn generate_kernel_proc_hash_file(kernel: KernelLibrary) -> Result<()> { // Because the kernel Rust file will be stored under ./src, this should be a no-op if we can't diff --git a/miden-lib/src/accounts/faucets/mod.rs b/miden-lib/src/accounts/faucets/mod.rs index d8a9bcf6c..e763cf8fe 100644 --- a/miden-lib/src/accounts/faucets/mod.rs +++ b/miden-lib/src/accounts/faucets/mod.rs @@ -104,8 +104,7 @@ pub fn create_basic_fungible_faucet( AuthScheme::RpoFalcon512 { pub_key } => RpoFalcon512::new(pub_key), }; - let (account, account_seed) = AccountBuilder::new() - .init_seed(init_seed) + let (account, account_seed) = AccountBuilder::new(init_seed) .anchor(id_anchor) .account_type(AccountType::FungibleFaucet) .storage_mode(account_storage_mode) diff --git a/miden-lib/src/accounts/wallets/mod.rs b/miden-lib/src/accounts/wallets/mod.rs index 849b83461..f55f0e57c 100644 --- a/miden-lib/src/accounts/wallets/mod.rs +++ b/miden-lib/src/accounts/wallets/mod.rs @@ -63,8 +63,7 @@ pub fn create_basic_wallet( AuthScheme::RpoFalcon512 { pub_key } => RpoFalcon512::new(pub_key), }; - let (account, account_seed) = AccountBuilder::new() - .init_seed(init_seed) + let (account, account_seed) = AccountBuilder::new(init_seed) .anchor(id_anchor) .account_type(account_type) .storage_mode(account_storage_mode) diff --git a/miden-lib/src/errors/tx_kernel_errors.rs b/miden-lib/src/errors/tx_kernel_errors.rs index 16fab03f6..e3e9441b0 100644 --- a/miden-lib/src/errors/tx_kernel_errors.rs +++ b/miden-lib/src/errors/tx_kernel_errors.rs @@ -15,6 +15,7 @@ pub const ERR_ACCOUNT_CODE_COMMITMENT_MISMATCH: u32 = 0x0002000F; pub const ERR_ACCOUNT_CODE_IS_NOT_UPDATABLE: u32 = 0x00020006; pub const ERR_ACCOUNT_ID_EPOCH_MUST_BE_LESS_THAN_U16_MAX: u32 = 0x00020058; pub const ERR_ACCOUNT_ID_LEAST_SIGNIFICANT_BYTE_MUST_BE_ZERO: u32 = 0x00020005; +pub const ERR_ACCOUNT_ID_UNKNOWN_STORAGE_MODE: u32 = 0x00020059; pub const ERR_ACCOUNT_ID_UNKNOWN_VERSION: u32 = 0x00020057; pub const ERR_ACCOUNT_INVALID_STORAGE_OFFSET_FOR_SIZE: u32 = 0x00020013; pub const ERR_ACCOUNT_IS_NOT_NATIVE: u32 = 0x00020030; @@ -114,13 +115,14 @@ pub const ERR_VAULT_NON_FUNGIBLE_ASSET_ALREADY_EXISTS: u32 = 0x0002001C; pub const ERR_VAULT_NON_FUNGIBLE_ASSET_TO_REMOVE_NOT_FOUND: u32 = 0x0002001F; pub const ERR_VAULT_REMOVE_FUNGIBLE_ASSET_FAILED_INITIAL_VALUE_INVALID: u32 = 0x0002001E; -pub const TX_KERNEL_ERRORS: [(u32, &str); 89] = [ +pub const TX_KERNEL_ERRORS: [(u32, &str); 90] = [ (ERR_ACCOUNT_ANCHOR_BLOCK_HASH_MUST_NOT_BE_EMPTY, "Anchor block hash must not be empty"), (ERR_ACCOUNT_CODE_COMMITMENT_MISMATCH, "Computed account code commitment does not match recorded account code commitment"), (ERR_ACCOUNT_CODE_IS_NOT_UPDATABLE, "Account code must be updatable for it to be possible to set new code"), (ERR_ACCOUNT_ID_EPOCH_MUST_BE_LESS_THAN_U16_MAX, "Epoch must be less than u16::MAX (0xffff)."), - (ERR_ACCOUNT_ID_LEAST_SIGNIFICANT_BYTE_MUST_BE_ZERO, "Least significant byte of second felt of the account id must be zero."), - (ERR_ACCOUNT_ID_UNKNOWN_VERSION, "Unknown version in account id."), + (ERR_ACCOUNT_ID_LEAST_SIGNIFICANT_BYTE_MUST_BE_ZERO, "Least significant byte of the account ID suffix must be zero."), + (ERR_ACCOUNT_ID_UNKNOWN_STORAGE_MODE, "Unknown account storage mode in account ID."), + (ERR_ACCOUNT_ID_UNKNOWN_VERSION, "Unknown version in account ID."), (ERR_ACCOUNT_INVALID_STORAGE_OFFSET_FOR_SIZE, "Storage offset is invalid for 0 storage size (should be 0)"), (ERR_ACCOUNT_IS_NOT_NATIVE, "The current account is not native"), (ERR_ACCOUNT_NONCE_DID_NOT_INCREASE_AFTER_STATE_CHANGE, "Account nonce did not increase after a state changing transaction"), diff --git a/miden-lib/src/notes/mod.rs b/miden-lib/src/notes/mod.rs index 8ef85bc78..2c7e27cd6 100644 --- a/miden-lib/src/notes/mod.rs +++ b/miden-lib/src/notes/mod.rs @@ -71,7 +71,7 @@ pub fn create_p2idr_note( let note_script = scripts::p2idr(); let inputs = - NoteInputs::new(vec![target.second_felt(), target.first_felt(), recall_height.into()])?; + NoteInputs::new(vec![target.suffix(), target.prefix().as_felt(), recall_height.into()])?; let tag = NoteTag::from_account_id(target, NoteExecutionMode::Local)?; let serial_num = rng.draw_word(); diff --git a/miden-lib/src/notes/utils.rs b/miden-lib/src/notes/utils.rs index 8491a56ca..0de8e1e03 100644 --- a/miden-lib/src/notes/utils.rs +++ b/miden-lib/src/notes/utils.rs @@ -16,7 +16,7 @@ pub fn build_p2id_recipient( serial_num: Word, ) -> Result { let note_script = scripts::p2id(); - let note_inputs = NoteInputs::new(vec![target.second_felt(), target.first_felt()])?; + let note_inputs = NoteInputs::new(vec![target.suffix(), target.prefix().as_felt()])?; Ok(NoteRecipient::new(serial_num, note_script, note_inputs)) } @@ -56,7 +56,7 @@ pub fn build_swap_tag( mod tests { use miden_objects::{ self, - accounts::{AccountStorageMode, AccountType}, + accounts::{AccountIdVersion, AccountStorageMode, AccountType}, assets::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}, }; @@ -78,6 +78,7 @@ mod tests { FungibleAsset::new( AccountId::dummy( fungible_faucet_id_bytes, + AccountIdVersion::Version0, AccountType::FungibleFaucet, AccountStorageMode::Public, ), @@ -91,6 +92,7 @@ mod tests { &NonFungibleAssetDetails::new( AccountId::dummy( non_fungible_faucet_id_bytes, + AccountIdVersion::Version0, AccountType::NonFungibleFaucet, AccountStorageMode::Public, ) diff --git a/miden-lib/src/transaction/inputs.rs b/miden-lib/src/transaction/inputs.rs index ad0b488a9..d62a16725 100644 --- a/miden-lib/src/transaction/inputs.rs +++ b/miden-lib/src/transaction/inputs.rs @@ -94,8 +94,8 @@ fn build_advice_stack( // Note: keep in sync with the process_account_data kernel procedure let account = tx_inputs.account(); inputs.extend_stack([ - account.id().second_felt(), - account.id().first_felt(), + account.id().suffix(), + account.id().prefix().as_felt(), ZERO, account.nonce(), ]); @@ -152,7 +152,7 @@ fn add_chain_mmr_to_advice_inputs(mmr: &ChainMmr, inputs: &mut AdviceInputs) { /// - The account storage commitment |-> storage slots and types vector. /// - The account code commitment |-> procedures vector. /// - The node |-> (key, value), for all leaf nodes of the asset vault SMT. -/// - [account_id_lo, account_id_hi, 0, 0] |-> account_seed, when account seed is provided. +/// - [account_id_suffix, account_id_prefix, 0, 0] |-> account_seed, when account seed is provided. /// - If present, the Merkle leaves associated with the account storage maps. fn add_account_to_advice_inputs( account: &Account, @@ -194,7 +194,7 @@ fn add_account_to_advice_inputs( // --- account seed ------------------------------------------------------- if let Some(account_seed) = account_seed { inputs.extend_map(vec![( - [account.id().second_felt(), account.id().first_felt(), ZERO, ZERO].into(), + [account.id().suffix(), account.id().prefix().as_felt(), ZERO, ZERO].into(), account_seed.to_vec(), )]); } diff --git a/miden-lib/src/transaction/memory.rs b/miden-lib/src/transaction/memory.rs index 55f9cb3c5..8650abce9 100644 --- a/miden-lib/src/transaction/memory.rs +++ b/miden-lib/src/transaction/memory.rs @@ -85,7 +85,7 @@ pub const GLOBAL_INPUTS_SECTION_OFFSET: MemoryOffset = 100; /// The memory address at which the latest known block hash is stored. pub const BLK_HASH_PTR: MemoryAddress = 100; -/// The memory address at which the account id is stored. +/// The memory address at which the account ID is stored. pub const ACCT_ID_PTR: MemoryAddress = 101; /// The memory address at which the initial account hash is stored. @@ -176,19 +176,19 @@ pub const NATIVE_ACCOUNT_DATA_PTR: MemoryAddress = 2048; /// The length of the memory interval that the account data occupies. pub const ACCOUNT_DATA_LENGTH: MemSize = 2048; -/// The offset at which the account id and nonce are stored relative to the start of +/// The offset at which the account ID and nonce are stored relative to the start of /// the account data segment. pub const ACCT_ID_AND_NONCE_OFFSET: MemoryOffset = 0; -/// The memory address at which the account id and nonce are stored in the native account. +/// The memory address at which the account ID and nonce are stored in the native account. pub const NATIVE_ACCT_ID_AND_NONCE_PTR: MemoryAddress = NATIVE_ACCOUNT_DATA_PTR + ACCT_ID_AND_NONCE_OFFSET; -/// The index of the account id within the account id and nonce data. -pub const ACCT_ID_LO_IDX: DataIndex = 0; -pub const ACCT_ID_HI_IDX: DataIndex = 1; +/// The index of the account ID within the account ID and nonce data. +pub const ACCT_ID_SUFFIX_IDX: DataIndex = 0; +pub const ACCT_ID_PREFIX_IDX: DataIndex = 1; -/// The index of the account nonce within the account id and nonce data. +/// The index of the account nonce within the account ID and nonce data. pub const ACCT_NONCE_IDX: DataIndex = 3; /// The offset at which the account vault root is stored relative to the start of the account diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index 965a26acc..aa8789894 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -153,8 +153,8 @@ impl TransactionKernel { let mut inputs: Vec = Vec::with_capacity(14); inputs.extend(input_notes_hash); inputs.extend_from_slice(init_acct_hash.as_elements()); - inputs.push(account_id.second_felt()); - inputs.push(account_id.first_felt()); + inputs.push(account_id.suffix()); + inputs.push(account_id.prefix().as_felt()); inputs.extend_from_slice(block_hash.as_elements()); StackInputs::new(inputs) .map_err(|e| e.to_string()) @@ -181,7 +181,7 @@ impl TransactionKernel { let code_root = account_header.code_commitment(); // Note: keep in sync with the start_foreign_context kernel procedure let account_key = - Digest::from([account_id.second_felt(), account_id.first_felt(), ZERO, ZERO]); + Digest::from([account_id.suffix(), account_id.prefix().as_felt(), ZERO, ZERO]); // Extend the advice inputs with the new data advice_inputs.extend_map([ @@ -195,8 +195,8 @@ impl TransactionKernel { // Extend the advice inputs with Merkle store data advice_inputs.extend_merkle_store( - // The first felt is the index in the account tree. - merkle_path.inner_nodes(account_id.first_felt().as_int(), account_header.hash())?, + // The prefix is the index in the account tree. + merkle_path.inner_nodes(account_id.prefix().as_u64(), account_header.hash())?, ); Ok(()) diff --git a/miden-lib/src/transaction/outputs.rs b/miden-lib/src/transaction/outputs.rs index ac16a7ed8..b14ee9d55 100644 --- a/miden-lib/src/transaction/outputs.rs +++ b/miden-lib/src/transaction/outputs.rs @@ -4,10 +4,10 @@ use miden_objects::{ }; use super::memory::{ - ACCT_CODE_COMMITMENT_OFFSET, ACCT_DATA_MEM_SIZE, ACCT_ID_AND_NONCE_OFFSET, ACCT_ID_HI_IDX, - ACCT_NONCE_IDX, ACCT_STORAGE_COMMITMENT_OFFSET, ACCT_VAULT_ROOT_OFFSET, + ACCT_CODE_COMMITMENT_OFFSET, ACCT_DATA_MEM_SIZE, ACCT_ID_AND_NONCE_OFFSET, ACCT_NONCE_IDX, + ACCT_STORAGE_COMMITMENT_OFFSET, ACCT_VAULT_ROOT_OFFSET, }; -use crate::transaction::memory::ACCT_ID_LO_IDX; +use crate::transaction::memory::{ACCT_ID_PREFIX_IDX, ACCT_ID_SUFFIX_IDX}; // STACK OUTPUTS // ================================================================================================ @@ -35,9 +35,10 @@ pub fn parse_final_account_header(elements: &[Word]) -> Result AccountId { + self.0.id() + } + pub fn mint(&self, amount: u64) -> Asset { FungibleAsset::new(self.0.id(), amount).unwrap().into() } @@ -201,15 +205,27 @@ impl PendingObjects { /// /// # Examples /// -/// ## Create mock objects -/// ``` -/// let mut mock_chain = MockChain::default(); +/// ## Create mock objects and build a transaction context +/// ```no_run +/// # use miden_tx::testing::{Auth, MockChain, TransactionContextBuilder}; +/// # use miden_objects::{assets::FungibleAsset, Felt, notes::NoteType}; +/// let mut mock_chain = MockChain::new(); /// let faucet = mock_chain.add_new_faucet(Auth::BasicAuth, "USDT", 100_000); // Create a USDT faucet /// let asset = faucet.mint(1000); -/// let note = mock_chain.add_p2id_note(asset, sender...); /// let sender = mock_chain.add_new_wallet(Auth::BasicAuth); -/// -/// mock_chain.build_tx_context(sender.id(), &[note.id()], &[]).build().execute() +/// let target = mock_chain.add_new_wallet(Auth::BasicAuth); +/// let note = mock_chain +/// .add_p2id_note( +/// faucet.id(), +/// target.id(), +/// &[FungibleAsset::mock(10)], +/// NoteType::Public, +/// None, +/// ) +/// .unwrap(); +/// mock_chain.seal_block(None); +/// let tx_context = mock_chain.build_tx_context(sender.id(), &[note.id()], &[]).build(); +/// let result = tx_context.execute(); /// ``` /// /// ## Executing a Simple Transaction @@ -218,12 +234,17 @@ impl PendingObjects { /// an authenticator. /// /// ``` +/// # use miden_tx::testing::{Auth, MockChain, TransactionContextBuilder}; +/// # use miden_objects::{assets::FungibleAsset, Felt, transaction::TransactionScript}; +/// # use miden_lib::transaction::TransactionKernel; /// let mut mock_chain = MockChain::new(); -/// let sender = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![asset]); // Add a wallet with assets +/// let sender = mock_chain.add_existing_wallet(Auth::BasicAuth, vec![FungibleAsset::mock(256)]); // Add a wallet with assets /// let receiver = mock_chain.add_new_wallet(Auth::BasicAuth); // Add a recipient wallet /// /// let tx_context = mock_chain.build_tx_context(sender.id(), &[], &[]); -/// let tx_script = TransactionScript::compile("...", vec![], TransactionKernel::testing_assembler()).unwrap(); +/// +/// let script = "begin nop end"; +/// let tx_script = TransactionScript::compile(script, vec![], TransactionKernel::testing_assembler()).unwrap(); /// /// let transaction = tx_context.tx_script(tx_script).build().execute().unwrap(); /// mock_chain.apply_executed_transaction(&transaction); // Apply transaction @@ -404,18 +425,15 @@ impl MockChain { /// Adds a new wallet with the specified authentication method and assets. pub fn add_new_wallet(&mut self, auth_method: Auth) -> Account { - let account_builder = - AccountBuilder::new().init_seed(self.rng.gen()).with_component(BasicWallet); + let account_builder = AccountBuilder::new(self.rng.gen()).with_component(BasicWallet); self.add_from_account_builder(auth_method, account_builder, AccountState::New) } /// Adds an existing wallet (nonce == 1) with the specified authentication method and assets. pub fn add_existing_wallet(&mut self, auth_method: Auth, assets: Vec) -> Account { - let account_builder = AccountBuilder::new() - .init_seed(self.rng.gen()) - .with_component(BasicWallet) - .with_assets(assets); + let account_builder = + Account::builder(self.rng.gen()).with_component(BasicWallet).with_assets(assets); self.add_from_account_builder(auth_method, account_builder, AccountState::Exists) } @@ -427,8 +445,7 @@ impl MockChain { token_symbol: &str, max_supply: u64, ) -> MockFungibleFaucet { - let account_builder = AccountBuilder::new() - .init_seed(self.rng.gen()) + let account_builder = AccountBuilder::new(self.rng.gen()) .account_type(AccountType::FungibleFaucet) .with_component( BasicFungibleFaucet::new( @@ -454,7 +471,7 @@ impl MockChain { max_supply: u64, total_issuance: Option, ) -> MockFungibleFaucet { - let mut account_builder = AccountBuilder::new() + let mut account_builder = AccountBuilder::new(self.rng.gen()) .with_component( BasicFungibleFaucet::new( TokenSymbol::new(token_symbol).unwrap(), @@ -463,7 +480,6 @@ impl MockChain { ) .unwrap(), ) - .init_seed(self.rng.gen()) .account_type(AccountType::FungibleFaucet); let authenticator = match auth_method.build_component() { diff --git a/miden-tx/src/testing/tx_context/builder.rs b/miden-tx/src/testing/tx_context/builder.rs index 5ed6ca287..4f439426e 100644 --- a/miden-tx/src/testing/tx_context/builder.rs +++ b/miden-tx/src/testing/tx_context/builder.rs @@ -40,18 +40,15 @@ pub type MockAuthenticator = BasicAuthenticator; /// [TransactionContextBuilder] is a utility to construct [TransactionContext] for testing /// purposes. It allows users to build accounts, create notes, provide advice inputs, and -/// execute code. +/// execute code. The VM process can be inspected afterward. /// /// # Examples /// /// Create a new account and execute code: /// ``` -/// let tx_context = TransactionContextBuilder::with_fungible_faucet( -/// acct_id.into(), -/// Felt::ZERO, -/// Felt::new(1000), -/// ) -/// .build(); +/// # use miden_tx::testing::TransactionContextBuilder; +/// # use miden_objects::{accounts::AccountBuilder,Felt, FieldElement}; +/// let tx_context = TransactionContextBuilder::with_standard_account(Felt::ONE).build(); /// /// let code = " /// use.kernel::prologue @@ -59,13 +56,13 @@ pub type MockAuthenticator = BasicAuthenticator; /// /// begin /// exec.prologue::prepare_transaction -/// push.0 -/// exec.account::get_item +/// push.5 +/// swap drop /// end /// "; /// -/// let process = tx_context.execute_code(code); -/// assert!(process.is_ok()); +/// let process = tx_context.execute_code(code).unwrap(); +/// assert_eq!(process.stack.get(0), Felt::new(5),); /// ``` pub struct TransactionContextBuilder { assembler: Assembler, diff --git a/miden-tx/src/tests/kernel_tests/test_account.rs b/miden-tx/src/tests/kernel_tests/test_account.rs index bf842d75f..84eb2ca73 100644 --- a/miden-tx/src/tests/kernel_tests/test_account.rs +++ b/miden-tx/src/tests/kernel_tests/test_account.rs @@ -1,4 +1,11 @@ -use miden_lib::transaction::TransactionKernel; +use miden_lib::{ + errors::tx_kernel_errors::{ + ERR_ACCOUNT_ID_EPOCH_MUST_BE_LESS_THAN_U16_MAX, + ERR_ACCOUNT_ID_LEAST_SIGNIFICANT_BYTE_MUST_BE_ZERO, ERR_ACCOUNT_ID_UNKNOWN_STORAGE_MODE, + ERR_ACCOUNT_ID_UNKNOWN_VERSION, TX_KERNEL_ERRORS, + }, + transaction::TransactionKernel, +}; use miden_objects::{ accounts::{ AccountBuilder, AccountCode, AccountComponent, AccountId, AccountStorage, AccountType, @@ -19,7 +26,7 @@ use miden_objects::{ }; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha20Rng; -use vm_processor::{Digest, MemAdviceProvider, ProcessState}; +use vm_processor::{Digest, ExecutionError, MemAdviceProvider, ProcessState}; use super::{Felt, StackInputs, Word, ONE, ZERO}; use crate::testing::{executor::CodeExecutor, TransactionContextBuilder}; @@ -86,7 +93,7 @@ pub fn test_account_type() { ); let process = CodeExecutor::with_advice_provider(MemAdviceProvider::default()) - .stack_inputs(StackInputs::new(vec![account_id.first_felt()]).unwrap()) + .stack_inputs(StackInputs::new(vec![account_id.prefix().as_felt()]).unwrap()) .run(&code) .unwrap(); @@ -109,6 +116,80 @@ pub fn test_account_type() { } } +#[test] +pub fn test_account_validate_id() -> anyhow::Result<()> { + let test_cases = [ + (ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, None), + (ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, None), + (ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, None), + (ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, None), + ( + // Set version to a non-zero value (10). + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN | (0x0a << 64), + Some(ERR_ACCOUNT_ID_UNKNOWN_VERSION), + ), + ( + // Set epoch to u16::MAX. + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN | (0xffff << 48), + Some(ERR_ACCOUNT_ID_EPOCH_MUST_BE_LESS_THAN_U16_MAX), + ), + ( + // Set storage mode to an unknown value (0b01). + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN | (0b01 << (64 + 6)), + Some(ERR_ACCOUNT_ID_UNKNOWN_STORAGE_MODE), + ), + ( + // Set lower 8 bits to a non-zero value (1). + ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN | 1, + Some(ERR_ACCOUNT_ID_LEAST_SIGNIFICANT_BYTE_MUST_BE_ZERO), + ), + ]; + + let error_map = alloc::collections::BTreeMap::from(TX_KERNEL_ERRORS); + for (account_id, expected_error) in test_cases.iter() { + // Manually split the account ID into prefix and suffix since we can't use AccountId methods + // on invalid ids. + let prefix = Felt::try_from((account_id / (1u128 << 64)) as u64).unwrap(); + let suffix = Felt::try_from((account_id % (1u128 << 64)) as u64).unwrap(); + + let code = " + use.kernel::account + + begin + exec.account::validate_id + end + "; + + let result = CodeExecutor::with_advice_provider(MemAdviceProvider::default()) + .stack_inputs(StackInputs::new(vec![suffix, prefix]).unwrap()) + .run(code); + + match (result, expected_error) { + (Ok(_), None) => (), + (Ok(_), Some(err)) => { + anyhow::bail!("expected error {} but validation was successful", error_map[err]) + }, + (Err(ExecutionError::FailedAssertion { err_code, .. }), Some(err)) => { + if err_code != *err { + anyhow::bail!( + "actual error {err_code} ({}) did not match expected error {err} ({})", + error_map[&err_code], + error_map[err] + ); + } + }, + (Err(err), None) => { + anyhow::bail!("validation is supposed to succeed but error occurred {}", err) + }, + (Err(err), Some(_)) => { + anyhow::bail!("unexpected different error than expected {}", err) + }, + } + } + + Ok(()) +} + #[test] fn test_is_faucet_procedure() { let test_cases = [ @@ -126,15 +207,15 @@ fn test_is_faucet_procedure() { use.kernel::account begin - push.{first_felt} + push.{prefix} exec.account::is_faucet - # => [is_faucet, account_id_hi] + # => [is_faucet, account_id_prefix] # truncate the stack swap drop end ", - first_felt = account_id.first_felt(), + prefix = account_id.prefix().as_felt(), ); let process = CodeExecutor::with_advice_provider(MemAdviceProvider::default()) @@ -186,8 +267,7 @@ fn test_get_item() { #[test] fn test_get_map_item() { - let account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component( AccountMockComponent::new_with_slots( TransactionKernel::testing_assembler(), @@ -333,8 +413,7 @@ fn test_set_map_item() { [Felt::new(9_u64), Felt::new(10_u64), Felt::new(11_u64), Felt::new(12_u64)], ); - let account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component( AccountMockComponent::new_with_slots( TransactionKernel::testing_assembler(), @@ -482,8 +561,7 @@ fn test_account_component_storage_offset() { .unwrap() .with_supported_type(AccountType::RegularAccountUpdatableCode); - let mut account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let mut account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component(component1) .with_component(component2) .build_existing() diff --git a/miden-tx/src/tests/kernel_tests/test_asset.rs b/miden-tx/src/tests/kernel_tests/test_asset.rs index a9dfce990..21030dc7b 100644 --- a/miden-tx/src/tests/kernel_tests/test_asset.rs +++ b/miden-tx/src/tests/kernel_tests/test_asset.rs @@ -49,8 +49,8 @@ fn test_create_fungible_asset_succeeds() { Word::from([ Felt::new(FUNGIBLE_ASSET_AMOUNT), Felt::new(0), - faucet_id.second_felt(), - faucet_id.first_felt(), + faucet_id.suffix(), + faucet_id.prefix().as_felt(), ]) ); } diff --git a/miden-tx/src/tests/kernel_tests/test_asset_vault.rs b/miden-tx/src/tests/kernel_tests/test_asset_vault.rs index 8ad83bbfb..40a21ce3e 100644 --- a/miden-tx/src/tests/kernel_tests/test_asset_vault.rs +++ b/miden-tx/src/tests/kernel_tests/test_asset_vault.rs @@ -41,15 +41,15 @@ fn test_get_balance() { begin exec.prologue::prepare_transaction - push.{second_felt}.{first_felt} + push.{suffix}.{prefix} exec.account::get_balance # truncate the stack swap drop end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), ); let process = tx_context.execute_code(&code).unwrap(); @@ -72,12 +72,12 @@ fn test_get_balance_non_fungible_fails() { begin exec.prologue::prepare_transaction - push.{second_felt}.{first_felt} + push.{suffix}.{prefix} exec.account::get_balance end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), ); let process = tx_context.execute_code(&code); @@ -122,9 +122,13 @@ fn test_add_fungible_asset_success() { let mut account_vault = tx_context.account().vault().clone(); let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let amount = FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT; - let add_fungible_asset = - Asset::try_from([Felt::new(amount), ZERO, faucet_id.second_felt(), faucet_id.first_felt()]) - .unwrap(); + let add_fungible_asset = Asset::try_from([ + Felt::new(amount), + ZERO, + faucet_id.suffix(), + faucet_id.prefix().as_felt(), + ]) + .unwrap(); let code = format!( " @@ -163,9 +167,13 @@ fn test_add_non_fungible_asset_fail_overflow() { let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let amount = FungibleAsset::MAX_AMOUNT - FUNGIBLE_ASSET_AMOUNT + 1; - let add_fungible_asset = - Asset::try_from([Felt::new(amount), ZERO, faucet_id.second_felt(), faucet_id.first_felt()]) - .unwrap(); + let add_fungible_asset = Asset::try_from([ + Felt::new(amount), + ZERO, + faucet_id.suffix(), + faucet_id.prefix().as_felt(), + ]) + .unwrap(); let code = format!( " @@ -267,9 +275,13 @@ fn test_remove_fungible_asset_success_no_balance_remaining() { let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let amount = FUNGIBLE_ASSET_AMOUNT; - let remove_fungible_asset = - Asset::try_from([Felt::new(amount), ZERO, faucet_id.second_felt(), faucet_id.first_felt()]) - .unwrap(); + let remove_fungible_asset = Asset::try_from([ + Felt::new(amount), + ZERO, + faucet_id.suffix(), + faucet_id.prefix().as_felt(), + ]) + .unwrap(); let code = format!( " @@ -306,9 +318,13 @@ fn test_remove_fungible_asset_fail_remove_too_much() { let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let amount = FUNGIBLE_ASSET_AMOUNT + 1; - let remove_fungible_asset = - Asset::try_from([Felt::new(amount), ZERO, faucet_id.second_felt(), faucet_id.first_felt()]) - .unwrap(); + let remove_fungible_asset = Asset::try_from([ + Felt::new(amount), + ZERO, + faucet_id.suffix(), + faucet_id.prefix().as_felt(), + ]) + .unwrap(); let code = format!( " @@ -336,9 +352,13 @@ fn test_remove_fungible_asset_success_balance_remaining() { let faucet_id: AccountId = ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN.try_into().unwrap(); let amount = FUNGIBLE_ASSET_AMOUNT - 1; - let remove_fungible_asset = - Asset::try_from([Felt::new(amount), ZERO, faucet_id.second_felt(), faucet_id.first_felt()]) - .unwrap(); + let remove_fungible_asset = Asset::try_from([ + Felt::new(amount), + ZERO, + faucet_id.suffix(), + faucet_id.prefix().as_felt(), + ]) + .unwrap(); let code = format!( " diff --git a/miden-tx/src/tests/kernel_tests/test_faucet.rs b/miden-tx/src/tests/kernel_tests/test_faucet.rs index 9a0f7406b..0fa6a9c13 100644 --- a/miden-tx/src/tests/kernel_tests/test_faucet.rs +++ b/miden-tx/src/tests/kernel_tests/test_faucet.rs @@ -55,22 +55,22 @@ fn test_mint_fungible_asset_succeeds() { begin # mint asset exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{second_felt}.{first_felt} + push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} call.account::mint # assert the correct asset is returned - push.{FUNGIBLE_ASSET_AMOUNT}.0.{second_felt}.{first_felt} + push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} assert_eqw # assert the input vault has been updated exec.memory::get_input_vault_root_ptr - push.{second_felt}.{first_felt} + push.{suffix}.{prefix} exec.asset_vault::get_balance push.{FUNGIBLE_ASSET_AMOUNT} assert_eq end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), ); let process = tx_context.execute_code(&code).unwrap(); @@ -99,12 +99,12 @@ fn test_mint_fungible_asset_fails_not_faucet_account() { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{second_felt}.{first_felt} + push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} call.account::mint end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), ); let process = tx_context.execute_code(&code); @@ -124,12 +124,12 @@ fn test_mint_fungible_asset_inconsistent_faucet_id() { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{second_felt}.{first_felt} + push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} call.account::mint end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), ); let process = tx_context.execute_code(&code); @@ -154,12 +154,12 @@ fn test_mint_fungible_asset_fails_saturate_max_amount() { begin exec.prologue::prepare_transaction - push.{saturating_amount}.0.{second_felt}.{first_felt} + push.{saturating_amount}.0.{suffix}.{prefix} call.account::mint end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), saturating_amount = FungibleAsset::MAX_AMOUNT - FUNGIBLE_FAUCET_INITIAL_BALANCE + 1 ); @@ -330,23 +330,23 @@ fn test_burn_fungible_asset_succeeds() { begin # mint asset exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{second_felt}.{first_felt} + push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} call.account::burn # assert the correct asset is returned - push.{FUNGIBLE_ASSET_AMOUNT}.0.{second_felt}.{first_felt} + push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} assert_eqw # assert the input vault has been updated exec.memory::get_input_vault_root_ptr - push.{second_felt}.{first_felt} + push.{suffix}.{prefix} exec.asset_vault::get_balance push.{final_input_vault_asset_amount} assert_eq end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), final_input_vault_asset_amount = CONSUMED_ASSET_1_AMOUNT - FUNGIBLE_ASSET_AMOUNT, ); @@ -376,12 +376,12 @@ fn test_burn_fungible_asset_fails_not_faucet_account() { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{second_felt}.{first_felt} + push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} call.account::burn end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), ); let process = tx_context.execute_code(&code); @@ -407,12 +407,12 @@ fn test_burn_fungible_asset_inconsistent_faucet_id() { begin exec.prologue::prepare_transaction - push.{FUNGIBLE_ASSET_AMOUNT}.0.{second_felt}.{first_felt} + push.{FUNGIBLE_ASSET_AMOUNT}.0.{suffix}.{prefix} call.account::burn end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), ); let process = tx_context.execute_code(&code); @@ -438,12 +438,12 @@ fn test_burn_fungible_asset_insufficient_input_amount() { begin exec.prologue::prepare_transaction - push.{saturating_amount}.0.{second_felt}.{first_felt} + push.{saturating_amount}.0.{suffix}.{prefix} call.account::burn end ", - first_felt = faucet_id.first_felt(), - second_felt = faucet_id.second_felt(), + prefix = faucet_id.prefix().as_felt(), + suffix = faucet_id.suffix(), saturating_amount = CONSUMED_ASSET_1_AMOUNT + 1 ); diff --git a/miden-tx/src/tests/kernel_tests/test_note.rs b/miden-tx/src/tests/kernel_tests/test_note.rs index 8374cc6c6..4f9b38b5a 100644 --- a/miden-tx/src/tests/kernel_tests/test_note.rs +++ b/miden-tx/src/tests/kernel_tests/test_note.rs @@ -5,9 +5,13 @@ use miden_lib::{ transaction::memory::CURRENT_INPUT_NOTE_PTR, }; use miden_objects::{ - notes::Note, testing::prepare_word, transaction::TransactionArgs, Hasher, WORD_SIZE, + accounts::AccountId, + notes::{Note, NoteExecutionHint, NoteExecutionMode, NoteMetadata, NoteTag, NoteType}, + testing::{account_id::ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, prepare_word}, + transaction::TransactionArgs, + Hasher, WORD_SIZE, }; -use vm_processor::{ProcessState, EMPTY_WORD, ONE}; +use vm_processor::{ProcessState, Word, EMPTY_WORD, ONE}; use super::{Felt, Process, ZERO}; use crate::{ @@ -69,8 +73,8 @@ fn test_get_sender() { let process = tx_context.execute_code(code).unwrap(); let sender = tx_context.input_notes().get_note(0).note().metadata().sender(); - assert_eq!(process.stack.get(0), sender.first_felt()); - assert_eq!(process.stack.get(1), sender.second_felt()); + assert_eq!(process.stack.get(0), sender.prefix().as_felt()); + assert_eq!(process.stack.get(1), sender.suffix()); } #[test] @@ -544,3 +548,64 @@ fn test_get_current_script_hash() { let script_hash = tx_context.input_notes().get_note(0).note().script().hash(); assert_eq!(process.stack.get_word(0), script_hash.as_elements()); } + +#[test] +fn test_build_note_metadata() { + let tx_context = TransactionContextBuilder::with_standard_account(ONE) + .with_mock_notes_preserved() + .build(); + let sender = tx_context.account().id(); + let receiver = + AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN).unwrap(); + + let test_metadata1 = NoteMetadata::new( + sender, + NoteType::Private, + NoteTag::from_account_id(receiver, NoteExecutionMode::Local).unwrap(), + NoteExecutionHint::after_block(500).unwrap(), + Felt::try_from(1u64 << 63).unwrap(), + ) + .unwrap(); + let test_metadata2 = NoteMetadata::new( + sender, + NoteType::Public, + // Use largest allowed use_case_id. + NoteTag::for_public_use_case((1 << 14) - 1, u16::MAX, NoteExecutionMode::Local).unwrap(), + NoteExecutionHint::on_block_slot(u8::MAX, u8::MAX, u8::MAX), + Felt::try_from(0u64).unwrap(), + ) + .unwrap(); + + for (iteration, test_metadata) in [test_metadata1, test_metadata2].into_iter().enumerate() { + let code = format!( + " + use.kernel::prologue + use.kernel::tx + + begin + exec.prologue::prepare_transaction + push.{execution_hint}.{note_type}.{aux}.{tag} + exec.tx::build_note_metadata + + # truncate the stack + swapw dropw + end + ", + execution_hint = Felt::from(test_metadata.execution_hint()), + note_type = Felt::from(test_metadata.note_type()), + aux = test_metadata.aux(), + tag = test_metadata.tag(), + ); + + let process = tx_context.execute_code(&code).unwrap(); + + let metadata_word = [ + process.stack.get(3), + process.stack.get(2), + process.stack.get(1), + process.stack.get(0), + ]; + + assert_eq!(Word::from(test_metadata), metadata_word, "failed in iteration {iteration}"); + } +} diff --git a/miden-tx/src/tests/kernel_tests/test_prologue.rs b/miden-tx/src/tests/kernel_tests/test_prologue.rs index dae5777cf..43f9de9ab 100644 --- a/miden-tx/src/tests/kernel_tests/test_prologue.rs +++ b/miden-tx/src/tests/kernel_tests/test_prologue.rs @@ -29,12 +29,15 @@ use miden_lib::{ }; use miden_objects::{ accounts::{ - Account, AccountBuilder, AccountIdAnchor, AccountProcedureInfo, AccountStorageMode, - AccountType, StorageSlot, + Account, AccountBuilder, AccountId, AccountIdAnchor, AccountIdVersion, + AccountProcedureInfo, AccountStorageMode, AccountType, StorageSlot, }, testing::{ account_component::AccountMockComponent, - storage::{generate_account_seed, AccountSeedType}, + account_id::{ + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, + }, + constants::FUNGIBLE_FAUCET_INITIAL_BALANCE, }, transaction::{TransactionArgs, TransactionScript}, BlockHeader, GENESIS_BLOCK, @@ -115,13 +118,13 @@ fn global_input_memory_assertions(process: &Process, inputs: &Transact assert_eq!( read_root_mem_value(process, ACCT_ID_PTR)[0], - inputs.account().id().second_felt(), - "The account ID first felt should be stored at the ACCT_ID_PTR[0]" + inputs.account().id().suffix(), + "The account ID prefix should be stored at the ACCT_ID_PTR[0]" ); assert_eq!( read_root_mem_value(process, ACCT_ID_PTR)[1], - inputs.account().id().first_felt(), - "The account ID second felt should be stored at the ACCT_ID_PTR[1]" + inputs.account().id().prefix().as_felt(), + "The account ID suffix should be stored at the ACCT_ID_PTR[1]" ); assert_eq!( @@ -247,12 +250,12 @@ fn account_data_memory_assertions(process: &Process, inputs: &Transact assert_eq!( read_root_mem_value(process, NATIVE_ACCT_ID_AND_NONCE_PTR), [ - inputs.account().id().second_felt(), - inputs.account().id().first_felt(), + inputs.account().id().suffix(), + inputs.account().id().prefix().as_felt(), ZERO, inputs.account().nonce() ], - "The account id should be stored at NATIVE_ACCT_ID_AND_NONCE_PTR[0]" + "The account ID should be stored at NATIVE_ACCT_ID_AND_NONCE_PTR[0]" ); assert_eq!( @@ -446,10 +449,9 @@ pub fn create_multiple_accounts_test( AccountType::FungibleFaucet, AccountType::NonFungibleFaucet, ] { - let (account, seed) = AccountBuilder::new() + let (account, seed) = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .account_type(account_type) .storage_mode(storage_mode) - .init_seed(ChaCha20Rng::from_entropy().gen()) .anchor( AccountIdAnchor::try_from(anchor_block_header) .context("block header to anchor conversion failed")?, @@ -518,6 +520,41 @@ pub fn create_accounts_with_non_zero_anchor_block() -> anyhow::Result<()> { create_multiple_accounts_test(&mock_chain, &epoch1_block_header, AccountStorageMode::Public) } +/// Takes an account with a placeholder ID and returns the same account but with its ID replaced. +/// The ID is newly generated and anchored in the given block header. +fn compute_valid_account_id( + account: Account, + anchor_block_header: &BlockHeader, +) -> (Account, Word) { + let init_seed: [u8; 32] = [5; 32]; + let seed = AccountId::compute_account_seed( + init_seed, + account.account_type(), + AccountStorageMode::Public, + AccountIdVersion::Version0, + account.code().commitment(), + account.storage().commitment(), + anchor_block_header.hash(), + ) + .unwrap(); + + let anchor = AccountIdAnchor::try_from(anchor_block_header).unwrap(); + let account_id = AccountId::new( + seed, + anchor, + AccountIdVersion::Version0, + account.code().commitment(), + account.storage().commitment(), + ) + .unwrap(); + + // Overwrite old ID with generated ID. + let (_, vault, storage, code, nonce) = account.into_parts(); + let account = Account::from_parts(account_id, vault, storage, code, nonce); + + (account, seed) +} + /// Tests that creating a fungible faucet account with a non-empty initial balance in its reserved /// slot fails. #[test] @@ -527,11 +564,13 @@ pub fn create_account_fungible_faucet_invalid_initial_balance() -> anyhow::Resul let genesis_block_header = mock_chain.block_header(GENESIS_BLOCK as usize); - let (account, _, account_seed) = generate_account_seed( - AccountSeedType::FungibleFaucetInvalidInitialBalance, - &genesis_block_header, + let account = Account::mock_fungible_faucet( + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, + ZERO, + Felt::new(FUNGIBLE_FAUCET_INITIAL_BALANCE), TransactionKernel::assembler().with_debug_mode(true), ); + let (account, account_seed) = compute_valid_account_id(account, &genesis_block_header); let result = create_account_test(&mock_chain, account, account_seed); @@ -549,11 +588,13 @@ pub fn create_account_non_fungible_faucet_invalid_initial_reserved_slot() -> any let genesis_block_header = mock_chain.block_header(GENESIS_BLOCK as usize); - let (account, _, account_seed) = generate_account_seed( - AccountSeedType::NonFungibleFaucetInvalidReservedSlot, - &genesis_block_header, + let account = Account::mock_non_fungible_faucet( + ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, + ZERO, + false, TransactionKernel::assembler().with_debug_mode(true), ); + let (account, account_seed) = compute_valid_account_id(account, &genesis_block_header); let result = create_account_test(&mock_chain, account, account_seed); @@ -566,8 +607,6 @@ pub fn create_account_non_fungible_faucet_invalid_initial_reserved_slot() -> any } /// Tests that supplying an invalid seed causes account creation to fail. -/// -/// TODO: Add variant of this test with incorrect block hash. #[test] pub fn create_account_invalid_seed() { let mut mock_chain = MockChain::new(); @@ -575,9 +614,8 @@ pub fn create_account_invalid_seed() { let genesis_block_header = mock_chain.block_header(GENESIS_BLOCK as usize); - let (account, seed) = AccountBuilder::new() + let (account, seed) = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .anchor(AccountIdAnchor::try_from(&genesis_block_header).unwrap()) - .init_seed(ChaCha20Rng::from_entropy().gen()) .account_type(AccountType::RegularAccountUpdatableCode) .with_component(BasicWallet) .build() @@ -586,7 +624,7 @@ pub fn create_account_invalid_seed() { let tx_inputs = mock_chain.get_transaction_inputs(account.clone(), Some(seed), &[], &[]); // override the seed with an invalid seed to ensure the kernel fails - let account_seed_key = [account.id().second_felt(), account.id().first_felt(), ZERO, ZERO]; + let account_seed_key = [account.id().suffix(), account.id().prefix().as_felt(), ZERO, ZERO]; let adv_inputs = AdviceInputs::default().with_map([(Digest::from(account_seed_key), vec![ZERO; 4])]); diff --git a/miden-tx/src/tests/kernel_tests/test_tx.rs b/miden-tx/src/tests/kernel_tests/test_tx.rs index 96b76d6f1..dfc7afdf0 100644 --- a/miden-tx/src/tests/kernel_tests/test_tx.rs +++ b/miden-tx/src/tests/kernel_tests/test_tx.rs @@ -368,7 +368,7 @@ fn test_create_note_and_add_asset() { let recipient = [ZERO, ONE, Felt::new(2), Felt::new(3)]; let aux = Felt::new(27); let tag = Felt::new(4); - let asset = [Felt::new(10), ZERO, faucet_id.second_felt(), faucet_id.first_felt()]; + let asset = [Felt::new(10), ZERO, faucet_id.suffix(), faucet_id.prefix().as_felt()]; let code = format!( " @@ -433,10 +433,10 @@ fn test_create_note_and_add_multiple_assets() { let aux = Felt::new(27); let tag = Felt::new(4); - let asset = [Felt::new(10), ZERO, faucet.second_felt(), faucet.first_felt()]; - let asset_2 = [Felt::new(20), ZERO, faucet_2.second_felt(), faucet_2.first_felt()]; - let asset_3 = [Felt::new(30), ZERO, faucet_2.second_felt(), faucet_2.first_felt()]; - let asset_2_and_3 = [Felt::new(50), ZERO, faucet_2.second_felt(), faucet_2.first_felt()]; + let asset = [Felt::new(10), ZERO, faucet.suffix(), faucet.prefix().as_felt()]; + let asset_2 = [Felt::new(20), ZERO, faucet_2.suffix(), faucet_2.prefix().as_felt()]; + let asset_3 = [Felt::new(30), ZERO, faucet_2.suffix(), faucet_2.prefix().as_felt()]; + let asset_2_and_3 = [Felt::new(50), ZERO, faucet_2.suffix(), faucet_2.prefix().as_felt()]; let non_fungible_asset = NonFungibleAsset::mock(&NON_FUNGIBLE_ASSET_DATA_2); let non_fungible_asset_encoded = Word::from(non_fungible_asset); @@ -689,14 +689,12 @@ fn test_fpi_memory() { .unwrap() .with_supports_all_types(); - let foreign_account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component(foreign_account_component) .build_existing() .unwrap(); - let native_account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let native_account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component( AccountMockComponent::new_with_slots( TransactionKernel::testing_assembler(), @@ -743,9 +741,9 @@ fn test_fpi_memory() { # get the hash of the `get_item_foreign` procedure of the foreign account push.{get_item_foreign_hash} - # push the foreign account id - push.{foreign_second_felt}.{foreign_first_felt} - # => [foreign_account_id_hi, foreign_account_id_lo, FOREIGN_PROC_ROOT, storage_item_index, pad(11)] + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(11)] exec.tx::execute_foreign_procedure # => [STORAGE_VALUE_1] @@ -754,8 +752,8 @@ fn test_fpi_memory() { exec.sys::truncate_stack end ", - foreign_first_felt = foreign_account.id().first_felt(), - foreign_second_felt = foreign_account.id().second_felt(), + foreign_prefix = foreign_account.id().prefix().as_felt(), + foreign_suffix = foreign_account.id().suffix(), get_item_foreign_hash = foreign_account.code().procedures()[0].mast_root(), ); @@ -796,9 +794,9 @@ fn test_fpi_memory() { # get the hash of the `get_map_item_foreign` account procedure push.{get_map_item_foreign_hash} - # push the foreign account id - push.{foreign_second_felt}.{foreign_first_felt} - # => [foreign_account_id_hi, foreign_account_id_lo, FOREIGN_PROC_ROOT, storage_item_index, MAP_ITEM_KEY, pad(10)] + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, MAP_ITEM_KEY, pad(10)] exec.tx::execute_foreign_procedure # => [MAP_VALUE] @@ -807,8 +805,8 @@ fn test_fpi_memory() { exec.sys::truncate_stack end ", - foreign_first_felt = foreign_account.id().first_felt(), - foreign_second_felt = foreign_account.id().second_felt(), + foreign_prefix = foreign_account.id().prefix().as_felt(), + foreign_suffix = foreign_account.id().suffix(), map_key = STORAGE_LEAVES_2[0].0, get_map_item_foreign_hash = foreign_account.code().procedures()[1].mast_root(), ); @@ -850,9 +848,9 @@ fn test_fpi_memory() { # get the hash of the `get_item_foreign` procedure of the foreign account push.{get_item_foreign_hash} - # push the foreign account id - push.{foreign_second_felt}.{foreign_first_felt} - # => [foreign_account_id_hi, foreign_account_id_lo, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure dropw # => [] @@ -868,9 +866,9 @@ fn test_fpi_memory() { # get the hash of the `get_item_foreign` procedure of the foreign account push.{get_item_foreign_hash} - # push the foreign account id - push.{foreign_second_felt}.{foreign_first_felt} - # => [foreign_account_id_hi, foreign_account_id_lo, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure @@ -878,8 +876,8 @@ fn test_fpi_memory() { exec.sys::truncate_stack end ", - foreign_first_felt = foreign_account.id().first_felt(), - foreign_second_felt = foreign_account.id().second_felt(), + foreign_prefix = foreign_account.id().prefix().as_felt(), + foreign_suffix = foreign_account.id().suffix(), get_item_foreign_hash = foreign_account.code().procedures()[0].mast_root(), ); @@ -937,14 +935,12 @@ fn test_fpi_execute_foreign_procedure() { .unwrap() .with_supports_all_types(); - let foreign_account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let foreign_account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component(foreign_account_component) .build_existing() .unwrap(); - let native_account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let native_account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component( AccountMockComponent::new_with_slots(TransactionKernel::testing_assembler(), vec![]) .unwrap(), @@ -975,9 +971,9 @@ fn test_fpi_execute_foreign_procedure() { # get the hash of the `get_item` account procedure push.{get_item_foreign_hash} - # push the foreign account id - push.{foreign_second_felt}.{foreign_first_felt} - # => [foreign_account_id_hi, foreign_account_id_lo, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, pad(14)] exec.tx::execute_foreign_procedure # => [STORAGE_VALUE] @@ -1000,9 +996,9 @@ fn test_fpi_execute_foreign_procedure() { # get the hash of the `get_map_item_foreign` account procedure push.{get_map_item_foreign_hash} - # push the foreign account id - push.{foreign_second_felt}.{foreign_first_felt} - # => [foreign_account_id_hi, foreign_account_id_lo, FOREIGN_PROC_ROOT, storage_item_index, MAP_ITEM_KEY, pad(10)] + # push the foreign account ID + push.{foreign_suffix}.{foreign_prefix} + # => [foreign_account_id_prefix, foreign_account_id_suffix, FOREIGN_PROC_ROOT, storage_item_index, MAP_ITEM_KEY, pad(10)] exec.tx::execute_foreign_procedure # => [MAP_VALUE] @@ -1015,8 +1011,8 @@ fn test_fpi_execute_foreign_procedure() { exec.sys::truncate_stack end ", - foreign_first_felt = foreign_account.id().first_felt(), - foreign_second_felt = foreign_account.id().second_felt(), + foreign_prefix = foreign_account.id().prefix().as_felt(), + foreign_suffix = foreign_account.id().suffix(), get_item_foreign_hash = foreign_account.code().procedures()[0].mast_root(), get_map_item_foreign_hash = foreign_account.code().procedures()[1].mast_root(), map_key = STORAGE_LEAVES_2[0].0, @@ -1074,7 +1070,7 @@ fn get_mock_fpi_adv_inputs(foreign_account: &Account, mock_chain: &MockChain) -> mock_chain .accounts() // TODO: Update. - .open(&LeafIndex::::new(foreign_account.id().first_felt().as_int()).unwrap()) + .open(&LeafIndex::::new(foreign_account.id().prefix().as_felt().as_int()).unwrap()) .path .into(), ), @@ -1101,8 +1097,8 @@ fn foreign_account_data_memory_assertions(foreign_account: &Account, process: &P assert_eq!( read_root_mem_value(process, foreign_account_data_ptr + ACCT_ID_AND_NONCE_OFFSET), [ - foreign_account.id().second_felt(), - foreign_account.id().first_felt(), + foreign_account.id().suffix(), + foreign_account.id().prefix().as_felt(), ZERO, foreign_account.nonce() ], diff --git a/miden-tx/src/tests/mod.rs b/miden-tx/src/tests/mod.rs index d7e817944..afaf07f8e 100644 --- a/miden-tx/src/tests/mod.rs +++ b/miden-tx/src/tests/mod.rs @@ -120,8 +120,7 @@ fn transaction_executor_witness() { #[test] fn executed_transaction_account_delta_new() { let account_assets = AssetVault::mock().assets().collect::>(); - let account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component( AccountMockComponent::new_with_slots( TransactionKernel::testing_assembler(), @@ -956,8 +955,7 @@ fn transaction_executor_account_code_using_custom_library() { .with_supports_all_types(); // Build an existing account with nonce 1. - let native_account = AccountBuilder::new() - .init_seed(ChaCha20Rng::from_entropy().gen()) + let native_account = AccountBuilder::new(ChaCha20Rng::from_entropy().gen()) .with_component(account_component) .build_existing() .unwrap(); diff --git a/objects/benches/account_seed.rs b/objects/benches/account_seed.rs index 3c04d9079..b836f7185 100644 --- a/objects/benches/account_seed.rs +++ b/objects/benches/account_seed.rs @@ -1,22 +1,36 @@ +use std::time::Duration; + use criterion::{criterion_group, criterion_main, Criterion}; use miden_objects::{ accounts::{AccountId, AccountIdVersion, AccountStorageMode, AccountType}, Digest, }; +use rand::{Rng, SeedableRng}; +/// Running this benchmark with --no-default-features will use the single-threaded account seed +/// computation. +/// +/// Passing --features concurrent will use the multi-threaded account seed computation. fn grind_account_seed(c: &mut Criterion) { + let mut group = c.benchmark_group("grind-seed"); + // Increase measurement time (= target time) from the default 5s as suggested by criterion + // during a run. + group.measurement_time(Duration::from_secs(20)); + let init_seed = [ 1, 18, 222, 14, 56, 94, 222, 213, 12, 57, 86, 1, 22, 34, 187, 100, 210, 1, 18, 222, 14, 56, 94, 43, 213, 12, 57, 86, 1, 22, 34, 187, ]; + // Use an rng to ensure we're starting from different seeds for each iteration. + let mut rng = rand_xoshiro::Xoshiro256PlusPlus::from_seed(init_seed); - c.bench_function("Grind regular on-chain account seed", |bench| { + group.bench_function("Grind regular on-chain account seed", |bench| { bench.iter(|| { AccountId::compute_account_seed( - init_seed, + rng.gen(), AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, - AccountIdVersion::VERSION_0, + AccountIdVersion::Version0, Digest::default(), Digest::default(), Digest::default(), @@ -24,19 +38,23 @@ fn grind_account_seed(c: &mut Criterion) { }) }); - c.bench_function("Grind fungible faucet on-chain account seed", |bench| { + // Reinitialize the RNG. + let mut rng = rand_xoshiro::Xoshiro256PlusPlus::from_seed(init_seed); + group.bench_function("Grind fungible faucet on-chain account seed", |bench| { bench.iter(|| { AccountId::compute_account_seed( - init_seed, + rng.gen(), AccountType::FungibleFaucet, AccountStorageMode::Public, - AccountIdVersion::VERSION_0, + AccountIdVersion::Version0, Digest::default(), Digest::default(), Digest::default(), ) }) }); + + group.finish(); } criterion_group!(account_seed, grind_account_seed); diff --git a/objects/src/accounts/account_id.rs b/objects/src/accounts/account_id.rs deleted file mode 100644 index f8e258405..000000000 --- a/objects/src/accounts/account_id.rs +++ /dev/null @@ -1,1014 +0,0 @@ -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; -use core::{fmt, str::FromStr}; - -use miden_crypto::{merkle::LeafIndex, utils::hex_to_bytes}; -use vm_core::{ - utils::{ByteReader, Deserializable, Serializable}, - Felt, Word, -}; -use vm_processor::{DeserializationError, Digest}; - -use super::Hasher; -use crate::{ - accounts::{AccountIdAnchor, AccountIdPrefix}, - AccountError, ACCOUNT_TREE_DEPTH, -}; - -// ACCOUNT TYPE -// ================================================================================================ - -const FUNGIBLE_FAUCET: u8 = 0b10; -const NON_FUNGIBLE_FAUCET: u8 = 0b11; -const REGULAR_ACCOUNT_IMMUTABLE_CODE: u8 = 0b00; -const REGULAR_ACCOUNT_UPDATABLE_CODE: u8 = 0b01; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u8)] -pub enum AccountType { - FungibleFaucet = FUNGIBLE_FAUCET, - NonFungibleFaucet = NON_FUNGIBLE_FAUCET, - RegularAccountImmutableCode = REGULAR_ACCOUNT_IMMUTABLE_CODE, - RegularAccountUpdatableCode = REGULAR_ACCOUNT_UPDATABLE_CODE, -} - -impl AccountType { - /// Returns `true` if the account is a faucet. - pub fn is_faucet(&self) -> bool { - matches!(self, Self::FungibleFaucet | Self::NonFungibleFaucet) - } - - /// Returns `true` if the account is a regular account. - pub fn is_regular_account(&self) -> bool { - matches!(self, Self::RegularAccountImmutableCode | Self::RegularAccountUpdatableCode) - } -} - -#[cfg(any(feature = "testing", test))] -impl rand::distributions::Distribution for rand::distributions::Standard { - /// Samples a uniformly random [`AccountType`] from the given `rng`. - fn sample(&self, rng: &mut R) -> AccountType { - match rng.gen_range(0..4) { - 0 => AccountType::RegularAccountImmutableCode, - 1 => AccountType::RegularAccountUpdatableCode, - 2 => AccountType::FungibleFaucet, - 3 => AccountType::NonFungibleFaucet, - _ => unreachable!("gen_range should not produce higher values"), - } - } -} - -// SERIALIZATION -// ================================================================================================ - -impl Serializable for AccountType { - fn write_into(&self, target: &mut W) { - target.write_u8(*self as u8); - } -} - -impl Deserializable for AccountType { - fn read_from(source: &mut R) -> Result { - let num: u8 = source.read()?; - match num { - FUNGIBLE_FAUCET => Ok(AccountType::FungibleFaucet), - NON_FUNGIBLE_FAUCET => Ok(AccountType::NonFungibleFaucet), - REGULAR_ACCOUNT_IMMUTABLE_CODE => Ok(AccountType::RegularAccountImmutableCode), - REGULAR_ACCOUNT_UPDATABLE_CODE => Ok(AccountType::RegularAccountUpdatableCode), - _ => Err(DeserializationError::InvalidValue(format!("invalid account type: {num}"))), - } - } -} - -#[cfg(feature = "std")] -impl serde::Serialize for AccountType { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let s = match self { - AccountType::FungibleFaucet => "FungibleFaucet", - AccountType::NonFungibleFaucet => "NonFungibleFaucet", - AccountType::RegularAccountImmutableCode => "RegularAccountImmutableCode", - AccountType::RegularAccountUpdatableCode => "RegularAccountUpdatableCode", - }; - serializer.serialize_str(s) - } -} - -#[cfg(feature = "std")] -impl<'de> serde::Deserialize<'de> for AccountType { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - let s: String = serde::Deserialize::deserialize(deserializer)?; - - match s.as_str() { - "FungibleFaucet" => Ok(AccountType::FungibleFaucet), - "NonFungibleFaucet" => Ok(AccountType::NonFungibleFaucet), - "RegularAccountImmutableCode" => Ok(AccountType::RegularAccountImmutableCode), - "RegularAccountUpdatableCode" => Ok(AccountType::RegularAccountUpdatableCode), - other => Err(D::Error::invalid_value( - serde::de::Unexpected::Str(other), - &"a valid account type (\"FungibleFaucet\", \"NonFungibleFaucet\", \"RegularAccountImmutableCode\", or \"RegularAccountUpdatableCode\")", - )), - } - } -} - -// ACCOUNT STORAGE MODE -// ================================================================================================ - -const PUBLIC: u8 = 0b00; -const PRIVATE: u8 = 0b10; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum AccountStorageMode { - Public = PUBLIC, - Private = PRIVATE, -} - -impl fmt::Display for AccountStorageMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - AccountStorageMode::Public => write!(f, "public"), - AccountStorageMode::Private => write!(f, "private"), - } - } -} - -impl TryFrom<&str> for AccountStorageMode { - type Error = AccountError; - - fn try_from(value: &str) -> Result { - match value.to_lowercase().as_str() { - "public" => Ok(AccountStorageMode::Public), - "private" => Ok(AccountStorageMode::Private), - _ => Err(AccountError::InvalidAccountStorageMode(value.into())), - } - } -} - -impl TryFrom for AccountStorageMode { - type Error = AccountError; - - fn try_from(value: String) -> Result { - AccountStorageMode::from_str(&value) - } -} - -impl FromStr for AccountStorageMode { - type Err = AccountError; - - fn from_str(input: &str) -> Result { - AccountStorageMode::try_from(input) - } -} - -#[cfg(any(feature = "testing", test))] -impl rand::distributions::Distribution for rand::distributions::Standard { - /// Samples a uniformly random [`AccountStorageMode`] from the given `rng`. - fn sample(&self, rng: &mut R) -> AccountStorageMode { - match rng.gen_range(0..2) { - 0 => AccountStorageMode::Public, - 1 => AccountStorageMode::Private, - _ => unreachable!("gen_range should not produce higher values"), - } - } -} - -// ACCOUNT ID VERSION -// ================================================================================================ - -/// The version of an [`AccountId`]. -/// -/// Each version has a public associated constant, e.g. [`AccountIdVersion::VERSION_0`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct AccountIdVersion(u8); - -impl AccountIdVersion { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - - const VERSION_0_NUMBER: u8 = 0; - /// Version 0 of the [`AccountId`]. - pub const VERSION_0: AccountIdVersion = AccountIdVersion(Self::VERSION_0_NUMBER); - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the version number. - pub const fn as_u8(&self) -> u8 { - self.0 - } -} - -impl From for u8 { - fn from(value: AccountIdVersion) -> Self { - value.as_u8() - } -} - -impl From for AccountIdVersion { - fn from(id: AccountId) -> Self { - id.version() - } -} - -impl From for AccountIdVersion { - fn from(id_prefix: AccountIdPrefix) -> Self { - id_prefix.version() - } -} - -// ACCOUNT ID -// ================================================================================================ - -/// The identifier of an [`Account`](crate::accounts::Account). -/// -/// # Layout -/// -/// An `AccountId` consists of two field elements and is layed out as follows: -/// -/// ```text -/// 1st felt: [random (56 bits) | storage mode (2 bits) | type (2 bits) | version (4 bits)] -/// 2nd felt: [anchor_epoch (16 bits) | random (40 bits) | 8 zero bits] -/// ``` -/// -/// # Generation -/// -/// An `AccountId` is a commitment to a user-generated seed, the code and storage of an account and -/// to a certain hash of an epoch block of the blockchain. An id is generated by picking an epoch -/// block as an anchor - which is why it is also referred to as the anchor block - and creating the -/// account's initial storage and code. Then a random seed is picked and the hash of (SEED, -/// CODE_COMMITMENT, STORAGE_COMMITMENT, ANCHOR_BLOCK_HASH) is computed. If the hash's first element -/// has the desired storage mode, account type and version, the computation part of the ID -/// generation is done. If not, another random seed is picked and the process is repeated. The first -/// felt of the ID is then the first element of the hash. -/// -/// The second felt of the ID is the second element of the hash. Its upper 16 bits are overwritten -/// with the epoch in which the ID is anchored and the lower 8 bits are zeroed. Thus, the first felt -/// of the ID must derive exactly from the hash, while only part of the second felt is derived from -/// the hash. -/// -/// # Constraints -/// -/// Constructors will return an error if: -/// -/// - The first felt contains account ID metadata (storage mode, type or version) that does not -/// match any of the known values. -/// - The anchor epoch in the second felt is equal to [`u16::MAX`]. -/// - The lower 8 bits of the second felt are not zero, although [`AccountId::new`] ensures this is -/// the case rather than return an error. -/// -/// # Design Rationale -/// -/// The rationale behind the above layout is as follows. -/// -/// - The first felt is the output of a hash function so it will be a valid field element without -/// requiring additional constraints. -/// - The version is placed at a static offset such that future ID versions which may change the -/// number of type or storage mode bits will not cause the version to be at a different offset. -/// This is important so that a parser can always reliably read the version and then parse the -/// remainder of the ID depending on the version. Having only 4 bits for the version is a trade -/// off between future proofing to be able to introduce more versions and the version requiring -/// Proof of Work as part of the ID generation. -/// - The version, type and storage mode are part of the first felt which is included in the -/// representation of a non-fungible asset. The first felt alone is enough to determine all of -/// these properties about the ID. -/// - The anchor epoch is not important beyond the creation process, so placing it in the second -/// felt is fine. Moreover, all properties of the first felt must be derived from the seed, so -/// they add to the proof of work difficulty. Adding 16 bits of PoW for the epoch would be -/// significant. -/// - The anchor epoch is placed at the most significant end of the second felt. Its value must be -/// less than [`u16::MAX`] so that at least one of the upper 16 bits is always zero. This ensures -/// that the entire second felt is valid even if the remaining bits of the felt are one. -/// - The lower 8 bits of the second felt may be overwritten when the ID is encoded in other layouts -/// such as the [`NoteMetadata`](crate::notes::NoteMetadata). In such cases, it can happen that -/// all bits of the encoded second felt would be one, so having the epoch constraint is important. -/// - The ID is dependent on the hash of an epoch block. This is a block whose number is a multiple -/// of 2^[`BlockHeader::EPOCH_LENGTH_EXPONENT`][epoch_len_exp], e.g. `0`, `65536`, `131072`, ... -/// These are the first blocks of epoch 0, 1, 2, ... We call this dependence _anchoring_ because -/// the ID is anchored to that epoch block's hash. Anchoring makes it practically impossible for -/// an attacker to construct a rainbow table of account IDs whose epoch is X, if the block for -/// epoch X has not been constructed yet because its hash is then unknown. Therefore, picking a -/// recent anchor block when generating a new ID makes it extremely unlikely that an attacker can -/// highjack this ID because the hash of that block has only been known for a short period of -/// time. -/// - An ID highjack refers to an attack where a user generates an ID and lets someone else send -/// assets to it. At this point the user has not registered the ID on-chain yet, likely -/// because they need the funds in the asset to pay for their first transaction where the -/// account would be registered. Until the ID is registered on chain, an attacker with a -/// rainbow table who happens to have a seed, code and storage commitment combination that -/// hashes to the user's ID can claim the assets sent to the user's ID. Adding the anchor -/// block hash to ID generation process makes this attack practically impossible. -/// -/// [epoch_len_exp]: crate::block::BlockHeader::EPOCH_LENGTH_EXPONENT -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct AccountId { - first_felt: Felt, - second_felt: Felt, -} - -impl AccountId { - // CONSTANTS - // -------------------------------------------------------------------------------------------- - - /// The serialized size of an [`AccountId`] in bytes. - pub const SERIALIZED_SIZE: usize = 15; - - /// The lower two bits of the second least significant nibble determine the account type. - pub(crate) const TYPE_SHIFT: u64 = 4; - pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT; - - /// The least significant nibble determines the account version. - const VERSION_MASK: u64 = 0b1111; - - const ANCHOR_EPOCH_SHIFT: u64 = 48; - const ANCHOR_EPOCH_MASK: u64 = 0xffff << Self::ANCHOR_EPOCH_SHIFT; - - /// The higher two bits of the second least significant nibble determine the account storage - /// mode. - pub(crate) const STORAGE_MODE_SHIFT: u64 = 6; - pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT; - - pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT; - - // CONSTRUCTORS - // -------------------------------------------------------------------------------------------- - - /// Creates an [`AccountId`] by hashing the given `seed`, `code_commitment`, - /// `storage_commitment` and [`AccountIdAnchor::block_hash`] from the `anchor` and using the - /// resulting first and second element of the hash as the first and second felt of the ID. - /// The [`AccountIdAnchor::epoch`] from the `anchor` overwrites part of the second felt. - /// - /// Note that the `anchor` must correspond to a valid block in the chain for the ID to be deemed - /// valid during creation. - /// - /// See the documentation of the [`AccountId`] for more details on the generation. - /// - /// # Errors - /// - /// Returns an error if any of the ID constraints are not met. See the [type - /// documentation](AccountId) for details. - pub fn new( - seed: Word, - anchor: AccountIdAnchor, - code_commitment: Digest, - storage_commitment: Digest, - ) -> Result { - let seed_digest = - compute_digest(seed, code_commitment, storage_commitment, anchor.block_hash()); - - let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2] - .try_into() - .expect("we should have sliced off 2 elements"); - - felts[1] = shape_second_felt(felts[1], anchor.epoch()); - - // This will validate that the anchor_epoch we have just written is not u16::MAX. - account_id_from_felts(felts) - } - - /// Creates an [`AccountId`] from the given felts where the felt at index 0 is the first felt - /// and the felt at index 2 is the second felt. - /// - /// # Warning - /// - /// Validity of the ID must be ensured by the caller. An invalid ID may lead to panics. - /// - /// # Panics - /// - /// If debug_assertions are enabled (e.g. in debug mode), this function panics if any of the ID - /// constraints are not met. See the [type documentation](AccountId) for details. - pub fn new_unchecked(elements: [Felt; 2]) -> Self { - let first_felt = elements[0]; - let second_felt = elements[1]; - - // Panic on invalid felts in debug mode. - if cfg!(debug_assertions) { - validate_first_felt(first_felt) - .expect("AccountId::new_unchecked called with invalid first felt"); - validate_second_felt(second_felt) - .expect("AccountId::new_unchecked called with invalid first felt"); - } - - Self { first_felt, second_felt } - } - - /// Constructs an [`AccountId`] for testing purposes with the given account type and storage - /// mode. - /// - /// This function does the following: - /// - Split the given bytes into a `first_felt = bytes[0..8]` and `second_felt = bytes[8..]` - /// part to be used for the first and second felt, respectively. - /// - The least significant byte of the first felt is set to the version 0, and the given type - /// and storage mode. - /// - The 32nd most significant bit in the first felt is cleared to ensure it is a valid felt. - /// The 32nd is chosen as it is the lowest bit that we can clear and still ensure felt - /// validity. This leaves the upper 31 bits to be set by the input `bytes` which makes it - /// simpler to create test values which more often need specific values for the most - /// significant end of the ID. - /// - In the second felt the anchor epoch is set to 0 and the lower 8 bits are cleared. - #[cfg(any(feature = "testing", test))] - pub fn dummy( - mut bytes: [u8; 15], - account_type: AccountType, - storage_mode: AccountStorageMode, - ) -> AccountId { - let version = AccountIdVersion::VERSION_0_NUMBER; - let low_nibble = (storage_mode as u8) << Self::STORAGE_MODE_SHIFT - | (account_type as u8) << Self::TYPE_SHIFT - | version; - - // Set least significant byte. - bytes[7] = low_nibble; - - // Clear the 32nd most significant bit. - bytes[3] &= 0b1111_1110; - - let first_felt_bytes = - bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes"); - let first_felt = Felt::try_from(u64::from_be_bytes(first_felt_bytes)) - .expect("should be a valid felt due to the most significant bit being zero"); - - let mut second_felt_bytes = [0; 8]; - // Overwrite first 7 bytes, leaving the 8th byte 0 (which will be cleared by - // shape_second_felt anyway). - second_felt_bytes[..7].copy_from_slice(&bytes[8..]); - // If the value is too large modular reduction is performed, which is fine here. - let mut second_felt = Felt::new(u64::from_be_bytes(second_felt_bytes)); - - second_felt = shape_second_felt(second_felt, 0); - - let account_id = account_id_from_felts([first_felt, second_felt]) - .expect("we should have shaped the felts to produce a valid id"); - - debug_assert_eq!(account_id.account_type(), account_type); - debug_assert_eq!(account_id.storage_mode(), storage_mode); - - account_id - } - - /// Grinds an account seed until its hash matches the given `account_type`, `storage_mode` and - /// `version` and returns it as a [`Word`]. The input to the hash function next to the seed are - /// the `code_commitment`, `storage_commitment` and `anchor_block_hash`. - /// - /// The grinding process is started from the given `init_seed` which should be a random seed - /// generated from a cryptographically secure source. - pub fn compute_account_seed( - init_seed: [u8; 32], - account_type: AccountType, - storage_mode: AccountStorageMode, - version: AccountIdVersion, - code_commitment: Digest, - storage_commitment: Digest, - anchor_block_hash: Digest, - ) -> Result { - crate::accounts::seed::compute_account_seed( - init_seed, - account_type, - storage_mode, - version, - code_commitment, - storage_commitment, - anchor_block_hash, - ) - } - - // PUBLIC ACCESSORS - // -------------------------------------------------------------------------------------------- - - /// Returns the type of this account ID. - pub const fn account_type(&self) -> AccountType { - extract_type(self.first_felt().as_int()) - } - - /// Returns true if an account with this ID is a faucet which can issue assets. - pub fn is_faucet(&self) -> bool { - self.account_type().is_faucet() - } - - /// Returns true if an account with this ID is a regular account. - pub fn is_regular_account(&self) -> bool { - self.account_type().is_regular_account() - } - - /// Returns the storage mode of this account ID. - pub fn storage_mode(&self) -> AccountStorageMode { - extract_storage_mode(self.first_felt().as_int()) - .expect("account id should have been constructed with a valid storage mode") - } - - /// Returns true if an account with this ID is a public account. - pub fn is_public(&self) -> bool { - self.storage_mode() == AccountStorageMode::Public - } - - /// Returns the version of this account ID. - pub fn version(&self) -> AccountIdVersion { - extract_version(self.first_felt().as_int()) - .expect("account id should have been constructed with a valid version") - } - - /// Returns the anchor epoch of this account ID. - /// - /// This is the epoch to which this ID is anchored. The hash of this epoch block is used in the - /// generation of the ID. - pub fn anchor_epoch(&self) -> u16 { - extract_anchor_epoch(self.second_felt().as_int()) - } - - /// Creates an [`AccountId`] from a hex string. Assumes the string starts with "0x" and - /// that the hexadecimal characters are big-endian encoded. - pub fn from_hex(hex_str: &str) -> Result { - hex_to_bytes(hex_str).map_err(AccountError::AccountIdHexParseError).and_then( - |mut bytes: [u8; 15]| { - // TryFrom<[u8; 15]> expects [first_felt, second_felt] in little-endian order, so we - // need to convert the bytes representation from big endian to little endian by - // reversing each felt. The first felt has 8 and the second felt has - // 7 bytes. - bytes[0..8].reverse(); - bytes[8..15].reverse(); - - AccountId::try_from(bytes) - }, - ) - } - - /// Returns a big-endian, hex-encoded string of length 32, including the `0x` prefix, so it - /// encodes 15 bytes. - pub fn to_hex(&self) -> String { - // We need to pad the second felt with 16 zeroes so it produces a correctly padded 8 byte - // big-endian hex string. Only then can we cut off the last zero byte by truncating. We - // cannot use `:014x` padding. - let mut hex_string = - format!("0x{:016x}{:016x}", self.first_felt().as_int(), self.second_felt().as_int()); - hex_string.truncate(32); - hex_string - } - - /// Returns the [`AccountIdPrefix`] of this ID which is equivalent to the first felt. - pub fn prefix(&self) -> AccountIdPrefix { - // SAFETY: We only construct accounts with valid first felts, so we don't have to validate - // it again. - AccountIdPrefix::new_unchecked(self.first_felt) - } - - /// Returns the first felt of this ID. - pub const fn first_felt(&self) -> Felt { - self.first_felt - } - - /// Returns the second felt of this ID. - pub const fn second_felt(&self) -> Felt { - self.second_felt - } -} - -// CONVERSIONS FROM ACCOUNT ID -// ================================================================================================ - -impl From for [Felt; 2] { - fn from(id: AccountId) -> Self { - [id.first_felt, id.second_felt] - } -} - -impl From for [u8; 15] { - fn from(id: AccountId) -> Self { - let mut result = [0_u8; 15]; - result[..8].copy_from_slice(&id.first_felt().as_int().to_le_bytes()); - // The last byte of the second felt is always zero, and in little endian this is the first - // byte, so we skip it here. - result[8..].copy_from_slice(&id.second_felt().as_int().to_le_bytes()[1..8]); - result - } -} - -impl From for u128 { - fn from(id: AccountId) -> Self { - let mut le_bytes = [0_u8; 16]; - le_bytes[..8].copy_from_slice(&id.second_felt().as_int().to_le_bytes()); - le_bytes[8..].copy_from_slice(&id.first_felt().as_int().to_le_bytes()); - u128::from_le_bytes(le_bytes) - } -} - -/// Account IDs are used as indexes in the account database, which is a tree of depth 64. -impl From for LeafIndex { - fn from(id: AccountId) -> Self { - LeafIndex::new_max_depth(id.first_felt().as_int()) - } -} - -// CONVERSIONS TO ACCOUNT ID -// ================================================================================================ - -impl TryFrom<[Felt; 2]> for AccountId { - type Error = AccountError; - - /// Returns an [`AccountId`] instantiated with the provided field elements where `elements[0]` - /// is taken as the first felt and `elements[1]` is taken as the second element. - /// - /// # Errors - /// - /// Returns an error if any of the ID constraints are not met. See the [type - /// documentation](AccountId) for details. - fn try_from(elements: [Felt; 2]) -> Result { - account_id_from_felts(elements) - } -} - -impl TryFrom<[u8; 15]> for AccountId { - type Error = AccountError; - - /// Tries to convert a byte array in little-endian order to an [`AccountId`]. - /// - /// # Errors - /// - /// Returns an error if any of the ID constraints are not met. See the [type - /// documentation](AccountId) for details. - fn try_from(bytes: [u8; 15]) -> Result { - // This slice has 8 bytes. - let first_felt_slice = &bytes[..8]; - // This slice has 7 bytes, since the 8th byte will always be zero. - let second_felt_slice = &bytes[8..15]; - - // The byte order is little-endian order, so prepending a 0 sets the least significant byte. - let mut second_felt_bytes = [0; 8]; - second_felt_bytes[1..8].copy_from_slice(second_felt_slice); - - let first_felt = - Felt::try_from(first_felt_slice).map_err(AccountError::AccountIdInvalidFieldElement)?; - - let second_felt = Felt::try_from(second_felt_bytes.as_slice()) - .map_err(AccountError::AccountIdInvalidFieldElement)?; - - Self::try_from([first_felt, second_felt]) - } -} - -impl TryFrom for AccountId { - type Error = AccountError; - - /// Tries to convert a u128 into an [`AccountId`]. - /// - /// # Errors - /// - /// Returns an error if any of the ID constraints are not met. See the [type - /// documentation](AccountId) for details. - fn try_from(int: u128) -> Result { - let little_endian_bytes = int.to_le_bytes(); - let mut bytes: [u8; 15] = [0; 15]; - - // Swap the positions of the Felts to match what the TryFrom<[u8; 15]> impl expects. - // This copies the first felt's 8 bytes. - bytes[..8].copy_from_slice(&little_endian_bytes[8..]); - // This copies the second felt's 7 bytes. The least significant byte is zero and is - // therefore skipped. - bytes[8..].copy_from_slice(&little_endian_bytes[1..8]); - - Self::try_from(bytes) - } -} - -// SERIALIZATION -// ================================================================================================ - -impl Serializable for AccountId { - fn write_into(&self, target: &mut W) { - let bytes: [u8; 15] = (*self).into(); - bytes.write_into(target); - } - - fn get_size_hint(&self) -> usize { - Self::SERIALIZED_SIZE - } -} - -impl Deserializable for AccountId { - fn read_from(source: &mut R) -> Result { - <[u8; 15]>::read_from(source)? - .try_into() - .map_err(|err: AccountError| DeserializationError::InvalidValue(err.to_string())) - } -} - -// HELPER FUNCTIONS -// ================================================================================================ - -/// Returns an [AccountId] instantiated with the provided field elements. -/// -/// # Errors -/// -/// Returns an error if any of the ID constraints are not met. See the See the [type -/// documentation](AccountId) for details. -fn account_id_from_felts(elements: [Felt; 2]) -> Result { - validate_first_felt(elements[0])?; - validate_second_felt(elements[1])?; - - Ok(AccountId { - first_felt: elements[0], - second_felt: elements[1], - }) -} - -/// Checks that the first felt: -/// - has known values for metadata (storage mode, type and version). -pub(super) fn validate_first_felt( - first_felt: Felt, -) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountError> { - let first_felt = first_felt.as_int(); - - // Validate storage bits. - let storage_mode = extract_storage_mode(first_felt)?; - - // Validate version bits. - let version = extract_version(first_felt)?; - - let account_type = extract_type(first_felt); - - Ok((account_type, storage_mode, version)) -} - -/// Checks that the second felt: -/// - has an anchor_epoch that is not [`u16::MAX`]. -/// - has its lower 8 bits set to zero. -fn validate_second_felt(second_felt: Felt) -> Result<(), AccountError> { - let second_felt = second_felt.as_int(); - - if extract_anchor_epoch(second_felt) == u16::MAX { - return Err(AccountError::AssumptionViolated( - "TODO: Make proper error: second felt epoch must be less than 2^16".into(), - )); - } - - // Validate lower 8 bits of second felt are zero. - if second_felt & 0xff != 0 { - return Err(AccountError::AssumptionViolated( - "TODO: Make proper error: second felt lower 8 bits must be zero".into(), - )); - } - - Ok(()) -} - -pub(crate) fn extract_storage_mode(first_felt: u64) -> Result { - let bits = - (first_felt & (AccountId::STORAGE_MODE_MASK as u64)) >> AccountId::STORAGE_MODE_SHIFT; - // SAFETY: `STORAGE_MODE_MASK` is u8 so casting bits is lossless - match bits as u8 { - PUBLIC => Ok(AccountStorageMode::Public), - PRIVATE => Ok(AccountStorageMode::Private), - _ => Err(AccountError::InvalidAccountStorageMode(format!("0b{bits:b}"))), - } -} - -pub(crate) fn extract_version(first_felt: u64) -> Result { - let bits = first_felt & AccountId::VERSION_MASK; - let version = bits.try_into().expect("TODO"); - match version { - AccountIdVersion::VERSION_0_NUMBER => Ok(AccountIdVersion::VERSION_0), - other => Err(AccountError::AssumptionViolated(format!( - "TODO: Error. Unexpected version {other}" - ))), - } -} - -pub(crate) const fn extract_type(first_felt: u64) -> AccountType { - let bits = (first_felt & (AccountId::TYPE_MASK as u64)) >> AccountId::TYPE_SHIFT; - // SAFETY: `TYPE_MASK` is u8 so casting bits is lossless - match bits as u8 { - REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode, - REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode, - FUNGIBLE_FAUCET => AccountType::FungibleFaucet, - NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet, - _ => { - // SAFETY: type mask contains only 2 bits and we've covered all 4 possible options. - unreachable!() - }, - } -} - -fn extract_anchor_epoch(second_felt: u64) -> u16 { - ((second_felt & AccountId::ANCHOR_EPOCH_MASK) >> AccountId::ANCHOR_EPOCH_SHIFT) as u16 -} - -/// Shapes the second felt so it meets the requirements of the account ID, by overwriting the -/// upper 16 bits with the epoch and setting the lower 8 bits to zero. -fn shape_second_felt(second_felt: Felt, anchor_epoch: u16) -> Felt { - if anchor_epoch == u16::MAX { - unimplemented!("TODO: Return error"); - } - - let mut second_felt = second_felt.as_int(); - - // Clear upper 16 epoch bits and the lower 8 bits. - second_felt &= 0x0000_ffff_ffff_ff00; - - // Set the upper 16 anchor epoch bits. - second_felt |= (anchor_epoch as u64) << AccountId::ANCHOR_EPOCH_SHIFT; - - // SAFETY: We disallow u16::MAX which would be all 1 bits, so at least one of the most - // significant bits will always be zero. - Felt::try_from(second_felt).expect("epoch is never all ones so felt should be valid") -} - -// COMMON TRAIT IMPLS -// ================================================================================================ - -impl PartialOrd for AccountId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for AccountId { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - u128::from(*self).cmp(&u128::from(*other)) - } -} - -impl fmt::Display for AccountId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_hex()) - } -} - -/// Returns the digest of two hashing permutations over the seed, code commitment, storage -/// commitment and padding. -pub(super) fn compute_digest( - seed: Word, - code_commitment: Digest, - storage_commitment: Digest, - anchor_block_hash: Digest, -) -> Digest { - let mut elements = Vec::with_capacity(16); - elements.extend(seed); - elements.extend(*code_commitment); - elements.extend(*storage_commitment); - elements.extend(*anchor_block_hash); - Hasher::hash_elements(&elements) -} - -// TESTS -// ================================================================================================ - -#[cfg(test)] -mod tests { - - use super::*; - use crate::testing::account_id::{ - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, - ACCOUNT_ID_OFF_CHAIN_SENDER, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - }; - - #[test] - fn test_account_id_from_seed_with_epoch() { - let code_commitment: Digest = Digest::default(); - let storage_commitment: Digest = Digest::default(); - let anchor_block_hash: Digest = Digest::default(); - - let seed = AccountId::compute_account_seed( - [10; 32], - AccountType::FungibleFaucet, - AccountStorageMode::Public, - AccountIdVersion::VERSION_0, - code_commitment, - storage_commitment, - anchor_block_hash, - ) - .unwrap(); - - for anchor_epoch in [0, u16::MAX - 1, 5000] { - let anchor = AccountIdAnchor::new_unchecked(anchor_epoch, anchor_block_hash); - let id = AccountId::new(seed, anchor, code_commitment, storage_commitment).unwrap(); - assert_eq!(id.anchor_epoch(), anchor_epoch, "failed for account id: {id}"); - } - } - - #[test] - fn account_id_from_felts_with_high_pop_count() { - let valid_second_felt = Felt::try_from(0xfffe_ffff_ffff_ff00u64).unwrap(); - let valid_first_felt = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap(); - - let id1 = AccountId::new_unchecked([valid_first_felt, valid_second_felt]); - assert_eq!(id1.account_type(), AccountType::RegularAccountImmutableCode); - assert_eq!(id1.storage_mode(), AccountStorageMode::Public); - assert_eq!(id1.version(), AccountIdVersion::VERSION_0); - assert_eq!(id1.anchor_epoch(), u16::MAX - 1); - } - - #[test] - fn account_id_construction() { - // Use the highest possible input to check if the constructed id is a valid Felt in that - // scenario. - // Use the lowest possible input to check whether the constructor produces valid IDs with - // all-zeroes input. - for input in [[0xff; 15], [0; 15]] { - for account_type in [ - AccountType::FungibleFaucet, - AccountType::NonFungibleFaucet, - AccountType::RegularAccountImmutableCode, - AccountType::RegularAccountUpdatableCode, - ] { - for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] { - let id = AccountId::dummy(input, account_type, storage_mode); - assert_eq!(id.account_type(), account_type); - assert_eq!(id.storage_mode(), storage_mode); - assert_eq!(id.version(), AccountIdVersion::VERSION_0); - assert_eq!(id.anchor_epoch(), 0); - - // Do a serialization roundtrip to ensure validity. - let serialized_id = id.to_bytes(); - AccountId::read_from_bytes(&serialized_id).unwrap(); - assert_eq!(serialized_id.len(), AccountId::SERIALIZED_SIZE); - } - } - } - } - - #[test] - fn account_id_prefix_serialization_compatibility() { - // Ensure that an AccountIdPrefix can be read from the serialized bytes of an AccountId. - let account_id = AccountId::try_from(ACCOUNT_ID_OFF_CHAIN_SENDER).unwrap(); - let id_bytes = account_id.to_bytes(); - let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap(); - assert_eq!(account_id.prefix(), deserialized_prefix); - - // Ensure AccountId and AccountIdPrefix's hex representation are compatible. - assert!(account_id.to_hex().starts_with(&account_id.prefix().to_string())); - } - - // CONVERSION TESTS - // ================================================================================================ - - #[test] - fn test_account_id_conversion_roundtrip() { - for (idx, account_id) in [ - ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, - ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, - ACCOUNT_ID_OFF_CHAIN_SENDER, - ] - .into_iter() - .enumerate() - { - let id = AccountId::try_from(account_id).expect("account ID should be valid"); - assert_eq!(id, AccountId::from_hex(&id.to_hex()).unwrap(), "failed in {idx}"); - assert_eq!(id, AccountId::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}"); - assert_eq!(id, AccountId::try_from(u128::from(id)).unwrap(), "failed in {idx}"); - assert_eq!(account_id, u128::from(id), "failed in {idx}"); - } - } - - #[test] - fn test_account_id_tag_identifiers() { - let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN) - .expect("valid account ID"); - assert!(account_id.is_regular_account()); - assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode); - assert!(account_id.is_public()); - - let account_id = AccountId::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN) - .expect("valid account ID"); - assert!(account_id.is_regular_account()); - assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode); - assert!(!account_id.is_public()); - - let account_id = - AccountId::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).expect("valid account ID"); - assert!(account_id.is_faucet()); - assert_eq!(account_id.account_type(), AccountType::FungibleFaucet); - assert!(account_id.is_public()); - - let account_id = AccountId::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN) - .expect("valid account ID"); - assert!(account_id.is_faucet()); - assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet); - assert!(!account_id.is_public()); - } - - /// The following test ensure there is a bit available to identify an account as a faucet or - /// normal. - #[test] - fn test_account_id_faucet_bit() { - const ACCOUNT_IS_FAUCET_MASK: u8 = 0b10; - - // faucets have a bit set - assert_ne!((FUNGIBLE_FAUCET) & ACCOUNT_IS_FAUCET_MASK, 0); - assert_ne!((NON_FUNGIBLE_FAUCET) & ACCOUNT_IS_FAUCET_MASK, 0); - - // normal accounts do not have the faucet bit set - assert_eq!((REGULAR_ACCOUNT_IMMUTABLE_CODE) & ACCOUNT_IS_FAUCET_MASK, 0); - assert_eq!((REGULAR_ACCOUNT_UPDATABLE_CODE) & ACCOUNT_IS_FAUCET_MASK, 0); - } -} diff --git a/objects/src/accounts/account_id/account_type.rs b/objects/src/accounts/account_id/account_type.rs new file mode 100644 index 000000000..49a442a54 --- /dev/null +++ b/objects/src/accounts/account_id/account_type.rs @@ -0,0 +1,146 @@ +use core::{fmt, str::FromStr}; + +use vm_core::utils::{ByteReader, Deserializable, Serializable}; +use vm_processor::DeserializationError; + +use crate::errors::AccountIdError; + +// ACCOUNT TYPE +// ================================================================================================ + +pub(super) const FUNGIBLE_FAUCET: u8 = 0b10; +pub(super) const NON_FUNGIBLE_FAUCET: u8 = 0b11; +pub(super) const REGULAR_ACCOUNT_IMMUTABLE_CODE: u8 = 0b00; +pub(super) const REGULAR_ACCOUNT_UPDATABLE_CODE: u8 = 0b01; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(u8)] +pub enum AccountType { + FungibleFaucet = FUNGIBLE_FAUCET, + NonFungibleFaucet = NON_FUNGIBLE_FAUCET, + RegularAccountImmutableCode = REGULAR_ACCOUNT_IMMUTABLE_CODE, + RegularAccountUpdatableCode = REGULAR_ACCOUNT_UPDATABLE_CODE, +} + +impl AccountType { + /// Returns `true` if the account is a faucet. + pub fn is_faucet(&self) -> bool { + matches!(self, Self::FungibleFaucet | Self::NonFungibleFaucet) + } + + /// Returns `true` if the account is a regular account. + pub fn is_regular_account(&self) -> bool { + matches!(self, Self::RegularAccountImmutableCode | Self::RegularAccountUpdatableCode) + } + + /// Returns the string representation of the [`AccountType`]. + fn as_str(&self) -> &'static str { + match self { + AccountType::FungibleFaucet => "FungibleFaucet", + AccountType::NonFungibleFaucet => "NonFungibleFaucet", + AccountType::RegularAccountImmutableCode => "RegularAccountImmutableCode", + AccountType::RegularAccountUpdatableCode => "RegularAccountUpdatableCode", + } + } +} + +#[cfg(any(feature = "testing", test))] +impl rand::distributions::Distribution for rand::distributions::Standard { + /// Samples a uniformly random [`AccountType`] from the given `rng`. + fn sample(&self, rng: &mut R) -> AccountType { + match rng.gen_range(0..4) { + 0 => AccountType::RegularAccountImmutableCode, + 1 => AccountType::RegularAccountUpdatableCode, + 2 => AccountType::FungibleFaucet, + 3 => AccountType::NonFungibleFaucet, + _ => unreachable!("gen_range should not produce higher values"), + } + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for AccountType { + fn write_into(&self, target: &mut W) { + target.write_u8(*self as u8); + } +} + +impl Deserializable for AccountType { + fn read_from(source: &mut R) -> Result { + let num: u8 = source.read()?; + match num { + FUNGIBLE_FAUCET => Ok(AccountType::FungibleFaucet), + NON_FUNGIBLE_FAUCET => Ok(AccountType::NonFungibleFaucet), + REGULAR_ACCOUNT_IMMUTABLE_CODE => Ok(AccountType::RegularAccountImmutableCode), + REGULAR_ACCOUNT_UPDATABLE_CODE => Ok(AccountType::RegularAccountUpdatableCode), + _ => Err(DeserializationError::InvalidValue(format!("invalid account type: {num}"))), + } + } +} + +impl FromStr for AccountType { + type Err = AccountIdError; + + fn from_str(string: &str) -> Result { + match string { + "FungibleFaucet" => Ok(AccountType::FungibleFaucet), + "NonFungibleFaucet" => Ok(AccountType::NonFungibleFaucet), + "RegularAccountImmutableCode" => Ok(AccountType::RegularAccountImmutableCode), + "RegularAccountUpdatableCode" => Ok(AccountType::RegularAccountUpdatableCode), + other => Err(AccountIdError::UnknownAccountType(other.into())), + } + } +} + +impl core::fmt::Display for AccountType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[cfg(feature = "std")] +impl serde::Serialize for AccountType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +#[cfg(feature = "std")] +impl<'de> serde::Deserialize<'de> for AccountType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use alloc::string::String; + + use serde::de::Error; + + let string: String = serde::Deserialize::deserialize(deserializer)?; + string.parse().map_err(D::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The following test ensure there is a bit available to identify an account as a faucet or + /// normal. + #[test] + fn test_account_id_faucet_bit() { + const ACCOUNT_IS_FAUCET_MASK: u8 = 0b10; + + // faucets have a bit set + assert_ne!((FUNGIBLE_FAUCET) & ACCOUNT_IS_FAUCET_MASK, 0); + assert_ne!((NON_FUNGIBLE_FAUCET) & ACCOUNT_IS_FAUCET_MASK, 0); + + // normal accounts do not have the faucet bit set + assert_eq!((REGULAR_ACCOUNT_IMMUTABLE_CODE) & ACCOUNT_IS_FAUCET_MASK, 0); + assert_eq!((REGULAR_ACCOUNT_UPDATABLE_CODE) & ACCOUNT_IS_FAUCET_MASK, 0); + } +} diff --git a/objects/src/accounts/account_id_anchor.rs b/objects/src/accounts/account_id/id_anchor.rs similarity index 86% rename from objects/src/accounts/account_id_anchor.rs rename to objects/src/accounts/account_id/id_anchor.rs index ef5d3fcd5..1fdb2baf5 100644 --- a/objects/src/accounts/account_id_anchor.rs +++ b/objects/src/accounts/account_id/id_anchor.rs @@ -1,4 +1,6 @@ -use crate::{block::block_epoch_from_number, AccountError, BlockHeader, Digest, EMPTY_WORD}; +use crate::{ + block::block_epoch_from_number, errors::AccountIdError, BlockHeader, Digest, EMPTY_WORD, +}; // ACCOUNT ID ANCHOR // ================================================================================================ @@ -48,20 +50,18 @@ impl AccountIdAnchor { /// /// Returns an error if any of the anchor constraints are not met. See the [type /// documentation](AccountIdAnchor) for details. - pub fn new(anchor_block_number: u32, anchor_block_hash: Digest) -> Result { + pub fn new( + anchor_block_number: u32, + anchor_block_hash: Digest, + ) -> Result { if anchor_block_number & 0x0000_ffff != 0 { - return Err(AccountError::AssumptionViolated(format!( - "TODO: Make proper error: anchor block must be an epoch block, i.e. its block number must be a multiple of 2^{}", - BlockHeader::EPOCH_LENGTH_EXPONENT))); + return Err(AccountIdError::AnchorBlockMustBeEpochBlock); } let anchor_epoch = block_epoch_from_number(anchor_block_number); if anchor_epoch == u16::MAX { - return Err(AccountError::AssumptionViolated(format!( - "TODO: Make proper error: anchor epoch cannot be {}", - u16::MAX - ))); + return Err(AccountIdError::AnchorEpochMustNotBeU16Max); } Ok(Self { @@ -109,7 +109,7 @@ impl AccountIdAnchor { // ================================================================================================ impl TryFrom<&BlockHeader> for AccountIdAnchor { - type Error = AccountError; + type Error = AccountIdError; /// Extracts the [`BlockHeader::block_num`] and [`BlockHeader::hash`] from the provided /// `block_header` and tries to convert it to an [`AccountIdAnchor`]. diff --git a/objects/src/accounts/account_id_prefix.rs b/objects/src/accounts/account_id/id_prefix.rs similarity index 53% rename from objects/src/accounts/account_id_prefix.rs rename to objects/src/accounts/account_id/id_prefix.rs index 0a955a4df..3d90c1b86 100644 --- a/objects/src/accounts/account_id_prefix.rs +++ b/objects/src/accounts/account_id/id_prefix.rs @@ -8,28 +8,29 @@ use vm_core::{ }; use vm_processor::DeserializationError; -use super::account_id; +use super::v0; use crate::{ accounts::{ - account_id::validate_first_felt, AccountIdVersion, AccountStorageMode, AccountType, + account_id::AccountIdPrefixV0, AccountIdV0, AccountIdVersion, AccountStorageMode, + AccountType, }, - AccountError, + errors::AccountIdError, }; // ACCOUNT ID PREFIX // ================================================================================================ -/// The first felt of an [`AccountId`][id], i.e. its prefix. +/// The prefix of an [`AccountId`][id], i.e. its first field element. /// -/// See the type's documentation for details. +/// See the [`AccountId`][id] documentation for details. /// /// The serialization formats of [`AccountIdPrefix`] and [`AccountId`][id] are compatible. In /// particular, a prefix can be deserialized from the serialized bytes of a full id. /// /// [id]: crate::accounts::AccountId #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct AccountIdPrefix { - first_felt: Felt, +pub enum AccountIdPrefix { + V0(AccountIdPrefixV0), } impl AccountIdPrefix { @@ -42,7 +43,7 @@ impl AccountIdPrefix { // CONSTRUCTORS // -------------------------------------------------------------------------------------------- - /// Constructs a new [`AccountIdPrefix`] from the given `first_felt` without checking its + /// Constructs a new [`AccountIdPrefix`] from the given `prefix` without checking its /// validity. /// /// # Warning @@ -51,37 +52,57 @@ impl AccountIdPrefix { /// /// # Panics /// + /// Panics if the prefix does not contain a known account ID version. + /// /// If debug_assertions are enabled (e.g. in debug mode), this function panics if the given /// felt is invalid according to the constraints in the /// [`AccountId`](crate::accounts::AccountId) documentation. - pub fn new_unchecked(first_felt: Felt) -> Self { - // Panic on invalid felts in debug mode. - if cfg!(debug_assertions) { - validate_first_felt(first_felt) - .expect("AccountIdPrefix::new_unchecked called with invalid first felt"); + pub fn new_unchecked(prefix: Felt) -> Self { + // The prefix contains the metadata. + // If we add more versions in the future, we may need to generalize this. + match v0::extract_version(prefix.as_int()) + .expect("prefix should contain a valid account ID version") + { + AccountIdVersion::Version0 => Self::V0(AccountIdPrefixV0::new_unchecked(prefix)), } - - AccountIdPrefix { first_felt } } - /// Constructs a new [`AccountIdPrefix`] from the given `first_felt` and checks its validity. + /// Constructs a new [`AccountIdPrefix`] from the given `prefix` and checks its validity. /// /// # Errors /// - /// Returns an error if any of the ID constraints of the first felt are not met. See the - /// [`AccountId`](crate::accounts::AccountId) type documentation for details. - pub fn new(first_felt: Felt) -> Result { - validate_first_felt(first_felt)?; - - Ok(AccountIdPrefix { first_felt }) + /// Returns an error if any of the ID constraints are not met. See the [constraints + /// documentation](super::AccountId#constraints) for details. + pub fn new(prefix: Felt) -> Result { + // The prefix contains the metadata. + // If we add more versions in the future, we may need to generalize this. + match v0::extract_version(prefix.as_int())? { + AccountIdVersion::Version0 => AccountIdPrefixV0::new(prefix).map(Self::V0), + } } // PUBLIC ACCESSORS // -------------------------------------------------------------------------------------------- + /// Returns the [`Felt`] that represents this prefix. + pub const fn as_felt(&self) -> Felt { + match self { + AccountIdPrefix::V0(id_prefix) => id_prefix.as_felt(), + } + } + + /// Returns the prefix as a [`u64`]. + pub const fn as_u64(&self) -> u64 { + match self { + AccountIdPrefix::V0(id_prefix) => id_prefix.as_u64(), + } + } + /// Returns the type of this account ID. pub const fn account_type(&self) -> AccountType { - account_id::extract_type(self.first_felt.as_int()) + match self { + AccountIdPrefix::V0(id_prefix) => id_prefix.account_type(), + } } /// Returns true if an account with this ID is a faucet (can issue assets). @@ -96,8 +117,9 @@ impl AccountIdPrefix { /// Returns the storage mode of this account ID. pub fn storage_mode(&self) -> AccountStorageMode { - account_id::extract_storage_mode(self.first_felt.as_int()) - .expect("account id prefix should have been constructed with a valid storage mode") + match self { + AccountIdPrefix::V0(id_prefix) => id_prefix.storage_mode(), + } } /// Returns true if an account with this ID is a public account. @@ -107,36 +129,63 @@ impl AccountIdPrefix { /// Returns the version of this account ID. pub fn version(&self) -> AccountIdVersion { - account_id::extract_version(self.first_felt.as_int()) - .expect("account id prefix should have been constructed with a valid version") + match self { + AccountIdPrefix::V0(_) => AccountIdVersion::Version0, + } } /// Returns the prefix as a big-endian, hex-encoded string. - pub fn to_hex(&self) -> String { - format!("0x{:016x}", self.first_felt.as_int()) + pub fn to_hex(self) -> String { + match self { + AccountIdPrefix::V0(id_prefix) => id_prefix.to_hex(), + } + } + + /// Returns `felt` with the fungible bit set to zero. The version must be passed as the location + /// of the fungible bit may depend on the underlying account ID version. + pub(crate) fn clear_fungible_bit(version: AccountIdVersion, felt: Felt) -> Felt { + match version { + AccountIdVersion::Version0 => { + // Set the fungible bit to zero by taking the bitwise `and` of the felt with the + // inverted is_faucet mask. + let clear_fungible_bit_mask = !AccountIdV0::IS_FAUCET_MASK; + Felt::try_from(felt.as_int() & clear_fungible_bit_mask) + .expect("felt should still be valid as we cleared a bit and did not set any") + }, + } } } // CONVERSIONS FROM ACCOUNT ID PREFIX // ================================================================================================ +impl From for AccountIdPrefix { + fn from(id: AccountIdPrefixV0) -> Self { + Self::V0(id) + } +} + impl From for Felt { fn from(id: AccountIdPrefix) -> Self { - id.first_felt + match id { + AccountIdPrefix::V0(id_prefix) => id_prefix.into(), + } } } impl From for [u8; 8] { fn from(id: AccountIdPrefix) -> Self { - let mut result = [0_u8; 8]; - result[..8].copy_from_slice(&id.first_felt.as_int().to_le_bytes()); - result + match id { + AccountIdPrefix::V0(id_prefix) => id_prefix.into(), + } } } impl From for u64 { fn from(id: AccountIdPrefix) -> Self { - id.first_felt.as_int() + match id { + AccountIdPrefix::V0(id_prefix) => id_prefix.into(), + } } } @@ -144,46 +193,52 @@ impl From for u64 { // ================================================================================================ impl TryFrom<[u8; 8]> for AccountIdPrefix { - type Error = AccountError; + type Error = AccountIdError; - /// Tries to convert a byte array in little-endian order to an [`AccountIdPrefix`]. + /// Tries to convert a byte array in big-endian order to an [`AccountIdPrefix`]. /// /// # Errors /// - /// Returns an error if any of the ID constraints of the first felt are not met. See the - /// [`AccountId`](crate::accounts::AccountId) type documentation for details. + /// Returns an error if any of the ID constraints are not met. See the [constraints + /// documentation](super::AccountId#constraints) for details. fn try_from(value: [u8; 8]) -> Result { - let element = - Felt::try_from(&value[..8]).map_err(AccountError::AccountIdInvalidFieldElement)?; - Self::new(element) + // The least significant byte of the ID prefix contains the metadata. + let metadata_byte = value[7]; + // We only have one supported version for now, so we use the extractor from that version. + // If we add more versions in the future, we may need to generalize this. + let version = v0::extract_version(metadata_byte as u64)?; + + match version { + AccountIdVersion::Version0 => AccountIdPrefixV0::try_from(value).map(Self::V0), + } } } impl TryFrom for AccountIdPrefix { - type Error = AccountError; + type Error = AccountIdError; /// Tries to convert a `u64` into an [`AccountIdPrefix`]. /// /// # Errors /// - /// Returns an error if any of the ID constraints of the first felt are not met. See the - /// [`AccountId`](crate::accounts::AccountId) type documentation for details. + /// Returns an error if any of the ID constraints are not met. See the [constraints + /// documentation](super::AccountId#constraints) for details. fn try_from(value: u64) -> Result { let element = Felt::try_from(value.to_le_bytes().as_slice()) - .map_err(AccountError::AccountIdInvalidFieldElement)?; + .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?; Self::new(element) } } impl TryFrom for AccountIdPrefix { - type Error = AccountError; + type Error = AccountIdError; - /// Returns an [`AccountIdPrefix`] instantiated with the provided field . + /// Returns an [`AccountIdPrefix`] instantiated with the provided field element. /// /// # Errors /// - /// Returns an error if any of the ID constraints of the first felt are not met. See the - /// [`AccountId`](crate::accounts::AccountId) type documentation for details. + /// Returns an error if any of the ID constraints are not met. See the [constraints + /// documentation](super::AccountId#constraints) for details. fn try_from(element: Felt) -> Result { Self::new(element) } @@ -200,7 +255,7 @@ impl PartialOrd for AccountIdPrefix { impl Ord for AccountIdPrefix { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.first_felt.as_int().cmp(&other.first_felt.as_int()) + u64::from(*self).cmp(&u64::from(*other)) } } @@ -215,12 +270,15 @@ impl fmt::Display for AccountIdPrefix { impl Serializable for AccountIdPrefix { fn write_into(&self, target: &mut W) { - let bytes: [u8; 8] = (*self).into(); - bytes.write_into(target); + match self { + AccountIdPrefix::V0(id_prefix) => id_prefix.write_into(target), + } } fn get_size_hint(&self) -> usize { - Self::SERIALIZED_SIZE + match self { + AccountIdPrefix::V0(id_prefix) => id_prefix.get_size_hint(), + } } } @@ -228,14 +286,17 @@ impl Deserializable for AccountIdPrefix { fn read_from(source: &mut R) -> Result { <[u8; 8]>::read_from(source)? .try_into() - .map_err(|err: AccountError| DeserializationError::InvalidValue(err.to_string())) + .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string())) } } +// TESTS +// ================================================================================================ + #[cfg(test)] mod tests { use super::*; - use crate::accounts::AccountId; + use crate::accounts::AccountIdV0; #[test] fn account_id_prefix_construction() { @@ -251,11 +312,11 @@ mod tests { AccountType::RegularAccountUpdatableCode, ] { for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] { - let id = AccountId::dummy(input, account_type, storage_mode); + let id = AccountIdV0::dummy(input, account_type, storage_mode); let prefix = id.prefix(); assert_eq!(prefix.account_type(), account_type); assert_eq!(prefix.storage_mode(), storage_mode); - assert_eq!(prefix.version(), AccountIdVersion::VERSION_0); + assert_eq!(prefix.version(), AccountIdVersion::Version0); // Do a serialization roundtrip to ensure validity. let serialized_prefix = prefix.to_bytes(); diff --git a/objects/src/accounts/account_id/id_version.rs b/objects/src/accounts/account_id/id_version.rs new file mode 100644 index 000000000..6790ec1ce --- /dev/null +++ b/objects/src/accounts/account_id/id_version.rs @@ -0,0 +1,40 @@ +use crate::errors::AccountIdError; + +// ACCOUNT ID VERSION +// ================================================================================================ + +const VERSION_0_NUMBER: u8 = 0; + +/// The version of an [`AccountId`](crate::accounts::AccountId). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum AccountIdVersion { + Version0 = VERSION_0_NUMBER, +} + +impl AccountIdVersion { + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the version number. + pub const fn as_u8(&self) -> u8 { + *self as u8 + } +} + +impl TryFrom for AccountIdVersion { + type Error = AccountIdError; + + fn try_from(value: u8) -> Result { + match value { + VERSION_0_NUMBER => Ok(AccountIdVersion::Version0), + other_version => Err(AccountIdError::UnknownAccountIdVersion(other_version)), + } + } +} + +impl From for u8 { + fn from(value: AccountIdVersion) -> Self { + value.as_u8() + } +} diff --git a/objects/src/accounts/account_id/mod.rs b/objects/src/accounts/account_id/mod.rs new file mode 100644 index 000000000..841321f0c --- /dev/null +++ b/objects/src/accounts/account_id/mod.rs @@ -0,0 +1,501 @@ +mod id_anchor; +pub use id_anchor::AccountIdAnchor; + +pub(crate) mod v0; +pub use v0::{AccountIdPrefixV0, AccountIdV0}; + +mod id_prefix; +pub use id_prefix::AccountIdPrefix; + +mod seed; + +mod account_type; +pub use account_type::AccountType; +mod storage_mode; +pub use storage_mode::AccountStorageMode; +mod id_version; +use alloc::string::{String, ToString}; +use core::fmt; + +pub use id_version::AccountIdVersion; +use miden_crypto::{merkle::LeafIndex, utils::hex_to_bytes}; +use vm_core::{ + utils::{ByteReader, Deserializable, Serializable}, + Felt, Word, +}; +use vm_processor::{DeserializationError, Digest}; + +use crate::{errors::AccountIdError, AccountError, ACCOUNT_TREE_DEPTH}; + +/// The identifier of an [`Account`](crate::accounts::Account). +/// +/// This enum is a wrapper around concrete versions of IDs. The following documents version 0. +/// +/// # Layout +/// +/// An `AccountId` consists of two field elements and is layed out as follows: +/// +/// ```text +/// 1st felt: [random (56 bits) | storage mode (2 bits) | type (2 bits) | version (4 bits)] +/// 2nd felt: [anchor_epoch (16 bits) | random (40 bits) | 8 zero bits] +/// ``` +/// +/// # Generation +/// +/// An `AccountId` is a commitment to a user-generated seed, the code and storage of an account and +/// to a certain hash of an epoch block of the blockchain. An id is generated by picking an epoch +/// block as an anchor - which is why it is also referred to as the anchor block - and creating the +/// account's initial storage and code. Then a random seed is picked and the hash of `(SEED, +/// CODE_COMMITMENT, STORAGE_COMMITMENT, ANCHOR_BLOCK_HASH)` is computed. If the hash's first +/// element has the desired storage mode, account type and version, the computation part of the ID +/// generation is done. If not, another random seed is picked and the process is repeated. The first +/// felt of the ID is then the first element of the hash. +/// +/// The suffix of the ID is the second element of the hash. Its upper 16 bits are overwritten +/// with the epoch in which the ID is anchored and the lower 8 bits are zeroed. Thus, the prefix +/// of the ID must derive exactly from the hash, while only part of the suffix is derived from +/// the hash. +/// +/// # Constraints +/// +/// Constructors will return an error if: +/// +/// - The prefix contains account ID metadata (storage mode, type or version) that does not match +/// any of the known values. +/// - The anchor epoch in the suffix is equal to [`u16::MAX`]. +/// - The lower 8 bits of the suffix are not zero, although [`AccountId::new`] ensures this is the +/// case rather than return an error. +/// +/// # Design Rationale +/// +/// The rationale behind the above layout is as follows. +/// +/// - The prefix is the output of a hash function so it will be a valid field element without +/// requiring additional constraints. +/// - The version is placed at a static offset such that future ID versions which may change the +/// number of type or storage mode bits will not cause the version to be at a different offset. +/// This is important so that a parser can always reliably read the version and then parse the +/// remainder of the ID depending on the version. Having only 4 bits for the version is a trade +/// off between future proofing to be able to introduce more versions and the version requiring +/// Proof of Work as part of the ID generation. +/// - The version, type and storage mode are part of the prefix which is included in the +/// representation of a non-fungible asset. The prefix alone is enough to determine all of these +/// properties about the ID. +/// - The anchor epoch is not important beyond the creation process, so placing it in the second +/// felt is fine. Moreover, all properties of the prefix must be derived from the seed, so +/// they add to the proof of work difficulty. Adding 16 bits of PoW for the epoch would be +/// significant. +/// - The anchor epoch is placed at the most significant end of the suffix. Its value must be less +/// than [`u16::MAX`] so that at least one of the upper 16 bits is always zero. This ensures that +/// the entire suffix is valid even if the remaining bits of the felt are one. +/// - The lower 8 bits of the suffix may be overwritten when the ID is encoded in other layouts such +/// as the [`NoteMetadata`](crate::notes::NoteMetadata). In such cases, it can happen that all +/// bits of the encoded suffix would be one, so having the epoch constraint is important. +/// - The ID is dependent on the hash of an epoch block. This is a block whose number is a multiple +/// of 2^[`BlockHeader::EPOCH_LENGTH_EXPONENT`][epoch_len_exp], e.g. `0`, `65536`, `131072`, ... +/// These are the first blocks of epoch 0, 1, 2, ... We call this dependence _anchoring_ because +/// the ID is anchored to that epoch block's hash. Anchoring makes it practically impossible for +/// an attacker to construct a rainbow table of account IDs whose epoch is X, if the block for +/// epoch X has not been constructed yet because its hash is then unknown. Therefore, picking a +/// recent anchor block when generating a new ID makes it extremely unlikely that an attacker can +/// highjack this ID because the hash of that block has only been known for a short period of +/// time. +/// - An ID highjack refers to an attack where a user generates an ID and lets someone else send +/// assets to it. At this point the user has not registered the ID on-chain yet, likely +/// because they need the funds in the asset to pay for their first transaction where the +/// account would be registered. Until the ID is registered on chain, an attacker with a +/// rainbow table who happens to have a seed, code and storage commitment combination that +/// hashes to the user's ID can claim the assets sent to the user's ID. Adding the anchor +/// block hash to ID generation process makes this attack practically impossible. +/// +/// [epoch_len_exp]: crate::block::BlockHeader::EPOCH_LENGTH_EXPONENT +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AccountId { + V0(AccountIdV0), +} + +impl AccountId { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The serialized size of an [`AccountId`] in bytes. + pub const SERIALIZED_SIZE: usize = 15; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// Creates an [`AccountId`] by hashing the given `seed`, `code_commitment`, + /// `storage_commitment` and [`AccountIdAnchor::block_hash`] from the `anchor` and using the + /// resulting first and second element of the hash as the prefix and suffix felts of the ID. + /// + /// The [`AccountIdAnchor::epoch`] from the `anchor` overwrites part of the suffix. + /// + /// Note that the `anchor` must correspond to a valid block in the chain for the ID to be deemed + /// valid during creation. + /// + /// See the documentation of the [`AccountId`] for more details on the generation. + /// + /// # Errors + /// + /// Returns an error if any of the ID constraints are not met. See the [constraints + /// documentation](AccountId#constraints) for details. + pub fn new( + seed: Word, + anchor: AccountIdAnchor, + version: AccountIdVersion, + code_commitment: Digest, + storage_commitment: Digest, + ) -> Result { + match version { + AccountIdVersion::Version0 => { + AccountIdV0::new(seed, anchor, code_commitment, storage_commitment).map(Self::V0) + }, + } + } + + /// Creates an [`AccountId`] from the given felts where the felt at index 0 is the prefix + /// and the felt at index 2 is the suffix. + /// + /// # Warning + /// + /// Validity of the ID must be ensured by the caller. An invalid ID may lead to panics. + /// + /// # Panics + /// + /// Panics if the prefix does not contain a known account ID version. + /// + /// If debug_assertions are enabled (e.g. in debug mode), this function panics if any of the ID + /// constraints are not met. See the [constraints documentation](AccountId#constraints) for + /// details. + pub fn new_unchecked(elements: [Felt; 2]) -> Self { + // The prefix contains the metadata. + // If we add more versions in the future, we may need to generalize this. + match v0::extract_version(elements[0].as_int()) + .expect("prefix should contain a valid account ID version") + { + AccountIdVersion::Version0 => Self::V0(AccountIdV0::new_unchecked(elements)), + } + } + + /// Constructs an [`AccountId`] for testing purposes with the given account type and storage + /// mode. + /// + /// This function does the following: + /// - Split the given bytes into a `prefix = bytes[0..8]` and `suffix = bytes[8..]` part to be + /// used for the prefix and suffix felts, respectively. + /// - The least significant byte of the prefix is set to the version 0, and the given type and + /// storage mode. + /// - The 32nd most significant bit in the prefix is cleared to ensure it is a valid felt. The + /// 32nd is chosen as it is the lowest bit that we can clear and still ensure felt validity. + /// This leaves the upper 31 bits to be set by the input `bytes` which makes it simpler to + /// create test values which more often need specific values for the most significant end of + /// the ID. + /// - In the suffix the anchor epoch is set to 0 and the lower 8 bits are cleared. + #[cfg(any(feature = "testing", test))] + pub fn dummy( + bytes: [u8; 15], + version: AccountIdVersion, + account_type: AccountType, + storage_mode: AccountStorageMode, + ) -> AccountId { + match version { + AccountIdVersion::Version0 => { + Self::V0(AccountIdV0::dummy(bytes, account_type, storage_mode)) + }, + } + } + + /// Grinds an account seed until its hash matches the given `account_type`, `storage_mode` and + /// `version` and returns it as a [`Word`]. The input to the hash function next to the seed are + /// the `code_commitment`, `storage_commitment` and `anchor_block_hash`. + /// + /// The grinding process is started from the given `init_seed` which should be a random seed + /// generated from a cryptographically secure source. + pub fn compute_account_seed( + init_seed: [u8; 32], + account_type: AccountType, + storage_mode: AccountStorageMode, + version: AccountIdVersion, + code_commitment: Digest, + storage_commitment: Digest, + anchor_block_hash: Digest, + ) -> Result { + match version { + AccountIdVersion::Version0 => AccountIdV0::compute_account_seed( + init_seed, + account_type, + storage_mode, + version, + code_commitment, + storage_commitment, + anchor_block_hash, + ), + } + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns the type of this account ID. + pub const fn account_type(&self) -> AccountType { + match self { + AccountId::V0(account_id) => account_id.account_type(), + } + } + + /// Returns `true` if an account with this ID is a faucet which can issue assets. + pub fn is_faucet(&self) -> bool { + self.account_type().is_faucet() + } + + /// Returns `true` if an account with this ID is a regular account. + pub fn is_regular_account(&self) -> bool { + self.account_type().is_regular_account() + } + + /// Returns the storage mode of this account ID. + pub fn storage_mode(&self) -> AccountStorageMode { + match self { + AccountId::V0(account_id) => account_id.storage_mode(), + } + } + + /// Returns `true` if an account with this ID is a public account. + pub fn is_public(&self) -> bool { + self.storage_mode() == AccountStorageMode::Public + } + + /// Returns the version of this account ID. + pub fn version(&self) -> AccountIdVersion { + match self { + AccountId::V0(_) => AccountIdVersion::Version0, + } + } + + /// Returns the anchor epoch of this account ID. + /// + /// This is the epoch to which this ID is anchored. The hash of this epoch block is used in the + /// generation of the ID. + pub fn anchor_epoch(&self) -> u16 { + match self { + AccountId::V0(account_id) => account_id.anchor_epoch(), + } + } + + /// Creates an [`AccountId`] from a hex string. Assumes the string starts with "0x" and + /// that the hexadecimal characters are big-endian encoded. + pub fn from_hex(hex_str: &str) -> Result { + hex_to_bytes(hex_str) + .map_err(AccountIdError::AccountIdHexParseError) + .and_then(AccountId::try_from) + } + + /// Returns a big-endian, hex-encoded string of length 32, including the `0x` prefix. This means + /// it encodes 15 bytes. + pub fn to_hex(self) -> String { + match self { + AccountId::V0(account_id) => account_id.to_hex(), + } + } + + /// Returns the [`AccountIdPrefix`] of this ID. + /// + /// The prefix of an account ID is guaranteed to be unique. + pub fn prefix(&self) -> AccountIdPrefix { + match self { + AccountId::V0(account_id) => AccountIdPrefix::V0(account_id.prefix()), + } + } + + /// Returns the suffix of this ID as a [`Felt`]. + pub const fn suffix(&self) -> Felt { + match self { + AccountId::V0(account_id) => account_id.suffix(), + } + } +} + +// CONVERSIONS FROM ACCOUNT ID +// ================================================================================================ + +impl From for [Felt; 2] { + fn from(id: AccountId) -> Self { + match id { + AccountId::V0(account_id) => account_id.into(), + } + } +} + +impl From for [u8; 15] { + fn from(id: AccountId) -> Self { + match id { + AccountId::V0(account_id) => account_id.into(), + } + } +} + +impl From for u128 { + fn from(id: AccountId) -> Self { + match id { + AccountId::V0(account_id) => account_id.into(), + } + } +} + +/// Account IDs are used as indexes in the account database, which is a tree of depth 64. +impl From for LeafIndex { + fn from(id: AccountId) -> Self { + match id { + AccountId::V0(account_id) => account_id.into(), + } + } +} + +// CONVERSIONS TO ACCOUNT ID +// ================================================================================================ + +impl From for AccountId { + fn from(id: AccountIdV0) -> Self { + Self::V0(id) + } +} + +impl TryFrom<[Felt; 2]> for AccountId { + type Error = AccountIdError; + + /// Returns an [`AccountId`] instantiated with the provided field elements where `elements[0]` + /// is taken as the prefix and `elements[1]` is taken as the suffix. + /// + /// # Errors + /// + /// Returns an error if any of the ID constraints are not met. See the [constraints + /// documentation](AccountId#constraints) for details. + fn try_from(elements: [Felt; 2]) -> Result { + // The prefix contains the metadata. + // If we add more versions in the future, we may need to generalize this. + match v0::extract_version(elements[0].as_int())? { + AccountIdVersion::Version0 => AccountIdV0::try_from(elements).map(Self::V0), + } + } +} + +impl TryFrom<[u8; 15]> for AccountId { + type Error = AccountIdError; + + /// Tries to convert a byte array in big-endian order to an [`AccountId`]. + /// + /// # Errors + /// + /// Returns an error if any of the ID constraints are not met. See the [constraints + /// documentation](AccountId#constraints) for details. + fn try_from(bytes: [u8; 15]) -> Result { + // The least significant byte of the ID prefix contains the metadata. + let metadata_byte = bytes[7]; + // We only have one supported version for now, so we use the extractor from that version. + // If we add more versions in the future, we may need to generalize this. + let version = v0::extract_version(metadata_byte as u64)?; + + match version { + AccountIdVersion::Version0 => AccountIdV0::try_from(bytes).map(Self::V0), + } + } +} + +impl TryFrom for AccountId { + type Error = AccountIdError; + + /// Tries to convert a u128 into an [`AccountId`]. + /// + /// # Errors + /// + /// Returns an error if any of the ID constraints are not met. See the [constraints + /// documentation](AccountId#constraints) for details. + fn try_from(int: u128) -> Result { + let mut bytes: [u8; 15] = [0; 15]; + bytes.copy_from_slice(&int.to_be_bytes()[0..15]); + + Self::try_from(bytes) + } +} + +// COMMON TRAIT IMPLS +// ================================================================================================ + +impl PartialOrd for AccountId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AccountId { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + u128::from(*self).cmp(&u128::from(*other)) + } +} + +impl fmt::Display for AccountId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for AccountId { + fn write_into(&self, target: &mut W) { + match self { + AccountId::V0(account_id) => { + account_id.write_into(target); + }, + } + } + + fn get_size_hint(&self) -> usize { + match self { + AccountId::V0(account_id) => account_id.get_size_hint(), + } + } +} + +impl Deserializable for AccountId { + fn read_from(source: &mut R) -> Result { + <[u8; 15]>::read_from(source)? + .try_into() + .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string())) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::testing::account_id::{ + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, + ACCOUNT_ID_OFF_CHAIN_SENDER, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, + }; + + #[test] + fn test_account_id_wrapper_conversion_roundtrip() { + for (idx, account_id) in [ + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, + ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, + ACCOUNT_ID_OFF_CHAIN_SENDER, + ] + .into_iter() + .enumerate() + { + let wrapper = AccountId::try_from(account_id).unwrap(); + assert_eq!( + wrapper, + AccountId::read_from_bytes(&wrapper.to_bytes()).unwrap(), + "failed in {idx}" + ); + } + } +} diff --git a/objects/src/accounts/seed.rs b/objects/src/accounts/account_id/seed.rs similarity index 94% rename from objects/src/accounts/seed.rs rename to objects/src/accounts/account_id/seed.rs index 095b97907..ca5ccd4cd 100644 --- a/objects/src/accounts/seed.rs +++ b/objects/src/accounts/account_id/seed.rs @@ -8,11 +8,19 @@ use std::{ thread::{self, spawn}, }; -use super::{ - account_id::compute_digest, AccountError, AccountStorageMode, AccountType, Digest, Felt, Word, +use vm_core::{Felt, Word}; +use vm_processor::Digest; + +use crate::{ + accounts::{ + account_id::{ + v0::{compute_digest, validate_prefix}, + AccountIdVersion, + }, + AccountStorageMode, AccountType, + }, + AccountError, }; -use crate::accounts::account_id::{validate_first_felt, AccountIdVersion}; - // SEED GENERATORS // -------------------------------------------------------------------------------------------- @@ -108,9 +116,9 @@ fn compute_account_seed_inner( return; } - let first_felt = current_digest.as_elements()[0]; + let prefix = current_digest.as_elements()[0]; if let Ok((computed_account_type, computed_storage_mode, computed_version)) = - validate_first_felt(first_felt) + validate_prefix(prefix) { if computed_account_type == account_type && computed_storage_mode == storage_mode @@ -183,9 +191,9 @@ pub fn compute_account_seed_single( log.iteration(current_digest, current_seed); // check if the seed satisfies the specified account type - let first_felt = current_digest.as_elements()[0]; + let prefix = current_digest.as_elements()[0]; if let Ok((computed_account_type, computed_storage_mode, computed_version)) = - validate_first_felt(first_felt) + validate_prefix(prefix) { if computed_account_type == account_type && computed_storage_mode == storage_mode @@ -210,11 +218,10 @@ mod log { use assembly::utils::to_hex; use miden_crypto::FieldElement; + use vm_core::Word; + use vm_processor::Digest; - use super::{ - super::{Digest, Word}, - AccountType, - }; + use super::AccountType; use crate::accounts::AccountStorageMode; /// Keeps track of the best digest found so far and count how many iterations have been done. diff --git a/objects/src/accounts/account_id/storage_mode.rs b/objects/src/accounts/account_id/storage_mode.rs new file mode 100644 index 000000000..199fd585e --- /dev/null +++ b/objects/src/accounts/account_id/storage_mode.rs @@ -0,0 +1,66 @@ +use alloc::string::String; +use core::{fmt, str::FromStr}; + +use crate::errors::AccountIdError; + +// ACCOUNT STORAGE MODE +// ================================================================================================ + +pub(super) const PUBLIC: u8 = 0b00; +pub(super) const PRIVATE: u8 = 0b10; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum AccountStorageMode { + Public = PUBLIC, + Private = PRIVATE, +} + +impl fmt::Display for AccountStorageMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AccountStorageMode::Public => write!(f, "public"), + AccountStorageMode::Private => write!(f, "private"), + } + } +} + +impl TryFrom<&str> for AccountStorageMode { + type Error = AccountIdError; + + fn try_from(value: &str) -> Result { + match value.to_lowercase().as_str() { + "public" => Ok(AccountStorageMode::Public), + "private" => Ok(AccountStorageMode::Private), + _ => Err(AccountIdError::UnknownAccountStorageMode(value.into())), + } + } +} + +impl TryFrom for AccountStorageMode { + type Error = AccountIdError; + + fn try_from(value: String) -> Result { + AccountStorageMode::from_str(&value) + } +} + +impl FromStr for AccountStorageMode { + type Err = AccountIdError; + + fn from_str(input: &str) -> Result { + AccountStorageMode::try_from(input) + } +} + +#[cfg(any(feature = "testing", test))] +impl rand::distributions::Distribution for rand::distributions::Standard { + /// Samples a uniformly random [`AccountStorageMode`] from the given `rng`. + fn sample(&self, rng: &mut R) -> AccountStorageMode { + match rng.gen_range(0..2) { + 0 => AccountStorageMode::Public, + 1 => AccountStorageMode::Private, + _ => unreachable!("gen_range should not produce higher values"), + } + } +} diff --git a/objects/src/accounts/account_id/v0/mod.rs b/objects/src/accounts/account_id/v0/mod.rs new file mode 100644 index 000000000..483079c09 --- /dev/null +++ b/objects/src/accounts/account_id/v0/mod.rs @@ -0,0 +1,646 @@ +mod prefix; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; +use core::fmt; + +use miden_crypto::{merkle::LeafIndex, utils::hex_to_bytes}; +pub use prefix::AccountIdPrefixV0; +use vm_core::{ + utils::{ByteReader, Deserializable, Serializable}, + Felt, Word, +}; +use vm_processor::{DeserializationError, Digest}; + +use crate::{ + accounts::{ + account_id::{ + account_type::{ + FUNGIBLE_FAUCET, NON_FUNGIBLE_FAUCET, REGULAR_ACCOUNT_IMMUTABLE_CODE, + REGULAR_ACCOUNT_UPDATABLE_CODE, + }, + storage_mode::{PRIVATE, PUBLIC}, + }, + AccountIdAnchor, AccountIdVersion, AccountStorageMode, AccountType, + }, + errors::AccountIdError, + AccountError, Hasher, ACCOUNT_TREE_DEPTH, +}; + +// ACCOUNT ID VERSION 0 +// ================================================================================================ + +/// Version 0 of the [`Account`](crate::accounts::Account) identifier. +/// +/// See the [`AccountId`](super::AccountId) type's documentation for details. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct AccountIdV0 { + prefix: Felt, + suffix: Felt, +} + +impl AccountIdV0 { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The serialized size of an [`AccountIdV0`] in bytes. + const SERIALIZED_SIZE: usize = 15; + + /// The lower two bits of the second least significant nibble encode the account type. + pub(crate) const TYPE_MASK: u8 = 0b11 << Self::TYPE_SHIFT; + pub(crate) const TYPE_SHIFT: u64 = 4; + + /// The least significant nibble determines the account version. + const VERSION_MASK: u64 = 0b1111; + + /// The two most significant bytes of the suffix encdode the anchor epoch. + const ANCHOR_EPOCH_MASK: u64 = 0xffff << Self::ANCHOR_EPOCH_SHIFT; + const ANCHOR_EPOCH_SHIFT: u64 = 48; + + /// The higher two bits of the second least significant nibble encode the account storage + /// mode. + pub(crate) const STORAGE_MODE_MASK: u8 = 0b11 << Self::STORAGE_MODE_SHIFT; + pub(crate) const STORAGE_MODE_SHIFT: u64 = 6; + + /// The bit at index 5 of the prefix encodes whether the account is a faucet. + pub(crate) const IS_FAUCET_MASK: u64 = 0b10 << Self::TYPE_SHIFT; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// See [`AccountId::new`](super::AccountId::new) for details. + pub fn new( + seed: Word, + anchor: AccountIdAnchor, + code_commitment: Digest, + storage_commitment: Digest, + ) -> Result { + let seed_digest = + compute_digest(seed, code_commitment, storage_commitment, anchor.block_hash()); + + let mut felts: [Felt; 2] = seed_digest.as_elements()[0..2] + .try_into() + .expect("we should have sliced off 2 elements"); + + felts[1] = shape_suffix(felts[1], anchor.epoch())?; + + // This will validate that the anchor_epoch we have just written is not u16::MAX. + account_id_from_felts(felts) + } + + /// See [`AccountId::new_unchecked`](super::AccountId::new_unchecked) for details. + pub fn new_unchecked(elements: [Felt; 2]) -> Self { + let prefix = elements[0]; + let suffix = elements[1]; + + // Panic on invalid felts in debug mode. + if cfg!(debug_assertions) { + validate_prefix(prefix).expect("AccountId::new_unchecked called with invalid prefix"); + validate_suffix(suffix).expect("AccountId::new_unchecked called with invalid suffix"); + } + + Self { prefix, suffix } + } + + /// See [`AccountId::dummy`](super::AccountId::dummy) for details. + #[cfg(any(feature = "testing", test))] + pub fn dummy( + mut bytes: [u8; 15], + account_type: AccountType, + storage_mode: AccountStorageMode, + ) -> AccountIdV0 { + let version = AccountIdVersion::Version0 as u8; + let low_nibble = (storage_mode as u8) << Self::STORAGE_MODE_SHIFT + | (account_type as u8) << Self::TYPE_SHIFT + | version; + + // Set least significant byte. + bytes[7] = low_nibble; + + // Clear the 32nd most significant bit. + bytes[3] &= 0b1111_1110; + + let prefix_bytes = + bytes[0..8].try_into().expect("we should have sliced off exactly 8 bytes"); + let prefix = Felt::try_from(u64::from_be_bytes(prefix_bytes)) + .expect("should be a valid felt due to the most significant bit being zero"); + + let mut suffix_bytes = [0; 8]; + // Overwrite first 7 bytes, leaving the 8th byte 0 (which will be cleared by + // shape_suffix anyway). + suffix_bytes[..7].copy_from_slice(&bytes[8..]); + // If the value is too large modular reduction is performed, which is fine here. + let mut suffix = Felt::new(u64::from_be_bytes(suffix_bytes)); + + suffix = shape_suffix(suffix, 0).expect("anchor epoch is not u16::MAX"); + + let account_id = account_id_from_felts([prefix, suffix]) + .expect("we should have shaped the felts to produce a valid id"); + + debug_assert_eq!(account_id.account_type(), account_type); + debug_assert_eq!(account_id.storage_mode(), storage_mode); + + account_id + } + + /// See [`AccountId::compute_account_seed`](super::AccountId::compute_account_seed) for details. + pub fn compute_account_seed( + init_seed: [u8; 32], + account_type: AccountType, + storage_mode: AccountStorageMode, + version: AccountIdVersion, + code_commitment: Digest, + storage_commitment: Digest, + anchor_block_hash: Digest, + ) -> Result { + crate::accounts::account_id::seed::compute_account_seed( + init_seed, + account_type, + storage_mode, + version, + code_commitment, + storage_commitment, + anchor_block_hash, + ) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// See [`AccountId::account_type`](super::AccountId::account_type) for details. + pub const fn account_type(&self) -> AccountType { + extract_type(self.prefix.as_int()) + } + + /// See [`AccountId::is_faucet`](super::AccountId::is_faucet) for details. + pub fn is_faucet(&self) -> bool { + self.account_type().is_faucet() + } + + /// See [`AccountId::is_regular_account`](super::AccountId::is_regular_account) for details. + pub fn is_regular_account(&self) -> bool { + self.account_type().is_regular_account() + } + + /// See [`AccountId::storage_mode`](super::AccountId::storage_mode) for details. + pub fn storage_mode(&self) -> AccountStorageMode { + extract_storage_mode(self.prefix().as_u64()) + .expect("account ID should have been constructed with a valid storage mode") + } + + /// See [`AccountId::is_public`](super::AccountId::is_public) for details. + pub fn is_public(&self) -> bool { + self.storage_mode() == AccountStorageMode::Public + } + + /// See [`AccountId::version`](super::AccountId::version) for details. + pub fn version(&self) -> AccountIdVersion { + extract_version(self.prefix().as_u64()) + .expect("account ID should have been constructed with a valid version") + } + + /// See [`AccountId::anchor_epoch`](super::AccountId::anchor_epoch) for details. + pub fn anchor_epoch(&self) -> u16 { + extract_anchor_epoch(self.suffix().as_int()) + } + + /// See [`AccountId::from_hex`](super::AccountId::from_hex) for details. + pub fn from_hex(hex_str: &str) -> Result { + hex_to_bytes(hex_str) + .map_err(AccountIdError::AccountIdHexParseError) + .and_then(AccountIdV0::try_from) + } + + /// See [`AccountId::to_hex`](super::AccountId::to_hex) for details. + pub fn to_hex(self) -> String { + // We need to pad the suffix with 16 zeroes so it produces a correctly padded 8 byte + // big-endian hex string. Only then can we cut off the last zero byte by truncating. We + // cannot use `:014x` padding. + let mut hex_string = + format!("0x{:016x}{:016x}", self.prefix().as_u64(), self.suffix().as_int()); + hex_string.truncate(32); + hex_string + } + + /// Returns the [`AccountIdPrefixV0`] of this account ID. + /// + /// See also [`AccountId::prefix`](super::AccountId::prefix) for details. + pub fn prefix(&self) -> AccountIdPrefixV0 { + // SAFETY: We only construct account IDs with valid prefixes, so we don't have to validate + // it again. + AccountIdPrefixV0::new_unchecked(self.prefix) + } + + /// See [`AccountId::suffix`](super::AccountId::suffix) for details. + pub const fn suffix(&self) -> Felt { + self.suffix + } +} + +// CONVERSIONS FROM ACCOUNT ID +// ================================================================================================ + +impl From for [Felt; 2] { + fn from(id: AccountIdV0) -> Self { + [id.prefix, id.suffix] + } +} + +impl From for [u8; 15] { + fn from(id: AccountIdV0) -> Self { + let mut result = [0_u8; 15]; + result[..8].copy_from_slice(&id.prefix().as_u64().to_be_bytes()); + // The last byte of the suffix is always zero so we skip it here. + result[8..].copy_from_slice(&id.suffix().as_int().to_be_bytes()[..7]); + result + } +} + +impl From for u128 { + fn from(id: AccountIdV0) -> Self { + let mut le_bytes = [0_u8; 16]; + le_bytes[..8].copy_from_slice(&id.suffix().as_int().to_le_bytes()); + le_bytes[8..].copy_from_slice(&id.prefix().as_u64().to_le_bytes()); + u128::from_le_bytes(le_bytes) + } +} + +/// Account IDs are used as indexes in the account database, which is a tree of depth 64. +impl From for LeafIndex { + fn from(id: AccountIdV0) -> Self { + LeafIndex::new_max_depth(id.prefix().as_u64()) + } +} + +// CONVERSIONS TO ACCOUNT ID +// ================================================================================================ + +impl TryFrom<[Felt; 2]> for AccountIdV0 { + type Error = AccountIdError; + + /// See [`TryFrom<[Felt; 2]> for + /// AccountId`](super::AccountId#impl-TryFrom<%5BFelt;+2%5D>-for-AccountId) for details. + fn try_from(elements: [Felt; 2]) -> Result { + account_id_from_felts(elements) + } +} + +impl TryFrom<[u8; 15]> for AccountIdV0 { + type Error = AccountIdError; + + /// See [`TryFrom<[u8; 15]> for + /// AccountId`](super::AccountId#impl-TryFrom<%5Bu8;+15%5D>-for-AccountId) for details. + fn try_from(mut bytes: [u8; 15]) -> Result { + // Felt::try_from expects little-endian order, so reverse the individual felt slices. + // This prefix slice has 8 bytes. + bytes[..8].reverse(); + // The suffix slice has 7 bytes, since the 8th byte will always be zero. + bytes[8..15].reverse(); + + let prefix_slice = &bytes[..8]; + let suffix_slice = &bytes[8..15]; + + // The byte order is little-endian here, so we prepend a 0 to set the least significant + // byte. + let mut suffix_bytes = [0; 8]; + suffix_bytes[1..8].copy_from_slice(suffix_slice); + + let prefix = Felt::try_from(prefix_slice) + .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?; + + let suffix = Felt::try_from(suffix_bytes.as_slice()) + .map_err(AccountIdError::AccountIdInvalidSuffixFieldElement)?; + + Self::try_from([prefix, suffix]) + } +} + +impl TryFrom for AccountIdV0 { + type Error = AccountIdError; + + /// See [`TryFrom for AccountId`](super::AccountId#impl-TryFrom-for-AccountId) for + /// details. + fn try_from(int: u128) -> Result { + let mut bytes: [u8; 15] = [0; 15]; + bytes.copy_from_slice(&int.to_be_bytes()[0..15]); + + Self::try_from(bytes) + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for AccountIdV0 { + fn write_into(&self, target: &mut W) { + let bytes: [u8; 15] = (*self).into(); + bytes.write_into(target); + } + + fn get_size_hint(&self) -> usize { + Self::SERIALIZED_SIZE + } +} + +impl Deserializable for AccountIdV0 { + fn read_from(source: &mut R) -> Result { + <[u8; 15]>::read_from(source)? + .try_into() + .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string())) + } +} + +// HELPER FUNCTIONS +// ================================================================================================ + +/// Returns an [AccountId] instantiated with the provided field elements. +/// +/// # Errors +/// +/// Returns an error if any of the ID constraints are not met. See the [constraints +/// documentation](AccountId#constraints) for details. +fn account_id_from_felts(elements: [Felt; 2]) -> Result { + validate_prefix(elements[0])?; + validate_suffix(elements[1])?; + + Ok(AccountIdV0 { prefix: elements[0], suffix: elements[1] }) +} + +/// Checks that the prefix: +/// - has known values for metadata (storage mode, type and version). +pub(crate) fn validate_prefix( + prefix: Felt, +) -> Result<(AccountType, AccountStorageMode, AccountIdVersion), AccountIdError> { + let prefix = prefix.as_int(); + + // Validate storage bits. + let storage_mode = extract_storage_mode(prefix)?; + + // Validate version bits. + let version = extract_version(prefix)?; + + let account_type = extract_type(prefix); + + Ok((account_type, storage_mode, version)) +} + +/// Checks that the suffix: +/// - has an anchor_epoch that is not [`u16::MAX`]. +/// - has its lower 8 bits set to zero. +const fn validate_suffix(suffix: Felt) -> Result<(), AccountIdError> { + let suffix = suffix.as_int(); + + if extract_anchor_epoch(suffix) == u16::MAX { + return Err(AccountIdError::AnchorEpochMustNotBeU16Max); + } + + // Validate lower 8 bits of second felt are zero. + if suffix & 0xff != 0 { + return Err(AccountIdError::AccountIdSuffixLeastSignificantByteMustBeZero); + } + + Ok(()) +} + +pub(crate) fn extract_storage_mode(prefix: u64) -> Result { + let bits = (prefix & AccountIdV0::STORAGE_MODE_MASK as u64) >> AccountIdV0::STORAGE_MODE_SHIFT; + // SAFETY: `STORAGE_MODE_MASK` is u8 so casting bits is lossless + match bits as u8 { + PUBLIC => Ok(AccountStorageMode::Public), + PRIVATE => Ok(AccountStorageMode::Private), + _ => Err(AccountIdError::UnknownAccountStorageMode(format!("0b{bits:b}").into())), + } +} + +pub(crate) fn extract_version(prefix: u64) -> Result { + // SAFETY: The mask guarantees that we only mask out the least significant nibble, so casting to + // u8 is safe. + let version = (prefix & AccountIdV0::VERSION_MASK) as u8; + AccountIdVersion::try_from(version) +} + +pub(crate) const fn extract_type(prefix: u64) -> AccountType { + let bits = (prefix & (AccountIdV0::TYPE_MASK as u64)) >> AccountIdV0::TYPE_SHIFT; + // SAFETY: `TYPE_MASK` is u8 so casting bits is lossless + match bits as u8 { + REGULAR_ACCOUNT_UPDATABLE_CODE => AccountType::RegularAccountUpdatableCode, + REGULAR_ACCOUNT_IMMUTABLE_CODE => AccountType::RegularAccountImmutableCode, + FUNGIBLE_FAUCET => AccountType::FungibleFaucet, + NON_FUNGIBLE_FAUCET => AccountType::NonFungibleFaucet, + _ => { + // SAFETY: type mask contains only 2 bits and we've covered all 4 possible options. + panic!("type mask contains only 2 bits and we've covered all 4 possible options") + }, + } +} + +const fn extract_anchor_epoch(suffix: u64) -> u16 { + ((suffix & AccountIdV0::ANCHOR_EPOCH_MASK) >> AccountIdV0::ANCHOR_EPOCH_SHIFT) as u16 +} + +/// Shapes the suffix so it meets the requirements of the account ID, by overwriting the +/// upper 16 bits with the epoch and setting the lower 8 bits to zero. +fn shape_suffix(suffix: Felt, anchor_epoch: u16) -> Result { + if anchor_epoch == u16::MAX { + return Err(AccountIdError::AnchorEpochMustNotBeU16Max); + } + + let mut suffix = suffix.as_int(); + + // Clear upper 16 epoch bits and the lower 8 bits. + suffix &= 0x0000_ffff_ffff_ff00; + + // Set the upper 16 anchor epoch bits. + suffix |= (anchor_epoch as u64) << AccountIdV0::ANCHOR_EPOCH_SHIFT; + + // SAFETY: We disallow u16::MAX which would be all 1 bits, so at least one of the most + // significant bits will always be zero. + Ok(Felt::try_from(suffix).expect("epoch is never all ones so felt should be valid")) +} + +// COMMON TRAIT IMPLS +// ================================================================================================ + +impl PartialOrd for AccountIdV0 { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AccountIdV0 { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + u128::from(*self).cmp(&u128::from(*other)) + } +} + +impl fmt::Display for AccountIdV0 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +/// Returns the digest of two hashing permutations over the seed, code commitment, storage +/// commitment and padding. +pub(crate) fn compute_digest( + seed: Word, + code_commitment: Digest, + storage_commitment: Digest, + anchor_block_hash: Digest, +) -> Digest { + let mut elements = Vec::with_capacity(16); + elements.extend(seed); + elements.extend(*code_commitment); + elements.extend(*storage_commitment); + elements.extend(*anchor_block_hash); + Hasher::hash_elements(&elements) +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + + use super::*; + use crate::{ + accounts::AccountIdPrefix, + testing::account_id::{ + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, + ACCOUNT_ID_OFF_CHAIN_SENDER, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, + }, + }; + + #[test] + fn test_account_id_from_seed_with_epoch() { + let code_commitment: Digest = Digest::default(); + let storage_commitment: Digest = Digest::default(); + let anchor_block_hash: Digest = Digest::default(); + + let seed = AccountIdV0::compute_account_seed( + [10; 32], + AccountType::FungibleFaucet, + AccountStorageMode::Public, + AccountIdVersion::Version0, + code_commitment, + storage_commitment, + anchor_block_hash, + ) + .unwrap(); + + for anchor_epoch in [0, u16::MAX - 1, 5000] { + let anchor = AccountIdAnchor::new_unchecked(anchor_epoch, anchor_block_hash); + let id = AccountIdV0::new(seed, anchor, code_commitment, storage_commitment).unwrap(); + assert_eq!(id.anchor_epoch(), anchor_epoch, "failed for account ID: {id}"); + } + } + + #[test] + fn account_id_from_felts_with_high_pop_count() { + let valid_suffix = Felt::try_from(0xfffe_ffff_ffff_ff00u64).unwrap(); + let valid_prefix = Felt::try_from(0x7fff_ffff_ffff_ff00u64).unwrap(); + + let id1 = AccountIdV0::new_unchecked([valid_prefix, valid_suffix]); + assert_eq!(id1.account_type(), AccountType::RegularAccountImmutableCode); + assert_eq!(id1.storage_mode(), AccountStorageMode::Public); + assert_eq!(id1.version(), AccountIdVersion::Version0); + assert_eq!(id1.anchor_epoch(), u16::MAX - 1); + } + + #[test] + fn account_id_construction() { + // Use the highest possible input to check if the constructed id is a valid Felt in that + // scenario. + // Use the lowest possible input to check whether the constructor produces valid IDs with + // all-zeroes input. + for input in [[0xff; 15], [0; 15]] { + for account_type in [ + AccountType::FungibleFaucet, + AccountType::NonFungibleFaucet, + AccountType::RegularAccountImmutableCode, + AccountType::RegularAccountUpdatableCode, + ] { + for storage_mode in [AccountStorageMode::Private, AccountStorageMode::Public] { + let id = AccountIdV0::dummy(input, account_type, storage_mode); + assert_eq!(id.account_type(), account_type); + assert_eq!(id.storage_mode(), storage_mode); + assert_eq!(id.version(), AccountIdVersion::Version0); + assert_eq!(id.anchor_epoch(), 0); + + // Do a serialization roundtrip to ensure validity. + let serialized_id = id.to_bytes(); + AccountIdV0::read_from_bytes(&serialized_id).unwrap(); + assert_eq!(serialized_id.len(), AccountIdV0::SERIALIZED_SIZE); + } + } + } + } + + #[test] + fn account_id_prefix_serialization_compatibility() { + // Ensure that an AccountIdPrefix can be read from the serialized bytes of an AccountId. + let account_id = AccountIdV0::try_from(ACCOUNT_ID_OFF_CHAIN_SENDER).unwrap(); + let id_bytes = account_id.to_bytes(); + assert_eq!(account_id.prefix().to_bytes(), id_bytes[..8]); + + let deserialized_prefix = AccountIdPrefix::read_from_bytes(&id_bytes).unwrap(); + assert_eq!(AccountIdPrefix::V0(account_id.prefix()), deserialized_prefix); + + // Ensure AccountId and AccountIdPrefix's hex representation are compatible. + assert!(account_id.to_hex().starts_with(&account_id.prefix().to_hex())); + } + + // CONVERSION TESTS + // ================================================================================================ + + #[test] + fn test_account_id_conversion_roundtrip() { + for (idx, account_id) in [ + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, + ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, + ACCOUNT_ID_OFF_CHAIN_SENDER, + ] + .into_iter() + .enumerate() + { + let id = AccountIdV0::try_from(account_id).expect("account ID should be valid"); + assert_eq!(id, AccountIdV0::from_hex(&id.to_hex()).unwrap(), "failed in {idx}"); + assert_eq!(id, AccountIdV0::try_from(<[u8; 15]>::from(id)).unwrap(), "failed in {idx}"); + assert_eq!(id, AccountIdV0::try_from(u128::from(id)).unwrap(), "failed in {idx}"); + // The u128 big-endian representation without the least significant byte and the + // [u8; 15] representations should be equivalent. + assert_eq!(u128::from(id).to_be_bytes()[0..15], <[u8; 15]>::from(id)); + assert_eq!(account_id, u128::from(id), "failed in {idx}"); + } + } + + #[test] + fn test_account_id_tag_identifiers() { + let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN) + .expect("valid account ID"); + assert!(account_id.is_regular_account()); + assert_eq!(account_id.account_type(), AccountType::RegularAccountImmutableCode); + assert!(account_id.is_public()); + + let account_id = AccountIdV0::try_from(ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN) + .expect("valid account ID"); + assert!(account_id.is_regular_account()); + assert_eq!(account_id.account_type(), AccountType::RegularAccountUpdatableCode); + assert!(!account_id.is_public()); + + let account_id = + AccountIdV0::try_from(ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN).expect("valid account ID"); + assert!(account_id.is_faucet()); + assert_eq!(account_id.account_type(), AccountType::FungibleFaucet); + assert!(account_id.is_public()); + + let account_id = AccountIdV0::try_from(ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN) + .expect("valid account ID"); + assert!(account_id.is_faucet()); + assert_eq!(account_id.account_type(), AccountType::NonFungibleFaucet); + assert!(!account_id.is_public()); + } +} diff --git a/objects/src/accounts/account_id/v0/prefix.rs b/objects/src/accounts/account_id/v0/prefix.rs new file mode 100644 index 000000000..c7c51bd87 --- /dev/null +++ b/objects/src/accounts/account_id/v0/prefix.rs @@ -0,0 +1,258 @@ +use alloc::string::{String, ToString}; +use core::fmt; + +use miden_crypto::utils::ByteWriter; +use vm_core::{ + utils::{ByteReader, Deserializable, Serializable}, + Felt, +}; +use vm_processor::DeserializationError; + +use crate::{ + accounts::{ + account_id::v0::{self, validate_prefix}, + AccountIdVersion, AccountStorageMode, AccountType, + }, + errors::AccountIdError, +}; + +// ACCOUNT ID PREFIX VERSION 0 +// ================================================================================================ + +/// The prefix of an [`AccountIdV0`](crate::accounts::AccountIdV0), i.e. its first field element. +/// +/// See the [`AccountId`](crate::accounts::AccountId)'s documentation for details. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct AccountIdPrefixV0 { + prefix: Felt, +} + +impl AccountIdPrefixV0 { + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The serialized size of an [`AccountIdPrefixV0`] in bytes. + const SERIALIZED_SIZE: usize = 8; + + // CONSTRUCTORS + // -------------------------------------------------------------------------------------------- + + /// See [`AccountIdPrefix::new_unchecked`](crate::accounts::AccountIdPrefix::new_unchecked) for + /// details. + pub fn new_unchecked(prefix: Felt) -> Self { + // Panic on invalid felts in debug mode. + if cfg!(debug_assertions) { + validate_prefix(prefix) + .expect("AccountIdPrefix::new_unchecked called with invalid prefix"); + } + + AccountIdPrefixV0 { prefix } + } + + /// See [`AccountIdPrefix::new`](crate::accounts::AccountIdPrefix::new) for details. + pub fn new(prefix: Felt) -> Result { + validate_prefix(prefix)?; + + Ok(AccountIdPrefixV0 { prefix }) + } + + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// See [`AccountIdPrefix::as_felt`](crate::accounts::AccountIdPrefix::as_felt) for details. + pub const fn as_felt(&self) -> Felt { + self.prefix + } + + /// See [`AccountIdPrefix::as_u64`](crate::accounts::AccountIdPrefix::as_u64) for details. + pub const fn as_u64(&self) -> u64 { + self.prefix.as_int() + } + + /// See [`AccountIdPrefix::account_type`](crate::accounts::AccountIdPrefix::account_type) for + /// details. + pub const fn account_type(&self) -> AccountType { + v0::extract_type(self.prefix.as_int()) + } + + /// See [`AccountIdPrefix::is_faucet`](crate::accounts::AccountIdPrefix::is_faucet) for details. + pub fn is_faucet(&self) -> bool { + self.account_type().is_faucet() + } + + /// See [`AccountIdPrefix::is_regular_account`](crate::accounts::AccountIdPrefix::is_regular_account) for + /// details. + pub fn is_regular_account(&self) -> bool { + self.account_type().is_regular_account() + } + + /// See [`AccountIdPrefix::storage_mode`](crate::accounts::AccountIdPrefix::storage_mode) for + /// details. + pub fn storage_mode(&self) -> AccountStorageMode { + v0::extract_storage_mode(self.prefix.as_int()) + .expect("account ID prefix should have been constructed with a valid storage mode") + } + + /// See [`AccountIdPrefix::is_public`](crate::accounts::AccountIdPrefix::is_public) for details. + pub fn is_public(&self) -> bool { + self.storage_mode() == AccountStorageMode::Public + } + + /// See [`AccountIdPrefix::version`](crate::accounts::AccountIdPrefix::version) for details. + pub fn version(&self) -> AccountIdVersion { + v0::extract_version(self.prefix.as_int()) + .expect("account ID prefix should have been constructed with a valid version") + } + + /// See [`AccountIdPrefix::to_hex`](crate::accounts::AccountIdPrefix::to_hex) for details. + pub fn to_hex(self) -> String { + format!("0x{:016x}", self.prefix.as_int()) + } +} + +// CONVERSIONS FROM ACCOUNT ID PREFIX +// ================================================================================================ + +impl From for Felt { + fn from(id: AccountIdPrefixV0) -> Self { + id.prefix + } +} + +impl From for [u8; 8] { + fn from(id: AccountIdPrefixV0) -> Self { + let mut result = [0_u8; 8]; + result[..8].copy_from_slice(&id.prefix.as_int().to_be_bytes()); + result + } +} + +impl From for u64 { + fn from(id: AccountIdPrefixV0) -> Self { + id.prefix.as_int() + } +} + +// CONVERSIONS TO ACCOUNT ID PREFIX +// ================================================================================================ + +impl TryFrom<[u8; 8]> for AccountIdPrefixV0 { + type Error = AccountIdError; + + /// See [`TryFrom<[u8; 8]> for + /// AccountIdPrefix`](crate::accounts::AccountIdPrefix#impl-TryFrom<%5Bu8;+8% + /// 5D>-for-AccountIdPrefix) for details. + fn try_from(mut value: [u8; 8]) -> Result { + // Felt::try_from expects little-endian order. + value.reverse(); + + Felt::try_from(value.as_slice()) + .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement) + .and_then(Self::new) + } +} + +impl TryFrom for AccountIdPrefixV0 { + type Error = AccountIdError; + + /// See [`TryFrom for + /// AccountIdPrefix`](crate::accounts::AccountIdPrefix#impl-TryFrom-for-AccountIdPrefix) + /// for details. + fn try_from(value: u64) -> Result { + let element = Felt::try_from(value.to_le_bytes().as_slice()) + .map_err(AccountIdError::AccountIdInvalidPrefixFieldElement)?; + Self::new(element) + } +} + +impl TryFrom for AccountIdPrefixV0 { + type Error = AccountIdError; + + /// See [`TryFrom for + /// AccountIdPrefix`](crate::accounts::AccountIdPrefix#impl-TryFrom-for-AccountIdPrefix) + /// for details. + fn try_from(element: Felt) -> Result { + Self::new(element) + } +} + +// COMMON TRAIT IMPLS +// ================================================================================================ + +impl PartialOrd for AccountIdPrefixV0 { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AccountIdPrefixV0 { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.prefix.as_int().cmp(&other.prefix.as_int()) + } +} + +impl fmt::Display for AccountIdPrefixV0 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +// SERIALIZATION +// ================================================================================================ + +impl Serializable for AccountIdPrefixV0 { + fn write_into(&self, target: &mut W) { + let bytes: [u8; 8] = (*self).into(); + bytes.write_into(target); + } + + fn get_size_hint(&self) -> usize { + Self::SERIALIZED_SIZE + } +} + +impl Deserializable for AccountIdPrefixV0 { + fn read_from(source: &mut R) -> Result { + <[u8; 8]>::read_from(source)? + .try_into() + .map_err(|err: AccountIdError| DeserializationError::InvalidValue(err.to_string())) + } +} + +// TESTS +// ================================================================================================ + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + accounts::{AccountId, AccountIdPrefix}, + testing::account_id::{ + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, + ACCOUNT_ID_OFF_CHAIN_SENDER, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, + }, + }; + + #[test] + fn test_account_id_prefix_conversion_roundtrip() { + for (idx, account_id) in [ + ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, + ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, + ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, + ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN, + ACCOUNT_ID_OFF_CHAIN_SENDER, + ] + .into_iter() + .enumerate() + { + let full_id = AccountId::try_from(account_id).unwrap(); + let prefix = full_id.prefix(); + assert_eq!( + prefix, + AccountIdPrefix::read_from_bytes(&prefix.to_bytes()).unwrap(), + "failed in {idx}" + ); + } + } +} diff --git a/objects/src/accounts/builder/mod.rs b/objects/src/accounts/builder/mod.rs index c6683be54..1ab8eadff 100644 --- a/objects/src/accounts/builder/mod.rs +++ b/objects/src/accounts/builder/mod.rs @@ -5,8 +5,8 @@ use vm_processor::Digest; use crate::{ accounts::{ - Account, AccountCode, AccountComponent, AccountId, AccountIdAnchor, AccountIdVersion, - AccountStorage, AccountStorageMode, AccountType, + Account, AccountCode, AccountComponent, AccountId, AccountIdAnchor, AccountIdV0, + AccountIdVersion, AccountStorage, AccountStorageMode, AccountType, }, assets::AssetVault, AccountError, Felt, Word, @@ -24,11 +24,10 @@ use crate::{ /// By default, the builder is initialized with: /// - The `account_type` set to [`AccountType::RegularAccountUpdatableCode`]. /// - The `storage_mode` set to [`AccountStorageMode::Private`]. -/// - The `version` set to [`AccountIdVersion::VERSION_0`]. +/// - The `version` set to [`AccountIdVersion::Version0`]. /// /// The methods that are required to be called are: /// -/// - [`AccountBuilder::init_seed`], /// - [`AccountBuilder::with_component`], which must be called at least once. /// - [`AccountBuilder::anchor`]. /// @@ -47,34 +46,28 @@ pub struct AccountBuilder { account_type: AccountType, storage_mode: AccountStorageMode, id_anchor: Option, - init_seed: Option<[u8; 32]>, + init_seed: [u8; 32], id_version: AccountIdVersion, } impl AccountBuilder { - /// Creates a new builder for a single account. - pub fn new() -> Self { + /// Creates a new builder for an account and sets the initial seed from which the grinding + /// process for that account's [`AccountId`] will start. + /// + /// This initial seed should come from a cryptographic random number generator. + pub fn new(init_seed: [u8; 32]) -> Self { Self { #[cfg(any(feature = "testing", test))] assets: vec![], components: vec![], - init_seed: None, id_anchor: None, + init_seed, account_type: AccountType::RegularAccountUpdatableCode, storage_mode: AccountStorageMode::Private, - id_version: AccountIdVersion::VERSION_0, + id_version: AccountIdVersion::Version0, } } - /// Sets the initial seed from which the grind for an [`AccountId`] will start. This initial - /// seed should come from a cryptographic random number generator. - /// - /// This method **must** be called. - pub fn init_seed(mut self, init_seed: [u8; 32]) -> Self { - self.init_seed = Some(init_seed); - self - } - /// Sets the [`AccountIdAnchor`] used for the generation of the account ID. pub fn anchor(mut self, anchor: AccountIdAnchor) -> Self { self.id_anchor = Some(anchor); @@ -109,14 +102,7 @@ impl AccountBuilder { } /// Builds the common parts of testing and non-testing code. - fn build_inner( - &self, - ) -> Result<([u8; 32], AssetVault, AccountCode, AccountStorage), AccountError> { - let init_seed = self.init_seed.ok_or(AccountError::BuildError( - "init_seed must be set on the account builder".into(), - None, - ))?; - + fn build_inner(&self) -> Result<(AssetVault, AccountCode, AccountStorage), AccountError> { #[cfg(any(feature = "testing", test))] let vault = AssetVault::new(&self.assets).map_err(|err| { AccountError::BuildError(format!("asset vault failed to build: {err}"), None) @@ -135,7 +121,7 @@ impl AccountBuilder { }, )?; - Ok((init_seed, vault, code, storage)) + Ok((vault, code, storage)) } /// Grinds a new [`AccountId`] using the `init_seed` as a starting point. @@ -147,7 +133,7 @@ impl AccountBuilder { storage_commitment: Digest, block_hash: Digest, ) -> Result { - let seed = AccountId::compute_account_seed( + let seed = AccountIdV0::compute_account_seed( init_seed, self.account_type, self.storage_mode, @@ -179,7 +165,7 @@ impl AccountBuilder { /// - If duplicate assets were added to the builder (only under the `testing` feature). /// - If the vault is not empty on new accounts (only under the `testing` feature). pub fn build(self) -> Result<(Account, Word), AccountError> { - let (init_seed, vault, code, storage) = self.build_inner()?; + let (vault, code, storage) = self.build_inner()?; let id_anchor = self .id_anchor @@ -194,15 +180,21 @@ impl AccountBuilder { } let seed = self.grind_account_id( - init_seed, + self.init_seed, self.id_version, code.commitment(), storage.commitment(), id_anchor.block_hash(), )?; - let account_id = AccountId::new(seed, id_anchor, code.commitment(), storage.commitment()) - .expect("get_account_seed should provide a suitable seed"); + let account_id = AccountId::new( + seed, + id_anchor, + AccountIdVersion::Version0, + code.commitment(), + storage.commitment(), + ) + .expect("get_account_seed should provide a suitable seed"); debug_assert_eq!(account_id.account_type(), self.account_type); debug_assert_eq!(account_id.storage_mode(), self.storage_mode); @@ -230,24 +222,23 @@ impl AccountBuilder { /// /// For possible errors, see the documentation of [`Self::build`]. pub fn build_existing(self) -> Result { - let (init_seed, vault, code, storage) = self.build_inner()?; + let (vault, code, storage) = self.build_inner()?; let account_id = { - let bytes = <[u8; 15]>::try_from(&init_seed[0..15]) + let bytes = <[u8; 15]>::try_from(&self.init_seed[0..15]) .expect("we should have sliced exactly 15 bytes off"); - AccountId::dummy(bytes, self.account_type, self.storage_mode) + AccountId::dummy( + bytes, + AccountIdVersion::Version0, + self.account_type, + self.storage_mode, + ) }; Ok(Account::from_parts(account_id, vault, storage, code, Felt::ONE)) } } -impl Default for AccountBuilder { - fn default() -> Self { - Self::new() - } -} - // TESTS // ================================================================================================ @@ -328,8 +319,7 @@ mod tests { let anchor_block_number = 1 << 16; let id_anchor = AccountIdAnchor::new(anchor_block_number, anchor_block_hash).unwrap(); - let (account, seed) = Account::builder() - .init_seed([5; 32]) + let (account, seed) = Account::builder([5; 32]) .anchor(id_anchor) .with_component(CustomComponent1 { slot0: storage_slot0 }) .with_component(CustomComponent2 { @@ -345,6 +335,7 @@ mod tests { let computed_id = AccountId::new( seed, id_anchor, + AccountIdVersion::Version0, account.code.commitment(), account.storage.commitment(), ) @@ -398,8 +389,7 @@ mod tests { let storage_slot0 = 25; let anchor = AccountIdAnchor::new_unchecked(5, Digest::default()); - let build_error = Account::builder() - .init_seed([0xff; 32]) + let build_error = Account::builder([0xff; 32]) .anchor(anchor) .with_component(CustomComponent1 { slot0: storage_slot0 }) .with_assets(AssetVault::mock().assets()) diff --git a/objects/src/accounts/component/mod.rs b/objects/src/accounts/component/mod.rs index d04c4eaa5..199fc8383 100644 --- a/objects/src/accounts/component/mod.rs +++ b/objects/src/accounts/component/mod.rs @@ -5,8 +5,8 @@ use vm_processor::MastForest; mod template; pub use template::{ - AccountComponentMetadata, AccountComponentTemplate, InitStorageData, StorageEntry, - StoragePlaceholder, StorageValue, + AccountComponentMetadata, AccountComponentTemplate, FeltRepresentation, InitStorageData, + StorageEntry, StoragePlaceholder, StorageValue, WordRepresentation, }; use crate::{ diff --git a/objects/src/accounts/component/template/mod.rs b/objects/src/accounts/component/template/mod.rs index 8ca7a07cc..18e269175 100644 --- a/objects/src/accounts/component/template/mod.rs +++ b/objects/src/accounts/component/template/mod.rs @@ -14,7 +14,7 @@ use super::AccountType; use crate::errors::AccountComponentTemplateError; mod storage; -pub use storage::{InitStorageData, StorageEntry, StoragePlaceholder, StorageValue}; +pub use storage::*; // ACCOUNT COMPONENT TEMPLATE // ================================================================================================ @@ -85,8 +85,57 @@ impl Deserializable for AccountComponentTemplate { /// Represents the full component template configuration. /// +/// An account component metadata describes the component alongside its storage layout. +/// On the storage layout, [placeholders](StoragePlaceholder) can be utilized to identify +/// [values](StorageValue) that should be provided at the moment of instantiation. +/// /// When the `std` feature is enabled, this struct allows for serialization and deserialization to /// and from a TOML file. +/// +/// # Example +/// +/// ``` +/// # use semver::Version; +/// # use std::collections::BTreeSet; +/// # use miden_objects::{testing::account_code::CODE, accounts::{ +/// # AccountComponent, AccountComponentMetadata, InitStorageData, StorageEntry, +/// # StoragePlaceholder, StorageValue, +/// # AccountComponentTemplate, FeltRepresentation, WordRepresentation}, +/// # assembly::Assembler, Felt}; +/// # fn main() -> Result<(), Box> { +/// let first_felt = FeltRepresentation::Decimal(Felt::new(0u64)); +/// let second_felt = FeltRepresentation::Decimal(Felt::new(1u64)); +/// let third_felt = FeltRepresentation::Decimal(Felt::new(2u64)); +/// // Templated element: +/// let last_element = FeltRepresentation::Template(StoragePlaceholder::new("foo")?); +/// +/// let storage_entry = StorageEntry::new_value( +/// "test-entry", +/// Some("a test entry"), +/// 0, +/// WordRepresentation::Array([first_felt, second_felt, third_felt, last_element]), +/// ); +/// +/// let init_storage_data = InitStorageData::new([( +/// StoragePlaceholder::new("foo")?, +/// StorageValue::Felt(Felt::new(300u64)), +/// )]); +/// +/// let component_template = AccountComponentMetadata::new( +/// "test".into(), +/// "desc".into(), +/// Version::parse("0.1.0")?, +/// BTreeSet::new(), +/// vec![], +/// )?; +/// +/// let library = Assembler::default().assemble_library([CODE]).unwrap(); +/// let template = AccountComponentTemplate::new(component_template, library); +/// +/// let component = AccountComponent::from_template(&template, &init_storage_data)?; +/// # Ok(()) +/// # } +/// ``` #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] pub struct AccountComponentMetadata { @@ -134,40 +183,11 @@ impl AccountComponentMetadata { Ok(component) } - /// Retrieves the set of storage placeholder keys (identified by a string) that require a value - /// at the moment of component instantiation. These values will be used for initializing - /// storage slot values, or storage map entries. - /// - /// # Examples - /// - /// An [AccountComponentMetadata] may have a single-slot storage entry where the last element - /// of the slot's word is templated: - /// - /// ``` - /// let first_felt = FeltRepresentation::Decimal(Felt::ZERO); - /// let second_felt = FeltRepresentation::Decimal(Felt::new(1u64)); - /// let third_felt = FeltRepresentation::Decimal(Felt::new(2u64)); - /// // Templated element: - /// let last_element = FeltRepresentation::Template(StoragePlaceholder::new("foo")); - /// - /// let storage_entry = StorageEntry::new_value( - /// "test-entry", - /// "a test entry", - /// 0, - /// WordRepresentation::new([first_felt, second_felt, third_felt, last_element]), - /// ); - /// ``` - /// - /// At the moment of instantiating a component that defines this storage entry, we - /// must pass a value that replaces "foo": - /// - /// ``` - /// let init_storage_data = InitStorageData::new([( - /// StoragePlaceholder::new("foo")?, - /// StorageValue::Felt(Felt::new(300u64)), - /// )]); - /// let component = AccountComponent::from_template(&template, &init_storage_data)?; - /// ``` + /// Retrieves the set of storage placeholder keys (identified by a string) that + /// require a value at the moment of component instantiation. These values will + /// be used for initializing storage slot values, or storage map entries. + /// For a full example on how a placeholder may be utilized, refer to the docs + /// for [AccountComponentMetadata]. pub fn get_storage_placeholders(&self) -> BTreeSet { let mut placeholder_set = BTreeSet::new(); for storage_entry in &self.storage { @@ -307,9 +327,9 @@ mod tests { ) .unwrap(); - let serialized = toml::to_string(&original_config).unwrap(); - let deserialized: AccountComponentMetadata = toml::from_str(&serialized).unwrap(); + let serialized = original_config.as_toml().unwrap(); + let deserialized = AccountComponentMetadata::from_toml(&serialized).unwrap(); assert_eq!(deserialized, original_config) } diff --git a/objects/src/accounts/component/template/storage/mod.rs b/objects/src/accounts/component/template/storage/mod.rs index c50026ebf..4ff64ddea 100644 --- a/objects/src/accounts/component/template/storage/mod.rs +++ b/objects/src/accounts/component/template/storage/mod.rs @@ -311,55 +311,6 @@ impl Deserializable for StorageEntry { } } -// STORAGE VALUES -// ================================================================================================ - -/// Represents the type of values that can be found in a storage slot's `values` field. -#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "std", serde(untagged))] -enum StorageValues { - /// List of individual words (for multi-slot entries). - Words(Vec), - /// List of key-value entries (for map storage slots). - MapEntries(Vec), - /// A placeholder value, represented as "{{key}}". - Template(StoragePlaceholder), -} - -impl StorageValues { - pub fn is_list_of_words(&self) -> bool { - match self { - StorageValues::Words(_) => true, - StorageValues::MapEntries(_) => false, - StorageValues::Template(_) => false, - } - } - - pub fn into_words(self) -> Option> { - match self { - StorageValues::Words(vec) => Some(vec), - StorageValues::MapEntries(_) => None, - StorageValues::Template(_) => None, - } - } - - pub fn into_map_entries(self) -> Option> { - match self { - StorageValues::Words(_) => None, - StorageValues::MapEntries(vec) => Some(vec), - StorageValues::Template(_) => None, - } - } - - pub fn len(&self) -> Option { - match self { - StorageValues::Words(vec) => Some(vec.len()), - StorageValues::MapEntries(vec) => Some(vec.len()), - StorageValues::Template(_) => None, - } - } -} - // MAP ENTRY // ================================================================================================ diff --git a/objects/src/accounts/component/template/storage/toml.rs b/objects/src/accounts/component/template/storage/toml.rs index 89100b01e..a7bebaa39 100644 --- a/objects/src/accounts/component/template/storage/toml.rs +++ b/objects/src/accounts/component/template/storage/toml.rs @@ -8,11 +8,10 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; use vm_core::Felt; use vm_processor::Digest; -use super::{ - FeltRepresentation, StorageEntry, StoragePlaceholder, StorageValues, WordRepresentation, -}; +use super::{FeltRepresentation, StorageEntry, StoragePlaceholder, WordRepresentation}; use crate::{ - accounts::AccountComponentMetadata, errors::AccountComponentTemplateError, + accounts::{component::template::MapEntry, AccountComponentMetadata}, + errors::AccountComponentTemplateError, utils::parse_hex_string_as_word, }; @@ -198,6 +197,55 @@ impl<'de> serde::Deserialize<'de> for StoragePlaceholder { } } +// STORAGE VALUES +// ================================================================================================ + +/// Represents the type of values that can be found in a storage slot's `values` field. +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(untagged)] +enum StorageValues { + /// List of individual words (for multi-slot entries). + Words(Vec), + /// List of key-value entries (for map storage slots). + MapEntries(Vec), + /// A placeholder value, represented as "{{key}}". + Template(StoragePlaceholder), +} + +impl StorageValues { + pub fn is_list_of_words(&self) -> bool { + match self { + StorageValues::Words(_) => true, + StorageValues::MapEntries(_) => false, + StorageValues::Template(_) => false, + } + } + + pub fn into_words(self) -> Option> { + match self { + StorageValues::Words(vec) => Some(vec), + StorageValues::MapEntries(_) => None, + StorageValues::Template(_) => None, + } + } + + pub fn into_map_entries(self) -> Option> { + match self { + StorageValues::Words(_) => None, + StorageValues::MapEntries(vec) => Some(vec), + StorageValues::Template(_) => None, + } + } + + pub fn len(&self) -> Option { + match self { + StorageValues::Words(vec) => Some(vec.len()), + StorageValues::MapEntries(vec) => Some(vec.len()), + StorageValues::Template(_) => None, + } + } +} + // STORAGE ENTRY SERIALIZATION // ================================================================================================ diff --git a/objects/src/accounts/header.rs b/objects/src/accounts/header.rs index f4594f83f..1780cfa4b 100644 --- a/objects/src/accounts/header.rs +++ b/objects/src/accounts/header.rs @@ -11,7 +11,7 @@ use super::{hash_account, Account, AccountId, Digest, Felt, ZERO}; /// components of the account. /// /// The [AccountHeader] is composed of: -/// - id: the account id ([`AccountId`]) of the account. +/// - id: the account ID ([`AccountId`]) of the account. /// - nonce: the nonce of the account. /// - vault_root: a commitment to the account's vault ([super::AssetVault]). /// - storage_commitment: a commitment to the account's storage ([super::AccountStorage]). @@ -92,7 +92,7 @@ impl AccountHeader { /// This is done by first converting the account header data into an array of Words as follows: /// ```text /// [ - /// [account_id_lo, account_id_hi, 0, account_nonce] + /// [account_id_suffix, account_id_prefix, 0, account_nonce] /// [VAULT_COMMITMENT] /// [STORAGE_COMMITMENT] /// [CODE_COMMITMENT] @@ -101,7 +101,7 @@ impl AccountHeader { /// And then concatenating the resulting elements into a single vector. pub fn as_elements(&self) -> Vec { [ - &[self.id.second_felt(), self.id.first_felt(), ZERO, self.nonce], + &[self.id.suffix(), self.id.prefix().as_felt(), ZERO, self.nonce], self.vault_root.as_elements(), self.storage_commitment.as_elements(), self.code_commitment.as_elements(), diff --git a/objects/src/accounts/mod.rs b/objects/src/accounts/mod.rs index 31b8e09a9..0c1101e21 100644 --- a/objects/src/accounts/mod.rs +++ b/objects/src/accounts/mod.rs @@ -4,14 +4,11 @@ use crate::{ AccountError, Digest, Felt, Hasher, Word, ZERO, }; -pub mod account_id; -pub use account_id::{AccountId, AccountIdVersion, AccountStorageMode, AccountType}; - -mod account_id_anchor; -pub use account_id_anchor::AccountIdAnchor; - -mod account_id_prefix; -pub use account_id_prefix::AccountIdPrefix; +mod account_id; +pub use account_id::{ + AccountId, AccountIdAnchor, AccountIdPrefix, AccountIdPrefixV0, AccountIdV0, AccountIdVersion, + AccountStorageMode, AccountType, +}; pub mod auth; @@ -25,8 +22,8 @@ pub use code::{procedure::AccountProcedureInfo, AccountCode}; mod component; pub use component::{ - AccountComponent, AccountComponentMetadata, AccountComponentTemplate, InitStorageData, - StorageEntry, StoragePlaceholder, StorageValue, + AccountComponent, AccountComponentMetadata, AccountComponentTemplate, FeltRepresentation, + InitStorageData, StorageEntry, StoragePlaceholder, StorageValue, WordRepresentation, }; pub mod delta; @@ -35,9 +32,6 @@ pub use delta::{ NonFungibleAssetDelta, NonFungibleDeltaAction, StorageMapDelta, }; -mod seed; -pub use seed::compute_account_seed; - mod storage; pub use storage::{AccountStorage, AccountStorageHeader, StorageMap, StorageSlot, StorageSlotType}; @@ -143,9 +137,12 @@ impl Account { Ok((code, storage)) } - /// Returns a new [`AccountBuilder`]. See its documentation for details. - pub fn builder() -> AccountBuilder { - AccountBuilder::new() + /// Creates a new [`AccountBuilder`] for an account and sets the initial seed from which the + /// grinding process for that account's [`AccountId`] will start. + /// + /// This initial seed should come from a cryptographic random number generator. + pub fn builder(init_seed: [u8; 32]) -> AccountBuilder { + AccountBuilder::new(init_seed) } // PUBLIC ACCESSORS @@ -354,8 +351,8 @@ pub fn hash_account( code_commitment: Digest, ) -> Digest { let mut elements = [ZERO; 16]; - elements[0] = id.second_felt(); - elements[1] = id.first_felt(); + elements[0] = id.suffix(); + elements[1] = id.prefix().as_felt(); elements[3] = nonce; elements[4..8].copy_from_slice(&*vault_root); elements[8..12].copy_from_slice(&*storage_commitment); diff --git a/objects/src/assets/fungible.rs b/objects/src/assets/fungible.rs index 36dbe8e1a..94378ea44 100644 --- a/objects/src/assets/fungible.rs +++ b/objects/src/assets/fungible.rs @@ -5,7 +5,7 @@ use vm_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; use vm_processor::DeserializationError; use super::{is_not_a_non_fungible_asset, AccountType, Asset, AssetError, Felt, Word, ZERO}; -use crate::accounts::AccountId; +use crate::accounts::{AccountId, AccountIdPrefix}; // FUNGIBLE ASSET // ================================================================================================ @@ -27,7 +27,7 @@ impl FungibleAsset { /// The serialized size of a [`FungibleAsset`] in bytes. /// - /// Currently an account id (15 bytes) plus an amount (u64). + /// Currently an account ID (15 bytes) plus an amount (u64). pub const SERIALIZED_SIZE: usize = AccountId::SERIALIZED_SIZE + core::mem::size_of::(); // CONSTRUCTOR @@ -59,6 +59,11 @@ impl FungibleAsset { self.faucet_id } + /// Return ID prefix of the faucet which issued this asset. + pub fn faucet_id_prefix(&self) -> AccountIdPrefix { + self.faucet_id.prefix() + } + /// Returns the amount of this asset. pub fn amount(&self) -> u64 { self.amount @@ -142,8 +147,8 @@ impl FungibleAsset { /// Returns the key which is used to store this asset in the account vault. pub(super) fn vault_key_from_faucet(faucet_id: AccountId) -> Word { let mut key = Word::default(); - key[2] = faucet_id.second_felt(); - key[3] = faucet_id.first_felt(); + key[2] = faucet_id.suffix(); + key[3] = faucet_id.prefix().as_felt(); key } } @@ -152,8 +157,8 @@ impl From for Word { fn from(asset: FungibleAsset) -> Self { let mut result = Word::default(); result[0] = Felt::new(asset.amount); - result[2] = asset.faucet_id.second_felt(); - result[3] = asset.faucet_id.first_felt(); + result[2] = asset.faucet_id.suffix(); + result[3] = asset.faucet_id.prefix().as_felt(); debug_assert!(is_not_a_non_fungible_asset(result)); result } @@ -203,9 +208,31 @@ impl Serializable for FungibleAsset { impl Deserializable for FungibleAsset { fn read_from(source: &mut R) -> Result { - let faucet_id: AccountId = source.read()?; - let amount: u64 = source.read()?; + let faucet_id_prefix: AccountIdPrefix = source.read()?; + FungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source) + } +} + +impl FungibleAsset { + /// Deserializes a [`FungibleAsset`] from an [`AccountIdPrefix`] and the remaining data from the + /// given `source`. + pub(super) fn deserialize_with_faucet_id_prefix( + faucet_id_prefix: AccountIdPrefix, + source: &mut R, + ) -> Result { + // The 8 bytes of the prefix have already been read, so we only need to read the remaining 7 + // bytes of the account ID's 15 total bytes. + let suffix_bytes: [u8; 7] = source.read()?; + // Convert prefix back to bytes so we can call the TryFrom<[u8; 15]> impl. + let prefix_bytes: [u8; 8] = faucet_id_prefix.into(); + let mut id_bytes: [u8; 15] = [0; 15]; + id_bytes[..8].copy_from_slice(&prefix_bytes); + id_bytes[8..].copy_from_slice(&suffix_bytes); + + let faucet_id = AccountId::try_from(id_bytes) + .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; + let amount: u64 = source.read()?; FungibleAsset::new(faucet_id, amount) .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } diff --git a/objects/src/assets/mod.rs b/objects/src/assets/mod.rs index 971fef50e..cc7cd7875 100644 --- a/objects/src/assets/mod.rs +++ b/objects/src/assets/mod.rs @@ -3,10 +3,7 @@ use super::{ utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, AssetError, Felt, Hasher, Word, ZERO, }; -use crate::accounts::{ - account_id::{self}, - AccountIdPrefix, -}; +use crate::accounts::AccountIdPrefix; mod fungible; pub use fungible::FungibleAsset; @@ -32,11 +29,11 @@ pub use vault::AssetVault; /// - ZERO for a fungible asset. /// - non-ZERO for a non-fungible asset. /// -/// Element 3 of both asset types is an [`AccountIdPrefix`] or equivalently, the first felt of an +/// Element 3 of both asset types is an [`AccountIdPrefix`] or equivalently, the prefix of an /// [`AccountId`](crate::accounts::AccountId), which can be used to distinguish assets /// based on [`AccountIdPrefix::account_type`]. /// -/// For element 3 of the vault keys of assets, the 6th least significant bit (referred to as the +/// For element 3 of the vault keys of assets, the bit at index 5 (referred to as the /// "fungible bit" will be): /// - `1` for a fungible asset. /// - `0` for a non-fungible asset. @@ -48,11 +45,11 @@ pub use vault::AssetVault; /// /// # Fungible assets /// -/// - A fungible asset's data layout is: `[amount, 0, faucet_id_lo, faucet_id_hi]`. -/// - A fungible asset's vault key layout is: `[0, 0, faucet_id_lo, faucet_id_hi]`. +/// - A fungible asset's data layout is: `[amount, 0, faucet_id_suffix, faucet_id_prefix]`. +/// - A fungible asset's vault key layout is: `[0, 0, faucet_id_suffix, faucet_id_prefix]`. /// -/// The most significant elements of a fungible asset are set to the first (`faucet_id_hi`) and -/// second felt (`faucet_id_lo`) of the ID of the faucet which issues the asset. This guarantees the +/// The most significant elements of a fungible asset are set to the prefix (`faucet_id_prefix`) and +/// suffix (`faucet_id_suffix`) of the ID of the faucet which issues the asset. This guarantees the /// properties described above (the fungible bit is `1`). /// /// The least significant element is set to the amount of the asset. This amount cannot be greater @@ -66,16 +63,16 @@ pub use vault::AssetVault; /// /// # Non-fungible assets /// -/// - A non-fungible asset's data layout is: `[hash0, hash1, hash2, faucet_id_hi]`. -/// - A non-fungible asset's vault key layout is: `[faucet_id_hi, hash1, hash2, hash0']`, where +/// - A non-fungible asset's data layout is: `[hash0, hash1, hash2, faucet_id_prefix]`. +/// - A non-fungible asset's vault key layout is: `[faucet_id_prefix, hash1, hash2, hash0']`, where /// `hash0'` is equivalent to `hash0` with the fungible bit set to `0`. See /// [`NonFungibleAsset::vault_key`] for more details. /// /// The 4 elements of non-fungible assets are computed as follows: /// - First the asset data is hashed. This compresses an asset of an arbitrary length to 4 field /// elements: `[hash0, hash1, hash2, hash3]`. -/// - `hash3` is then replaced with the first felt of the faucet ID (`faucet_id_hi`) which issues -/// the asset: `[hash0, hash1, hash2, faucet_id_hi]`. +/// - `hash3` is then replaced with the prefix of the faucet ID (`faucet_id_prefix`) which issues +/// the asset: `[hash0, hash1, hash2, faucet_id_prefix]`. /// /// It is impossible to find a collision between two non-fungible assets issued by different faucets /// as the faucet_id is included in the description of the non-fungible asset and this is guaranteed @@ -123,12 +120,12 @@ impl Asset { /// Returns the prefix of the faucet ID which issued this asset. /// - /// To get the full [`AccountId`](crate::accounts::AccountId ) of a fungible asset the asset + /// To get the full [`AccountId`](crate::accounts::AccountId) of a fungible asset the asset /// must be matched on. pub fn faucet_id_prefix(&self) -> AccountIdPrefix { match self { - Self::Fungible(asset) => asset.faucet_id().prefix(), - Self::NonFungible(asset) => asset.faucet_id(), + Self::Fungible(asset) => asset.faucet_id_prefix(), + Self::NonFungible(asset) => asset.faucet_id_prefix(), } } @@ -215,20 +212,18 @@ impl Deserializable for Asset { fn read_from(source: &mut R) -> Result { // Both asset types have their faucet ID prefix as the first element, so we can use it to // inspect what type of asset it is. - // Due to little endian byte order, the first byte contains the account ID metadata. - let account_metadata = source.peek_u8()?; - let account_type = account_id::extract_type(account_metadata as u64); + let faucet_id_prefix: AccountIdPrefix = source.read()?; - match account_type { + match faucet_id_prefix.account_type() { AccountType::FungibleFaucet => { - FungibleAsset::read_from(source).map(Asset::from) + FungibleAsset::deserialize_with_faucet_id_prefix(faucet_id_prefix, source).map(Asset::from) }, AccountType::NonFungibleFaucet => { - NonFungibleAsset::read_from(source).map(Asset::from) + NonFungibleAsset::deserialize_with_faucet_id_prefix (faucet_id_prefix, source).map(Asset::from) }, other_type => { Err(DeserializationError::InvalidValue(format!( - "failed to deserialize asset: expected an account ID of type faucet, found {other_type:?}" + "failed to deserialize asset: expected an account ID prefix of type faucet, found {other_type:?}" ))) }, } @@ -249,7 +244,7 @@ fn is_not_a_non_fungible_asset(asset: Word) -> bool { }, Err(err) => { #[cfg(debug_assertions)] - panic!("invalid account id prefix passed to is_not_a_non_fungible_asset: {err}"); + panic!("invalid account ID prefix passed to is_not_a_non_fungible_asset: {err}"); #[cfg(not(debug_assertions))] false }, @@ -269,7 +264,7 @@ mod tests { use super::{Asset, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails}; use crate::{ - accounts::{account_id, AccountId}, + accounts::{AccountId, AccountIdPrefix}, testing::account_id::{ ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1, ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2, @@ -333,29 +328,15 @@ mod tests { } } - /// This test asserts that account ID's metadata is serialized in the first byte of assets. + /// This test asserts that account ID's prefix is serialized in the first felt of assets. /// Asset deserialization relies on that fact and if this changes the serialization must /// be updated. #[test] - fn test_account_id_metadata_is_in_first_serialized_byte() { + fn test_account_id_prefix_is_in_first_serialized_felt() { for asset in [FungibleAsset::mock(300), NonFungibleAsset::mock(&[0xaa, 0xbb])] { let serialized_asset = asset.to_bytes(); - // Get the first byte and interpret it as a u64 because the extract functions require - // it. - let first_byte = serialized_asset[0] as u64; - - assert_eq!( - account_id::extract_type(first_byte), - asset.faucet_id_prefix().account_type() - ); - assert_eq!( - account_id::extract_storage_mode(first_byte).unwrap(), - asset.faucet_id_prefix().storage_mode() - ); - assert_eq!( - account_id::extract_version(first_byte).unwrap(), - asset.faucet_id_prefix().version() - ); + let prefix = AccountIdPrefix::read_from_bytes(&serialized_asset).unwrap(); + assert_eq!(prefix, asset.faucet_id_prefix()); } } } diff --git a/objects/src/assets/nonfungible.rs b/objects/src/assets/nonfungible.rs index feaee7067..048cc5106 100644 --- a/objects/src/assets/nonfungible.rs +++ b/objects/src/assets/nonfungible.rs @@ -5,7 +5,6 @@ use vm_core::{FieldElement, WORD_SIZE}; use super::{AccountIdPrefix, AccountType, Asset, AssetError, Felt, Hasher, Word}; use crate::{ - accounts::AccountId, utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, Digest, }; @@ -21,8 +20,8 @@ const FAUCET_ID_POS: usize = 3; /// The commitment is constructed as follows: /// /// - Hash the asset data producing `[hash0, hash1, hash2, hash3]`. -/// - Replace the value of `hash3` with the first felt of the faucet id (`faucet_id_hi`) producing -/// `[hash0, hash1, hash2, faucet_id_hi]`. +/// - Replace the value of `hash3` with the prefix of the faucet id (`faucet_id_prefix`) producing +/// `[hash0, hash1, hash2, faucet_id_prefix]`. /// - This layout ensures that fungible and non-fungible assets are distinguishable by interpreting /// the 3rd element of an asset as an [`AccountIdPrefix`] and checking its type. /// @@ -112,20 +111,18 @@ impl NonFungibleAsset { pub fn vault_key(&self) -> Word { let mut vault_key = self.0; - // Swap first felt of faucet ID with hash0. + // Swap prefix of faucet ID with hash0. vault_key.swap(0, FAUCET_ID_POS); - // Set the fungible bit to zero by taking the bitwise `and` of the felt with the inverted - // is_faucet mask. - let clear_fungible_bit_mask = !AccountId::IS_FAUCET_MASK; - vault_key[3] = Felt::try_from(vault_key[3].as_int() & clear_fungible_bit_mask) - .expect("felt should still be valid as we cleared a bit and did not set any"); + // Set the fungible bit to zero. + vault_key[3] = + AccountIdPrefix::clear_fungible_bit(self.faucet_id_prefix().version(), vault_key[3]); vault_key } - /// Return ID of the faucet which issued this asset. - pub fn faucet_id(&self) -> AccountIdPrefix { + /// Return ID prefix of the faucet which issued this asset. + pub fn faucet_id_prefix(&self) -> AccountIdPrefix { AccountIdPrefix::new_unchecked(self.0[FAUCET_ID_POS]) } @@ -185,7 +182,7 @@ impl Serializable for NonFungibleAsset { fn write_into(&self, target: &mut W) { // All assets should serialize their faucet ID at the first position to allow them to be // easily distinguishable during deserialization. - target.write(self.0[FAUCET_ID_POS]); + target.write(self.faucet_id_prefix()); target.write(self.0[2]); target.write(self.0[1]); target.write(self.0[0]); @@ -200,11 +197,23 @@ impl Deserializable for NonFungibleAsset { fn read_from(source: &mut R) -> Result { let faucet_id_prefix: AccountIdPrefix = source.read()?; + Self::deserialize_with_faucet_id_prefix(faucet_id_prefix, source) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) + } +} + +impl NonFungibleAsset { + /// Deserializes a [`NonFungibleAsset`] from an [`AccountIdPrefix`] and the remaining data from + /// the given `source`. + pub(super) fn deserialize_with_faucet_id_prefix( + faucet_id_prefix: AccountIdPrefix, + source: &mut R, + ) -> Result { let hash_2: Felt = source.read()?; let hash_1: Felt = source.read()?; let hash_0: Felt = source.read()?; - // The second felt in the data_hash will be replaced by the faucet id, so we can set it to + // The last felt in the data_hash will be replaced by the faucet id, so we can set it to // zero here. NonFungibleAsset::from_parts(faucet_id_prefix, [hash_0, hash_1, hash_2, Felt::ZERO]) .map_err(|err| DeserializationError::InvalidValue(err.to_string())) diff --git a/objects/src/errors.rs b/objects/src/errors.rs index 94cedf585..ff823d4a8 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -19,7 +19,8 @@ use crate::{ accounts::{AccountCode, AccountIdPrefix, AccountStorage, AccountType, StoragePlaceholder}, block::block_num_from_epoch, notes::{NoteAssets, NoteExecutionHint, NoteTag, NoteType, Nullifier}, - ACCOUNT_UPDATE_MAX_SIZE, MAX_INPUTS_PER_NOTE, MAX_INPUT_NOTES_PER_TX, MAX_OUTPUT_NOTES_PER_TX, + BlockHeader, ACCOUNT_UPDATE_MAX_SIZE, MAX_INPUTS_PER_NOTE, MAX_INPUT_NOTES_PER_TX, + MAX_OUTPUT_NOTES_PER_TX, }; // ACCOUNT COMPONENT TEMPLATE ERROR @@ -70,8 +71,6 @@ pub enum AccountError { AccountComponentMergeError(String), #[error("failed to create account component")] AccountComponentTemplateInstantiationError(#[source] AccountComponentTemplateError), - #[error("failed to convert bytes into account id field element")] - AccountIdInvalidFieldElement(#[source] DeserializationError), #[error("failed to update asset vault")] AssetVaultUpdateError(#[source] AssetVaultError), #[error("account build error: {0}")] @@ -82,12 +81,6 @@ pub enum AccountError { FungibleFaucetMaxSupplyTooLarge { actual: u64, max: u64 }, #[error("account header data has length {actual} but it must be of length {expected}")] HeaderDataIncorrectLength { actual: usize, expected: usize }, - // TODO: Make #[source] and remove from msg once HexParseError implements Error trait in - // no-std. - #[error("failed to parse hex string into account id: {0}")] - AccountIdHexParseError(HexParseError), - #[error("`{0}` is not a valid account storage mode")] - InvalidAccountStorageMode(String), #[error("new account nonce {new} is less than the current nonce {current}")] NonceNotMonotonicallyIncreasing { current: u64, new: u64 }, #[error("digest of the seed has {actual} trailing zeroes but must have at least {expected} trailing zeroes")] @@ -108,17 +101,49 @@ pub enum AccountError { "procedure which does not access storage (storage size = 0) has non-zero storage offset" )] PureProcedureWithStorageOffset, - #[error("account component at index {component_index} is incompatible with account of type {account_type:?}")] + #[error("account component at index {component_index} is incompatible with account of type {account_type}")] UnsupportedComponentForAccountType { account_type: AccountType, component_index: usize, }, + #[error("failed to parse account ID from final account header")] + FinalAccountHeaderIdParsingFailed(#[source] AccountIdError), /// This variant can be used by methods that are not inherent to the account but want to return /// this error type. #[error("assumption violated: {0}")] AssumptionViolated(String), } +// ACCOUNT ID ERROR +// ================================================================================================ + +#[derive(Debug, Error)] +pub enum AccountIdError { + #[error("failed to convert bytes into account ID prefix field element")] + AccountIdInvalidPrefixFieldElement(#[source] DeserializationError), + #[error("failed to convert bytes into account ID suffix field element")] + AccountIdInvalidSuffixFieldElement(#[source] DeserializationError), + #[error("`{0}` is not a known account storage mode")] + UnknownAccountStorageMode(Box), + #[error(r#"`{0}` is not a known account type, expected one of "FungibleFaucet", "NonFungibleFaucet", "RegularAccountImmutableCode" or "RegularAccountUpdatableCode""#)] + UnknownAccountType(Box), + // TODO: Make #[source] and remove from msg once HexParseError implements Error trait in + // no-std. + #[error("failed to parse hex string into account ID: {0}")] + AccountIdHexParseError(HexParseError), + #[error("`{0}` is not a known account ID version")] + UnknownAccountIdVersion(u8), + #[error("anchor epoch in account ID must not be u16::MAX ({})", u16::MAX)] + AnchorEpochMustNotBeU16Max, + #[error("least significant byte of account ID suffix must be zero")] + AccountIdSuffixLeastSignificantByteMustBeZero, + #[error( + "anchor block must be an epoch block, that is, its block number must be a multiple of 2^{}", + BlockHeader::EPOCH_LENGTH_EXPONENT + )] + AnchorBlockMustBeEpochBlock, +} + // ACCOUNT DELTA ERROR // ================================================================================================ @@ -146,7 +171,7 @@ pub enum AccountDeltaError { }, #[error("inconsistent nonce update: {0}")] InconsistentNonceUpdate(String), - #[error("account id {0} in fungible asset delta is not of type fungible faucet")] + #[error("account ID {0} in fungible asset delta is not of type fungible faucet")] NotAFungibleFaucetId(AccountId), } @@ -171,16 +196,16 @@ pub enum AssetError { original_issuer: AccountId, other_issuer: AccountId, }, - #[error("faucet account id in asset is invalid")] + #[error("faucet account ID in asset is invalid")] InvalidFaucetAccountId(#[source] Box), #[error( - "faucet id {0} of type {id_type:?} must be of type {expected_ty:?} for fungible assets", + "faucet id {0} of type {id_type} must be of type {expected_ty} for fungible assets", id_type = .0.account_type(), expected_ty = AccountType::FungibleFaucet )] FungibleFaucetIdTypeMismatch(AccountId), #[error( - "faucet id {0} of type {id_type:?} must be of type {expected_ty:?} for non fungible assets", + "faucet id {0} of type {id_type} must be of type {expected_ty} for non fungible assets", id_type = .0.account_type(), expected_ty = AccountType::NonFungibleFaucet )] @@ -224,8 +249,8 @@ pub enum NoteError { InconsistentNoteTag(NoteType, u64), #[error("adding fungible asset amounts would exceed maximum allowed amount")] AddFungibleAssetBalanceError(#[source] AssetError), - #[error("note sender is not a valid account id")] - NoteSenderInvalidAccountId(#[source] AccountError), + #[error("note sender is not a valid account ID")] + NoteSenderInvalidAccountId(#[source] AccountIdError), #[error("note tag use case {0} must be less than 2^{exp}", exp = NoteTag::MAX_USE_CASE_ID_EXPONENT)] NoteTagUseCaseTooLarge(u16), #[error( @@ -327,8 +352,8 @@ pub enum TransactionInputError { InputNoteBlockNotInChainMmr(NoteId), #[error("input note with id {0} was not created in block {1}")] InputNoteNotInBlock(NoteId, u32), - #[error("account id computed from seed is invalid")] - InvalidAccountIdSeed(#[source] AccountError), + #[error("account ID computed from seed is invalid")] + InvalidAccountIdSeed(#[source] AccountIdError), #[error( "total number of input notes is {0} which exceeds the maximum of {MAX_INPUT_NOTES_PER_TX}" )] @@ -364,7 +389,7 @@ pub enum ProvenTransactionError { tx_final_hash: Digest, details_hash: Digest, }, - #[error("proven transaction's final account id {tx_account_id} and account details id {details_account_id} must match")] + #[error("proven transaction's final account ID {tx_account_id} and account details id {details_account_id} must match")] AccountIdMismatch { tx_account_id: AccountId, details_account_id: AccountId, diff --git a/objects/src/notes/execution_hint.rs b/objects/src/notes/execution_hint.rs index 44fbcea87..fe2f88e89 100644 --- a/objects/src/notes/execution_hint.rs +++ b/objects/src/notes/execution_hint.rs @@ -32,21 +32,21 @@ pub enum NoteExecutionHint { /// The block number cannot be [`u32::MAX`] which is enforced through the [`AfterBlockNumber`] /// type. AfterBlock { block_num: AfterBlockNumber }, - /// The note's script can be executed in the specified slot within the specified epoch. + /// The note's script can be executed in the specified slot within the specified round. /// /// The slot is defined as follows: - /// - First we define the length of the epoch in powers of 2. For example, epoch_len = 10 is an - /// epoch of 1024 blocks. - /// - Then we define the length of a slot within the epoch also using powers of 2. For example, + /// - First we define the length of the round in powers of 2. For example, round_len = 10 is a + /// round of 1024 blocks. + /// - Then we define the length of a slot within the round also using powers of 2. For example, /// slot_len = 7 is a slot of 128 blocks. - /// - Lastly, the offset specifies the index of the slot within the epoch - i.e., 0 is the + /// - Lastly, the offset specifies the index of the slot within the round - i.e., 0 is the /// first slot, 1 is the second slot etc. /// - /// For example: { epoch_len: 10, slot_len: 7, slot_offset: 1 } means that the note can - /// be executed in any second 128 block slot of a 1024 block epoch. These would be blocks + /// For example: { round_len: 10, slot_len: 7, slot_offset: 1 } means that the note can + /// be executed in any second 128 block slot of a 1024 block round. These would be blocks /// 128..255, 1152..1279, 2176..2303 etc. OnBlockSlot { - epoch_len: u8, + round_len: u8, slot_len: u8, slot_offset: u8, }, @@ -84,9 +84,10 @@ impl NoteExecutionHint { .map(|block_number| NoteExecutionHint::AfterBlock { block_num: block_number }) } - /// Creates a [NoteExecutionHint::OnBlockSlot] for the given parameters - pub fn on_block_slot(epoch_len: u8, slot_len: u8, slot_offset: u8) -> Self { - NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset } + /// Creates a [NoteExecutionHint::OnBlockSlot] for the given parameters. See the variants + /// documentation for details on the parameters. + pub fn on_block_slot(round_len: u8, slot_len: u8, slot_offset: u8) -> Self { + NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } } pub fn from_parts(tag: u8, payload: u32) -> Result { @@ -110,10 +111,10 @@ impl NoteExecutionHint { return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload)); } - let epoch_len = ((payload >> 16) & 0xff) as u8; + let round_len = ((payload >> 16) & 0xff) as u8; let slot_len = ((payload >> 8) & 0xff) as u8; let slot_offset = (payload & 0xff) as u8; - let hint = NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset }; + let hint = NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset }; Ok(hint) }, @@ -134,14 +135,14 @@ impl NoteExecutionHint { NoteExecutionHint::AfterBlock { block_num: hint_block_num } => { Some(block_num >= hint_block_num.as_u32()) }, - NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset } => { - let epoch_len_blocks: u32 = 1 << epoch_len; + NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => { + let round_len_blocks: u32 = 1 << round_len; let slot_len_blocks: u32 = 1 << slot_len; - let block_epoch_index = block_num / epoch_len_blocks; + let block_round_index = block_num / round_len_blocks; let slot_start_block = - block_epoch_index * epoch_len_blocks + (*slot_offset as u32) * slot_len_blocks; + block_round_index * round_len_blocks + (*slot_offset as u32) * slot_len_blocks; let slot_end_block = slot_start_block + slot_len_blocks; let can_be_consumed = block_num >= slot_start_block && block_num < slot_end_block; @@ -166,9 +167,9 @@ impl NoteExecutionHint { NoteExecutionHint::AfterBlock { block_num } => { (Self::AFTER_BLOCK_TAG, block_num.as_u32()) }, - NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset } => { + NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => { let payload: u32 = - ((*epoch_len as u32) << 16) | ((*slot_len as u32) << 8) | (*slot_offset as u32); + ((*round_len as u32) << 16) | ((*slot_len as u32) << 8) | (*slot_offset as u32); (Self::ON_BLOCK_SLOT_TAG, payload) }, } @@ -270,7 +271,7 @@ mod tests { assert_hint_serde(NoteExecutionHint::Always); assert_hint_serde(NoteExecutionHint::after_block(15).unwrap()); assert_hint_serde(NoteExecutionHint::OnBlockSlot { - epoch_len: 9, + round_len: 9, slot_len: 12, slot_offset: 18, }); @@ -284,7 +285,7 @@ mod tests { assert_eq!(hint, decoded_hint); let hint = NoteExecutionHint::OnBlockSlot { - epoch_len: 22, + round_len: 22, slot_len: 33, slot_offset: 44, }; diff --git a/objects/src/notes/metadata.rs b/objects/src/notes/metadata.rs index f2c9d20cf..50547f762 100644 --- a/objects/src/notes/metadata.rs +++ b/objects/src/notes/metadata.rs @@ -20,17 +20,18 @@ use super::{ /// [`NoteMetadata`] can be encoded into a [`Word`] with the following layout: /// /// ```text -/// 1st felt: [sender_id_hi (64 bits)] -/// 2nd felt: [sender_id_lo (56 bits) | note_type (2 bits) | note_execution_hint_tag (6 bits)] +/// 1st felt: [sender_id_prefix (64 bits)] +/// 2nd felt: [sender_id_suffix (56 bits) | note_type (2 bits) | note_execution_hint_tag (6 bits)] /// 3rd felt: [note_execution_hint_payload (32 bits) | note_tag (32 bits)] /// 4th felt: [aux (64 bits)] /// ``` /// /// The rationale for the above layout is to ensure the validity of each felt: -/// - 1st felt: Is equivalent to the first felt of the account ID so it inherits its validity. -/// - 2nd felt: The second felt of the account ID is designed such that its lower 8 bits can all be -/// set to `1` and still retain its validity due to the anchor epoch in the upper 16 bits always -/// containing at least one `0` bit. +/// - 1st felt: Is equivalent to the prefix of the account ID so it inherits its validity. +/// - 2nd felt: The lower 8 bits of the account ID suffix are `0` by construction, so that they can +/// be overwritten with other data. The suffix is designed such that it retains its felt validity +/// even if all of its lower 8 bits are be set to `1`. This is because the anchor epoch in the +/// upper 16 bits always contains at least one `0` bit. /// - 3rd felt: The note execution hint payload must contain at least one `0` bit in its encoding, /// so the upper 32 bits of the felt will contain at least one `0` bit making the entire felt /// valid. @@ -121,9 +122,9 @@ impl From<&NoteMetadata> for Word { /// The produced layout of the word is documented on the [`NoteMetadata`] type. fn from(metadata: &NoteMetadata) -> Self { let mut elements = Word::default(); - elements[0] = metadata.sender.first_felt(); + elements[0] = metadata.sender.prefix().as_felt(); elements[1] = merge_id_type_and_hint_tag( - metadata.sender.second_felt(), + metadata.sender.suffix(), metadata.note_type, metadata.execution_hint, ); @@ -140,12 +141,12 @@ impl TryFrom for NoteMetadata { /// /// The expected layout of the word is documented on the [`NoteMetadata`] type. fn try_from(elements: Word) -> Result { - let sender_id_first_felt: Felt = elements[0]; + let sender_id_prefix: Felt = elements[0]; - let (sender_id_second_felt, note_type, execution_hint_tag) = + let (sender_id_suffix, note_type, execution_hint_tag) = unmerge_id_type_and_hint_tag(elements[1])?; - let sender = AccountId::try_from([sender_id_first_felt, sender_id_second_felt]) + let sender = AccountId::try_from([sender_id_prefix, sender_id_suffix]) .map_err(NoteError::NoteSenderInvalidAccountId)?; let (execution_hint, note_tag) = @@ -174,25 +175,25 @@ impl Deserializable for NoteMetadata { // HELPER FUNCTIONS // ================================================================================================ -/// Merges the second felt of an [`AccountId`], a [`NoteType`] and the tag of a +/// Merges the suffix of an [`AccountId`], a [`NoteType`] and the tag of a /// [`NoteExecutionHint`] into a single [`Felt`]. /// /// The layout is as follows: /// /// ```text -/// [sender_id_lo (56 bits) | note_type (2 bits) | note_execution_hint_tag (6 bits)] +/// [sender_id_suffix (56 bits) | note_type (2 bits) | note_execution_hint_tag (6 bits)] /// ``` /// /// One of the upper 16 bits is guaranteed to be zero due to the guarantees of the epoch in the -/// account id. +/// account ID. /// -/// Note that `sender_id_lo` is the second felt of the sender's account ID. +/// Note that `sender_id_suffix` is the suffix of the sender's account ID. fn merge_id_type_and_hint_tag( - sender_id_second_felt: Felt, + sender_id_suffix: Felt, note_type: NoteType, note_execution_hint: NoteExecutionHint, ) -> Felt { - let mut merged = sender_id_second_felt.as_int(); + let mut merged = sender_id_suffix.as_int(); let type_bits = note_type as u8; let (tag_bits, _) = note_execution_hint.into_parts(); @@ -208,12 +209,12 @@ fn merge_id_type_and_hint_tag( merged |= (type_bits << 6) as u64; merged |= tag_bits as u64; - // SAFETY: One of the top 16 bits of the second felt (the anchor epoch) is zero by construction + // SAFETY: One of the top 16 bits (the anchor epoch) of the suffix is zero by construction // so the bytes will be a valid felt. Felt::try_from(merged).expect("encoded value should be a valid felt") } -/// Unmerges the given felt into the second felt of an [`AccountId`], a [`NoteType`] and the tag of +/// Unmerges the given felt into the suffix of an [`AccountId`], a [`NoteType`] and the tag of /// a [`NoteExecutionHint`]. fn unmerge_id_type_and_hint_tag(element: Felt) -> Result<(Felt, NoteType, u8), NoteError> { let element = element.as_int(); @@ -230,9 +231,9 @@ fn unmerge_id_type_and_hint_tag(element: Felt) -> Result<(Felt, NoteType, u8), N // SAFETY: The input was a valid felt and and we cleared additional bits and did not set any // bits, so it must still be a valid felt. - let sender_id_second_felt = Felt::try_from(element).expect("element should still be valid"); + let sender_id_suffix = Felt::try_from(element).expect("element should still be valid"); - Ok((sender_id_second_felt, note_type, tag_bits)) + Ok((sender_id_suffix, note_type, tag_bits)) } /// Merges the [`NoteExecutionHint`] payload and a [`NoteTag`] into a single [`Felt`]. @@ -320,46 +321,46 @@ mod tests { // Use the Account ID with the maximum one bits to test if the merge function always // produces valid felts. let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap(); - let sender_second_felt = sender.second_felt(); + let sender_id_suffix = sender.suffix(); let note_type = NoteType::Public; let note_execution_hint = NoteExecutionHint::OnBlockSlot { - epoch_len: 10, + round_len: 10, slot_len: 11, slot_offset: 12, }; let merged_value = - merge_id_type_and_hint_tag(sender_second_felt, note_type, note_execution_hint); - let (extracted_second_felt, extracted_note_type, extracted_note_execution_hint_tag) = + merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint); + let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) = unmerge_id_type_and_hint_tag(merged_value).unwrap(); assert_eq!(note_type, extracted_note_type); assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag); - assert_eq!(sender_second_felt, extracted_second_felt); + assert_eq!(sender_id_suffix, extracted_suffix); let note_type = NoteType::Private; let note_execution_hint = NoteExecutionHint::Always; let merged_value = - merge_id_type_and_hint_tag(sender_second_felt, note_type, note_execution_hint); - let (extracted_second_felt, extracted_note_type, extracted_note_execution_hint_tag) = + merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint); + let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) = unmerge_id_type_and_hint_tag(merged_value).unwrap(); assert_eq!(note_type, extracted_note_type); assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag); - assert_eq!(sender_second_felt, extracted_second_felt); + assert_eq!(sender_id_suffix, extracted_suffix); let note_type = NoteType::Private; let note_execution_hint = NoteExecutionHint::None; let merged_value = - merge_id_type_and_hint_tag(sender_second_felt, note_type, note_execution_hint); - let (extracted_second_felt, extracted_note_type, extracted_note_execution_hint_tag) = + merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint); + let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) = unmerge_id_type_and_hint_tag(merged_value).unwrap(); assert_eq!(note_type, extracted_note_type); assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag); - assert_eq!(sender_second_felt, extracted_second_felt); + assert_eq!(sender_id_suffix, extracted_suffix); } } diff --git a/objects/src/notes/note_tag.rs b/objects/src/notes/note_tag.rs index eaf8f6a0f..b53e4f90c 100644 --- a/objects/src/notes/note_tag.rs +++ b/objects/src/notes/note_tag.rs @@ -96,11 +96,11 @@ impl NoteTag { ) -> Result { match execution { NoteExecutionMode::Local => { - let first_felt_id: u64 = account_id.first_felt().into(); + let prefix_id: u64 = account_id.prefix().into(); // Shift the high bits of the account ID such that they are layed out as: // [34 zero bits | remaining high bits (30 bits)]. - let high_bits = first_felt_id >> 34; + let high_bits = prefix_id >> 34; // This is equivalent to the following layout, interpreted as a u32: // [2 zero bits | remaining high bits (30 bits)]. @@ -118,11 +118,11 @@ impl NoteTag { if !account_id.is_public() { Err(NoteError::NetworkExecutionRequiresOnChainAccount) } else { - let first_felt_id: u64 = account_id.first_felt().into(); + let prefix_id: u64 = account_id.prefix().into(); // Shift the high bits of the account ID such that they are layed out as: // [34 zero bits | remaining high bits (30 bits)]. - let high_bits = first_felt_id >> 34; + let high_bits = prefix_id >> 34; // This is equivalent to the following layout, interpreted as a u32: // [2 zero bits | remaining high bits (30 bits)]. @@ -354,13 +354,13 @@ mod tests { assert_matches!( NoteTag::from_account_id(off_chain, NoteExecutionMode::Network).unwrap_err(), NoteError::NetworkExecutionRequiresOnChainAccount, - "Tag generation must fail if network execution and off-chain account id are mixed" + "Tag generation must fail if network execution and off-chain account ID are mixed" ) } for on_chain in on_chain_accounts { let tag = NoteTag::from_account_id(on_chain, NoteExecutionMode::Network) - .expect("tag generation must work with network execution and on-chain account id"); + .expect("tag generation must work with network execution and on-chain account ID"); assert!(tag.is_single_target()); assert_eq!(tag.execution_mode(), NoteExecutionMode::Network); @@ -378,7 +378,7 @@ mod tests { for off_chain in off_chain_accounts { let tag = NoteTag::from_account_id(off_chain, NoteExecutionMode::Local) - .expect("tag generation must work with local execution and off-chain account id"); + .expect("tag generation must work with local execution and off-chain account ID"); assert!(!tag.is_single_target()); assert_eq!(tag.execution_mode(), NoteExecutionMode::Local); @@ -392,7 +392,7 @@ mod tests { for on_chain in on_chain_accounts { let tag = NoteTag::from_account_id(on_chain, NoteExecutionMode::Local) - .expect("Tag generation must work with local execution and on-chain account id"); + .expect("Tag generation must work with local execution and on-chain account ID"); assert!(!tag.is_single_target()); assert_eq!(tag.execution_mode(), NoteExecutionMode::Local); diff --git a/objects/src/testing/account_id.rs b/objects/src/testing/account_id.rs index fcd065c52..e3a1455ef 100644 --- a/objects/src/testing/account_id.rs +++ b/objects/src/testing/account_id.rs @@ -1,83 +1,83 @@ use rand::SeedableRng; -use crate::accounts::{AccountId, AccountStorageMode, AccountType}; +use crate::accounts::{AccountId, AccountIdV0, AccountIdVersion, AccountStorageMode, AccountType}; // CONSTANTS // -------------------------------------------------------------------------------------------- // REGULAR ACCOUNTS - OFF-CHAIN -pub const ACCOUNT_ID_SENDER: u128 = account_id::( +pub const ACCOUNT_ID_SENDER: u128 = account_id( AccountType::RegularAccountImmutableCode, AccountStorageMode::Private, 0xfabb_ccde, ); -pub const ACCOUNT_ID_OFF_CHAIN_SENDER: u128 = account_id::( +pub const ACCOUNT_ID_OFF_CHAIN_SENDER: u128 = account_id( AccountType::RegularAccountImmutableCode, AccountStorageMode::Private, 0xbfcc_dcee, ); -pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN: u128 = account_id::( +pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN: u128 = account_id( AccountType::RegularAccountUpdatableCode, AccountStorageMode::Private, 0xccdd_eeff, ); // REGULAR ACCOUNTS - ON-CHAIN -pub const ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN: u128 = account_id::( +pub const ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN: u128 = account_id( AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, 0xaabb_ccdd, ); -pub const ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2: u128 = account_id::( +pub const ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN_2: u128 = account_id( AccountType::RegularAccountImmutableCode, AccountStorageMode::Public, 0xbbcc_ddee, ); -pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN: u128 = account_id::( +pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN: u128 = account_id( AccountType::RegularAccountUpdatableCode, AccountStorageMode::Public, 0xacdd_eefc, ); -pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2: u128 = account_id::( +pub const ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2: u128 = account_id( AccountType::RegularAccountUpdatableCode, AccountStorageMode::Public, 0xeeff_ccdd, ); -// These faucet IDs all have a unique first and second felt. This is to ensure that when they +// These faucet IDs all have a unique prefix and suffix felts. This is to ensure that when they // are used to issue an asset they don't cause us to run into the "multiple leaf" case when // calling std::collections::smt::{set,get} which doesn't support the "multiple leaf" case at // this time. // FUNGIBLE TOKENS - OFF-CHAIN pub const ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN: u128 = - account_id::(AccountType::FungibleFaucet, AccountStorageMode::Private, 0xfabb_cddd); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Private, 0xfabb_cddd); // FUNGIBLE TOKENS - ON-CHAIN pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN: u128 = - account_id::(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xaabc_bcde); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xaabc_bcde); pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_1: u128 = - account_id::(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xbaca_ddef); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xbaca_ddef); pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_2: u128 = - account_id::(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xccdb_eefa); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xccdb_eefa); pub const ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN_3: u128 = - account_id::(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xeeff_cc99); + account_id(AccountType::FungibleFaucet, AccountStorageMode::Public, 0xeeff_cc99); // NON-FUNGIBLE TOKENS - OFF-CHAIN pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_OFF_CHAIN: u128 = - account_id::(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0xaabc_ccde); + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0xaabc_ccde); // NON-FUNGIBLE TOKENS - ON-CHAIN pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN: u128 = - account_id::(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0xbcca_ddef); + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0xbcca_ddef); pub const ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN_1: u128 = - account_id::(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0xccdf_eefa); + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Public, 0xccdf_eefa); // TEST ACCOUNT IDs WITH CERTAIN PROPERTIES /// The Account Id with the maximum possible one bits. pub const ACCOUNT_ID_MAX_ONES: u128 = - account_id::(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0) + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0) | 0x7fff_ffff_ffff_ff00_7fff_ffff_ffff_ff00; /// The Account Id with the maximum possible zero bits. pub const ACCOUNT_ID_MAX_ZEROES: u128 = - account_id::(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0x001f_0000); + account_id(AccountType::NonFungibleFaucet, AccountStorageMode::Private, 0x001f_0000); // UTILITIES // -------------------------------------------------------------------------------------------- @@ -95,15 +95,15 @@ pub const ACCOUNT_ID_MAX_ZEROES: u128 = /// 1st felt: [0xaa | 5 zero bytes | 0xbb | metadata byte] /// 2nd felt: [2 zero bytes (epoch) | 0xcc | 3 zero bytes | 0xdd | zero byte] /// ``` -pub const fn account_id( +pub const fn account_id( account_type: AccountType, storage_mode: AccountStorageMode, random: u32, ) -> u128 { - let mut first_felt: u64 = 0; + let mut prefix: u64 = 0; - first_felt |= (account_type as u64) << AccountId::TYPE_SHIFT; - first_felt |= (storage_mode as u64) << AccountId::STORAGE_MODE_SHIFT; + prefix |= (account_type as u64) << AccountIdV0::TYPE_SHIFT; + prefix |= (storage_mode as u64) << AccountIdV0::STORAGE_MODE_SHIFT; // Produce non-trivial IDs by distributing the random value. let random_1st_felt_upper = random & 0xff00_0000; @@ -112,10 +112,10 @@ pub const fn account_id( let random_2nd_felt_lower = random & 0x0000_00ff; // Shift the random part of the ID to start at the most significant end. - first_felt |= (random_1st_felt_upper as u64) << 32; - first_felt |= (random_1st_felt_lower as u64) >> 8; + prefix |= (random_1st_felt_upper as u64) << 32; + prefix |= (random_1st_felt_lower as u64) >> 8; - let mut id = (first_felt as u128) << 64; + let mut id = (prefix as u128) << 64; id |= (random_2nd_felt_upper as u128) << 32; id |= (random_2nd_felt_lower as u128) << 8; @@ -185,7 +185,7 @@ impl AccountIdBuilder { None => rng.gen(), }; - AccountId::dummy(rng.gen(), account_type, storage_mode) + AccountId::dummy(rng.gen(), AccountIdVersion::Version0, account_type, storage_mode) } /// Builds an [`AccountId`] using the provided seed as input for an RNG implemented in diff --git a/objects/src/testing/block.rs b/objects/src/testing/block.rs index 6a324b4f3..5b3054e86 100644 --- a/objects/src/testing/block.rs +++ b/objects/src/testing/block.rs @@ -52,8 +52,17 @@ impl BlockHeader { }; #[cfg(target_family = "wasm")] - let (prev_hash, chain_root, nullifier_root, note_root, tx_hash, proof_hash, timestamp) = - Default::default(); + let (prev_hash, chain_root, nullifier_root, note_root, tx_hash, proof_hash, timestamp) = { + ( + Default::default(), + chain_root.unwrap_or_default(), + Default::default(), + note_root.unwrap_or_default(), + Default::default(), + Default::default(), + Default::default(), + ) + }; BlockHeader::new( 0, diff --git a/objects/src/testing/storage.rs b/objects/src/testing/storage.rs index c6e6f5189..7d746ac3e 100644 --- a/objects/src/testing/storage.rs +++ b/objects/src/testing/storage.rs @@ -1,23 +1,14 @@ use alloc::{collections::BTreeMap, string::String, vec::Vec}; -use assembly::Assembler; use miden_crypto::EMPTY_WORD; -use vm_core::{Felt, FieldElement, Word, ZERO}; +use vm_core::{Felt, Word}; use vm_processor::Digest; -use super::{constants::FUNGIBLE_FAUCET_INITIAL_BALANCE, prepare_word}; +use super::prepare_word; use crate::{ - accounts::{ - Account, AccountId, AccountIdAnchor, AccountIdVersion, AccountStorage, AccountStorageDelta, - AccountStorageMode, AccountType, StorageMap, StorageMapDelta, StorageSlot, - }, + accounts::{AccountStorage, AccountStorageDelta, StorageMap, StorageMapDelta, StorageSlot}, notes::NoteAssets, - testing::account_id::{ - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, - }, - AccountDeltaError, BlockHeader, + AccountDeltaError, }; // ACCOUNT STORAGE DELTA BUILDER @@ -125,106 +116,6 @@ impl AccountStorage { } } -// ACCOUNT SEED GENERATION -// ================================================================================================ - -pub enum AccountSeedType { - FungibleFaucetInvalidInitialBalance, - FungibleFaucetValidInitialBalance, - NonFungibleFaucetInvalidReservedSlot, - NonFungibleFaucetValidReservedSlot, - RegularAccountUpdatableCodeOnChain, - RegularAccountUpdatableCodeOffChain, -} - -/// Returns the account id and seed for the specified account type. -/// -/// TODO: Not all variants are needed anymore, remove unneeded parts. -pub fn generate_account_seed( - account_seed_type: AccountSeedType, - anchor_block_header: &BlockHeader, - assembler: Assembler, -) -> (Account, AccountId, Word) { - let init_seed: [u8; 32] = Default::default(); - - let (account, account_type) = match account_seed_type { - AccountSeedType::FungibleFaucetInvalidInitialBalance => ( - Account::mock_fungible_faucet( - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, - ZERO, - Felt::new(FUNGIBLE_FAUCET_INITIAL_BALANCE), - assembler, - ), - AccountType::FungibleFaucet, - ), - AccountSeedType::FungibleFaucetValidInitialBalance => ( - Account::mock_fungible_faucet( - ACCOUNT_ID_FUNGIBLE_FAUCET_ON_CHAIN, - ZERO, - ZERO, - assembler, - ), - AccountType::FungibleFaucet, - ), - AccountSeedType::NonFungibleFaucetInvalidReservedSlot => ( - Account::mock_non_fungible_faucet( - ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, - ZERO, - false, - assembler, - ), - AccountType::NonFungibleFaucet, - ), - AccountSeedType::NonFungibleFaucetValidReservedSlot => ( - Account::mock_non_fungible_faucet( - ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, - ZERO, - true, - assembler, - ), - AccountType::NonFungibleFaucet, - ), - AccountSeedType::RegularAccountUpdatableCodeOnChain => ( - Account::mock( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_ON_CHAIN, - Felt::ZERO, - assembler, - ), - AccountType::RegularAccountUpdatableCode, - ), - AccountSeedType::RegularAccountUpdatableCodeOffChain => ( - Account::mock( - ACCOUNT_ID_REGULAR_ACCOUNT_UPDATABLE_CODE_OFF_CHAIN, - Felt::ZERO, - assembler, - ), - AccountType::RegularAccountUpdatableCode, - ), - }; - - let seed = AccountId::compute_account_seed( - init_seed, - account_type, - AccountStorageMode::Public, - AccountIdVersion::VERSION_0, - account.code().commitment(), - account.storage().commitment(), - anchor_block_header.hash(), - ) - .unwrap(); - - let anchor = AccountIdAnchor::try_from(anchor_block_header).unwrap(); - let account_id = - AccountId::new(seed, anchor, account.code().commitment(), account.storage().commitment()) - .unwrap(); - - // Overwrite old ID with generated ID. - let (_, vault, storage, code, nonce) = account.into_parts(); - let account = Account::from_parts(account_id, vault, storage, code, nonce); - - (account, account_id, seed) -} - // UTILITIES // -------------------------------------------------------------------------------------------- diff --git a/objects/src/transaction/chain_mmr.rs b/objects/src/transaction/chain_mmr.rs index 2519962e1..de0e1af61 100644 --- a/objects/src/transaction/chain_mmr.rs +++ b/objects/src/transaction/chain_mmr.rs @@ -19,7 +19,8 @@ use crate::{ /// /// [ChainMmr] represents a partial view into the actual MMR and contains authentication paths /// for a limited set of blocks. The intent is to include only the blocks relevant for execution -/// of a specific transaction (i.e., the blocks corresponding to all input notes). +/// of a specific transaction (i.e., the blocks corresponding to all input notes and the one needed +/// to validate the seed of a new account, if applicable). #[derive(Debug, Clone, PartialEq, Eq)] pub struct ChainMmr { /// Partial view of the Chain MMR with authentication paths for the blocks listed below. diff --git a/objects/src/transaction/inputs.rs b/objects/src/transaction/inputs.rs index fc450a4e8..33c16833f 100644 --- a/objects/src/transaction/inputs.rs +++ b/objects/src/transaction/inputs.rs @@ -497,6 +497,7 @@ pub fn validate_account_seed( let account_id = AccountId::new( seed, anchor, + account.id().version(), account.code().commitment(), account.storage().commitment(), ) diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 149464f7c..492ecf5f0 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -285,7 +285,7 @@ impl ProvenTransactionBuilder { /// # Errors /// /// An error will be returned if an on-chain account is used without provided on-chain detail. - /// Or if the account details, i.e. account id and final hash, don't match the transaction. + /// Or if the account details, i.e. account ID and final hash, don't match the transaction. pub fn build(self) -> Result { let input_notes = InputNotes::new(self.input_notes).map_err(ProvenTransactionError::InputNotesError)?;