feat: implement setting multiple note attachments#2795
feat: implement setting multiple note attachments#2795PhilippGackstatter wants to merge 58 commits intonextfrom
Conversation
Also optimizes metadata header serialization.
5625e17 to
e1fdeb5
Compare
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! Not a complete review yet - but I reviewed all Rust code (excluding tests) and almost all MASM code.
Regarding whether to encode attachment size in the metadata or not: I think it should be fine to remove them. As you mentioned, the commitments commit to the number of words implicitly (i.e., hash(WORD_A) will be different from hash(WORD_A || EMPTY_WORD). And I can't think of a reason why we'd need attachment sizes in the metadata.
If we do remove the attachment sizes, I'd keep the freed-up bits for future use (rather than increase the max number of attachments to 6).
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! I'm still making my way through MASM code - but the overall structure looks good, and I don't think there will be any major comments. For now, I left a few minor comments inline.
mmagician
left a comment
There was a problem hiding this comment.
Partial review so far.
Note (😁 ): docs/src/note.md should still be updated.
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! I left just a few small comments inline (and some of them are for the future).
Issue #2874 noted that, after PR #2795, `NoteMetadataHeader` contains more data than `NoteMetadata`, which inverts the usual meaning of "header" and made the type name awkward. Rather than rename, this commit merges the two types so a single `NoteMetadata` carries the user-facing fields (sender, note type, tag) together with the per-attachment headers and attachments commitment that make up the protocol-level metadata. `NoteMetadataHeader` had no external usages as a Rust type beyond ctor and accessor sites, so the merge is mostly mechanical: - `NoteHeader` now holds `NoteMetadata` directly; `metadata_header()` / `into_metadata_header()` accessors are gone, replaced by `metadata()` / `into_metadata()`. - `Note::with_attachments` and `PartialNote::new` now build the metadata via the new `NoteMetadata::with_attachments(&NoteAttachments)` builder. - `compute_note_commitment` takes `&NoteMetadata`. Wire format keeps the same kernel-facing metadata word encoding. To avoid duplicating attachment headers + commitment on the wire (since they are derivable from the `NoteAttachments` payload that follows), `Note` and `PartialNote` use small `pub(super)` `write_core` / `read_core` helpers on `NoteMetadata` that handle just the 3 core fields. Standalone `NoteMetadata` and `NoteHeader` serialization still write the full struct (matching what the old `NoteMetadataHeader` serialized). `TryFrom<Word>` was already removed in #2795. Closes #2874. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…masm Co-authored-by: Marti <marti@miden.team>
Overview
This branch extends the note attachment model from supporting a single attachment per note to supporting up to 4 attachments per note. Previously, a note could carry at most one attachment (None, Word, or Array); now notes have an ordered collection of attachments that are appended incrementally.
Key Conceptual Changes
Single attachment → Collection of attachments
NoteMetadatano longer owns an attachment. It is now a pure user-facing type containing only sender, note type, and tag.NoteAttachmentscollection type holds 0–4NoteAttachmentvalues per note.Notestruct gains anattachments: NoteAttachmentsfield.Attachment commitment is now over individual attachment commitments
NoteAttachmentscommitment is computed over the individual attachment commitment words (i.e.,hash(attachment_0_commitment || ... || attachment_N_commitment)), rather than directly over raw attachment data.New
NoteMetadataHeaderas the protocol-level encodingNoteMetadataHeaderwrapsNoteMetadatatogether with attachment headers and an attachments commitment.num_wordsvalues (8 bits each) alongside the note tag (32 bits).hash(NOTE_ID || METADATA_HEADER_COMMITMENT)where the header commitment includes attachments.TryFrom<Word> for NoteMetadataHeaderbecause the code was unused and it no longer works: We cannot recover the attachments commitment from a single word.NoteAttachmentHeaderreplacesNoteAttachmentKindNoteAttachmentKindenum (None/Word/Array) is removed.NoteAttachmentHeaderstruct carriesscheme: NoteAttachmentSchemeandnum_words: u8, which together describe the attachment's type and size.NoteAttachmentScheme::noneis kept at 0 since we can check presence of an attachment withword_size != 0.num_words = 1indicates a Word attachment;num_words > 1indicates an Array attachment. There is no longer a "None" variant - absence of an attachment is an empty list of attachments.NoteAttachmentSchemenarrowed tou16u32; it is now au16(max 65534) to fit four schemes into a single felt in the metadata encoding.NoteAttachmentArraynow storesVec<Word>instead ofVec<Felt>Vec<Word>rather than a flat vector of field elements.NoteAttachment::MAX_NUM_WORDS(254) constrains the size of any single attachment.NoteAttachments::MAX_NUM_WORDS(512) constrains the size of all attachments of a note - at most 16 KiB total like before.Word vs Array
NoteAttachmentContent::Word, otherwiseArray. Other than the size difference, these two are the same.Wordvariant is a bit nicer - no length checking.NoteAttachmentContentstructure.Kernel procedure:
set_attachment→add_attachmentoutput_note_set_attachmentis replaced byoutput_note_add_attachment, which appends an attachment to the next available slot and increments a counter.Memory layout changes
num_attachmentscounter. It is technically redundant since the number of attachmens can be derived from the metadata. This is for ease of access in the kernel.ATTACHMENTS_COMMITMENT(renamed fromATTACHMENT) representing the commitment over all attachments. All individual attachment commitments or their raw data is expected to be accessed through the advice provider.TransactionHeader does not contain attachment data
TransactionHeaderpreviously contained the full attachment data as part of theNoteMetadatawithinNoteHeader, and this is no longer the case. But this now seems correct, because attachments are always publicly available in blocks because aBlockBodycontains all fullOutputNoteobjects and:PublicOutputeNote(Note), where theNoteholds the full attachments data,PrivateOutputNote(previouslyPrivateNoteHeader), which also hols the full attachments data in addition to the note header.Alternatives: Hash Single Word Attachments
As discussed in #2555 (comment), there are some alternatives.
Choices:
I ended up implementing the second option, "hash all attachments". The main benefit is that attachments appear more uniformly (``), an attachment is conceptually simply the hash of some number of words. The main downside is that accessing a word-sized attachment requires hashing.
Follow-up
In subsequent PRs:
input_note_get_metadataand possibly a newinput_note_get_attachmentsprocedure.metadata_into_tagfor note tag extraction which now becomes necessary.Note.