miden-protocol 0.15.2

Core components of the Miden protocol
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
use miden::protocol::account_id
use miden::protocol::util::constants::WORD_NUM_ELEMENTS
use miden::core::crypto::hashes::poseidon2
use miden::core::mem

# Re-export the max inputs per note constant.
pub use miden::protocol::util::note::MAX_NOTE_STORAGE_ITEMS
pub use miden::protocol::util::note::NOTE_TYPE_PUBLIC
pub use miden::protocol::util::note::NOTE_TYPE_PRIVATE
pub use miden::protocol::util::note::MAX_ATTACHMENT_SCHEME
pub use miden::protocol::util::note::MAX_ATTACHMENT_WORDS
pub use miden::protocol::util::note::MAX_ATTACHMENT_TOTAL_WORDS
pub use miden::protocol::util::note::ATTACHMENT_SCHEME_NONE

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

const ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT="number of note storage exceeded the maximum limit of 1024"

const ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS = "attachment index out of bounds"

# NOTE UTILITY PROCEDURES
# =================================================================================================

#! Computes the commitment to the note storage starting at the specified memory address.
#!
#! This procedure checks that the provided number of note storage items is within limits and then computes
#! the commitment.
#!
#! If the number of note storage items is 0, procedure returns the empty word: [0, 0, 0, 0].
#!
#! Inputs:  [storage_ptr, num_storage_items]
#! Outputs: [STORAGE_COMMITMENT]
#!
#! Cycles:
#! - If number of elements divides by 8: 56 cycles + 3 * words
#! - Else: 189 cycles + 3 * words
#!
#! Panics if:
#! - storage_ptr is not word-aligned (i.e., is not a multiple of 4).
#! - num_storage_items is greater than 1024.
#!
#! Invocation: exec
pub proc compute_storage_commitment
    # check that number of storage items is less than or equal to MAX_NOTE_STORAGE_ITEMS
    dup.1 push.MAX_NOTE_STORAGE_ITEMS u32assert2.err=ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT
    u32lte assert.err=ERR_PROLOGUE_NOTE_NUM_STORAGE_ITEMS_EXCEEDED_LIMIT
    # => [storage_ptr, num_storage_items]

    # compute the storage commitment (over the unpadded values)
    exec.poseidon2::hash_elements
    # => [STORAGE_COMMITMENT]
end

#! Writes the assets data stored in the advice map to the memory specified by the provided
#! destination pointer.
#!
#! Inputs:
#!   Operand stack: [ASSETS_COMMITMENT, num_assets, dest_ptr]
#!   Advice map: {
#!     ASSETS_COMMITMENT: [[ASSETS_DATA]]
#!   }
#! Outputs:
#!   Operand stack: [num_assets, dest_ptr]
pub proc write_assets_to_memory
    # load the asset data from the advice map to the advice stack
    adv.push_mapval
    # OS => [ASSETS_COMMITMENT, num_assets, dest_ptr]
    # AS => [[ASSETS_DATA]]

    movup.5 movup.5
    # OS => [num_assets, dest_ptr, ASSETS_COMMITMENT]
    # AS => [[ASSETS_DATA]]

    # each asset takes up two words, so num_words = 2 * num_assets
    # this also guarantees we pass an even number to pipe_double_words_preimage_to_memory
    mul.2
    # OS => [num_words, dest_ptr, ASSETS_COMMITMENT]
    # AS => [[ASSETS_DATA]]

    # write the data from the advice stack into memory
    exec.mem::pipe_double_words_preimage_to_memory drop
    # OS => []
    # AS => []
end

#! Writes the attachment commitments stored in the advice map to memory specified by the provided
#! destination pointer.
#!
#! Inputs:
#!   Operand stack: [ATTACHMENTS_COMMITMENT, dest_ptr]
#!   Advice map: {
#!     ATTACHMENTS_COMMITMENT: [[ATTACHMENT_COMMITMENT]]
#!   }
#! Outputs:
#!   Operand stack: [num_attachments]
pub proc write_attachment_commitments_to_memory
    # push the individual ATTACHMENT commitments from the advice map onto the advice stack
    adv.push_mapvaln
    # OS => [ATTACHMENTS_COMMITMENT, dest_ptr]
    # AS => [num_elements, [ATTACHMENT_COMMITMENT]]

    # SAFETY: if the provided num_elements is invalid, the commitment check would fail in
    # pipe_preimage_to_memory so we assume validity and only do basic checks to protect against
    # invalid advice inputs.
    adv_push u32assert.err="invalid attachment num_elements advice input"
    u32divmod.WORD_NUM_ELEMENTS
    # OS => [remainder, num_words, ATTACHMENTS_COMMITMENT, dest_ptr]
    # AS => [[ATTACHMENT_COMMITMENT]]

    # assert that num_elements is a multiple of WORD_NUM_ELEMENTS
    eq.0 assert.err="attachment commitments num_elements is not a multiple of WORD_NUM_ELEMENTS"
    # OS => [num_words, ATTACHMENTS_COMMITMENT, dest_ptr]
    # AS => [[ATTACHMENT_COMMITMENT]]

    # store the number of words as the number of attachments for return
    swap.5 dup.5
    # OS => [num_words, dest_ptr, ATTACHMENTS_COMMITMENT, num_attachments]
    # AS => [[ATTACHMENT_COMMITMENT]]

    # pipe attachment commitments to memory and validate they match the ATTACHMENTS_COMMITMENT
    exec.mem::pipe_preimage_to_memory drop
    # => [num_attachments]
