Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
33c13ef
feat: Prepare note structs for multiple note attachments
PhilippGackstatter Apr 15, 2026
b114c18
chore: update block and batch note trees
PhilippGackstatter Apr 15, 2026
4b42abc
feat: extend private note header with attachments and rename
PhilippGackstatter Apr 16, 2026
deaff66
chore: update note kernel memory layout
PhilippGackstatter Apr 16, 2026
ed01038
feat: update set_attachment kernel proc to add_attachment
PhilippGackstatter Apr 16, 2026
59020c6
chore: update miden::protocol attachment handlers
PhilippGackstatter Apr 16, 2026
d378e13
chore: introduce dedicated attachment error variants
PhilippGackstatter Apr 17, 2026
5d8a4ea
feat: update standards types for multiple attachments
PhilippGackstatter Apr 17, 2026
bba9952
feat: update tx host attachment handler for multiples
PhilippGackstatter Apr 17, 2026
5147042
chore: update agglayer code for multiple attachments
PhilippGackstatter Apr 17, 2026
52f8b96
chore: update tests for multiple attachments
PhilippGackstatter Apr 17, 2026
d209a77
chore: rename word_size to num_words
PhilippGackstatter Apr 17, 2026
69a3654
chore: refactor array attachment to contain Vec<Word>
PhilippGackstatter Apr 17, 2026
4fd0a6e
chore: move MAX_NUM_WORDS from header to NoteAttachment
PhilippGackstatter Apr 17, 2026
cd1389a
feat: define attachments commitment over attachment commitments
PhilippGackstatter Apr 19, 2026
9260d37
chore: rename input note memory attachments comm offset
PhilippGackstatter Apr 19, 2026
60f3bfa
fix: num words extraction from metadata header
PhilippGackstatter Apr 19, 2026
1b66078
feat: always hash attachments
PhilippGackstatter Apr 20, 2026
002a4d4
fix: broken doc links
PhilippGackstatter Apr 20, 2026
f0eccbc
chore: add changelog
PhilippGackstatter Apr 20, 2026
e1fdeb5
chore: update PSWAP note with multiple attachments
PhilippGackstatter Apr 20, 2026
4e0495f
fix: default to empty attachments in pswap conversion
PhilippGackstatter Apr 24, 2026
afcc87d
Merge remote-tracking branch 'origin/next' into pgackst-multiple-note…
PhilippGackstatter Apr 24, 2026
9a347ac
fix: post-merge NoteAttachment -> NoteAttachments
PhilippGackstatter Apr 24, 2026
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
### Features

