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