end

#! Writes a single attachment's data stored in the advice map to the memory specified by the
#! provided destination pointer.
#!
#! Inputs:
#!   Operand stack: [ATTACHMENT_COMMITMENT, dest_ptr]
#!   Advice map: {
#!     ATTACHMENT_COMMITMENT: [[ATTACHMENT_ELEMENTS]],
#!   }
#! Outputs:
#!   Operand stack: [num_words]
#!
#! Where:
#! - ATTACHMENT_COMMITMENT is the hash commitment to the attachment elements.
#! - dest_ptr is the memory address to which to write the attachment data.
#! - num_words is the number of words in the attachment.
pub proc write_attachment_to_memory
    # push the number of attachment elements from the advice map onto the advice stack
    adv.push_mapvaln
    # OS => [ATTACHMENT_COMMITMENT, dest_ptr]
    # AS => [num_elements, [ATTACHMENT_ELEMENTS]]

    # SAFETY: if the provided num_elements is invalid, the commitment check would fail in
    # pipe_preimage_to_memory so we assume validity and only do basic checks to protect against
    # invalid advice inputs.
    adv_push u32assert.err="invalid attachment num_elements advice input"
    u32divmod.WORD_NUM_ELEMENTS
    # OS => [remainder, num_words, ATTACHMENT_COMMITMENT, dest_ptr]
    # AS => [[ATTACHMENT_ELEMENTS]]

    # assert that num_elements is a multiple of WORD_NUM_ELEMENTS
    eq.0 assert.err="attachment num_elements is not a multiple of WORD_NUM_ELEMENTS"
    # OS => [num_words, ATTACHMENT_COMMITMENT, dest_ptr]
    # AS => [[ATTACHMENT_ELEMENTS]]

    swap.5 dup.5
    # OS => [num_words, dest_ptr, ATTACHMENT_COMMITMENT, num_words]
    # AS => [[ATTACHMENT_ELEMENTS]]

    # pipe the attachment data into memory, validating against ATTACHMENT_COMMITMENT
    exec.mem::pipe_preimage_to_memory drop
    # => [num_words]
end

#! Writes the attachment with the provided index from the provided attachment commitments to the
#! memory specified by the destination pointer.
#!
#! Inputs:  [num_attachments, attachment_commitments_ptr, attachment_idx, dest_ptr]
#! Outputs: [num_words]
#!
#! Where:
#! - attachment_idx is the index of the attachment to retrieve.
#! - attachment_commitments_ptr is a pointer to the attachment commitments in memory.
#! - dest_ptr is the memory address to which to write the attachment data.
#! - num_attachments is the number of attachments.
#! - num_words is the number of words in the attachment.
#!
#! Panics if:
#! - the attachment index is greater or equal to the number of attachments.
#! - the sequential hash over the attachment data in the advice inputs does not match the
#!   attachment commitment.
#!
#! Invocation: exec
pub proc write_indexed_attachment_to_memory
    # assert attachment_idx < num_attachments
    dup.2 swap u32assert2.err=ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS
    u32lt assert.err=ERR_OUTPUT_NOTE_ATTACHMENT_IDX_OUT_OF_BOUNDS
    # => [attachment_commitments_ptr, attachment_idx, dest_ptr]

    # compute the memory address of the attachment commitment:
    # commitment_ptr = attachment_commitments_ptr + attachment_idx * WORD_NUM_ELEMENTS
    swap mul.WORD_NUM_ELEMENTS add
    # => [commitment_ptr, dest_ptr]

    # load the ATTACHMENT_COMMITMENT from memory
    padw movup.4 mem_loadw_le
    # => [ATTACHMENT_COMMITMENT, dest_ptr]

    exec.write_attachment_to_memory
    # => [num_words]
end

