Skip to content

Commit 0a90aeb

Browse files
feat: NetworkAccountTarget helpers in miden::standards (#2338)
* feat: get_id network acc target util * chore: rename target_account_id -> account_id * feat: add new_attachment helper * chore: tests for network account target util * chore: add a roundtrip test * Apply suggestions from code review Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com> * chore: make const public * chore: add panics & warnings docs * chore: rename new_attachment -> new * chore: "new" returns scheme and kind * feat: extract_attachment_info_from_metadata in note.masm * chore: use extract_attachment_info_from_metadata * feat: verify attachment kind also * feat: change get_id to only take necessary inputs instead of the whole METADATA word, pass only scheme and kind * lint * changelog * chore: rename attachment -> attachments (plural) * chore: move tests under miden-testing move under miden-testing/src/standards * chore: change stack comparison to word * chore: use doc comments * chore: remove tokio dep from miden-testing --------- Co-authored-by: Philipp Gackstatter <PhilippGackstatter@users.noreply.github.com>
1 parent 3fda7c4 commit 0a90aeb

File tree

8 files changed

+263
-2
lines changed

8 files changed

+263
-2
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## 0.14.0 (TBD)
4+
5+
### Features
6+
7+
- Enable `CodeBuilder` to add advice map entries to compiled scripts ([#2275](https://github.com/0xMiden/miden-base/pull/2275)).
8+
- Added `BlockNumber::MAX` constant to represent the maximum block number ([#2324](https://github.com/0xMiden/miden-base/pull/2324)).
9+
10+
### Changes
11+
12+
- [BREAKING] Renamed `WellKnownComponent` to `StandardAccountComponent`, `WellKnownNote` to `StandardNote`, and `WellKnownNoteAttachment` to `StandardNoteAttachment` ([#2332](https://github.com/0xMiden/miden-base/pull/2332)).
13+
- Skip requests to the `DataStore` for asset vault witnesses which are already in transaction inputs ([#2298](https://github.com/0xMiden/miden-base/pull/2298)).
14+
- [BREAKING] refactored `TransactionAuthenticator::get_public_key()` method to return `Arc<PublicKey> `instead of `&PublicKey` ([#2304](https://github.com/0xMiden/miden-base/pull/2304)).
15+
- [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)).
16+
- Removed `NoteType::Encrypted` ([#2315](https://github.com/0xMiden/miden-base/pull/2315)).
17+
18+
# 0.13.3 (TBD)
19+
20+
- Added standards for working with `NetworkAccountTarget` attachments ([#2338](https://github.com/0xMiden/miden-base/pull/2338)).
21+
322
## 0.13.2 (2026-01-21)
423

524
- Make transaction executor respect debug mode settings ([#2327](https://github.com/0xMiden/miden-base/pull/2327)).

crates/miden-protocol/asm/protocol/note.masm

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,29 @@ pub proc extract_sender_from_metadata
216216
# => [sender_id_prefix, sender_id_suffix]
217217
end
218218

219+
#! Extracts the attachment kind and scheme from the provided metadata header.
220+
#!
221+
#! Inputs: [METADATA_HEADER]
222+
#! Outputs: [attachment_kind, attachment_scheme]
223+
#!
224+
#! Where:
225+
#! - METADATA_HEADER is the metadata of a note.
226+
#! - attachment_kind is the attachment kind of the note.
227+
#! - attachment_scheme is the attachment scheme of the note.
228+
#!
229+
#! Invocation: exec
230+
pub proc extract_attachment_info_from_metadata
231+
# => [attachment_kind_scheme, METADATA_HEADER[1..4]]
232+
movdn.3 drop drop drop
233+
# => [attachment_kind_scheme]
234+
235+
# deconstruct the attachment_kind_scheme to extract the attachment_scheme
236+
# attachment_kind_scheme = [30 zero bits | attachment_kind (2 bits) | attachment_scheme (32 bits)]
237+
# u32split splits into [high, low] where low is attachment_scheme
238+
u32split
239+
# => [attachment_kind, attachment_scheme]
240+
end
241+
219242
#! Computes the tag for a network note for a given network account such that it is
220243
#! picked up by the network transaction builder.
221244
#!
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#! miden::standards::attachments::network_account_target
2+
#!
3+
#! Provides a standardized way to work with network account targets.
4+
5+
use miden::protocol::active_note
6+
use miden::protocol::note
7+
8+
# CONSTANTS
9+
# ================================================================================================
10+
11+
#! The attachment scheme for NetworkAccountTarget attachments.
12+
#! This is a valid u32 that can be compared against an extracted attachment scheme.
13+
pub const NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME = 1
14+
15+
#! The attachment kind for NetworkAccountTarget attachments (Word = 1).
16+
#! This is a valid u32 that can be compared against an extracted attachment kind.
17+
pub const NETWORK_ACCOUNT_TARGET_ATTACHMENT_KIND = 1
18+
19+
# ERRORS
20+
# ================================================================================================
21+
const ERR_ATTACHMENT_SCHEME_MISMATCH = "expected network account target attachment scheme"
22+
const ERR_ATTACHMENT_KIND_MISMATCH = "expected attachment kind to be Word for network account target"
23+
24+
#! Returns the account ID encoded in the attachment.
25+
#!
26+
#! The attachment is expected to have the following layout:
27+
#! [0, exec_hint_tag, account_id_prefix, account_id_suffix]
28+
#!
29+
#! WARNING: This procedure does not validate that the returned account ID is well-formed.
30+
#! The caller should validate the account ID if needed using `account_id::validate`.
31+
#!
32+
#! Inputs: [attachment_scheme, attachment_kind, NOTE_ATTACHMENT]
33+
#! Outputs: [account_id_prefix, account_id_suffix]
34+
#!
35+
#! Where:
36+
#! - account_id_{prefix,suffix} are the prefix and suffix felts of an account ID.
37+
#!
38+
#! Panics if:
39+
#! - the attachment scheme does not match NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME.
40+
#!
41+
#! Invocation: exec
42+
pub proc get_id
43+
# verify that the attachment scheme and kind are correct
44+
# => [attachment_scheme, attachment_kind, NOTE_ATTACHMENT]
45+
eq.NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME assert.err=ERR_ATTACHMENT_SCHEME_MISMATCH
46+
eq.NETWORK_ACCOUNT_TARGET_ATTACHMENT_KIND assert.err=ERR_ATTACHMENT_KIND_MISMATCH
47+
# => [NOTE_ATTACHMENT] = [0, exec_hint_tag, account_id_prefix, account_id_suffix]
48+
49+
drop drop
50+
# => [account_id_prefix, account_id_suffix]
51+
end
52+
53+
#! Creates a new attachment of type NetworkAccountTarget with the following layout:
54+
#! [0, exec_hint_tag, account_id_prefix, account_id_suffix]
55+
#!
56+
#! Inputs: [account_id_prefix, account_id_suffix, exec_hint]
57+
#! Outputs: [attachment_scheme, attachment_kind, NOTE_ATTACHMENT]
58+
#!
59+
#! Where:
60+
#! - account_id_{prefix,suffix} are the prefix and suffix felts of an account ID.
61+
#! - exec_hint is the execution hint for the note.
62+
#! - attachment_kind is the attachment kind (Word = 1) for use with `output_note::set_attachment`.
63+
#! - attachment_scheme is the attachment scheme (1) for use with `output_note::set_attachment`.
64+
#!
65+
#! Invocation: exec
66+
pub proc new
67+
movup.2
68+
push.0
69+
push.NETWORK_ACCOUNT_TARGET_ATTACHMENT_KIND
70+
push.NETWORK_ACCOUNT_TARGET_ATTACHMENT_SCHEME
71+
# => [attachment_scheme, attachment_kind, ATTACHMENT]
72+
end

crates/miden-standards/src/errors/standards.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ use miden_protocol::errors::MasmError;
99
// STANDARDS ERRORS
1010
// ================================================================================================
1111

12+
/// Error Message: "expected attachment kind to be Word for network account target"
13+
pub const ERR_ATTACHMENT_KIND_MISMATCH: MasmError = MasmError::from_static_str("expected attachment kind to be Word for network account target");
14+
/// Error Message: "expected network account target attachment scheme"
15+
pub const ERR_ATTACHMENT_SCHEME_MISMATCH: MasmError = MasmError::from_static_str("expected network account target attachment scheme");
16+
1217
/// Error Message: "burn requires exactly 1 note asset"
1318
pub const ERR_BASIC_FUNGIBLE_BURN_WRONG_NUMBER_OF_ASSETS: MasmError = MasmError::from_static_str("burn requires exactly 1 note asset");
1419

crates/miden-testing/src/executor.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,20 @@ impl<H: AsyncHost> CodeExecutor<H> {
8888
#[cfg(test)]
8989
impl CodeExecutor<DefaultHost> {
9090
pub fn with_default_host() -> Self {
91+
use miden_protocol::ProtocolLib;
9192
use miden_protocol::transaction::TransactionKernel;
93+
use miden_standards::StandardsLib;
9294

9395
let mut host = DefaultHost::default();
9496

95-
let test_lib = TransactionKernel::library();
96-
host.load_library(test_lib.mast_forest()).unwrap();
97+
let standards_lib = StandardsLib::default();
98+
host.load_library(standards_lib.mast_forest()).unwrap();
99+
100+
let protocol_lib = ProtocolLib::default();
101+
host.load_library(protocol_lib.mast_forest()).unwrap();
102+
103+
let kernel_lib = TransactionKernel::library();
104+
host.load_library(kernel_lib.mast_forest()).unwrap();
97105

98106
CodeExecutor::new(host)
99107
}

crates/miden-testing/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ pub mod utils;
2727

2828
#[cfg(test)]
2929
mod kernel_tests;
30+
31+
#[cfg(test)]
32+
mod standards;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod network_account_target;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//! Tests for the `miden::standards::attachments::network_account_target` module.
2+
3+
use miden_protocol::Felt;
4+
use miden_protocol::account::AccountStorageMode;
5+
use miden_protocol::note::{NoteAttachment, NoteExecutionHint, NoteMetadata, NoteTag, NoteType};
6+
use miden_protocol::testing::account_id::AccountIdBuilder;
7+
use miden_standards::note::NetworkAccountTarget;
8+
9+
use crate::executor::CodeExecutor;
10+
11+
#[tokio::test]
12+
async fn network_account_target_get_id() -> anyhow::Result<()> {
13+
let target_id = AccountIdBuilder::new()
14+
.storage_mode(AccountStorageMode::Network)
15+
.build_with_rng(&mut rand::rng());
16+
let exec_hint = NoteExecutionHint::Always;
17+
18+
let attachment = NoteAttachment::from(NetworkAccountTarget::new(target_id, exec_hint)?);
19+
let metadata =
20+
NoteMetadata::new(target_id, NoteType::Public, NoteTag::with_account_target(target_id))
21+
.with_attachment(attachment.clone());
22+
let metadata_header = metadata.to_header_word();
23+
24+
let source = format!(
25+
r#"
26+
use miden::standards::attachments::network_account_target
27+
use miden::protocol::note
28+
29+
begin
30+
push.{attachment_word}
31+
push.{metadata_header}
32+
exec.note::extract_attachment_info_from_metadata
33+
# => [attachment_kind, attachment_scheme, NOTE_ATTACHMENT]
34+
exec.network_account_target::get_id
35+
# cleanup stack
36+
movup.2 drop movup.2 drop
37+
end
38+
"#,
39+
metadata_header = metadata_header,
40+
attachment_word = attachment.content().to_word(),
41+
);
42+
43+
let exec_output = CodeExecutor::with_default_host().run(&source).await?;
44+
45+
assert_eq!(exec_output.stack[0], target_id.prefix().as_felt());
46+
assert_eq!(exec_output.stack[1], target_id.suffix());
47+
48+
Ok(())
49+
}
50+
51+
#[tokio::test]
52+
async fn network_account_target_new_attachment() -> anyhow::Result<()> {
53+
let target_id = AccountIdBuilder::new()
54+
.storage_mode(AccountStorageMode::Network)
55+
.build_with_rng(&mut rand::rng());
56+
let exec_hint = NoteExecutionHint::Always;
57+
58+
let attachment = NoteAttachment::from(NetworkAccountTarget::new(target_id, exec_hint)?);
59+
let attachment_word = attachment.content().to_word();
60+
let expected_attachment_kind = Felt::from(attachment.attachment_kind().as_u8());
61+
62+
let source = format!(
63+
r#"
64+
use miden::standards::attachments::network_account_target
65+
66+
begin
67+
push.{exec_hint}
68+
push.{target_id_suffix}
69+
push.{target_id_prefix}
70+
# => [target_id_prefix, target_id_suffix, exec_hint]
71+
exec.network_account_target::new
72+
# => [attachment_scheme, attachment_kind, ATTACHMENT, pad(16)]
73+
74+
# cleanup stack
75+
swapdw dropw dropw
76+
end
77+
"#,
78+
target_id_prefix = target_id.prefix().as_felt(),
79+
target_id_suffix = target_id.suffix(),
80+
exec_hint = Felt::from(exec_hint),
81+
);
82+
83+
let exec_output = CodeExecutor::with_default_host().run(&source).await?;
84+
85+
assert_eq!(exec_output.stack[0], expected_attachment_kind);
86+
assert_eq!(
87+
exec_output.stack[1],
88+
Felt::from(NetworkAccountTarget::ATTACHMENT_SCHEME.as_u32())
89+
);
90+
91+
assert_eq!(exec_output.stack.get_stack_word_be(2).unwrap(), attachment_word);
92+
93+
Ok(())
94+
}
95+
96+
#[tokio::test]
97+
async fn network_account_target_attachment_round_trip() -> anyhow::Result<()> {
98+
let target_id = AccountIdBuilder::new()
99+
.storage_mode(AccountStorageMode::Network)
100+
.build_with_rng(&mut rand::rng());
101+
let exec_hint = NoteExecutionHint::Always;
102+
103+
let source = format!(
104+
r#"
105+
use miden::standards::attachments::network_account_target
106+
107+
begin
108+
push.{exec_hint}
109+
push.{target_id_suffix}
110+
push.{target_id_prefix}
111+
# => [target_id_prefix, target_id_suffix, exec_hint]
112+
exec.network_account_target::new
113+
# => [attachment_scheme, attachment_kind, ATTACHMENT]
114+
exec.network_account_target::get_id
115+
# => [target_id_prefix, target_id_suffix]
116+
movup.2 drop movup.2 drop
117+
end
118+
"#,
119+
target_id_prefix = target_id.prefix().as_felt(),
120+
target_id_suffix = target_id.suffix(),
121+
exec_hint = Felt::from(exec_hint),
122+
);
123+
124+
let exec_output = CodeExecutor::with_default_host().run(&source).await?;
125+
126+
assert_eq!(exec_output.stack[0], target_id.prefix().as_felt());
127+
assert_eq!(exec_output.stack[1], target_id.suffix());
128+
129+
Ok(())
130+
}

0 commit comments

Comments
 (0)