- Added PSWAP (partial swap) note for decentralized partial-fill asset exchange with remainder note re-creation ([#2636](https://github.com/0xMiden/protocol/pull/2636)).
- [BREAKING] Add support for multiple attachments per note ([#2795](https://github.com/0xMiden/protocol/pull/2795)).

### Changes

- Added a FungibleTokenMetadata ([#2439](https://github.com/0xMiden/miden-base/pull/2439)) component supporting name, description, logo URI, and external links, along with MASM procedures for retrieving token metadata (get_token_metadata, get_max_supply, get_decimals, get_token_symbol). Also aligned fungible faucet token metadata with the standard by using the canonical storage slot, enabling compatibility with MASM metadata getters.
- Added validation of leaf type on CLAIM note processing to prevent message leaves from being processed as asset claims ([#2730](https://github.com/0xMiden/protocol/pull/2730)).
- [BREAKING] Reduced `MAX_ASSETS_PER_NOTE` from 255 to 64 and `NOTE_MEM_SIZE` from 3072 to 1024 ([#2741](https://github.com/0xMiden/protocol/issues/2741)).
Expand Down
10 changes: 3 additions & 7 deletions crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm
Original file line number Diff line number Diff line change
Expand Up @@ -950,19 +950,15 @@ proc create_mint_note_with_attachment
# Set the attachment on the MINT note to target the faucet account
# NetworkAccountTarget attachment: targets the faucet so only it can consume the note
# network_account_target::new expects [suffix, prefix, exec_hint]
# and returns [attachment_scheme, attachment_kind, ATTACHMENT]
# and returns [attachment_scheme, ATTACHMENT]
push.ALWAYS # exec_hint = ALWAYS
movdn.2
# => [faucet_id_suffix, faucet_id_prefix, exec_hint, note_idx]

exec.network_account_target::new
# => [attachment_scheme, attachment_kind, ATTACHMENT, note_idx]
# => [attachment_scheme, ATTACHMENT, note_idx]

# Rearrange for set_attachment: [note_idx, attachment_scheme, attachment_kind, ATTACHMENT]
movup.6
# => [note_idx, attachment_scheme, attachment_kind, ATTACHMENT(4)]

exec.output_note::set_attachment
exec.output_note::add_word_attachment
# => []
end

Expand Down
30 changes: 14 additions & 16 deletions crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ const DESTINATION_NETWORK_LOC=13
const CREATE_BURN_NOTE_BURN_ASSET_LOC=0
const ATTACHMENT_LOC=8
const ATTACHMENT_SCHEME_LOC=12
const ATTACHMENT_KIND_LOC=13

# Other constants
# -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -508,11 +507,10 @@ proc create_burn_note
# => [faucet_id_suffix, faucet_id_prefix, exec_hint, ASSET_KEY]

exec.network_account_target::new
# => [attachment_scheme, attachment_kind, NOTE_ATTACHMENT, ASSET_KEY]
# => [attachment_scheme, NOTE_ATTACHMENT, ASSET_KEY]

# Save attachment data to locals
loc_store.ATTACHMENT_SCHEME_LOC
loc_store.ATTACHMENT_KIND_LOC
loc_storew_le.ATTACHMENT_LOC dropw
# => [ASSET_KEY]

Expand All @@ -539,27 +537,27 @@ proc create_burn_note
call.output_note::create
# => [note_idx, pad(15)]

# duplicate note_idx: one for set_attachment, one for add_asset
dup swapw loc_loadw_le.ATTACHMENT_LOC
# => [NOTE_ATTACHMENT, note_idx, note_idx, pad(11)]
# duplicate note_idx: one for add_word_attachment, one for add_asset
dup
# => [note_idx, note_idx, pad(15)]

loc_load.ATTACHMENT_KIND_LOC
loc_load.ATTACHMENT_SCHEME_LOC
# => [scheme, kind, NOTE_ATTACHMENT, note_idx, note_idx, pad(11)]
padw loc_loadw_le.ATTACHMENT_LOC
# => [NOTE_ATTACHMENT, note_idx, note_idx, pad(15)]

movup.6
# => [note_idx, scheme, kind, NOTE_ATTACHMENT, note_idx, pad(11)]
loc_load.ATTACHMENT_SCHEME_LOC
# => [scheme, NOTE_ATTACHMENT, note_idx, note_idx, pad(15)]

exec.output_note::set_attachment
# => [note_idx, pad(11)]
# network_account_target is a word-sized attachment
exec.output_note::add_word_attachment
# => [note_idx, pad(15)]

locaddr.CREATE_BURN_NOTE_BURN_ASSET_LOC
exec.asset::load
# => [ASSET_KEY, ASSET_VALUE, note_idx, pad(11)]
# => [ASSET_KEY, ASSET_VALUE, note_idx, pad(15)]

exec.output_note::add_asset
# => [pad(11)]
# => [pad(15)]

dropw dropw drop drop drop
dropw dropw dropw drop drop drop
# => []
end
16 changes: 8 additions & 8 deletions crates/miden-agglayer/src/b2agg_note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
//! This module provides helpers for creating B2AGG (Bridge to AggLayer) notes,
//! which are used to bridge assets out from Miden to the AggLayer network.

use alloc::string::ToString;
use alloc::vec::Vec;

use miden_assembly::serde::Deserializable;
Expand All @@ -16,6 +15,7 @@ use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachments,
NoteMetadata,
NoteRecipient,
NoteScript,
Expand Down Expand Up @@ -96,17 +96,17 @@ impl B2AggNote {
) -> Result<Note, NoteError> {
let note_storage = build_note_storage(destination_network, destination_address)?;

let attachment = NoteAttachment::from(
NetworkAccountTarget::new(target_account_id, NoteExecutionHint::Always)
.map_err(|e| NoteError::other(e.to_string()))?,
);
let attachment = NetworkAccountTarget::new(target_account_id, NoteExecutionHint::Always)
.map_err(|error| {
NoteError::other_with_source("failed to create b2agg network account target", error)
})?;
let attachments = NoteAttachments::from(NoteAttachment::from(attachment));

let metadata =
NoteMetadata::new(sender_account_id, NoteType::Public).with_attachment(attachment);
let metadata = NoteMetadata::new(sender_account_id, NoteType::Public);

let recipient = NoteRecipient::new(rng.draw_word(), Self::script(), note_storage);

Ok(Note::new(assets, metadata, recipient))
Ok(Note::with_attachments(assets, metadata, recipient, attachments))
}
}

Expand Down
20 changes: 14 additions & 6 deletions crates/miden-agglayer/src/claim_note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ use miden_protocol::account::AccountId;
use miden_protocol::crypto::SequentialCommit;
use miden_protocol::crypto::rand::FeltRng;
use miden_protocol::errors::NoteError;
use miden_protocol::note::{Note, NoteAssets, NoteMetadata, NoteRecipient, NoteStorage, NoteType};
use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachments,
NoteMetadata,
NoteRecipient,
NoteStorage,
NoteType,
};
use miden_standards::note::{NetworkAccountTarget, NoteExecutionHint};

use crate::utils::Keccak256Output;
Expand Down Expand Up @@ -182,14 +191,13 @@ pub fn create_claim_note<R: FeltRng>(
let note_storage = NoteStorage::try_from(storage.clone())?;

let attachment = NetworkAccountTarget::new(target_bridge_id, NoteExecutionHint::Always)
.map_err(|e| NoteError::other(e.to_string()))?
.into();
.map_err(|e| NoteError::other(e.to_string()))?;
let attachments = NoteAttachments::from(NoteAttachment::from(attachment));

let metadata =
NoteMetadata::new(sender_account_id, NoteType::Public).with_attachment(attachment);
let metadata = NoteMetadata::new(sender_account_id, NoteType::Public);

let recipient = NoteRecipient::new(rng.draw_word(), claim_script(), note_storage);
let assets = NoteAssets::new(vec![])?;

Ok(Note::new(assets, metadata, recipient))
Ok(Note::with_attachments(assets, metadata, recipient, attachments))
}
13 changes: 6 additions & 7 deletions crates/miden-agglayer/src/config_note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachments,
NoteMetadata,
NoteRecipient,
NoteScript,
Expand Down Expand Up @@ -112,16 +113,14 @@ impl ConfigAggBridgeNote {

let recipient = NoteRecipient::new(serial_num, Self::script(), note_storage);

let attachment = NoteAttachment::from(
NetworkAccountTarget::new(target_account_id, NoteExecutionHint::Always)
.map_err(|e| NoteError::other(e.to_string()))?,
);
let metadata =
NoteMetadata::new(sender_account_id, NoteType::Public).with_attachment(attachment);
let attachment = NetworkAccountTarget::new(target_account_id, NoteExecutionHint::Always)
.map_err(|e| NoteError::other(e.to_string()))?;
let attachments = NoteAttachments::from(NoteAttachment::from(attachment));
let metadata = NoteMetadata::new(sender_account_id, NoteType::Public);

// CONFIG_AGG_BRIDGE notes don't carry assets
let assets = NoteAssets::new(vec![])?;

Ok(Note::new(assets, metadata, recipient))
Ok(Note::with_attachments(assets, metadata, recipient, attachments))
}
}
13 changes: 6 additions & 7 deletions crates/miden-agglayer/src/update_ger_note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteAttachments,
NoteMetadata,
NoteRecipient,
NoteScript,
Expand Down Expand Up @@ -100,16 +101,14 @@ impl UpdateGerNote {

let recipient = NoteRecipient::new(serial_num, Self::script(), note_storage);

let attachment = NoteAttachment::from(
NetworkAccountTarget::new(target_account_id, NoteExecutionHint::Always)
.map_err(|e| NoteError::other(e.to_string()))?,
);
let metadata =
NoteMetadata::new(sender_account_id, NoteType::Public).with_attachment(attachment);
let attachment = NetworkAccountTarget::new(target_account_id, NoteExecutionHint::Always)
.map_err(|e| NoteError::other(e.to_string()))?;
let attachments = NoteAttachments::from(NoteAttachment::from(attachment));
let metadata = NoteMetadata::new(sender_account_id, NoteType::Public);

// UPDATE_GER notes don't carry assets
let assets = NoteAssets::new(vec![])?;

Ok(Note::new(assets, metadata, recipient))
Ok(Note::with_attachments(assets, metadata, recipient, attachments))
}
}
67 changes: 47 additions & 20 deletions crates/miden-protocol/asm/kernels/transaction/api.masm
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use $kernel::input_note
use $kernel::memory
use $kernel::output_note
use $kernel::tx
# use $kernel::types::AccountId
use $kernel::constants::WORD_SIZE

use $kernel::memory::UPCOMING_FOREIGN_PROCEDURE_PTR
use $kernel::memory::UPCOMING_FOREIGN_PROC_INPUT_VALUE_15_PTR
Expand Down Expand Up @@ -913,15 +913,15 @@ end
#! Returns the metadata of the specified input note.
#!
#! Inputs: [is_active_note, note_index, pad(14)]
#! Outputs: [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)]
#! Outputs: [NOTE_ATTACHMENT_0, METADATA_HEADER, pad(8)]
#!
#! Where:
#! - is_active_note is the boolean flag indicating whether we should return the metadata from
#! the active note or from the note with the specified index.
#! - note_index is the index of the input note whose metadata should be returned. Notice that if
#! is_active_note is 1, note_index is ignored.
#! - METADATA_HEADER is the metadata header of the specified input note.
#! - NOTE_ATTACHMENT is the attachment of the specified input note.
#! - NOTE_ATTACHMENT_0 is the first attachment of the specified input note.
#!
#! Panics if:
#! - the note index is greater or equal to the total number of input notes.
Expand All @@ -930,7 +930,7 @@ end
#!
#! Invocation: dynexec
pub proc input_note_get_metadata
# get the input note pointer depending on whether the requested note is current or it was
# get the input note pointer depending on whether the requested note is current or it was
# requested by index.
exec.get_requested_note_ptr
# => [input_note_ptr, pad(15)]
Expand All @@ -949,12 +949,34 @@ pub proc input_note_get_metadata
# => [METADATA_HEADER, input_note_ptr, pad(16)]

# get the attachment
movup.4 exec.memory::get_input_note_attachment
# => [NOTE_ATTACHMENT, METADATA_HEADER, pad(16)]
movup.4 exec.memory::get_input_note_attachments_commitment
# => [NOTE_ATTACHMENTS_COMMITMENT, METADATA_HEADER, pad(16)]

# truncate the stack
swapdw dropw dropw
# => [NOTE_ATTACHMENT, METADATA_HEADER, pad(8)]
# => [NOTE_ATTACHMENTS_COMMITMENT, METADATA_HEADER, pad(8)]

# TODO(multi_attachments): Maintain temporary compatibility with the previous API by returning
# the first attachment. This will be refactored in a follow-up PR.
exec.word::testz not
# => [!is_attachments_commitment_empty, NOTE_ATTACHMENTS_COMMITMENT, METADATA_HEADER, pad(8)]

# if the attachments commitment is the empty word, the first attachment is also the empty word,
# so we leave the empty word on the stack
#
# otherwise:
if.true
# fetch the first attachment from the advice stack and overwrite the attachments commitment
adv.push_mapval
adv_loadw
# => [ATTACHMENT_COMMITMENT_0, METADATA_HEADER, pad(8)]

adv.push_mapvaln
adv_push.1 eq.WORD_SIZE assert.err="retrieved attachments must be temporarily word-sized"
adv_loadw
# => [ATTACHMENT_0, METADATA_HEADER, pad(8)]
end
# => [ATTACHMENT_0, METADATA_HEADER, pad(8)]
end

#! Returns the serial number of the specified input note.
Expand Down Expand Up @@ -1129,30 +1151,34 @@ pub proc output_note_add_asset
# => [pad(16)]
end

#! Sets the attachment of the note specified by the index.
#! Adds an attachment to the note specified by the index.
#!
#! Inputs: [note_idx, attachment_scheme, attachment_kind, ATTACHMENT, pad(9)]
#! Inputs: [attachment_scheme, ATTACHMENT, note_idx, pad(9)]
#! Outputs: [pad(16)]
#!
#! Where:
#! - note_idx is the index of the note on which the attachment is set.
#! - attachment_scheme is the user-defined scheme of the attachment.
#! - attachment_kind is the kind of the attachment content.
#! - ATTACHMENT is the attachment to be set.
#! - ATTACHMENT is the attachment word to store.
#! - note_idx is the index of the note to which the attachment is added.
#!
#! Panics if:
#! - the procedure is called when the active account is not the native one.
#! - the note index points to a non-existent output note.
#! - the attachment kind or scheme does not fit into a u32.
#! - the attachment kind is an unknown variant.
#! - the attachment scheme exceeds 65534.
#! - the attachment num_words exceeds 254 or is zero.
#! - the note already has 4 attachments.
#!
#! Invocation: dynexec
pub proc output_note_set_attachment
pub proc output_note_add_attachment
# assert that the provided note index is less than the total number of output notes
dup.6 exec.output_note::assert_note_index_in_bounds drop
# => [attachment_scheme, ATTACHMENT, note_idx, pad(9)]

# check that this procedure was executed against the native account
exec.memory::assert_native_account
# => [note_idx, attachment_scheme, attachment_kind, ATTACHMENT, pad(9)]
# => [attachment_scheme, ATTACHMENT, note_idx, pad(9)]

exec.output_note::set_attachment
exec.output_note::add_attachment
# => [pad(16)]
end

Expand Down Expand Up @@ -1248,9 +1274,10 @@ pub proc output_note_get_metadata
dup exec.memory::get_output_note_metadata_header
# => [METADATA_HEADER, note_ptr, pad(16)]

# get the attachment
movup.4 exec.memory::get_output_note_attachment
# => [NOTE_ATTACHMENT, METADATA_HEADER, pad(16)]
# TODO(multi_attachments): Temporarily maintain compatibility with the old API and return the
# first attachment.
movup.4 push.0 swap exec.memory::get_output_note_attachment
# => [ATTACHMENT_0, METADATA_HEADER, pad(16)]

# truncate the stack
swapdw dropw dropw
Expand Down
Loading
Loading