#! Computes the recipient hash from note storage, script root, and serial number.
#!
#! This procedure computes the commitment of the note storage and then uses it to calculate the note
#! recipient by hashing this commitment, the provided script root, and the serial number.
#!
#! Inputs:
#!   Operand stack: [storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT]
#! Outputs:
#!   Operand stack: [RECIPIENT]
#!   Advice map: {
#!      STORAGE_COMMITMENT: [INPUTS],
#!      RECIPIENT: [SERIAL_SCRIPT_HASH, STORAGE_COMMITMENT],
#!      SERIAL_SCRIPT_HASH: [SERIAL_HASH, SCRIPT_ROOT],
#!      SERIAL_HASH: [SERIAL_NUM, EMPTY_WORD],
#!   }
#!
#! Where:
#! - storage_ptr is the memory address where the note storage are stored.
#! - num_storage_items is the number of input values.
#! - SCRIPT_ROOT is the script root of the note.
#! - SERIAL_NUM is the serial number of the note.
#! - RECIPIENT is the commitment to the input note's script, storage, and the serial number.
#!
#! Locals:
#! - 0: storage_ptr
#! - 1: num_storage_items
#!
#! Panics if:
#! - storage_ptr is not word-aligned (i.e., is not a multiple of 4).
#! - num_storage_items is greater than 1024.
#!
#! Invocation: exec
pub proc compute_and_store_recipient
    dup.1 dup.1
    # => [storage_ptr, num_storage_items, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT]

    exec.compute_storage_commitment
    # => [STORAGE_COMMITMENT, storage_ptr, num_storage_items, SERIAL_NUM, SCRIPT_ROOT]

    movup.5 movup.5 dup movdn.2
    # => [storage_ptr, num_storage_items, storage_ptr, STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT]

    add swap
    # => [storage_ptr, end_ptr, STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT]

    movdn.5 movdn.5
    # => [STORAGE_COMMITMENT, storage_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT]

    adv.insert_mem
    # => [STORAGE_COMMITMENT, storage_ptr, end_ptr, SERIAL_NUM, SCRIPT_ROOT]

    movup.4 drop movup.4 drop
    # => [STORAGE_COMMITMENT, SERIAL_NUM, SCRIPT_ROOT]

    movdnw.2
    # => [SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT]

    padw swapw
    # => [SERIAL_NUM, EMPTY_WORD, SCRIPT_ROOT, STORAGE_COMMITMENT]

    adv.insert_hdword exec.poseidon2::merge
    # => [SERIAL_COMMITMENT, SCRIPT_ROOT, STORAGE_COMMITMENT]

    adv.insert_hdword exec.poseidon2::merge
    # => [SERIAL_SCRIPT_COMMITMENT, STORAGE_COMMITMENT]

    adv.insert_hdword exec.poseidon2::merge
    # => [RECIPIENT]
end

#! Computes the RECIPIENT for a specified SERIAL_NUM, SCRIPT_ROOT and STORAGE_COMMITMENT.
#!
#! Inputs:  [SERIAL_NUM, SCRIPT_ROOT, STORAGE_COMMITMENT]
#! Outputs: [RECIPIENT]
#!
#! Where:
#! - SERIAL_NUM is the serial number of the recipient.
#! - SCRIPT_ROOT is the commitment of the note script.
#! - STORAGE_COMMITMENT is the commitment of the note storage.
#! - RECIPIENT is the recipient of the note.
#!
#! Invocation: exec
pub proc compute_recipient
    padw swapw
    # => [SERIAL_NUM, EMPTY_WORD, SCRIPT_ROOT, STORAGE_COMMITMENT]

    exec.poseidon2::merge
    # => [SERIAL_NUM_HASH, SCRIPT_ROOT, STORAGE_COMMITMENT]

    exec.poseidon2::merge
    # => [MERGE_SCRIPT, STORAGE_COMMITMENT]

    exec.poseidon2::merge
    # => [RECIPIENT]
end

#! Extracts the sender ID from the provided metadata.
#!
#! Inputs:  [METADATA]
#! Outputs: [sender_id_suffix, sender_id_prefix]
#!
#! Where:
#! - METADATA is the metadata of a note.
#! - sender_{suffix,prefix} are the suffix and prefix felts of the sender ID of the note which
#!   metadata was provided.
pub proc metadata_into_sender
    # => [sender_id_suffix_type_version, sender_id_prefix, tag, attachment_kind_scheme]

    # drop tag and attachment_kind_scheme
    movup.3 drop movup.2 drop
    # => [sender_id_suffix_type_version, sender_id_prefix]

    # extract suffix of sender from merged layout, which means clearing the least significant byte
    exec.account_id::shape_suffix
    # => [sender_id_suffix, sender_id_prefix]
end

