Skip to content

Commit 5402852

Browse files
feat: reduce NoteType to a 1 bit encoding and add metadata version (#2691)
1 parent fbab89b commit 5402852

File tree

14 files changed

+161
-110
lines changed

14 files changed

+161
-110
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Changes
66

77
- [BREAKING] Renamed `ProvenBatch::new` to `new_unchecked` ([#2687](https://github.com/0xMiden/miden-base/issues/2687)).
8+
- [BREAKING] Changed `NoteType` encoding from 2 bits to 1 and makes `NoteType::Private` the default ([#2691](https://github.com/0xMiden/miden-base/issues/2691)).
89

910
## 0.14.0 (2026-03-23)
1011

crates/miden-agglayer/asm/agglayer/bridge/bridge_in.masm

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use miden::core::crypto::hashes::poseidon2
99
use miden::core::mem
1010
use miden::core::word
1111
use miden::protocol::note
12+
use miden::protocol::note::NOTE_TYPE_PUBLIC
1213
use miden::protocol::output_note
1314
use miden::protocol::output_note::ATTACHMENT_KIND_NONE
1415
use miden::protocol::active_account
@@ -64,9 +65,6 @@ const CLAIM_LEAF_DATA_WORD_LEN = 8
6465
# [16]: account_id_suffix, [17]: account_id_prefix
6566
const MINT_NOTE_NUM_STORAGE_ITEMS = 18
6667

67-
# P2ID output note constants
68-
const OUTPUT_NOTE_TYPE_PUBLIC = 1
69-
7068
# P2ID attachment constants (the P2ID note created by the faucet has no attachment)
7169
const P2ID_ATTACHMENT_SCHEME_NONE = 0
7270

@@ -917,7 +915,7 @@ end
917915
#! Invocation: exec
918916
proc create_mint_note_with_attachment
919917
# Create the MINT output note targeting the faucet
920-
push.OUTPUT_NOTE_TYPE_PUBLIC
918+
push.NOTE_TYPE_PUBLIC
921919
# => [note_type, MINT_RECIPIENT, faucet_id_prefix, faucet_id_suffix]
922920

923921
# Set tag to DEFAULT

crates/miden-agglayer/asm/agglayer/bridge/bridge_out.masm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const ATTACHMENT_KIND_LOC=13
7878
# -------------------------------------------------------------------------------------------------
7979

8080
const LEAF_TYPE_ASSET=0
81-
const PUBLIC_NOTE=1
81+
use miden::protocol::note::NOTE_TYPE_PUBLIC
8282
const BURN_NOTE_NUM_STORAGE_ITEMS=0
8383

8484
# PUBLIC INTERFACE
@@ -527,7 +527,7 @@ proc create_burn_note
527527
exec.note::build_recipient
528528
# => [RECIPIENT]
529529

530-
push.PUBLIC_NOTE
530+
push.NOTE_TYPE_PUBLIC
531531
push.DEFAULT_TAG
532532
# => [tag, note_type, RECIPIENT]
533533

crates/miden-protocol/asm/kernels/transaction/lib/note.masm

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ use $kernel::asset::ASSET_SIZE
44
use $kernel::constants::NOTE_MEM_SIZE
55
use $kernel::memory
66

7+
pub use $kernel::util::note::NOTE_TYPE_PUBLIC
8+
pub use $kernel::util::note::NOTE_TYPE_PRIVATE
9+
710
# ERRORS
811
# =================================================================================================
912

crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use $kernel::callbacks
44
use $kernel::fungible_asset
55
use $kernel::memory
66
use $kernel::note
7+
use $kernel::note::NOTE_TYPE_PUBLIC
78
use $kernel::constants::MAX_OUTPUT_NOTES_PER_TX
89
use $kernel::util::note::ATTACHMENT_KIND_NONE
910
use $kernel::util::note::ATTACHMENT_KIND_ARRAY
@@ -14,10 +15,6 @@ use miden::core::word
1415
# CONSTANTS
1516
# =================================================================================================
1617

17-
# Constants for different note types
18-
const PUBLIC_NOTE=1 # 0b01
19-
const PRIVATE_NOTE=2 # 0b10
20-
2118
# The default value of the felt at index 3 in the note metadata header when a new note is created.
2219
# All zeros sets the attachment kind to None and the user-defined attachment scheme to "none".
2320
const ATTACHMENT_DEFAULT_KIND_AND_SCHEME=0
@@ -318,10 +315,12 @@ end
318315
#! or off-chain).
319316
#! - NOTE_METADATA_HEADER is the metadata associated with a note.
320317
pub proc build_metadata_header
321-
# Validate that note type is private or public.
318+
# Validate that note type is private (0) or public (1).
322319
# --------------------------------------------------------------------------------------------
323320

324-
dup.1 eq.PRIVATE_NOTE dup.2 eq.PUBLIC_NOTE or assert.err=ERR_NOTE_INVALID_TYPE
321+
dup.1
322+
u32assert.err=ERR_NOTE_INVALID_TYPE u32lte.NOTE_TYPE_PUBLIC
323+
assert.err=ERR_NOTE_INVALID_TYPE
325324
# => [tag, note_type]
326325

327326
# Validate the note tag fits into a u32.
@@ -330,22 +329,23 @@ pub proc build_metadata_header
330329
u32assert.err=ERR_NOTE_TAG_MUST_BE_U32
331330
# => [tag, note_type]
332331

333-
# Merge note type and sender ID suffix.
332+
# Merge note type, version, and sender ID suffix.
334333
# --------------------------------------------------------------------------------------------
335334

336335
exec.account::get_id
337336
# => [sender_id_suffix, sender_id_prefix, tag, note_type]
338337

339-
# the lower bits of an account ID suffix are guaranteed to be zero, so we can safely use that
340-
# space to encode the note type
341-
movup.3 add
342-
# => [sender_id_suffix_and_note_type, sender_id_prefix, tag]
338+
# The lower 8 bits of the account ID suffix are guaranteed to be zero by construction.
339+
# Encode note_type at bit 4, leaving version at 0 (in bits 0..=3).
340+
# Shifting note_type left by 4 is equivalent to multiplying by 16.
341+
movup.3 mul.16 add
342+
# => [sender_id_suffix_type_version, sender_id_prefix, tag]
343343

344344
# Build metadata header.
345345
# --------------------------------------------------------------------------------------------
346346

347347
push.ATTACHMENT_DEFAULT_KIND_AND_SCHEME movdn.3
348-
# => [sender_id_suffix_and_note_type, sender_id_prefix, tag, attachment_kind_scheme]
348+
# => [sender_id_suffix_type_version, sender_id_prefix, tag, attachment_kind_scheme]
349349
# => [NOTE_METADATA_HEADER]
350350
end
351351

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use miden::core::mem
44

55
# Re-export the max inputs per note constant.
66
pub use miden::protocol::util::note::MAX_NOTE_STORAGE_ITEMS
7+
pub use miden::protocol::util::note::NOTE_TYPE_PUBLIC
8+
pub use miden::protocol::util::note::NOTE_TYPE_PRIVATE
79

810
# ERRORS
911
# =================================================================================================
@@ -184,11 +186,11 @@ end
184186
#! - sender_{suffix,prefix} are the suffix and prefix felts of the sender ID of the note which
185187
#! metadata was provided.
186188
pub proc extract_sender_from_metadata
187-
# => [sender_id_suffix_and_note_type, sender_id_prefix, tag, attachment_kind_scheme]
189+
# => [sender_id_suffix_type_version, sender_id_prefix, tag, attachment_kind_scheme]
188190

189191
# drop tag and attachment_kind_scheme
190192
movup.3 drop movup.2 drop
191-
# => [sender_id_suffix_and_note_type, sender_id_prefix]
193+
# => [sender_id_suffix_type_version, sender_id_prefix]
192194

193195
# extract suffix of sender from merged layout, which means clearing the least significant byte
194196
exec.account_id::shape_suffix
@@ -207,7 +209,7 @@ end
207209
#!
208210
#! Invocation: exec
209211
pub proc extract_attachment_info_from_metadata
210-
# => [sender_id_suffix_and_note_type, sender_id_prefix, tag, attachment_kind_scheme]
212+
# => [sender_id_suffix_type_version, sender_id_prefix, tag, attachment_kind_scheme]
211213
drop drop drop
212214
# => [attachment_kind_scheme]
213215

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,12 @@ pub const ATTACHMENT_KIND_NONE=0
1010
pub const ATTACHMENT_KIND_WORD=1
1111
#! A note attachment consisting of the commitment to a set of felts.
1212
pub const ATTACHMENT_KIND_ARRAY=2
13+
14+
# Note type constants. These encode the note type in the lower byte of the metadata header.
15+
# See NoteType in the Rust protocol crate for details.
16+
17+
#! The note type of private notes.
18+
pub const NOTE_TYPE_PRIVATE=0
19+
20+
#! The note type of public notes.
21+
pub const NOTE_TYPE_PUBLIC=1

crates/miden-protocol/src/note/metadata.rs

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ use crate::Hasher;
1414
use crate::errors::NoteError;
1515
use crate::note::{NoteAttachment, NoteAttachmentKind, NoteAttachmentScheme};
1616

17+
// CONSTANTS
18+
// ================================================================================================
19+
20+
/// The number of bits by which the note type is offset in the first felt of the note metadata.
21+
const NOTE_TYPE_SHIFT: u64 = 4;
22+
1723
// NOTE METADATA
1824
// ================================================================================================
1925

@@ -34,7 +40,7 @@ use crate::note::{NoteAttachment, NoteAttachmentKind, NoteAttachmentScheme};
3440
/// The header word has the following layout:
3541
///
3642
/// ```text
37-
/// 0th felt: [sender_id_suffix (56 bits) | 6 zero bits | note_type (2 bit)]
43+
/// 0th felt: [sender_id_suffix (56 bits) | reserved (3 bits) | note_type (1 bit) | version (4 bits)]
3844
/// 1st felt: [sender_id_prefix (64 bits)]
3945
/// 2nd felt: [32 zero bits | note_tag (32 bits)]
4046
/// 3rd felt: [30 zero bits | attachment_kind (2 bits) | attachment_scheme (32 bits)]
@@ -44,11 +50,13 @@ use crate::note::{NoteAttachment, NoteAttachmentKind, NoteAttachmentScheme};
4450
/// - 1st felt: The lower 8 bits of the account ID suffix are `0` by construction, so that they can
4551
/// be overwritten with other data. The suffix' most significant bit must be zero such that the
4652
/// entire felt retains its validity even if all of its lower 8 bits are set to `1`. So the note
47-
/// type can be comfortably encoded.
53+
/// type and version can be comfortably encoded.
4854
/// - 2nd felt: Is equivalent to the prefix of the account ID so it inherits its validity.
4955
/// - 3rd felt: The upper 32 bits are always zero.
5056
/// - 4th felt: The upper 30 bits are always zero.
5157
///
58+
/// The version is hardcoded to 0 and is reserved to make it easier to introduce another version.
59+
///
5260
/// The value of the attachment word depends on the
5361
/// [`NoteAttachmentKind`](crate::note::NoteAttachmentKind):
5462
/// - [`NoteAttachmentKind::None`](crate::note::NoteAttachmentKind::None): Empty word.
@@ -73,6 +81,12 @@ pub struct NoteMetadata {
7381
}
7482

7583
impl NoteMetadata {
84+
/// Version 0 of the note metadata encoding.
85+
///
86+
/// If we make this public, we may want to instead consider introducing a `NoteMetadataVersion`
87+
/// struct, similar to `AccountIdVersion`.
88+
const VERSION_0: u8 = 0;
89+
7690
// CONSTRUCTORS
7791
// --------------------------------------------------------------------------------------------
7892

@@ -337,12 +351,12 @@ impl TryFrom<Word> for NoteMetadataHeader {
337351
// HELPER FUNCTIONS
338352
// ================================================================================================
339353

340-
/// Merges the suffix of an [`AccountId`] and the [`NoteType`] into a single [`Felt`].
354+
/// Merges the suffix of an [`AccountId`] and note metadata into a single [`Felt`].
341355
///
342356
/// The layout is as follows:
343357
///
344358
/// ```text
345-
/// [sender_id_suffix (56 bits) | 6 zero bits | note_type (2 bits)]
359+
/// [sender_id_suffix (56 bits) | reserved (3 bits) | note_type (1 bit) | version (4 bits)]
346360
/// ```
347361
///
348362
/// The most significant bit of the suffix is guaranteed to be zero, so the felt retains its
@@ -353,28 +367,44 @@ fn merge_sender_suffix_and_note_type(sender_id_suffix: Felt, note_type: NoteType
353367
let mut merged = sender_id_suffix.as_canonical_u64();
354368

355369
let note_type_byte = note_type as u8;
356-
debug_assert!(note_type_byte < 4, "note type must not contain values >= 4");
357-
merged |= note_type_byte as u64;
370+
debug_assert!(note_type_byte < 2, "note type must not contain values >= 2");
371+
// note_type at bit 4, version at bits 0..=3 (hardcoded to NoteMetadata::VERSION_0_NUMBER)
372+
merged |= (note_type_byte as u64) << NOTE_TYPE_SHIFT;
373+
merged |= NoteMetadata::VERSION_0 as u64;
358374

359375
// SAFETY: The most significant bit of the suffix is zero by construction so the u64 will be a
360376
// valid felt.
361377
Felt::try_from(merged).expect("encoded value should be a valid felt")
362378
}
363379

364-
/// Unmerges the sender ID suffix and note type.
380+
/// Unmerges the sender ID suffix and note metadata (note type and version).
365381
fn unmerge_sender_suffix_and_note_type(element: Felt) -> Result<(Felt, NoteType), NoteError> {
366-
const NOTE_TYPE_MASK: u8 = 0b11;
367-
// Inverts the note type mask.
368-
const SENDER_SUFFIX_MASK: u64 = !(NOTE_TYPE_MASK as u64);
382+
// The mask that clears out the lower 8 bits to recover the sender suffix.
383+
const SENDER_SUFFIX_MASK: u64 = 0xffff_ffff_ffff_ff00;
384+
385+
let raw = element.as_canonical_u64();
386+
let version = (raw & 0b1111) as u8;
387+
let note_type_bit = ((raw >> NOTE_TYPE_SHIFT) & 0b1) as u8;
388+
let reserved = ((raw >> 5) & 0b111) as u8;
389+
390+
if reserved != 0 {
391+
return Err(NoteError::other("reserved bits in note metadata header must be zero"));
392+
}
393+
394+
if version != NoteMetadata::VERSION_0 {
395+
return Err(NoteError::other(format!(
396+
"unsupported note metadata version {version}, expected {}",
397+
NoteMetadata::VERSION_0
398+
)));
399+
}
369400

370-
let note_type_byte = element.as_canonical_u64() as u8 & NOTE_TYPE_MASK;
371-
let note_type = NoteType::try_from(note_type_byte).map_err(|source| {
401+
let note_type = NoteType::try_from(note_type_bit).map_err(|source| {
372402
NoteError::other_with_source("failed to decode note type from metadata header", source)
373403
})?;
374404

375405
// No bits were set so felt should still be valid.
376-
let sender_suffix = Felt::try_from(element.as_canonical_u64() & SENDER_SUFFIX_MASK)
377-
.expect("felt should still be valid");
406+
let sender_suffix =
407+
Felt::try_from(raw & SENDER_SUFFIX_MASK).expect("felt should still be valid");
378408

379409
Ok((sender_suffix, note_type))
380410
}

0 commit comments

Comments
 (0)