Skip to content

feat: implement setting multiple note attachments#2795

Open
PhilippGackstatter wants to merge 24 commits intonextfrom
pgackst-multiple-note-attachments
Open

feat: implement setting multiple note attachments#2795
PhilippGackstatter wants to merge 24 commits intonextfrom
pgackst-multiple-note-attachments

Conversation

@PhilippGackstatter
Copy link
Copy Markdown
Contributor

@PhilippGackstatter PhilippGackstatter commented Apr 20, 2026

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

  • NoteMetadata no longer owns an attachment. It is now a pure user-facing type containing only sender, note type, and tag.
  • A new NoteAttachments collection type holds 0–4 NoteAttachment values per note.
  • The Note struct gains an attachments: NoteAttachments field.

Attachment commitment is now over individual attachment commitments

  • The NoteAttachments commitment is computed over the individual attachment commitment words (i.e., hash(attachment_0_commitment || ... || attachment_N_commitment)), rather than directly over raw attachment data.
  • The main motivation is to keep the kernel simpler internally (we'd have to keep track of partial hasher states otherwise - see Allow multiple attachments per note and make them immutable #2555 (comment) for more details)
  • This also allows accessing individual attachments more easily (e.g. no need to unhash an array with 200 words if all you need to access is a 1-word attachment).

New NoteMetadataHeader as the protocol-level encoding

  • A repurposed NoteMetadataHeader wraps NoteMetadata together with attachment headers and an attachments commitment.
  • The metadata word now packs per-attachment information for all 4 slots:
    • Felt[2] encodes four num_words values (8 bits each) alongside the note tag (32 bits).
    • Felt[3] encodes four attachment schemes (16 bits each).
  • The note commitment is now hash(NOTE_ID || METADATA_HEADER_COMMITMENT) where the header commitment includes attachments.
  • Removes TryFrom<Word> for NoteMetadataHeader because the code was unused and it no longer works: We cannot recover the attachments commitment from a single word.

NoteAttachmentHeader replaces NoteAttachmentKind

  • The old NoteAttachmentKind enum (None/Word/Array) is removed.
  • A new NoteAttachmentHeader struct carries scheme: NoteAttachmentScheme and num_words: u8, which together describe the attachment's type and size.
  • NoteAttachmentScheme::none is kept at 0 since we can check presence of an attachment with word_size != 0.
  • num_words = 1 indicates a Word attachment; num_words > 1 indicates an Array attachment. There is no longer a "None" variant - absence of an attachment is an empty list of attachments.

NoteAttachmentScheme narrowed to u16

  • The attachment scheme was previously a u32; it is now a u16 (max 65534) to fit four schemes into a single felt in the metadata encoding.

NoteAttachmentArray now stores Vec<Word> instead of Vec<Felt>

  • Array attachments are word-aligned: the content is 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

  • If the number of words is 1, an attachment is represented as NoteAttachmentContent::Word, otherwise Array. Other than the size difference, these two are the same.
  • This begs the question if we should collapse these into a single type:
    • Con: Working with a Word variant is a bit nicer - no length checking.
    • Pro: Simplifies NoteAttachmentContent structure.

Kernel procedure: set_attachmentadd_attachment

  • The kernel API changes from a "set" semantic to an "add/append" semantic. Attachments are immutable once added.
  • output_note_set_attachment is replaced by output_note_add_attachment, which appends an attachment to the next available slot and increments a counter.
  • Input validation ensures scheme ≤ 65534, 0 < num_words ≤ 254, and attachment count ≤ 4.

Memory layout changes

  • Output notes now have 4 dedicated attachment word slots (offsets 12, 16, 20, 24) plus a num_attachments counter. It is technically redundant since the number of attachmens can be derived from the metadata. This is for ease of access in the kernel.
  • Input notes store an ATTACHMENTS_COMMITMENT (renamed from ATTACHMENT) 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

  • TransactionHeader previously contained the full attachment data as part of the NoteMetadata within NoteHeader, and this is no longer the case. But this now seems correct, because attachments are always publicly available in blocks because a BlockBody contains all full OutputNote objects and:
    • it either contains PublicOutputeNote(Note), where the Note holds the full attachments data,
    • or it contains the new PrivateOutputNote (previously PrivateNoteHeader), 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:

  • word attachment is itself the content,
  • word attachment is the commitment to a single, raw word.

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:

  • Implement a kernel API for accessing all attachments.
    • This requires updates to input_note_get_metadata and possibly a new input_note_get_attachments procedure.
  • output_note::add_array_attachment should probably take a ptr and num words as input and internally hash the data for convenience - currently it is a direct wrapper around add_attachment.
  • Validate in tx kernel that total number of attachments words per note is not exceeded (epilogue).
  • Add metadata_into_tag for note tag extraction which now becomes necessary.
  • Check if we can and should change PartialNote to contain NoteId, NoteMetadata, NoteAssets and NoteAttachments. The note header would have to be constructed on demand. Same for Note.
  • Make NoteHeader Copy again, which has some ripple effects.
  • Consider adding ability to specify if an attachment can be added once or multiple times and enforce it in the kernel
    • Change NetworkAccountTarget to make use of this.

@PhilippGackstatter PhilippGackstatter marked this pull request as draft April 20, 2026 14:47
@PhilippGackstatter PhilippGackstatter force-pushed the pgackst-multiple-note-attachments branch from 5625e17 to e1fdeb5 Compare April 20, 2026 17:44
@PhilippGackstatter PhilippGackstatter marked this pull request as ready for review April 20, 2026 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant