Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
- [BREAKING] Refactored `TransactionAuthenticator::get_public_key()` method to return `Arc<PublicKey> `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)).
- [BREAKING] Updated note tag length to support up to 32 bits ([#2329](https://github.com/0xMiden/miden-base/pull/2329)).
- [BREAKING] Moved standard note code into individual note modules ([#2363](https://github.com/0xMiden/miden-base/pull/2363)).
- [BREAKING] Added `miden::standards::note_tag` module for account target note tags ([#2366](https://github.com/0xMiden/miden-base/pull/2366)).

## 0.13.3 (2026-01-27)

Expand Down
3 changes: 2 additions & 1 deletion crates/miden-agglayer/asm/bridge/bridge_out.masm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use miden::protocol::active_note
use miden::protocol::note
use miden::standards::note_tag
use miden::protocol::output_note
use miden::core::crypto::hashes::keccak256
use miden::core::crypto::hashes::rpo256
Expand Down Expand Up @@ -56,7 +57,7 @@ proc create_burn_note
movup.2 drop movup.2 drop
# => [faucet_id_prefix, faucet_id_suffix, ASSET]

exec.note::build_note_tag_for_network_account
exec.note_tag::create_account_target
# => [network_faucet_tag, ASSET]
Comment on lines +60 to 61
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this PR, but why do we need to set a tag for a BURN note? These are network notes and instead of a tag we should be setting an attachment for them, no?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll merge this PR as is - but cc @partylikeits1983 and @mmagician in case this is something we'd want to update in a different PR.


loc_store.5
Expand Down
30 changes: 0 additions & 30 deletions crates/miden-protocol/asm/protocol/note.masm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use miden::protocol::account_id
use miden::core::crypto::hashes::rpo256
use miden::core::math::u64
use miden::core::mem

# Re-export the max inputs per note constant.
Expand Down Expand Up @@ -238,32 +237,3 @@ pub proc extract_attachment_info_from_metadata
u32split
# => [attachment_kind, attachment_scheme]
end

#! Computes the tag for a network note for a given network account such that it is
#! picked up by the network transaction builder.
#!
#! This procedure implements the same logic as in Rust in NoteTag::from_network_account_id().
#! Note: This procedure does not check if the account id is a network account id.
#!
#! Inputs: [account_id_prefix, account_id_suffix]
#! Outputs: [network_account_tag]
#!
#! Where:
#! - account_id_prefix, account_id_suffix is the account id to compute the note tag for.
#! - network_account_tag is the computed network note tag.
#!
#! Invocation: exec
pub proc build_note_tag_for_network_account
swap drop
# => [account_id_prefix]

u32split
# => [a_hi, a_lo]

# mask out the 14 (NoteTag::DEFAULT_ACCOUNT_TARGET_TAG_LENGTH) most significant bits
u32and.0xfffc0000
# => [a_hi_masked, a_lo]

swap drop
# => [network_account_tag]
end
10 changes: 4 additions & 6 deletions crates/miden-protocol/src/address/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,6 @@ impl 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 {
self.routing_params = Some(routing_params);
self
Expand All @@ -101,7 +98,7 @@ impl Address {

/// Returns the preferred tag length.
///
/// This is guaranteed to be in range `0..=32` (e.g. the maximum of
/// This is guaranteed to be in range `0..=32` (i.e. at most
/// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH `]).
pub fn note_tag_len(&self) -> u8 {
self.routing_params
Expand Down Expand Up @@ -498,10 +495,11 @@ mod tests {
.build_with_rng(&mut rand::rng());

let address = Address::new(account_id).with_routing_parameters(
RoutingParameters::new(AddressInterface::BasicWallet).with_note_tag_len(32)?,
RoutingParameters::new(AddressInterface::BasicWallet)
.with_note_tag_len(NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH)?,
);

assert_eq!(address.note_tag_len(), 32);
assert_eq!(address.note_tag_len(), NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH);

Ok(())
}
Expand Down
4 changes: 2 additions & 2 deletions crates/miden-protocol/src/address/routing_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ 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`]).
/// This is guaranteed to be in range `0..=32` (i.e. at most
/// [`NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH `]).
pub fn note_tag_len(&self) -> Option<u8> {
self.note_tag_len
}
Expand Down
6 changes: 1 addition & 5 deletions crates/miden-protocol/src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,6 @@ pub enum AccountTreeError {

#[derive(Debug, Error)]
pub enum AddressError {
#[error("tag length {0} should be {expected} bits for network accounts",
expected = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH
)]
CustomTagLengthNotAllowedForNetworkAccounts(u8),
#[error("tag length {0} is too large, must be less than or equal to {max}",
max = NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH
)]
Expand All @@ -320,7 +316,7 @@ pub enum AddressError {
#[error("{error_msg}")]
DecodeError {
error_msg: Box<str>,
// thiserror will return this when calling Error::source on NoteError.
// thiserror will return this when calling Error::source on AddressError.
source: Option<Box<dyn Error + Send + Sync + 'static>>,
},
#[error("found unknown routing parameter key {0}")]
Expand Down
23 changes: 8 additions & 15 deletions crates/miden-protocol/src/note/note_tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,17 @@ impl NoteTag {

/// Constructs a note tag that targets the given `account_id`.
///
/// 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`.
///
/// The number of account-prefix bits included in the tag is determined by
/// `DEFAULT_ACCOUNT_TARGET_TAG_LENGTH`.
/// The tag is a u32 constructed by taking the [`NoteTag::DEFAULT_ACCOUNT_TARGET_TAG_LENGTH`]
/// most significant bits of the account ID prefix and setting the remaining bits to zero.
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")
}

/// Constructs a note tag that targets the given `account_id` with a custom `tag_len`.
///
/// The tag is constructed by:
/// - Setting the two most significant bits to zero.
/// - The next `tag_len` bits are set to the most significant bits of the account ID prefix.
/// - The remaining bits are set to zero.
/// The tag is a u32 constructed by taking the `tag_len` most significant bits of the account ID
/// prefix and setting the remaining bits to zero.
///
/// # Errors
///
Expand Down Expand Up @@ -258,9 +249,11 @@ mod tests {
#[test]
fn from_custom_account_target() -> anyhow::Result<()> {
let account_id = AccountId::try_from(ACCOUNT_ID_SENDER)?;
let len = 32;

let tag = NoteTag::with_custom_account_target(account_id, len)?;
let tag = NoteTag::with_custom_account_target(
account_id,
NoteTag::MAX_ACCOUNT_TARGET_TAG_LENGTH,
)?;

assert_eq!(
(account_id.prefix().as_u64() >> 32) as u32,
Expand Down
94 changes: 94 additions & 0 deletions crates/miden-standards/asm/standards/note_tag/mod.masm
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use miden::core::math::u64

# ERRORS
# =================================================================================================

const ERR_NOTE_TAG_MAX_ACCOUNT_TARGET_LENGTH_EXCEEDED="note tag length can be at most 32"

# CONSTANTS
# =================================================================================================

# The maximum account target tag length.
const MAX_ACCOUNT_TARGET_TAG_LENGTH = 32

# The default account target tag length.
const DEFAULT_ACCOUNT_TARGET_TAG_LENGTH = 14

# PROCEDURES
# =================================================================================================

#! Constructs a note tag that targets the given account_id_prefix with the default tag_len of 14.
#!
#! The tag is a u32 constructed by taking the 14 most significant bits of the account ID prefix and
#! setting the remaining bits to zero.
#!
#! Inputs: [account_id_prefix]
#! Outputs: [note_tag]
#!
#! Where:
#! - account_id_prefix is the account id prefix to compute the note tag for.
#! - note_tag is the created note tag.
#!
#! Invocation: exec
pub proc create_account_target
push.DEFAULT_ACCOUNT_TARGET_TAG_LENGTH
exec.create_custom_account_target
# => [note_tag]
end

#! Constructs a note tag that targets the given account_id_prefix with the provided tag_len.
#!
#! The tag is a u32 constructed by taking the `tag_len` most significant bits of the account ID
#! prefix and setting the remaining bits to zero.
#!
#! See the Rust `NoteTag` documentation for what changing the tag length means.
#!
#! Inputs: [tag_len, account_id_prefix]
#! Outputs: [note_tag]
#!
#! Where:
#! - account_id_prefix is the account id prefix to compute the note tag for.
#! - note_tag is the created note tag.
#! - tag_len is the number of most significant bits from the account ID prefix that should be used
#! for the tag.
#!
Comment thread
PhilippGackstatter marked this conversation as resolved.
#! Panics if:
#! - the tag_len exceeds 32.
#!
#! Invocation: exec
pub proc create_custom_account_target
u32assert.err=ERR_NOTE_TAG_MAX_ACCOUNT_TARGET_LENGTH_EXCEEDED
# => [tag_len, account_id_prefix]

dup u32lte.MAX_ACCOUNT_TARGET_TAG_LENGTH
assert.err=ERR_NOTE_TAG_MAX_ACCOUNT_TARGET_LENGTH_EXCEEDED
# => [tag_len, account_id_prefix]

# create a bit mask that zeros out the lower (32 - tag_len) bits.
# since u32shl panics for a 32 shift, we need to use u64::shl in case tag_len is 0

# push u32::MAX as a u64
push.0 push.0xffffffff
# => [u32::MAX, 0, tag_len, account_id_prefix]

# compute "number of bits in u32" - tag_len
push.32 movup.3 sub
# => [shift_by, u32::MAX, 0, account_id_prefix]

exec.u64::shl
# => [bit_mask_hi, bit_mask_lo, account_id_prefix]

# discard the lo part
swap drop
# => [bit_mask, account_id_prefix]

swap u32split
# => [account_id_prefix_hi, account_id_prefix_lo, bit_mask]

# discard the lo part of the ID prefix
swap drop
# => [account_id_prefix_hi, bit_mask]

u32and
# => [note_tag]
end
3 changes: 3 additions & 0 deletions crates/miden-standards/src/errors/standards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ pub const ERR_MALFORMED_MULTISIG_CONFIG: MasmError = MasmError::from_static_str(
/// Error Message: "MINT script expects exactly 12 storage items for private or 16+ storage items for public output notes"
pub const ERR_MINT_UNEXPECTED_NUMBER_OF_STORAGE_ITEMS: MasmError = MasmError::from_static_str("MINT script expects exactly 12 storage items for private or 16+ storage items for public output notes");

/// Error Message: "note tag length can be at most 32"
pub const ERR_NOTE_TAG_MAX_ACCOUNT_TARGET_LENGTH_EXCEEDED: MasmError = MasmError::from_static_str("note tag length can be at most 32");

/// Error Message: "failed to reclaim P2IDE note because the reclaiming account is not the sender"
pub const ERR_P2IDE_RECLAIM_ACCT_IS_NOT_SENDER: MasmError = MasmError::from_static_str("failed to reclaim P2IDE note because the reclaiming account is not the sender");
/// Error Message: "P2IDE reclaim is disabled"
Expand Down
21 changes: 11 additions & 10 deletions crates/miden-testing/src/executor.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#[cfg(test)]
use miden_processor::DefaultHost;
use miden_processor::fast::{ExecutionOutput, FastProcessor};
use miden_processor::{AdviceInputs, AsyncHost, ExecutionError, Program, StackInputs};
use miden_processor::{AdviceInputs, AsyncHost, Program, StackInputs};
#[cfg(test)]
use miden_protocol::assembly::Assembler;

use crate::ExecError;

// CODE EXECUTOR
// ================================================================================================

Expand Down Expand Up @@ -37,11 +39,8 @@ impl<H: AsyncHost> CodeExecutor<H> {
}

/// Compiles and runs the desired code in the host and returns the [`Process`] state.
///
/// To improve the error message quality, convert the returned [`ExecutionError`] into a
/// [`Report`](miden_protocol::assembly::diagnostics::Report).
#[cfg(test)]
pub async fn run(self, code: &str) -> Result<ExecutionOutput, ExecutionError> {
pub async fn run(self, code: &str) -> Result<ExecutionOutput, ExecError> {
use alloc::borrow::ToOwned;
use alloc::sync::Arc;

Expand All @@ -64,10 +63,7 @@ impl<H: AsyncHost> CodeExecutor<H> {
///
/// To improve the error message quality, convert the returned [`ExecutionError`] into a
/// [`Report`](miden_protocol::assembly::diagnostics::Report).
pub async fn execute_program(
mut self,
program: Program,
) -> Result<ExecutionOutput, ExecutionError> {
pub async fn execute_program(mut self, program: Program) -> Result<ExecutionOutput, ExecError> {
// This reverses the stack inputs (even though it doesn't look like it does) because the
// fast processor expects the reverse order.
//
Expand All @@ -79,7 +75,8 @@ impl<H: AsyncHost> CodeExecutor<H> {

let processor = FastProcessor::new_debug(stack_inputs.as_slice(), self.advice_inputs);

let execution_output = processor.execute(&program, &mut self.host).await?;
let execution_output =
processor.execute(&program, &mut self.host).await.map_err(ExecError::new)?;

Ok(execution_output)
}
Expand All @@ -88,12 +85,16 @@ impl<H: AsyncHost> CodeExecutor<H> {
#[cfg(test)]
impl CodeExecutor<DefaultHost> {
pub fn with_default_host() -> Self {
use miden_core_lib::CoreLibrary;
use miden_protocol::ProtocolLib;
use miden_protocol::transaction::TransactionKernel;
use miden_standards::StandardsLib;

let mut host = DefaultHost::default();

let core_lib = CoreLibrary::default();
host.load_library(core_lib.mast_forest()).unwrap();

let standards_lib = StandardsLib::default();
host.load_library(standards_lib.mast_forest()).unwrap();

Expand Down
7 changes: 3 additions & 4 deletions crates/miden-testing/src/kernel_tests/tx/test_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ use crate::kernel_tests::tx::ExecutionOutputExt;
use crate::utils::create_public_p2any_note;
use crate::{
Auth,
ExecError,
MockChain,
TransactionContextBuilder,
TxContextInput,
Expand Down Expand Up @@ -265,7 +266,7 @@ async fn test_account_validate_id() -> anyhow::Result<()> {
.run(code)
.await;

match (result, expected_error) {
match (result.map_err(ExecError::into_execution_error), expected_error) {
(Ok(_), None) => (),
(Ok(_), Some(err)) => {
anyhow::bail!("expected error {err} but validation was successful")
Expand Down Expand Up @@ -324,9 +325,7 @@ async fn test_is_faucet_procedure() -> anyhow::Result<()> {
prefix = account_id.prefix().as_felt(),
);

let exec_output = CodeExecutor::with_default_host().run(&code).await.map_err(|err| {
anyhow::anyhow!("failed to execute is_faucet procedure: {}", PrintDiagnostic::new(&err))
})?;
let exec_output = CodeExecutor::with_default_host().run(&code).await?;

let is_faucet = account_id.is_faucet();
assert_eq!(
Expand Down
Loading