Skip to main content

miden_protocol/transaction/outputs/
notes.rs

1use alloc::collections::BTreeSet;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4use core::fmt::Debug;
5
6use crate::constants::NOTE_MAX_SIZE;
7use crate::errors::{OutputNoteError, TransactionOutputError};
8use crate::note::{
9    Note,
10    NoteAssets,
11    NoteAttachments,
12    NoteDetailsCommitment,
13    NoteHeader,
14    NoteId,
15    NoteMetadata,
16    NoteRecipient,
17    PartialNote,
18};
19use crate::utils::serde::{
20    ByteReader,
21    ByteWriter,
22    Deserializable,
23    DeserializationError,
24    Serializable,
25};
26use crate::{Felt, Hasher, MAX_OUTPUT_NOTES_PER_TX, Word};
27
28// OUTPUT NOTE COLLECTION
29// ================================================================================================
30
31/// Contains a list of output notes of a transaction. The list can be empty if the transaction does
32/// not produce any notes.
33///
34/// This struct is generic over the note type `N`, allowing it to be used with both
35/// [`RawOutputNote`] (in [`ExecutedTransaction`](crate::transaction::ExecutedTransaction)) and
36/// [`OutputNote`] (in [`ProvenTransaction`](crate::transaction::ProvenTransaction)).
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct OutputNoteCollection<N> {
39    notes: Vec<N>,
40    commitment: Word,
41}
42
43impl<N> OutputNoteCollection<N>
44where
45    for<'a> &'a NoteHeader: From<&'a N>,
46    for<'a> NoteId: From<&'a N>,
47{
48    // CONSTRUCTOR
49    // --------------------------------------------------------------------------------------------
50
51    /// Returns new [OutputNoteCollection] instantiated from the provided vector of notes.
52    ///
53    /// # Errors
54    /// Returns an error if:
55    /// - The total number of notes is greater than [`MAX_OUTPUT_NOTES_PER_TX`].
56    /// - The vector of notes contains duplicates.
57    pub fn new(notes: Vec<N>) -> Result<Self, TransactionOutputError> {
58        if notes.len() > MAX_OUTPUT_NOTES_PER_TX {
59            return Err(TransactionOutputError::TooManyOutputNotes(notes.len()));
60        }
61
62        let mut seen_notes = BTreeSet::new();
63        for note in notes.iter() {
64            let note_id = NoteId::from(note);
65            if !seen_notes.insert(note_id) {
66                return Err(TransactionOutputError::DuplicateOutputNote(note_id));
67            }
68        }
69
70        let commitment = Self::compute_commitment(notes.iter().map(<&NoteHeader>::from));
71
72        Ok(Self { notes, commitment })
73    }
74
75    // PUBLIC ACCESSORS
76    // --------------------------------------------------------------------------------------------
77
78    /// Returns the commitment to the output notes.
79    ///
80    /// The commitment is computed as a sequential hash of `(note_details_commitment,
81    /// metadata_commitment)` tuples for the notes created in a transaction.
82    pub fn commitment(&self) -> Word {
83        self.commitment
84    }
85
86    /// Returns total number of output notes.
87    pub fn num_notes(&self) -> usize {
88        self.notes.len()
89    }
90
91    /// Returns true if this [OutputNoteCollection] does not contain any notes.
92    pub fn is_empty(&self) -> bool {
93        self.notes.is_empty()
94    }
95
96    /// Returns a reference to the note located at the specified index.
97    pub fn get_note(&self, idx: usize) -> &N {
98        &self.notes[idx]
99    }
100
101    // ITERATORS
102    // --------------------------------------------------------------------------------------------
103
104    /// Returns an iterator over notes in this [OutputNoteCollection].
105    pub fn iter(&self) -> impl Iterator<Item = &N> {
106        self.notes.iter()
107    }
108
109    // HELPERS
110    // --------------------------------------------------------------------------------------------
111
112    /// Computes a commitment to output notes.
113    ///
114    /// - For an empty list, [`Word::empty`] is returned.
115    /// - For a non-empty list of notes, this is a sequential hash of `(note_details_commitment,
116    ///   metadata_commitment)` tuples for the notes created in a transaction, where
117    ///   `metadata_commitment` is the return value of [`NoteMetadata::to_commitment`].
118    pub(crate) fn compute_commitment<'header>(
119        notes: impl ExactSizeIterator<Item = &'header NoteHeader>,
120    ) -> Word {
121        if notes.len() == 0 {
122            return Word::empty();
123        }
124
125        let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 8);
126        for note_header in notes {
127            elements.extend_from_slice(note_header.details_commitment().as_elements());
128            elements.extend_from_slice(note_header.metadata().to_commitment().as_elements());
129        }
130
131        Hasher::hash_elements(&elements)
132    }
133}
134
135impl<N> IntoIterator for OutputNoteCollection<N> {
136    type Item = N;
137
138    type IntoIter = alloc::vec::IntoIter<N>;
139
140    fn into_iter(self) -> Self::IntoIter {
141        self.notes.into_iter()
142    }
143}
144
145// SERIALIZATION
146// ------------------------------------------------------------------------------------------------
147
148impl<N: Serializable> Serializable for OutputNoteCollection<N> {
149    fn write_into<W: ByteWriter>(&self, target: &mut W) {
150        // assert is OK here because we enforce max number of notes in the constructor
151        assert!(self.notes.len() <= u16::MAX.into());
152        target.write_u16(self.notes.len() as u16);
153        target.write_many(&self.notes);
154    }
155}
156
157impl<N> Deserializable for OutputNoteCollection<N>
158where
159    N: Deserializable,
160    for<'a> &'a NoteHeader: From<&'a N>,
161    for<'a> NoteId: From<&'a N>,
162{
163    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
164        let num_notes = source.read_u16()?;
165        let notes = source.read_many_iter::<N>(num_notes.into())?.collect::<Result<_, _>>()?;
166        Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
167    }
168}
169
170// RAW OUTPUT NOTES
171// ================================================================================================
172
173/// Output notes produced during transaction execution (before proving).
174///
175/// Contains [`RawOutputNote`] instances which represent notes as they exist immediately after
176/// transaction execution.
177pub type RawOutputNotes = OutputNoteCollection<RawOutputNote>;
178
179/// The types of note outputs produced during transaction execution (before proving).
180///
181/// This enum represents notes as they exist immediately after transaction execution,
182/// before they are processed for inclusion in a proven transaction. It includes:
183/// - Full notes with all details (public or private)
184/// - Partial notes (notes created with only recipient digest, not full recipient details)
185///
186/// During proving, these are converted to [`OutputNote`] via the
187/// [`into_output_note`](Self::into_output_note) method, which enforces size limits on public notes
188/// and converts private/partial notes to headers.
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub enum RawOutputNote {
191    Full(Note),
192    Partial(PartialNote),
193}
194
195impl RawOutputNote {
196    const FULL: u8 = 0;
197    const PARTIAL: u8 = 1;
198
199    /// The assets contained in the note.
200    pub fn assets(&self) -> &NoteAssets {
201        match self {
202            Self::Full(note) => note.assets(),
203            Self::Partial(note) => note.assets(),
204        }
205    }
206
207    /// Unique note identifier.
208    ///
209    /// This value commits to the note details and metadata.
210    pub fn id(&self) -> NoteId {
211        match self {
212            Self::Full(note) => note.id(),
213            Self::Partial(note) => note.id(),
214        }
215    }
216
217    /// Returns the commitment to the note's details, excluding metadata.
218    pub fn details_commitment(&self) -> NoteDetailsCommitment {
219        match self {
220            Self::Full(note) => note.details_commitment(),
221            Self::Partial(note) => note.details_commitment(),
222        }
223    }
224
225    /// Returns the recipient of the processed [`Full`](RawOutputNote::Full) output note, [`None`]
226    /// if the note type is not [`Full`](RawOutputNote::Full).
227    ///
228    /// See [crate::note::NoteRecipient] for more details.
229    pub fn recipient(&self) -> Option<&NoteRecipient> {
230        match self {
231            Self::Full(note) => Some(note.recipient()),
232            Self::Partial(_) => None,
233        }
234    }
235
236    /// Returns the recipient digest of the output note.
237    ///
238    /// See [crate::note::NoteRecipient] for more details.
239    pub fn recipient_digest(&self) -> Word {
240        match self {
241            RawOutputNote::Full(note) => note.recipient().digest(),
242            RawOutputNote::Partial(note) => note.recipient_digest(),
243        }
244    }
245
246    /// Returns the note's metadata.
247    pub fn metadata(&self) -> &NoteMetadata {
248        match self {
249            Self::Full(note) => note.metadata(),
250            Self::Partial(note) => note.metadata(),
251        }
252    }
253
254    /// Converts this output note to a proven output note.
255    ///
256    /// This method performs the following transformations:
257    /// - Private notes (full or partial) are converted into note headers (only public info
258    ///   retained).
259    /// - Full public notes are wrapped in [`PublicOutputNote`], which enforces size limits
260    ///
261    /// # Errors
262    /// Returns an error if a public note exceeds the maximum allowed size ([`NOTE_MAX_SIZE`]).
263    pub fn into_output_note(self) -> Result<OutputNote, OutputNoteError> {
264        match self {
265            Self::Full(note) if note.metadata().is_private() => {
266                let details_commitment = note.details_commitment();
267                let (_, metadata, _, attachments) = note.into_parts();
268                let note_header = NoteHeader::new(details_commitment, metadata);
269                Ok(OutputNote::Private(PrivateOutputNote::new(note_header, attachments)?))
270            },
271            Self::Full(note) => Ok(OutputNote::Public(PublicOutputNote::new(note)?)),
272            Self::Partial(note) => {
273                let (_, header, attachments) = note.into_parts();
274                Ok(OutputNote::Private(PrivateOutputNote::new(header, attachments)?))
275            },
276        }
277    }
278
279    /// Returns a reference to the [`NoteHeader`] of this note.
280    pub fn header(&self) -> &NoteHeader {
281        match self {
282            Self::Full(note) => note.header(),
283            Self::Partial(note) => note.header(),
284        }
285    }
286
287    /// Returns a reference to the note's attachments.
288    pub fn attachments(&self) -> &NoteAttachments {
289        match self {
290            Self::Full(note) => note.attachments(),
291            Self::Partial(note) => note.attachments(),
292        }
293    }
294}
295
296impl From<&RawOutputNote> for NoteId {
297    fn from(note: &RawOutputNote) -> Self {
298        note.id()
299    }
300}
301
302impl<'note> From<&'note RawOutputNote> for &'note NoteHeader {
303    fn from(note: &'note RawOutputNote) -> Self {
304        note.header()
305    }
306}
307
308impl Serializable for RawOutputNote {
309    fn write_into<W: ByteWriter>(&self, target: &mut W) {
310        match self {
311            Self::Full(note) => {
312                target.write(Self::FULL);
313                target.write(note);
314            },
315            Self::Partial(note) => {
316                target.write(Self::PARTIAL);
317                target.write(note);
318            },
319        }
320    }
321
322    fn get_size_hint(&self) -> usize {
323        // Serialized size of the enum tag.
324        let tag_size = 0u8.get_size_hint();
325
326        match self {
327            Self::Full(note) => tag_size + note.get_size_hint(),
328            Self::Partial(note) => tag_size + note.get_size_hint(),
329        }
330    }
331}
332
333impl Deserializable for RawOutputNote {
334    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
335        match source.read_u8()? {
336            Self::FULL => Ok(Self::Full(Note::read_from(source)?)),
337            Self::PARTIAL => Ok(Self::Partial(PartialNote::read_from(source)?)),
338            v => Err(DeserializationError::InvalidValue(format!("invalid output note type: {v}"))),
339        }
340    }
341}
342
343// OUTPUT NOTES
344// ================================================================================================
345
346/// Output notes in a proven transaction.
347///
348/// Contains [`OutputNote`] instances which have been processed for inclusion in proven
349/// transactions, with size limits enforced on public notes.
350pub type OutputNotes = OutputNoteCollection<OutputNote>;
351
352/// Output note types that can appear in a proven transaction.
353///
354/// This enum represents the final form of output notes after proving. Unlike [`RawOutputNote`],
355/// this enum:
356/// - Does not include partial notes (they are converted to headers).
357/// - Wraps public notes in [`PublicOutputNote`] which enforces size limits.
358/// - Contains only the minimal information needed for verification.
359#[allow(clippy::large_enum_variant)]
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub enum OutputNote {
362    /// A public note with full details, size-validated.
363    Public(PublicOutputNote),
364    /// A private note with a header and attachments.
365    Private(PrivateOutputNote),
366}
367
368impl OutputNote {
369    const PUBLIC: u8 = 0;
370    const PRIVATE: u8 = 1;
371
372    /// Unique note identifier.
373    ///
374    /// This value commits to the note details and metadata.
375    pub fn id(&self) -> NoteId {
376        match self {
377            Self::Public(note) => note.id(),
378            Self::Private(header) => header.id(),
379        }
380    }
381
382    /// Returns the commitment to the note's details, excluding metadata.
383    pub fn details_commitment(&self) -> NoteDetailsCommitment {
384        match self {
385            Self::Public(note) => note.details_commitment(),
386            Self::Private(header) => header.details_commitment(),
387        }
388    }
389
390    /// The assets contained in the note, if available.
391    ///
392    /// Returns `Some` for public notes, `None` for private notes.
393    pub fn assets(&self) -> Option<&NoteAssets> {
394        match self {
395            Self::Public(note) => Some(note.assets()),
396            Self::Private(_) => None,
397        }
398    }
399
400    /// Returns the note's metadata.
401    pub fn metadata(&self) -> &NoteMetadata {
402        <&NoteHeader>::from(self).metadata()
403    }
404
405    /// Returns the recipient of the public note, if this is a public note.
406    pub fn recipient(&self) -> Option<&NoteRecipient> {
407        match self {
408            Self::Public(note) => Some(note.recipient()),
409            Self::Private(_) => None,
410        }
411    }
412}
413
414// CONVERSIONS
415// ------------------------------------------------------------------------------------------------
416
417impl<'note> From<&'note OutputNote> for &'note NoteHeader {
418    fn from(note: &'note OutputNote) -> Self {
419        match note {
420            OutputNote::Public(public_note) => public_note.header(),
421            OutputNote::Private(private_note) => private_note.header(),
422        }
423    }
424}
425
426impl From<&OutputNote> for NoteId {
427    fn from(note: &OutputNote) -> Self {
428        note.id()
429    }
430}
431
432// SERIALIZATION
433// ------------------------------------------------------------------------------------------------
434
435impl Serializable for OutputNote {
436    fn write_into<W: ByteWriter>(&self, target: &mut W) {
437        match self {
438            Self::Public(note) => {
439                target.write(Self::PUBLIC);
440                target.write(note);
441            },
442            Self::Private(header) => {
443                target.write(Self::PRIVATE);
444                target.write(header);
445            },
446        }
447    }
448
449    fn get_size_hint(&self) -> usize {
450        let tag_size = 0u8.get_size_hint();
451        match self {
452            Self::Public(note) => tag_size + note.get_size_hint(),
453            Self::Private(header) => tag_size + header.get_size_hint(),
454        }
455    }
456}
457
458impl Deserializable for OutputNote {
459    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
460        match source.read_u8()? {
461            Self::PUBLIC => Ok(Self::Public(PublicOutputNote::read_from(source)?)),
462            Self::PRIVATE => Ok(Self::Private(PrivateOutputNote::read_from(source)?)),
463            v => Err(DeserializationError::InvalidValue(format!(
464                "invalid proven output note type: {v}"
465            ))),
466        }
467    }
468}
469
470// PUBLIC OUTPUT NOTE
471// ================================================================================================
472
473/// A public output note with enforced size limits.
474///
475/// This struct wraps a [`Note`] and guarantees that:
476/// - The note is public (not private).
477/// - The serialized size does not exceed [`NOTE_MAX_SIZE`].
478///
479/// This type is used in [`OutputNote::Public`] to ensure that all public notes in proven
480/// transactions meet the protocol's size requirements.
481#[derive(Debug, Clone, PartialEq, Eq)]
482pub struct PublicOutputNote(Note);
483
484impl PublicOutputNote {
485    /// Creates a new [`PublicOutputNote`] from the given note.
486    ///
487    /// # Errors
488    /// Returns an error if:
489    /// - The note is private.
490    /// - The serialized size exceeds [`NOTE_MAX_SIZE`].
491    pub fn new(mut note: Note) -> Result<Self, OutputNoteError> {
492        // Ensure the note is public
493        if note.metadata().is_private() {
494            return Err(OutputNoteError::NoteIsPrivate(note.id()));
495        }
496
497        // Strip decorators from the note script
498        note.minify_script();
499
500        // Check the size limit after stripping decorators
501        let note_size = note.get_size_hint();
502        if note_size > NOTE_MAX_SIZE as usize {
503            return Err(OutputNoteError::NoteSizeLimitExceeded { note_id: note.id(), note_size });
504        }
505
506        Ok(Self(note))
507    }
508
509    /// Returns the unique identifier of this note.
510    pub fn id(&self) -> NoteId {
511        self.0.id()
512    }
513
514    /// Returns the commitment to the note's details, excluding metadata.
515    pub fn details_commitment(&self) -> NoteDetailsCommitment {
516        self.0.details_commitment()
517    }
518
519    /// Returns the note's metadata.
520    pub fn metadata(&self) -> &NoteMetadata {
521        self.0.metadata()
522    }
523
524    /// Returns the note's assets.
525    pub fn assets(&self) -> &NoteAssets {
526        self.0.assets()
527    }
528
529    /// Returns the note's recipient.
530    pub fn recipient(&self) -> &NoteRecipient {
531        self.0.recipient()
532    }
533
534    /// Returns the note's header.
535    pub fn header(&self) -> &NoteHeader {
536        self.0.header()
537    }
538
539    /// Returns a reference to the underlying note.
540    pub fn as_note(&self) -> &Note {
541        &self.0
542    }
543
544    /// Consumes this wrapper and returns the underlying note.
545    pub fn into_note(self) -> Note {
546        self.0
547    }
548}
549
550impl Serializable for PublicOutputNote {
551    fn write_into<W: ByteWriter>(&self, target: &mut W) {
552        self.0.write_into(target);
553    }
554
555    fn get_size_hint(&self) -> usize {
556        self.0.get_size_hint()
557    }
558}
559
560impl Deserializable for PublicOutputNote {
561    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
562        let note = Note::read_from(source)?;
563        Self::new(note).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
564    }
565}
566
567// PRIVATE NOTE HEADER
568// ================================================================================================
569
570/// A [`NoteHeader`] of a private note, along with its public attachments.
571#[derive(Debug, Clone, PartialEq, Eq)]
572pub struct PrivateOutputNote {
573    header: NoteHeader,
574    attachments: NoteAttachments,
575}
576
577impl PrivateOutputNote {
578    /// Creates a new [`PrivateOutputNote`] from the given note header and attachments.
579    ///
580    /// # Errors
581    /// Returns an error if:
582    /// - The provided header is for a public note.
583    pub fn new(header: NoteHeader, attachments: NoteAttachments) -> Result<Self, OutputNoteError> {
584        if header.metadata().is_public() {
585            return Err(OutputNoteError::NoteIsPublic(header.id()));
586        }
587
588        Ok(Self { header, attachments })
589    }
590
591    /// Returns the note's identifier.
592    ///
593    /// The [NoteId] commits to both note details and metadata.
594    pub fn id(&self) -> NoteId {
595        self.header.id()
596    }
597
598    /// Returns the note's metadata.
599    pub fn metadata(&self) -> &NoteMetadata {
600        self.header.metadata()
601    }
602
603    /// Returns the note's attachments.
604    pub fn attachments(&self) -> &NoteAttachments {
605        &self.attachments
606    }
607
608    /// Returns the commitment to the note's details, excluding metadata.
609    pub fn details_commitment(&self) -> NoteDetailsCommitment {
610        self.header.details_commitment()
611    }
612
613    /// Returns a reference to the underlying note header.
614    pub fn header(&self) -> &NoteHeader {
615        &self.header
616    }
617
618    /// Consumes this wrapper and returns the underlying note header.
619    pub fn into_header(self) -> NoteHeader {
620        self.header
621    }
622}
623
624impl Serializable for PrivateOutputNote {
625    fn write_into<W: ByteWriter>(&self, target: &mut W) {
626        self.header.write_into(target);
627        self.attachments.write_into(target);
628    }
629
630    fn get_size_hint(&self) -> usize {
631        self.header.get_size_hint() + self.attachments.get_size_hint()
632    }
633}
634
635impl Deserializable for PrivateOutputNote {
636    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
637        let header = NoteHeader::read_from(source)?;
638        let attachments = NoteAttachments::read_from(source)?;
639        Self::new(header, attachments)
640            .map_err(|err| DeserializationError::InvalidValue(err.to_string()))
641    }
642}