#! Extracts the attachment's schemes from the provided metadata.
#!
#! Inputs:  [METADATA]
#! Outputs: [attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme]
#!
#! Where:
#! - METADATA is the metadata word of a note.
#! - attachment_n_scheme is the scheme of the nth attachment (0 if absent).
#!
#! Invocation: exec
pub proc metadata_into_attachment_schemes
    # => [sender_id_suffix_type_version, sender_id_prefix, tag, schemes]

    drop drop drop
    # => [schemes]

    u32split swap
    # => [schemes_hi, schemes_lo]

    # extract attachment scheme 3 from bits 48..64
    dup u32and.0xffff0000 u32shr.16
    # => [attachment_3_scheme, schemes_hi, schemes_lo]

    # extract attachment scheme 2 from bits 32..48
    swap u32and.0xffff
    # => [attachment_2_scheme, attachment_3_scheme, schemes_lo]

    # extract attachment scheme 1 from bits 16..32
    dup.2 u32and.0xffff0000 u32shr.16
    # => [attachment_1_scheme, attachment_2_scheme, attachment_3_scheme, schemes_lo]

    movup.3 u32and.0xffff
    # => [attachment_0_scheme, attachment_1_scheme, attachment_2_scheme, attachment_3_scheme]
end

#! Extracts the note type from the provided metadata.
#!
#! The note type is encoded as a single bit at the 4th position from the right side (LSB) of the
#! first felt of the metadata, where 0 = Private and 1 = Public.
#!
#! Inputs:  [METADATA]
#! Outputs: [note_type]
#!
#! Where:
#! - METADATA is the metadata of a note, laid out on the stack as
#!   [sender_id_suffix_type_version, sender_id_prefix, tag, attachment_schemes].
#!   The first felt (sender_id_suffix_type_version) has the following bit layout:
#!   [sender_id_suffix (56 bits) | reserved (3 bits) | note_type (1 bit) | version (4 bits)]
#! - note_type is the type of the note (0 for private, 1 for public).
#!
#! Invocation: exec
pub proc metadata_into_note_type
    movdn.3 drop drop drop
    # => [sender_id_suffix_type_version]

    u32split swap drop
    # => [lo32]

    u32shr.4
    # => [shifted]

    u32and.1
    # => [note_type]
end

#! Extracts the tag from the provided metadata.
#!
#! The tag is stored in the lower 32 bits of the tag element. The upper 32 bits are reserved.
#!
#! Inputs:  [METADATA]
#! Outputs: [tag]
#!
#! Where:
#! - METADATA is the metadata word of a note.
#! - tag is the lower 32 bits of the tag element.
#!
#! Invocation: exec
pub proc metadata_into_tag
    drop drop swap drop
    # => [tag_element]

    # extract the lower 32 bits as the tag
    u32split swap drop
    # => [tag]
end

#! Searches the metadata for the specified attachment scheme and returns the index of the
#! first matching slot.
#!
#! Inputs:  [attachment_scheme, METADATA]
#! Outputs: [is_found, attachment_idx]
#!
#! Where:
#! - attachment_scheme is the scheme to search for.
#! - METADATA is the metadata word of a note.
#! - is_found is 1 if the scheme was found, 0 otherwise.
#! - attachment_idx is the index (0-3) of the first matching slot, or undefined if not found.
#!
#! Invocation: exec
pub proc find_attachment_idx
    movdn.4 exec.metadata_into_attachment_schemes
    # => [scheme_0, scheme_1, scheme_2, scheme_3, attachment_scheme]

    # initialize is_found = 0 and attachment_idx = 0
    movup.4 push.0 push.0
    # => [attachment_idx = 0, is_found = 0, attachment_scheme, scheme_0, scheme_1, scheme_2, scheme_3]

    # iterate over the four schemes
    repeat.4
        # => [attachment_idx, is_found, attachment_scheme, scheme_n, ...]

        # check if scheme_n is the scheme we're trying to find
        dup.2 movup.4 eq
        # => [is_scheme_n, attachment_idx, is_found, attachment_scheme, scheme_n+1, ...]

        # set is_found = is_found || is_scheme_n
        movup.2 or swap
        # => [attachment_idx, is_found', attachment_scheme, scheme_n+1, ...]

        # create prospective attachment idx by incrementing the current one
        dup add.1 swap dup.2
        # => [is_found', attachment_idx, attachment_idx+1, is_found', attachment_scheme, scheme_n+1, ...]

        # if is_found' attachment_idx remains.
        # if !is_found' attachment_idx+1 remains.
        # this essentially increments the attachment idx as long as no match was found.
        cdrop
        # => [attachment_idx', is_found', attachment_scheme, scheme_n+1, ...]
    end
    # => [attachment_idx', is_found', attachment_scheme]

    movup.2 drop swap
    # => [is_found', attachment_idx']
end