Skip to main content

miden_base_sys/bindings/
note.rs

1extern crate alloc;
2
3use alloc::vec::Vec;
4
5use miden_stdlib_sys::{Felt, Word, WordAligned};
6
7use super::{
8    AccountId, MAX_ATTACHMENT_WORDS, MAX_ATTACHMENTS_PER_NOTE, NoteType, RawAccountId,
9    RawFoundIndex, Recipient, Tag, assert_attachment_count,
10};
11
12const MAX_NOTE_STORAGE_ITEMS: usize = 1024;
13
14#[allow(improper_ctypes)]
15unsafe extern "C" {
16    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
17    #[link_name = "miden::protocol::note::compute_and_store_recipient"]
18    fn extern_note_build_recipient(
19        storage_ptr: *mut Felt,
20        num_storage_items: usize,
21        serial_num_f0: Felt,
22        serial_num_f1: Felt,
23        serial_num_f2: Felt,
24        serial_num_f3: Felt,
25        script_root_f0: Felt,
26        script_root_f1: Felt,
27        script_root_f2: Felt,
28        script_root_f3: Felt,
29        ptr: *mut Recipient,
30    );
31    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
32    #[link_name = "miden::protocol::note::compute_storage_commitment"]
33    fn extern_note_compute_storage_commitment(
34        storage_ptr: *const Felt,
35        num_storage_items: usize,
36        ptr: *mut Word,
37    );
38    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
39    #[link_name = "miden::protocol::note::compute_recipient"]
40    fn extern_note_compute_recipient(
41        serial_num_f0: Felt,
42        serial_num_f1: Felt,
43        serial_num_f2: Felt,
44        serial_num_f3: Felt,
45        script_root_f0: Felt,
46        script_root_f1: Felt,
47        script_root_f2: Felt,
48        script_root_f3: Felt,
49        storage_commitment_f0: Felt,
50        storage_commitment_f1: Felt,
51        storage_commitment_f2: Felt,
52        storage_commitment_f3: Felt,
53        ptr: *mut Recipient,
54    );
55    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
56    #[link_name = "miden::protocol::note::metadata_into_sender"]
57    fn extern_note_metadata_into_sender(
58        metadata_f0: Felt,
59        metadata_f1: Felt,
60        metadata_f2: Felt,
61        metadata_f3: Felt,
62        ptr: *mut RawAccountId,
63    );
64    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
65    #[link_name = "miden::protocol::note::metadata_into_attachment_schemes"]
66    fn extern_note_metadata_into_attachment_schemes(
67        metadata_f0: Felt,
68        metadata_f1: Felt,
69        metadata_f2: Felt,
70        metadata_f3: Felt,
71        ptr: *mut Word,
72    );
73    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
74    #[link_name = "miden::protocol::note::metadata_into_note_type"]
75    fn extern_note_metadata_into_note_type(
76        metadata_f0: Felt,
77        metadata_f1: Felt,
78        metadata_f2: Felt,
79        metadata_f3: Felt,
80    ) -> Felt;
81    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
82    #[link_name = "miden::protocol::note::metadata_into_tag"]
83    fn extern_note_metadata_into_tag(
84        metadata_f0: Felt,
85        metadata_f1: Felt,
86        metadata_f2: Felt,
87        metadata_f3: Felt,
88    ) -> Felt;
89    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
90    #[link_name = "miden::protocol::note::find_attachment_idx"]
91    fn extern_note_find_attachment_idx(
92        attachment_scheme: Felt,
93        metadata_f0: Felt,
94        metadata_f1: Felt,
95        metadata_f2: Felt,
96        metadata_f3: Felt,
97        ptr: *mut RawFoundIndex,
98    );
99    // The name must stay in lockstep with the stub's `export_name` (`stubs/note.rs`) and
100    // `SCRIPT_ROOT_STUB_NAME` in the compiler frontend (`frontend/wasm/src/intrinsics/note.rs`).
101    #[cfg_attr(target_family = "wasm", linkage = "extern_weak")]
102    #[link_name = "intrinsics::note::script_root"]
103    fn extern_note_script_root(ptr: *mut Word);
104}
105
106/// Returns the MAST root digest of the note script defined by the current crate.
107///
108/// Macro plumbing behind the `get_entrypoint_root()` associated method that `#[note]` generates
109/// on the note input type — call that method instead of this function. It lives here because
110/// the underlying weak extern requires `feature(linkage)`, which user crates do not enable.
111///
112/// This is a compiler intrinsic: the call compiles to a MASM `procref` of the crate's
113/// `#[note_script]` entrypoint export, so the digest is the note script root observed by the
114/// transaction kernel when the note is executed. The digest is computed at assembly time.
115///
116/// Compilation fails if the current project does not define a `#[note_script]` entrypoint.
117///
118/// Must not be called from code reachable from the `#[note_script]` entrypoint itself: the note
119/// script's MAST root would then depend on its own digest, and assembly fails with a call-graph
120/// cycle error. Inside a running note script, use [`active_note::get_script_root`] instead.
121///
122/// [`active_note::get_script_root`]: crate::bindings::active_note::get_script_root
123#[doc(hidden)]
124pub fn __entrypoint_root() -> Word {
125    unsafe {
126        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
127        extern_note_script_root(ret_area.as_mut_ptr());
128        ret_area.into_inner().assume_init()
129    }
130}
131
132/// Computes and stores a note recipient from serial number, script root, and storage elements.
133///
134/// This maps to `miden::protocol::note::compute_and_store_recipient`, which also inserts the
135/// provided storage into the advice map under the storage commitment used by the returned
136/// recipient digest.
137///
138/// Panics if `storage` contains more than 1024 elements.
139pub fn compute_and_store_recipient(
140    serial_num: Word,
141    script_root: Word,
142    storage: Vec<Felt>,
143) -> Recipient {
144    assert!(
145        storage.len() <= MAX_NOTE_STORAGE_ITEMS,
146        "note storage cannot contain more than {MAX_NOTE_STORAGE_ITEMS} items"
147    );
148
149    let rust_ptr = if storage.is_empty() {
150        0
151    } else {
152        storage.as_ptr().addr() as u32
153    };
154    let miden_ptr = rust_ptr / 4;
155
156    // Vec storage comes from the SDK allocator, which only produces word-aligned pointers.
157    assert_eq!(miden_ptr % 4, 0, "storage pointer must be word-aligned");
158
159    unsafe {
160        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Recipient>::uninit());
161        extern_note_build_recipient(
162            miden_ptr as *mut Felt,
163            storage.len(),
164            serial_num[0],
165            serial_num[1],
166            serial_num[2],
167            serial_num[3],
168            script_root[0],
169            script_root[1],
170            script_root[2],
171            script_root[3],
172            ret_area.as_mut_ptr(),
173        );
174        ret_area.into_inner().assume_init()
175    }
176}
177
178/// Builds a note recipient from the provided serial number, script root, and storage elements.
179///
180/// This is retained as an SDK-friendly alias for [`compute_and_store_recipient`].
181pub fn build_recipient(serial_num: Word, script_root: Word, storage: Vec<Felt>) -> Recipient {
182    compute_and_store_recipient(serial_num, script_root, storage)
183}
184
185/// Computes the commitment to the provided note storage elements.
186///
187/// Panics if `storage` contains more than 1024 elements.
188pub fn compute_storage_commitment(storage: &[Felt]) -> Word {
189    assert!(
190        storage.len() <= MAX_NOTE_STORAGE_ITEMS,
191        "note storage cannot contain more than {MAX_NOTE_STORAGE_ITEMS} items"
192    );
193
194    let rust_ptr = if storage.is_empty() {
195        0
196    } else {
197        storage.as_ptr().addr() as u32
198    };
199    let miden_ptr = rust_ptr / 4;
200
201    assert_eq!(miden_ptr % 4, 0, "storage pointer must be word-aligned");
202
203    unsafe {
204        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
205        extern_note_compute_storage_commitment(
206            miden_ptr as *const Felt,
207            storage.len(),
208            ret_area.as_mut_ptr(),
209        );
210        ret_area.into_inner().assume_init()
211    }
212}
213
214/// Loads the attachment commitments committed to by `attachments_commitment` from the advice map.
215///
216/// The advice map must contain the preimage committed to by `attachments_commitment`.
217///
218/// # Panics
219///
220/// Panics if the preimage is not a whole number of words or holds more commitments than a note
221/// can have attachments.
222pub fn load_attachment_commitments(attachments_commitment: Word) -> Vec<Word> {
223    load_attachment_words(attachments_commitment, MAX_ATTACHMENTS_PER_NOTE)
224}
225
226/// Loads the attachment committed to by `attachment_commitment` from the advice map.
227///
228/// The advice map must contain the attachment elements committed to by `attachment_commitment`.
229///
230/// # Panics
231///
232/// Panics if the attachment is not a whole number of words or exceeds the protocol's attachment
233/// size limit.
234pub fn load_attachment(attachment_commitment: Word) -> Vec<Word> {
235    load_attachment_words(attachment_commitment, MAX_ATTACHMENT_WORDS)
236}
237
238/// Loads the attachment at `attachment_idx` of an attachment commitment list from the advice map.
239///
240/// The advice map must contain the selected attachment elements.
241///
242/// # Panics
243///
244/// Panics if `attachment_commitments` holds more entries than a note can have attachments, if
245/// `attachment_idx` is out of bounds for it, or under the conditions of [`load_attachment`].
246pub fn load_indexed_attachment(attachment_commitments: &[Word], attachment_idx: u32) -> Vec<Word> {
247    assert_attachment_count(attachment_commitments.len());
248    load_attachment(attachment_commitments[attachment_idx as usize])
249}
250
251/// Loads and authenticates a bounded word preimage using the public core library primitives.
252fn load_attachment_words(commitment: Word, max_words: usize) -> Vec<Word> {
253    use miden_stdlib_sys::{adv_load_preimage, intrinsics::advice::adv_push_mapvaln};
254
255    let num_elements = adv_push_mapvaln(commitment).as_canonical_u64();
256    assert!(
257        num_elements <= (max_words * 4) as u64,
258        "attachment preimage exceeds protocol limit"
259    );
260    assert_eq!(num_elements % 4, 0, "attachment must contain whole words");
261    let elements = adv_load_preimage(Felt::from_u32((num_elements / 4) as u32), commitment);
262    // The whole-word assertion above guarantees there is no remainder chunk.
263    elements.as_chunks::<4>().0.iter().map(|word| Word::new(*word)).collect()
264}
265
266/// Computes a note recipient from serial number, script root, and storage commitment.
267pub fn compute_recipient(
268    serial_num: Word,
269    script_root: Word,
270    storage_commitment: Word,
271) -> Recipient {
272    unsafe {
273        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Recipient>::uninit());
274        extern_note_compute_recipient(
275            serial_num[0],
276            serial_num[1],
277            serial_num[2],
278            serial_num[3],
279            script_root[0],
280            script_root[1],
281            script_root[2],
282            script_root[3],
283            storage_commitment[0],
284            storage_commitment[1],
285            storage_commitment[2],
286            storage_commitment[3],
287            ret_area.as_mut_ptr(),
288        );
289        ret_area.into_inner().assume_init()
290    }
291}
292
293/// Extracts the sender account ID from a note metadata header word.
294pub fn metadata_into_sender(metadata: Word) -> AccountId {
295    unsafe {
296        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<RawAccountId>::uninit());
297        extern_note_metadata_into_sender(
298            metadata[0],
299            metadata[1],
300            metadata[2],
301            metadata[3],
302            ret_area.as_mut_ptr(),
303        );
304        ret_area.into_inner().assume_init().into_account_id()
305    }
306}
307
308/// Extracts the four attachment schemes encoded in a note metadata header word.
309pub fn metadata_into_attachment_schemes(metadata: Word) -> Word {
310    unsafe {
311        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<Word>::uninit());
312        extern_note_metadata_into_attachment_schemes(
313            metadata[0],
314            metadata[1],
315            metadata[2],
316            metadata[3],
317            ret_area.as_mut_ptr(),
318        );
319        ret_area.into_inner().assume_init()
320    }
321}
322
323/// Extracts the note type encoded in a note metadata header word.
324pub fn metadata_into_note_type(metadata: Word) -> NoteType {
325    unsafe {
326        NoteType::from(extern_note_metadata_into_note_type(
327            metadata[0],
328            metadata[1],
329            metadata[2],
330            metadata[3],
331        ))
332    }
333}
334
335/// Extracts the note tag encoded in a note metadata header word.
336pub fn metadata_into_tag(metadata: Word) -> Tag {
337    unsafe {
338        Tag::from(extern_note_metadata_into_tag(
339            metadata[0],
340            metadata[1],
341            metadata[2],
342            metadata[3],
343        ))
344    }
345}
346
347/// Searches a metadata header word for `attachment_scheme`.
348pub fn find_attachment_idx(attachment_scheme: Felt, metadata: Word) -> Option<u32> {
349    unsafe {
350        let mut ret_area = WordAligned::new(::core::mem::MaybeUninit::<RawFoundIndex>::uninit());
351        extern_note_find_attachment_idx(
352            attachment_scheme,
353            metadata[0],
354            metadata[1],
355            metadata[2],
356            metadata[3],
357            ret_area.as_mut_ptr(),
358        );
359        ret_area.into_inner().assume_init().into_attachment_index()
360    }
361}