diff --git a/CHANGELOG.md b/CHANGELOG.md index f7626770e..e2751fda4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [BREAKING] Allowed account components to share identical account code procedures ([#2164](https://github.com/0xMiden/miden-base/pull/2164)). - Add `From<&ExecutedTransaction> for TransactionHeader` implementation ([#2178](https://github.com/0xMiden/miden-base/pull/2178)). - Add `AccountId::parse()` helper function to parse both hex and bech32 formats ([#2223](https://github.com/0xMiden/miden-base/pull/2223)). +- Added `note::build_note_tag_for_local_account` procedure ([#2235](https://github.com/0xMiden/miden-base/pull/2235)). ### Changes diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 284c68d4e..fda0dcd34 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -252,3 +252,62 @@ pub proc build_note_tag_for_network_account drop # => [network_account_tag] end + +#! Constructs a LocalAny note tag from the given account_id. +#! +#! This procedure implements the same logic as the Rust NoteTag::from_local_account_id() +#! but assumes tag_len to be 14 bits. +#! +#! The tag is constructed as follows: +#! - The two most significant bits are set to `0b11` to indicate a LOCAL_ANY tag. +#! - The next 14 bits are set to the most significant bits of the account ID prefix. +#! - The remaining bits are set to zero. +#! +#! Inputs: [account_id_prefix, account_id_suffix] +#! Outputs: [local_account_tag] +#! +#! Invocation: exec +pub proc build_note_tag_for_local_account + # => [account_id_prefix, account_id_suffix] + + # Drop the suffix as we only need the prefix + swap drop + # => [account_id_prefix] + + # Convert prefix to u64 by splitting into high and low parts + u32split + # => [prefix_hi, prefix_lo] + + # Shift the high bits of the account ID such that they are laid out as: + # [34 zero bits | remaining high bits (30 bits)] + push.2 + # => [2, prefix_hi, prefix_lo] + + exec.u64::shr + # => [shifted_hi, shifted_lo] + + # This is equivalent to the following layout, interpreted as a u32: + # [2 zero bits | remaining high bits (30 bits)] + swap drop + # => [high_bits] + + # Select the top 14 bits of the account ID, i.e.: + # [2 zero bits | remaining high bits (14 bits) | (30 - 14) zero bits] + # Create mask: u32::MAX << (32 - 2 - 14) = u32::MAX << 16 + push.4294967295 + push.16 + # => [16, u32::MAX, high_bits] + + u32shl + # => [mask, high_bits] + + u32and + # => [masked_high_bits] + + # Set the local execution tag in the two most significant bits + push.3221225472 # 0xc0000000 = LOCAL_ANY + # => [LOCAL_ANY, masked_high_bits] + + u32or + # => [local_account_tag] +end diff --git a/crates/miden-testing/src/kernel_tests/tx/test_note.rs b/crates/miden-testing/src/kernel_tests/tx/test_note.rs index 8bee85627..a1e3b3d89 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -24,6 +24,7 @@ use miden_protocol::note::{ }; use miden_protocol::testing::account_id::{ ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET, + ACCOUNT_ID_PRIVATE_SENDER, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_SENDER, }; @@ -594,3 +595,39 @@ async fn test_build_note_tag_for_network_account() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test] +async fn test_build_note_tag_for_local_account() -> anyhow::Result<()> { + let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; + + let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER)?; + let expected_tag = NoteTag::from_account_id(account_id).as_u32(); + + let prefix: u64 = account_id.prefix().into(); + let suffix: u64 = account_id.suffix().into(); + + let code = format!( + " + use miden::core::sys + use miden::protocol::note + + begin + push.{suffix}.{prefix} + + exec.note::build_note_tag_for_local_account + # => [local_account_tag] + + exec.sys::truncate_stack + end + ", + suffix = suffix, + prefix = prefix, + ); + + let exec_output = tx_context.execute_code(&code).await?; + let actual_tag = exec_output.stack[0].as_int(); + + assert_eq!(expected_tag, actual_tag as u32,); + + Ok(()) +}