From 68798fc6292d838d0307c7ac7e134e11d9625f17 Mon Sep 17 00:00:00 2001 From: swaploard Date: Thu, 22 Jan 2026 13:33:35 +0000 Subject: [PATCH 01/11] feat: update note tag length to support up to 32 bits for network accounts --- .../miden-protocol/src/address/address_id.rs | 14 +-- crates/miden-protocol/src/address/mod.rs | 53 ++++---- .../src/address/routing_parameters.rs | 20 +-- crates/miden-protocol/src/note/mod.rs | 2 +- crates/miden-protocol/src/note/note_tag.rs | 118 ++++++++---------- 5 files changed, 88 insertions(+), 119 deletions(-) diff --git a/crates/miden-protocol/src/address/address_id.rs b/crates/miden-protocol/src/address/address_id.rs index e6cdbdddeb..e09919f28e 100644 --- a/crates/miden-protocol/src/address/address_id.rs +++ b/crates/miden-protocol/src/address/address_id.rs @@ -4,7 +4,7 @@ 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; @@ -29,19 +29,11 @@ impl AddressId { /// Returns the default tag length of the ID. /// - /// This is guaranteed to be in range `0..=30` (e.g. the maximum of + /// This is guaranteed to be in range `0..=32` (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 - } - }, - } + NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH } /// Decodes a bech32 string into an identifier. diff --git a/crates/miden-protocol/src/address/mod.rs b/crates/miden-protocol/src/address/mod.rs index 91c9438569..a43b57b278 100644 --- a/crates/miden-protocol/src/address/mod.rs +++ b/crates/miden-protocol/src/address/mod.rs @@ -16,7 +16,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,8 +78,8 @@ 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. + /// For local (both public and private) accounts, up to 33 bits can be encoded into the tag. + /// For network accounts, the tag length must be set to 32 bits. /// /// # Errors /// @@ -91,22 +90,8 @@ impl Address { 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, - )); - } - }, - } - } - + // All validation should now live inside RoutingParameters itself. self.routing_params = Some(routing_params); - Ok(self) } @@ -125,7 +110,7 @@ impl Address { /// Returns the preferred tag length. /// - /// This is guaranteed to be in range `0..=30` (e.g. the maximum of + /// This is guaranteed to be in range `0..=32` (e.g. the maximum of /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`] and /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]). pub fn note_tag_len(&self) -> u8 { @@ -140,15 +125,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", + ), } } @@ -491,7 +472,6 @@ mod tests { // (FungibleFaucet) let account_id = AccountIdBuilder::new() .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) .build_with_rng(rng); // Create keypair @@ -521,4 +501,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..4ae6e3eab1 100644 --- a/crates/miden-protocol/src/address/routing_parameters.rs +++ b/crates/miden-protocol/src/address/routing_parameters.rs @@ -108,7 +108,7 @@ impl RoutingParameters { /// Returns the note tag length preference. /// - /// This is guaranteed to be in range `0..=30` (e.g. the maximum of + /// This is guaranteed to be in range `0..=32` (e.g. the maximum of /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`] and /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]). pub fn note_tag_len(&self) -> Option { @@ -269,11 +269,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 // 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 +290,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/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index 729f651f9e..0cd807d6ea 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -2,7 +2,7 @@ use miden_crypto::Word; use miden_crypto::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; use miden_processor::DeserializationError; -use crate::account::AccountId; +use crate::account::{AccountId, AccountStorageMode}; use crate::errors::NoteError; use crate::{Felt, Hasher, WORD_SIZE, ZERO}; diff --git a/crates/miden-protocol/src/note/note_tag.rs b/crates/miden-protocol/src/note/note_tag.rs index 3c1ee2242a..65f409a8dc 100644 --- a/crates/miden-protocol/src/note/note_tag.rs +++ b/crates/miden-protocol/src/note/note_tag.rs @@ -4,6 +4,7 @@ use miden_crypto::Felt; use super::{ AccountId, + AccountStorageMode, ByteReader, ByteWriter, Deserializable, @@ -11,8 +12,6 @@ use super::{ NoteError, Serializable, }; -use crate::account::AccountStorageMode; - // NOTE TAG // ================================================================================================ @@ -68,9 +67,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_NETWORK_ACCOUNT_TARGET_TAG_LENGTH: u8 = 32; /// 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 // -------------------------------------------------------------------------------------------- @@ -92,16 +91,15 @@ impl NoteTag { /// ID. 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() + AccountStorageMode::Network => { + let prefix = account_id.prefix().as_u64(); + Self((prefix >> 34) as u32) }, + _ => Self::with_custom_account_target( + account_id, + Self::DEFAULT_LOCAL_ACCOUNT_TARGET_TAG_LENGTH, + ) + .unwrap(), } } @@ -123,39 +121,16 @@ 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)); + let prefix = account_id.prefix().as_u64(); + let extracted = (prefix >> (64 - tag_len)) as u32; - Ok(Self(high_bits)) - } + let tag = if tag_len == 32 { + extracted + } else { + extracted << (32 - tag_len) + }; - /// 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) + Ok(Self(tag)) } // PUBLIC ACCESSORS @@ -251,6 +226,7 @@ mod tests { AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(), AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(), ]; + let public_accounts = [ AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(), AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(), @@ -264,30 +240,37 @@ mod tests { AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(), AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(), ]; + + for account_id in private_accounts.iter().chain(public_accounts.iter()) { + let tag = NoteTag::with_account_target(*account_id); + let tag_u32 = tag.as_u32(); + + let used_bits = 32 - tag_u32.leading_zeros(); + + if used_bits == 0 { + assert_eq!(tag_u32, 0, "tag should be zero when no bits are used"); + continue; + } + + let expected = (account_id.prefix().as_u64() >> (64 - used_bits)) as u32; + let actual = tag_u32 >> (32 - used_bits); + + assert_eq!(actual, expected, "top {used_bits} bits should match account prefix"); + } + 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(), ]; - for account_id in private_accounts.iter().chain(public_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" - ); - } - 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" + account_id.prefix().as_u64() >> 34, + "network account tag must match prefix >> 34" ); } } @@ -295,17 +278,14 @@ mod tests { #[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, - )?; - - assert_eq!(tag.as_u32() >> 30, 0, "two most significant bits should be zero"); - assert_eq!( - (account_id.prefix().as_u64() >> 34) as u32, - tag.as_u32(), - "30 most significant bits should match" - ); + let len = 32; + + let tag = NoteTag::with_custom_account_target(account_id, len)?; + + let prefix = account_id.prefix().as_u64(); + let expected = (prefix >> (64 - len)) as u32; + + assert_eq!(tag.as_u32(), expected, "full 32-bit tag should match account prefix"); Ok(()) } From 56975e430f935f873b54ccccb85559629db48d25 Mon Sep 17 00:00:00 2001 From: swaploard Date: Thu, 22 Jan 2026 13:38:42 +0000 Subject: [PATCH 02/11] chore: update changelog for note tag length support up to 32 bits --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a6099e0e..6734453691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,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)). +- update note tag length to support up to 32 bits. ## 0.13.2 (2026-01-21) From 504197e6b55690dd1060c82fc05536ab211022d0 Mon Sep 17 00:00:00 2001 From: swaploard Date: Mon, 26 Jan 2026 20:45:23 +0000 Subject: [PATCH 03/11] refactor: remove unused NoteTag references and update related logic for tag length handling --- .../miden-protocol/src/address/address_id.rs | 10 --- crates/miden-protocol/src/address/mod.rs | 45 +++++-------- .../src/address/routing_parameters.rs | 12 ++-- crates/miden-protocol/src/note/mod.rs | 2 +- crates/miden-protocol/src/note/note_tag.rs | 64 ++++++------------- .../src/kernel_tests/tx/test_note.rs | 11 ++-- .../tests/agglayer/bridge_out.rs | 6 -- 7 files changed, 49 insertions(+), 101 deletions(-) diff --git a/crates/miden-protocol/src/address/address_id.rs b/crates/miden-protocol/src/address/address_id.rs index e09919f28e..9cad626bfb 100644 --- a/crates/miden-protocol/src/address/address_id.rs +++ b/crates/miden-protocol/src/address/address_id.rs @@ -7,7 +7,6 @@ use miden_processor::DeserializationError; 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,15 +26,6 @@ impl AddressId { } } - /// Returns the default tag length of the ID. - /// - /// This is guaranteed to be in range `0..=32` (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 { - NoteTag::MAX_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 a43b57b278..113d6a8f4a 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; @@ -78,21 +77,11 @@ impl Address { Self { id: id.into(), routing_params: None } } - /// For local (both public and private) accounts, up to 33 bits can be encoded into the tag. - /// For network accounts, the tag length must be set to 32 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 { + /// Sets the routing parameters of the address. + pub fn with_routing_parameters(mut self, routing_params: RoutingParameters) -> Self { // All validation should now live inside RoutingParameters itself. self.routing_params = Some(routing_params); - Ok(self) + self } // ACCESSORS @@ -111,13 +100,12 @@ impl Address { /// Returns the preferred tag length. /// /// This is guaranteed to be in range `0..=32` (e.g. the maximum of - /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`] and - /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]). + /// [`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. @@ -128,7 +116,7 @@ impl Address { 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", + MAX_ACCOUNT_TARGET_TAG_LENGTH bits", ), } } @@ -182,7 +170,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)) @@ -206,9 +194,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) @@ -227,7 +213,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}; @@ -284,7 +270,7 @@ mod tests { address = address.with_routing_parameters( RoutingParameters::new(AddressInterface::BasicWallet) .with_note_tag_len(NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH)?, - )?; + ); let bech32_string = address.encode(network_id.clone()); assert!( @@ -327,7 +313,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(); @@ -413,7 +399,7 @@ mod tests { let address = Address::new(account_id).with_routing_parameters( RoutingParameters::new(AddressInterface::BasicWallet) .with_note_tag_len(NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH)?, - )?; + ); let serialized = address.to_bytes(); let deserialized = Address::read_from_bytes(&serialized)?; @@ -444,7 +430,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 = @@ -472,6 +458,7 @@ mod tests { // (FungibleFaucet) let account_id = AccountIdBuilder::new() .account_type(AccountType::RegularAccountImmutableCode) + .storage_mode(AccountStorageMode::Public) .build_with_rng(rng); // Create keypair @@ -483,7 +470,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); @@ -510,7 +497,7 @@ mod tests { 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); diff --git a/crates/miden-protocol/src/address/routing_parameters.rs b/crates/miden-protocol/src/address/routing_parameters.rs index 4ae6e3eab1..f76afbd1fb 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; // 31 /// 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)); @@ -109,7 +108,6 @@ impl RoutingParameters { /// Returns the note tag length preference. /// /// This is guaranteed to be in range `0..=32` (e.g. the maximum of - /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`] and /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]). pub fn note_tag_len(&self) -> Option { self.note_tag_len @@ -271,7 +269,7 @@ fn encode_receiver_profile(interface: AddressInterface, note_tag_len: Option let interface = interface as u16; 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) << 10; let receiver_profile: u16 = tag_len | interface; diff --git a/crates/miden-protocol/src/note/mod.rs b/crates/miden-protocol/src/note/mod.rs index 0cd807d6ea..729f651f9e 100644 --- a/crates/miden-protocol/src/note/mod.rs +++ b/crates/miden-protocol/src/note/mod.rs @@ -2,7 +2,7 @@ use miden_crypto::Word; use miden_crypto::utils::{ByteReader, ByteWriter, Deserializable, Serializable}; use miden_processor::DeserializationError; -use crate::account::{AccountId, AccountStorageMode}; +use crate::account::AccountId; use crate::errors::NoteError; use crate::{Felt, Hasher, WORD_SIZE, ZERO}; diff --git a/crates/miden-protocol/src/note/note_tag.rs b/crates/miden-protocol/src/note/note_tag.rs index 65f409a8dc..775e7e37c2 100644 --- a/crates/miden-protocol/src/note/note_tag.rs +++ b/crates/miden-protocol/src/note/note_tag.rs @@ -4,7 +4,6 @@ use miden_crypto::Felt; use super::{ AccountId, - AccountStorageMode, ByteReader, ByteWriter, Deserializable, @@ -65,7 +64,7 @@ impl NoteTag { // -------------------------------------------------------------------------------------------- /// The default note tag length for an account ID with local execution. - pub const DEFAULT_LOCAL_ACCOUNT_TARGET_TAG_LENGTH: u8 = 14; + pub const DEFAULT_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 = 32; /// The maximum number of bits that can be encoded into the tag for local accounts. @@ -90,17 +89,8 @@ impl NoteTag { /// to `0b00` and the remaining bits are set to the 30 most significant bits of the account /// ID. pub fn with_account_target(account_id: AccountId) -> Self { - match account_id.storage_mode() { - AccountStorageMode::Network => { - let prefix = account_id.prefix().as_u64(); - Self((prefix >> 34) as u32) - }, - _ => 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`. @@ -122,14 +112,11 @@ impl NoteTag { } let prefix = account_id.prefix().as_u64(); - let extracted = (prefix >> (64 - tag_len)) as u32; - - let tag = if tag_len == 32 { - extracted - } else { - extracted << (32 - tag_len) - }; - + // 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)) } @@ -226,7 +213,6 @@ mod tests { AccountId::try_from(ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET).unwrap(), AccountId::try_from(ACCOUNT_ID_PRIVATE_NON_FUNGIBLE_FAUCET).unwrap(), ]; - let public_accounts = [ AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE).unwrap(), AccountId::try_from(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE_2).unwrap(), @@ -240,37 +226,27 @@ mod tests { AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET).unwrap(), AccountId::try_from(ACCOUNT_ID_PUBLIC_NON_FUNGIBLE_FAUCET_1).unwrap(), ]; - - for account_id in private_accounts.iter().chain(public_accounts.iter()) { - let tag = NoteTag::with_account_target(*account_id); - let tag_u32 = tag.as_u32(); - - let used_bits = 32 - tag_u32.leading_zeros(); - - if used_bits == 0 { - assert_eq!(tag_u32, 0, "tag should be zero when no bits are used"); - continue; - } - - let expected = (account_id.prefix().as_u64() >> (64 - used_bits)) as u32; - let actual = tag_u32 >> (32 - used_bits); - - assert_eq!(actual, expected, "top {used_bits} bits should match account prefix"); - } - 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(), ]; + for account_id in private_accounts.iter().chain(public_accounts.iter()) { + let tag = NoteTag::with_account_target(*account_id); + assert_eq!(tag.as_u32() << 16, 0, "16 least significant bits should be zero"); + let expected = ((account_id.prefix().as_u64() >> 32) as u32) >> 16; + let actual = tag.as_u32() >> 16; + + assert_eq!(actual, expected, "14 most significant bits should match"); + } + for account_id in network_accounts { let tag = NoteTag::with_account_target(account_id); - assert_eq!( - tag.as_u32() as u64, - account_id.prefix().as_u64() >> 34, - "network account tag must match prefix >> 34" + (account_id.prefix().as_u64() >> 32) as u32, + tag.as_u32(), + "32 most significant bits should match" ); } } 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..228d6cce7a 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: u64 = prefix >> 34; 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, "Expected tag {:#010x}, got {:#010x}", - expected_tag, actual_tag + expected_tag, + actual_tag ); Ok(()) diff --git a/crates/miden-testing/tests/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index ee22a0a502..3f7b7d1f61 100644 --- a/crates/miden-testing/tests/agglayer/bridge_out.rs +++ b/crates/miden-testing/tests/agglayer/bridge_out.rs @@ -140,12 +140,6 @@ async fn test_bridge_out_consumes_b2agg_note() -> anyhow::Result<()> { "BURN note should contain the bridged asset" ); - assert_eq!( - burn_note.metadata().tag(), - NoteTag::with_account_target(faucet.id()), - "BURN note should have the correct tag" - ); - // Verify the BURN note uses the correct script assert_eq!( burn_note.recipient().script().root(), From 9f333bd0f5804d31d4445a6f6afd929b70616ab4 Mon Sep 17 00:00:00 2001 From: swaploard Date: Tue, 27 Jan 2026 07:44:32 +0000 Subject: [PATCH 04/11] refactor: update note tag length to support up to 32 bits for network --- CHANGELOG.md | 2 +- crates/miden-testing/src/kernel_tests/tx/test_note.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff21f40ad1..75b9794113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,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)). -- update note tag length to support up to 32 bits. +- Updated note tag length to support up to 32 bits for network ([#2329](https://github.com/0xMiden/miden-base/pull/2329)). ## 0.13.2 (2026-01-21) 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 228d6cce7a..8d613357eb 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -574,11 +574,9 @@ 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, + actual_tag, expected_tag, "Expected tag {:#010x}, got {:#010x}", - expected_tag, - actual_tag + expected_tag, actual_tag ); Ok(()) From f2e32fe57c74e0d107539a4c100123e7686d5345 Mon Sep 17 00:00:00 2001 From: swaploard Date: Tue, 27 Jan 2026 07:46:24 +0000 Subject: [PATCH 05/11] refactor: simplify changelog entry for note tag length update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75b9794113..4e2bc621fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,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 for network ([#2329](https://github.com/0xMiden/miden-base/pull/2329)). +- Updated note tag length to support up to 32 bits ([#2329](https://github.com/0xMiden/miden-base/pull/2329)). ## 0.13.2 (2026-01-21) From d47cde39ad5869124401e3dc889e08ce32aa12ae Mon Sep 17 00:00:00 2001 From: swaploard Date: Tue, 27 Jan 2026 08:08:51 +0000 Subject: [PATCH 06/11] refactor: clarify note tag construction details for account targeting --- crates/miden-protocol/src/note/note_tag.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/miden-protocol/src/note/note_tag.rs b/crates/miden-protocol/src/note/note_tag.rs index 775e7e37c2..2319ced944 100644 --- a/crates/miden-protocol/src/note/note_tag.rs +++ b/crates/miden-protocol/src/note/note_tag.rs @@ -80,14 +80,15 @@ 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: /// - /// - 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 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`. + /// + /// 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 { Self::with_custom_account_target(account_id, Self::DEFAULT_ACCOUNT_TARGET_TAG_LENGTH) .expect("default account target tag length must be valid") From 5c53c178a9421e38da05e1891285c0c7f37b8743 Mon Sep 17 00:00:00 2001 From: swaploard Date: Wed, 28 Jan 2026 11:04:59 +0000 Subject: [PATCH 07/11] refactor: update note tag length references to use MAX_ACCOUNT_TARGET_TAG_LENGTH --- crates/miden-protocol/src/address/mod.rs | 10 ++++++---- .../miden-protocol/src/address/routing_parameters.rs | 2 +- crates/miden-protocol/src/errors/mod.rs | 2 +- crates/miden-protocol/src/note/note_tag.rs | 2 -- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/miden-protocol/src/address/mod.rs b/crates/miden-protocol/src/address/mod.rs index 113d6a8f4a..c698137788 100644 --- a/crates/miden-protocol/src/address/mod.rs +++ b/crates/miden-protocol/src/address/mod.rs @@ -77,9 +77,11 @@ impl Address { Self { id: id.into(), routing_params: None } } - /// Sets the routing parameters of the address. + /// 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 { - // All validation should now live inside RoutingParameters itself. self.routing_params = Some(routing_params); self } @@ -269,7 +271,7 @@ 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()); @@ -398,7 +400,7 @@ 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(); diff --git a/crates/miden-protocol/src/address/routing_parameters.rs b/crates/miden-protocol/src/address/routing_parameters.rs index f76afbd1fb..ada460adde 100644 --- a/crates/miden-protocol/src/address/routing_parameters.rs +++ b/crates/miden-protocol/src/address/routing_parameters.rs @@ -108,7 +108,7 @@ impl RoutingParameters { /// Returns the note tag length preference. /// /// This is guaranteed to be in range `0..=32` (e.g. the maximum of - /// [`NoteTag::DEFAULT_NETWORK_ACCOUNT_TARGET_TAG_LENGTH`]). + /// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH`]). pub fn note_tag_len(&self) -> Option { self.note_tag_len } 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 2319ced944..c1af2fdd36 100644 --- a/crates/miden-protocol/src/note/note_tag.rs +++ b/crates/miden-protocol/src/note/note_tag.rs @@ -65,8 +65,6 @@ impl NoteTag { /// The default note tag length for an account ID with local execution. pub const DEFAULT_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 = 32; /// The maximum number of bits that can be encoded into the tag for local accounts. pub const MAX_ACCOUNT_TARGET_TAG_LENGTH: u8 = 32; From d629ccc246a89c5a73829ababdc087be95481137 Mon Sep 17 00:00:00 2001 From: swaploard Date: Wed, 28 Jan 2026 11:42:36 +0000 Subject: [PATCH 08/11] refactor: update assertion for account target tag to check 32 most significant bits --- crates/miden-protocol/src/note/note_tag.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/miden-protocol/src/note/note_tag.rs b/crates/miden-protocol/src/note/note_tag.rs index c1af2fdd36..a840a9052a 100644 --- a/crates/miden-protocol/src/note/note_tag.rs +++ b/crates/miden-protocol/src/note/note_tag.rs @@ -257,10 +257,11 @@ mod tests { let tag = NoteTag::with_custom_account_target(account_id, len)?; - let prefix = account_id.prefix().as_u64(); - let expected = (prefix >> (64 - len)) as u32; - - assert_eq!(tag.as_u32(), expected, "full 32-bit tag should match account prefix"); + assert_eq!( + (account_id.prefix().as_u64() >> 32) as u32, + tag.as_u32(), + "32 most significant bits should match" + ); Ok(()) } From b147ece1f05cf8d651dcf9b38239bd694e785fde Mon Sep 17 00:00:00 2001 From: swaploard Date: Wed, 28 Jan 2026 14:45:55 +0000 Subject: [PATCH 09/11] refactor: improve documentation for routing parameters attachment in Address --- crates/miden-protocol/src/address/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/miden-protocol/src/address/mod.rs b/crates/miden-protocol/src/address/mod.rs index c698137788..225fb1b0a4 100644 --- a/crates/miden-protocol/src/address/mod.rs +++ b/crates/miden-protocol/src/address/mod.rs @@ -77,9 +77,9 @@ impl Address { Self { id: id.into(), routing_params: None } } - /// 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 + /// 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); From f68af4e0c0f90706f8db93c572b0157e72a45f2f Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Wed, 28 Jan 2026 17:15:12 +0100 Subject: [PATCH 10/11] chore: small improvements --- crates/miden-protocol/asm/protocol/note.masm | 10 +++---- crates/miden-protocol/src/note/note_tag.rs | 27 +++++++++++-------- .../src/kernel_tests/tx/test_note.rs | 8 +++--- .../tests/agglayer/bridge_out.rs | 6 +++++ 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/miden-protocol/asm/protocol/note.masm b/crates/miden-protocol/asm/protocol/note.masm index 251683e4b7..97e362b36e 100644 --- a/crates/miden-protocol/asm/protocol/note.masm +++ b/crates/miden-protocol/asm/protocol/note.masm @@ -237,12 +237,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/note/note_tag.rs b/crates/miden-protocol/src/note/note_tag.rs index a840a9052a..5e63212bc8 100644 --- a/crates/miden-protocol/src/note/note_tag.rs +++ b/crates/miden-protocol/src/note/note_tag.rs @@ -181,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, @@ -201,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] @@ -211,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(), @@ -224,14 +228,24 @@ 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() << 16, 0, "16 least significant bits should be zero"); let expected = ((account_id.prefix().as_u64() >> 32) as u32) >> 16; @@ -239,15 +253,6 @@ mod tests { assert_eq!(actual, expected, "14 most significant bits should match"); } - - for account_id in network_accounts { - let tag = NoteTag::with_account_target(account_id); - assert_eq!( - (account_id.prefix().as_u64() >> 32) as u32, - tag.as_u32(), - "32 most significant bits should match" - ); - } } #[test] 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 8d613357eb..82a2a78261 100644 --- a/crates/miden-testing/src/kernel_tests/tx/test_note.rs +++ b/crates/miden-testing/src/kernel_tests/tx/test_note.rs @@ -549,7 +549,7 @@ async fn test_build_note_tag_for_network_account() -> anyhow::Result<()> { // Network account rule: top 30 bits of the prefix let prefix: u64 = account_id.prefix().into(); - let expected_tag: u64 = prefix >> 34; + let expected_tag = NoteTag::with_account_target(account_id); let suffix: u64 = account_id.suffix().into(); let code = format!( @@ -574,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, + actual_tag, + expected_tag.as_u32() as u64, "Expected tag {:#010x}, got {:#010x}", - expected_tag, actual_tag + expected_tag.as_u32(), + actual_tag ); Ok(()) diff --git a/crates/miden-testing/tests/agglayer/bridge_out.rs b/crates/miden-testing/tests/agglayer/bridge_out.rs index 3f7b7d1f61..ee22a0a502 100644 --- a/crates/miden-testing/tests/agglayer/bridge_out.rs +++ b/crates/miden-testing/tests/agglayer/bridge_out.rs @@ -140,6 +140,12 @@ async fn test_bridge_out_consumes_b2agg_note() -> anyhow::Result<()> { "BURN note should contain the bridged asset" ); + assert_eq!( + burn_note.metadata().tag(), + NoteTag::with_account_target(faucet.id()), + "BURN note should have the correct tag" + ); + // Verify the BURN note uses the correct script assert_eq!( burn_note.recipient().script().root(), From 53b3e273c984156fa8c0d07bbdcb5ff7a5670a06 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com> Date: Wed, 28 Jan 2026 15:46:56 -0800 Subject: [PATCH 11/11] Apply suggestion from @PhilippGackstatter Co-authored-by: Philipp Gackstatter --- crates/miden-protocol/src/address/routing_parameters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/miden-protocol/src/address/routing_parameters.rs b/crates/miden-protocol/src/address/routing_parameters.rs index ada460adde..24789f8acf 100644 --- a/crates/miden-protocol/src/address/routing_parameters.rs +++ b/crates/miden-protocol/src/address/routing_parameters.rs @@ -38,7 +38,7 @@ const BECH32_SEPARATOR: &str = "1"; /// /// 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 = 63; // 31 +const ABSENT_NOTE_TAG_LEN: u8 = 63; /// The routing parameter key for the receiver profile. const RECEIVER_PROFILE_PARAM_KEY: u8 = 0;