diff --git a/CHANGELOG.md b/CHANGELOG.md index 29472d9d2a..7c85213598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - [BREAKING] refactored `TransactionAuthenticator::get_public_key()` method to return `Arc `instead of `&PublicKey` ([#2304](https://github.com/0xMiden/miden-base/pull/2304)). - [BREAKING] Renamed `NoteInputs` to `NoteStorage` to better reflect that values are stored data associated with a note rather than inputs ([#1662](https://github.com/0xMiden/miden-base/issues/1662), [#2316](https://github.com/0xMiden/miden-base/issues/2316)). - Removed `NoteType::Encrypted` ([#2315](https://github.com/0xMiden/miden-base/pull/2315)). +- Updated note tag length to support up to 32 bits ([#2329](https://github.com/0xMiden/miden-base/pull/2329)). ## 0.13.3 (2026-01-27) diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index daa8e97e5e..cf8132ae72 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -260,12 +260,10 @@ pub proc build_note_tag_for_network_account u32split # => [a_hi, a_lo] - push.34 - # => [b, a_hi, a_lo] + # mask out the 14 (NoteTag::DEFAULT_ACCOUNT_TARGET_TAG_LENGTH) most significant bits + u32and.0xfffc0000 + # => [a_hi_masked, a_lo] - exec.u64::shr - # => [c_hi, c_lo] - - drop + swap drop # => [network_account_tag] end diff --git a/crates/miden-protocol/src/address/address_id.rs b/crates/miden-protocol/src/address/address_id.rs index e6cdbdddeb..9cad626bfb 100644 --- a/crates/miden-protocol/src/address/address_id.rs +++ b/crates/miden-protocol/src/address/address_id.rs @@ -4,10 +4,9 @@ use bech32::Bech32m; use bech32::primitives::decode::CheckedHrpstring; use miden_processor::DeserializationError; -use crate::account::{AccountId, AccountStorageMode}; +use crate::account::AccountId; use crate::address::{AddressType, NetworkId}; use crate::errors::{AddressError, Bech32Error}; -use crate::note::NoteTag; use crate::utils::serde::{ByteWriter, Deserializable, Serializable}; /// The identifier of an [`Address`](super::Address). @@ -27,23 +26,6 @@ impl AddressId { } } - /// Returns the default tag length of the ID. - /// - /// This is guaranteed to be in range `0..=30` (e.g. the maximum of - /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`] and - /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]). - pub fn default_note_tag_len(&self) -> u8 { - match self { - AddressId::AccountId(id) => { - if id.storage_mode() == AccountStorageMode::Network { - NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH - } else { - NoteTag::DEFAULT_LOCAL_ACCOUNT_TARGET_TAG_LENGTH - } - }, - } - } - /// Decodes a bech32 string into an identifier. pub(crate) fn decode(bech32_string: &str) -> Result<(NetworkId, Self), AddressError> { // We use CheckedHrpString with an explicit checksum algorithm so we don't allow the diff --git a/crates/miden-protocol/src/address/mod.rs b/crates/miden-protocol/src/address/mod.rs index 91c9438569..225fb1b0a4 100644 --- a/crates/miden-protocol/src/address/mod.rs +++ b/crates/miden-protocol/src/address/mod.rs @@ -1,5 +1,4 @@ mod r#type; -use alloc::string::ToString; pub use r#type::AddressType; @@ -16,7 +15,6 @@ pub use interface::AddressInterface; use miden_processor::DeserializationError; pub use network_id::{CustomNetworkId, NetworkId}; -use crate::account::AccountStorageMode; use crate::crypto::ies::SealingKey; use crate::errors::AddressError; use crate::note::NoteTag; @@ -79,35 +77,13 @@ impl Address { Self { id: id.into(), routing_params: None } } - /// For local (both public and private) accounts, up to 30 bits can be encoded into the tag. - /// For network accounts, the tag length must be set to 30 bits. - /// - /// # Errors - /// - /// Returns an error if: - /// - The tag length routing parameter is not - /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`] for network accounts. - pub fn with_routing_parameters( - mut self, - routing_params: RoutingParameters, - ) -> Result { - if let Some(tag_len) = routing_params.note_tag_len() { - match self.id { - AddressId::AccountId(account_id) => { - if account_id.storage_mode() == AccountStorageMode::Network - && tag_len != NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH - { - return Err(AddressError::CustomTagLengthNotAllowedForNetworkAccounts( - tag_len, - )); - } - }, - } - } - + /// Sets the routing parameters of the address. + /// Validation of tag length, interface, and encryption key is handled + /// internally by [`RoutingParameters`]. This method simply attaches the + /// provided parameters to the address. + pub fn with_routing_parameters(mut self, routing_params: RoutingParameters) -> Self { self.routing_params = Some(routing_params); - - Ok(self) + self } // ACCESSORS @@ -125,14 +101,13 @@ impl Address { /// Returns the preferred tag length. /// - /// This is guaranteed to be in range `0..=30` (e.g. the maximum of - /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`] and - /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]). + /// This is guaranteed to be in range `0..=32` (e.g. the maximum of + /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH `]). pub fn note_tag_len(&self) -> u8 { self.routing_params .as_ref() .and_then(RoutingParameters::note_tag_len) - .unwrap_or(self.id.default_note_tag_len()) + .unwrap_or(NoteTag::DEFAULT_ACCOUNT_TARGET_TAG_LENGTH) } /// Returns a note tag derived from this address. @@ -140,15 +115,11 @@ impl Address { let note_tag_len = self.note_tag_len(); match self.id { - AddressId::AccountId(id) => { - match id.storage_mode() { - AccountStorageMode::Network => NoteTag::from_network_account_id(id), - AccountStorageMode::Private | AccountStorageMode::Public => { - NoteTag::with_custom_account_target(id, note_tag_len) - .expect("address should validate that tag len does not exceed MAX_ACCOUNT_TARGET_TAG_LENGTH bits") - } - } - }, + AddressId::AccountId(id) => NoteTag::with_custom_account_target(id, note_tag_len) + .expect( + "address should validate that tag len does not exceed \ + MAX_ACCOUNT_TARGET_TAG_LENGTH bits", + ), } } @@ -201,7 +172,7 @@ impl Address { if let Some(encoded_routing_params) = split.next() { let routing_params = RoutingParameters::decode(encoded_routing_params.to_owned())?; - address = address.with_routing_parameters(routing_params)?; + address = address.with_routing_parameters(routing_params); } Ok((network_id, address)) @@ -225,9 +196,7 @@ impl Deserializable for Address { let mut address = Self::new(identifier); if let Some(routing_params) = routing_params { - address = address - .with_routing_parameters(routing_params) - .map_err(|err| DeserializationError::InvalidValue(err.to_string()))?; + address = address.with_routing_parameters(routing_params); } Ok(address) @@ -246,7 +215,7 @@ mod tests { use bech32::{Bech32, Bech32m, NoChecksum}; use super::*; - use crate::account::{AccountId, AccountType}; + use crate::account::{AccountId, AccountStorageMode, AccountType}; use crate::address::CustomNetworkId; use crate::errors::{AccountIdError, Bech32Error}; use crate::testing::account_id::{ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, AccountIdBuilder}; @@ -302,8 +271,8 @@ mod tests { // Encode/Decode with routing parameters should be valid. address = address.with_routing_parameters( RoutingParameters::new(AddressInterface::BasicWallet) - .with_note_tag_len(NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH)?, - )?; + .with_note_tag_len(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)?, + ); let bech32_string = address.encode(network_id.clone()); assert!( @@ -346,7 +315,7 @@ mod tests { let account_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET)?; let address = Address::new(account_id).with_routing_parameters( RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(14)?, - )?; + ); let bech32_string = address.encode(network_id); let mut invalid_bech32_1 = bech32_string.clone(); @@ -431,8 +400,8 @@ mod tests { let account_id = AccountIdBuilder::new().account_type(account_type).build_with_rng(rng); let address = Address::new(account_id).with_routing_parameters( RoutingParameters::new(AddressInterface::BasicWallet) - .with_note_tag_len(NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH)?, - )?; + .with_note_tag_len(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)?, + ); let serialized = address.to_bytes(); let deserialized = Address::read_from_bytes(&serialized)?; @@ -463,7 +432,7 @@ mod tests { let address = Address::new(account_id).with_routing_parameters( RoutingParameters::new(AddressInterface::BasicWallet) .with_encryption_key(sealing_key.clone()), - )?; + ); // Verify encryption key is present let retrieved_key = @@ -503,7 +472,7 @@ mod tests { let address = Address::new(account_id).with_routing_parameters( RoutingParameters::new(AddressInterface::BasicWallet) .with_encryption_key(sealing_key.clone()), - )?; + ); // Encode and decode let encoded = address.encode(NetworkId::Mainnet); @@ -521,4 +490,19 @@ mod tests { Ok(()) } + + #[test] + fn address_allows_max_note_tag_len() -> anyhow::Result<()> { + let account_id = AccountIdBuilder::new() + .account_type(AccountType::RegularAccountImmutableCode) + .build_with_rng(&mut rand::rng()); + + let address = Address::new(account_id).with_routing_parameters( + RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(32)?, + ); + + assert_eq!(address.note_tag_len(), 32); + + Ok(()) + } } diff --git a/crates/miden-protocol/src/address/routing_parameters.rs b/crates/miden-protocol/src/address/routing_parameters.rs index 0fb5ec6923..24789f8acf 100644 --- a/crates/miden-protocol/src/address/routing_parameters.rs +++ b/crates/miden-protocol/src/address/routing_parameters.rs @@ -33,12 +33,12 @@ const BECH32_SEPARATOR: &str = "1"; /// The value to encode the absence of a note tag routing parameter (i.e. `None`). /// -/// The note tag length occupies 5 bits (values 0..=31). Valid tag lengths are 0..=30, -/// so we reserve the maximum 5-bit value (31) to represent `None`. +/// The note tag length occupies 6 bits (values 0..=63). Valid tag lengths are 0..=32, +/// so we reserve the maximum 6-bit value (63) to represent `None`. /// /// If the note tag length is absent from routing parameters, the note tag length for the address /// will be set to the default default tag length of the address' ID component. -const ABSENT_NOTE_TAG_LEN: u8 = (1 << 5) - 1; // 31 +const ABSENT_NOTE_TAG_LEN: u8 = 63; /// The routing parameter key for the receiver profile. const RECEIVER_PROFILE_PARAM_KEY: u8 = 0; @@ -92,8 +92,7 @@ impl RoutingParameters { /// # Errors /// /// Returns an error if: - /// - The tag length exceeds the maximum of [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`] and - /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]. + /// - The tag length exceeds the maximum of [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH `]. pub fn with_note_tag_len(mut self, note_tag_len: u8) -> Result { if note_tag_len > NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH { return Err(AddressError::TagLengthTooLarge(note_tag_len)); @@ -108,9 +107,8 @@ impl RoutingParameters { /// Returns the note tag length preference. /// - /// This is guaranteed to be in range `0..=30` (e.g. the maximum of - /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`] and - /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]). + /// This is guaranteed to be in range `0..=32` (e.g. the maximum of + /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`]). pub fn note_tag_len(&self) -> Option { self.note_tag_len } @@ -269,11 +267,11 @@ fn encode_receiver_profile(interface: AddressInterface, note_tag_len: Option let note_tag_len = note_tag_len.unwrap_or(ABSENT_NOTE_TAG_LEN); let interface = interface as u16; - debug_assert_eq!(interface >> 11, 0, "address interface should have its upper 5 bits unset"); + debug_assert_eq!(interface >> 10, 0, "address interface should fit into 10 bits"); - // The interface takes up 11 bits and the tag length 5 bits, so we can merge them + // The interface takes up 10 bits and the tag length 6 bits, so we can merge them // together. - let tag_len = (note_tag_len as u16) << 11; + let tag_len = (note_tag_len as u16) << 10; let receiver_profile: u16 = tag_len | interface; receiver_profile.to_be_bytes() } @@ -290,14 +288,16 @@ fn decode_receiver_profile( let byte1 = byte_iter.next().expect("byte1 should exist"); let receiver_profile = u16::from_be_bytes([byte0, byte1]); - let tag_len = (receiver_profile >> 11) as u8; - let note_tag_len = if tag_len == ABSENT_NOTE_TAG_LEN { - None - } else { - Some(tag_len) + let tag_len = (receiver_profile >> 10) as u8; + let note_tag_len = match tag_len { + ABSENT_NOTE_TAG_LEN => None, + 0..=32 => Some(tag_len), + _ => { + return Err(AddressError::decode_error(format!("invalid note tag length {}", tag_len))); + }, }; - let addr_interface = receiver_profile & 0b0000_0111_1111_1111; + let addr_interface = receiver_profile & 0b0000_0011_1111_1111; let addr_interface = AddressInterface::try_from(addr_interface).map_err(|err| { AddressError::decode_error_with_source("failed to decode address interface", err) })?; diff --git a/crates/miden-protocol/src/errors/mod.rs b/crates/miden-protocol/src/errors/mod.rs index 25df83059d..329156c301 100644 --- a/crates/miden-protocol/src/errors/mod.rs +++ b/crates/miden-protocol/src/errors/mod.rs @@ -302,7 +302,7 @@ pub enum AccountTreeError { #[derive(Debug, Error)] pub enum AddressError { #[error("tag length {0} should be {expected} bits for network accounts", - expected = NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH + expected = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH )] CustomTagLengthNotAllowedForNetworkAccounts(u8), #[error("tag length {0} is too large, must be less than or equal to {max}", diff --git a/crates/miden-protocol/src/note/note_tag.rs b/crates/miden-protocol/src/note/note_tag.rs index 3c1ee2242a..5e63212bc8 100644 --- a/crates/miden-protocol/src/note/note_tag.rs +++ b/crates/miden-protocol/src/note/note_tag.rs @@ -11,8 +11,6 @@ use super::{ NoteError, Serializable, }; -use crate::account::AccountStorageMode; - // NOTE TAG // ================================================================================================ @@ -66,11 +64,9 @@ impl NoteTag { // -------------------------------------------------------------------------------------------- /// The default note tag length for an account ID with local execution. - pub const DEFAULT_LOCAL_ACCOUNT_TARGET_TAG_LENGTH: u8 = 14; - /// The default note tag length for an account ID with network execution. - pub const DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH: u8 = 30; + pub const DEFAULT_ACCOUNT_TARGET_TAG_LENGTH: u8 = 14; /// The maximum number of bits that can be encoded into the tag for local accounts. - pub const MAX_ACCOUNT_TARGET_TAG_LENGTH: u8 = 30; + pub const MAX_ACCOUNT_TARGET_TAG_LENGTH: u8 = 32; // CONSTRUCTORS // -------------------------------------------------------------------------------------------- @@ -82,27 +78,18 @@ impl NoteTag { /// Constructs a note tag that targets the given `account_id`. /// - /// The tag is constructed as follows: + /// The tag is a 32-bit value constructed as follows: + /// + /// - The tag is derived from the account ID *prefix*. + /// - The most significant `DEFAULT_ACCOUNT_TARGET_TAG_LENGTH` bits of the 32-bit prefix are + /// preserved. + /// - All remaining least significant bits are set to `0`. /// - /// - For local execution ([`AccountStorageMode::Private`] or [`AccountStorageMode::Public`]), - /// the two most significant bits are set to `0b00`. The following 14 bits are set to the most - /// significant bits of the account ID, and the remaining 16 bits are set to 0. - /// - For network execution ([`AccountStorageMode::Network`]), the most significant bits are set - /// to `0b00` and the remaining bits are set to the 30 most significant bits of the account - /// ID. + /// The number of account-prefix bits included in the tag is determined by + /// `DEFAULT_ACCOUNT_TARGET_TAG_LENGTH`. pub fn with_account_target(account_id: AccountId) -> Self { - match account_id.storage_mode() { - AccountStorageMode::Network => Self::from_network_account_id(account_id), - AccountStorageMode::Private | AccountStorageMode::Public => { - // safe to unwrap since DEFAULT_LOCAL_ACCOUNT_TARGET_TAG_LENGTH < - // MAX_ACCOUNT_TARGET_TAG_LENGTH - Self::with_custom_account_target( - account_id, - Self::DEFAULT_LOCAL_ACCOUNT_TARGET_TAG_LENGTH, - ) - .unwrap() - }, - } + Self::with_custom_account_target(account_id, Self::DEFAULT_ACCOUNT_TARGET_TAG_LENGTH) + .expect("default account target tag length must be valid") } /// Constructs a note tag that targets the given `account_id` with a custom `tag_len`. @@ -123,39 +110,13 @@ impl NoteTag { return Err(NoteError::NoteTagLengthTooLarge(tag_len)); } - let prefix_id: u64 = account_id.prefix().into(); - - // Shift the high bits of the account ID such that they are laid out as: - // [34 zero bits | remaining high bits (30 bits)]. - 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)]. - let high_bits = high_bits as u32; - - // Select the top `tag_len` bits of the account ID, i.e.: - // [2 zero bits | remaining high bits (tag_len bits) | (30 - tag_len) zero bits]. - let high_bits = high_bits & (u32::MAX << (32 - 2 - tag_len)); - - Ok(Self(high_bits)) - } - - /// Constructs a network account note tag from the specified `account_id`. - /// - /// The tag is constructed as follows: - /// - /// - The two most significant bits are set to `0b00`. - /// - The remaining bits are set to the 30 most significant bits of the account ID. - pub(crate) fn from_network_account_id(account_id: AccountId) -> Self { - let prefix_id: u64 = account_id.prefix().into(); - - // Shift the high bits of the account ID such that they are laid out as: - // [34 zero bits | remaining high bits (30 bits)]. - 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)]. - Self(high_bits as u32) + let prefix = account_id.prefix().as_u64(); + // Get the high bits as a u32. + let high_bits = (prefix >> 32) as u32; + // Create a mask that zeros out the lower 32 - len bits. + let mask = u32::MAX.checked_shl(u32::BITS - tag_len as u32).unwrap_or(0); + let tag = high_bits & mask; + Ok(Self(tag)) } // PUBLIC ACCESSORS @@ -220,7 +181,7 @@ impl Deserializable for NoteTag { mod tests { use super::NoteTag; - use crate::account::AccountId; + use crate::account::{AccountId, AccountStorageMode}; use crate::testing::account_id::{ ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET, ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET, @@ -240,6 +201,7 @@ mod tests { ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE_ON_CHAIN_2, ACCOUNT_ID_SENDER, + AccountIdBuilder, }; #[test] @@ -250,6 +212,9 @@ mod tests { AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap(), AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(), AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(), + AccountIdBuilder::new() + .storage_mode(AccountStorageMode::Private) + .build_with_seed([2; 32]), ]; let public_accounts = [ AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(), @@ -263,48 +228,44 @@ mod tests { AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_3).unwrap(), AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(), AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(), + AccountIdBuilder::new() + .storage_mode(AccountStorageMode::Public) + .build_with_seed([3; 32]), ]; let network_accounts = [ AccountId::try_from(ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE).unwrap(), AccountId::try_from(ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET).unwrap(), AccountId::try_from(ACCOUNT_ID_NETWORK_NON_FUNGIBLE_FAUCET).unwrap(), + AccountIdBuilder::new() + .storage_mode(AccountStorageMode::Network) + .build_with_seed([4; 32]), ]; - for account_id in private_accounts.iter().chain(public_accounts.iter()) { + for account_id in private_accounts + .iter() + .chain(public_accounts.iter()) + .chain(network_accounts.iter()) + { let tag = NoteTag::with_account_target(*account_id); - assert_eq!(tag.as_u32() >> 30, 0, "two most significant bits should be zero"); assert_eq!(tag.as_u32() << 16, 0, "16 least significant bits should be zero"); - assert_eq!( - (account_id.prefix().as_u64() >> 50) as u32, - tag.as_u32() >> 16, - "14 most significant bits should match" - ); - } + let expected = ((account_id.prefix().as_u64() >> 32) as u32) >> 16; + let actual = tag.as_u32() >> 16; - for account_id in network_accounts { - let tag = NoteTag::with_account_target(account_id); - assert_eq!(tag.as_u32() >> 30, 0, "two most significant bits should be zero"); - assert_eq!( - account_id.prefix().as_u64() >> 34, - tag.as_u32() as u64, - "30 most significant bits should match" - ); + assert_eq!(actual, expected, "14 most significant bits should match"); } } #[test] fn from_custom_account_target() -> anyhow::Result<()> { let account_id = AccountId::try_from(ACCOUNT_ID_SENDER)?; - let tag = NoteTag::with_custom_account_target( - account_id, - NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH, - )?; + let len = 32; + + let tag = NoteTag::with_custom_account_target(account_id, len)?; - assert_eq!(tag.as_u32() >> 30, 0, "two most significant bits should be zero"); assert_eq!( - (account_id.prefix().as_u64() >> 34) as u32, + (account_id.prefix().as_u64() >> 32) as u32, tag.as_u32(), - "30 most significant bits should match" + "32 most significant bits should match" ); Ok(()) 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 e147a3ccb0..82a2a78261 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -546,9 +546,10 @@ async fn test_build_note_tag_for_network_account() -> anyhow::Result<()> { let tx_context = TransactionContextBuilder::with_existing_mock_account().build()?; let account_id = AccountId::try_from(ACCOUNT_ID_NETWORK_FUNGIBLE_FAUCET)?; - let expected_tag = NoteTag::with_account_target(account_id).as_u32(); + // Network account rule: top 30 bits of the prefix let prefix: u64 = account_id.prefix().into(); + let expected_tag = NoteTag::with_account_target(account_id); let suffix: u64 = account_id.suffix().into(); let code = format!( @@ -557,7 +558,7 @@ async fn test_build_note_tag_for_network_account() -> anyhow::Result<()> { use miden::protocol::note begin - push.{suffix}.{prefix} + push.{suffix}.{prefix} exec.note::build_note_tag_for_network_account # => [network_account_tag] @@ -573,9 +574,11 @@ async fn test_build_note_tag_for_network_account() -> anyhow::Result<()> { let actual_tag = exec_output.stack[0].as_int(); assert_eq!( - actual_tag, expected_tag as u64, + actual_tag, + expected_tag.as_u32() as u64, "Expected tag {:#010x}, got {:#010x}", - expected_tag, actual_tag + expected_tag.as_u32(), + actual_tag ); Ok(())