miden_objects/transaction/
outputs.rs

1use alloc::{collections::BTreeSet, string::ToString, vec::Vec};
2use core::fmt::Debug;
3
4use crate::{
5    Digest, Felt, Hasher, MAX_OUTPUT_NOTES_PER_TX, TransactionOutputError, Word,
6    account::AccountHeader,
7    block::BlockNumber,
8    note::{
9        Note, NoteAssets, NoteHeader, NoteId, NoteMetadata, PartialNote, compute_note_commitment,
10    },
11    utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
12};
13// TRANSACTION OUTPUTS
14// ================================================================================================
15
16/// Describes the result of executing a transaction.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct TransactionOutputs {
19    /// Information related to the account's final state.
20    pub account: AccountHeader,
21    /// Set of output notes created by the transaction.
22    pub output_notes: OutputNotes,
23    /// Defines up to which block the transaction is considered valid.
24    pub expiration_block_num: BlockNumber,
25}
26
27impl Serializable for TransactionOutputs {
28    fn write_into<W: ByteWriter>(&self, target: &mut W) {
29        self.account.write_into(target);
30        self.output_notes.write_into(target);
31        self.expiration_block_num.write_into(target);
32    }
33}
34
35impl Deserializable for TransactionOutputs {
36    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
37        let account = AccountHeader::read_from(source)?;
38        let output_notes = OutputNotes::read_from(source)?;
39        let expiration_block_num = BlockNumber::read_from(source)?;
40
41        Ok(Self {
42            account,
43            output_notes,
44            expiration_block_num,
45        })
46    }
47}
48
49// OUTPUT NOTES
50// ================================================================================================
51
52/// Contains a list of output notes of a transaction. The list can be empty if the transaction does
53/// not produce any notes.
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct OutputNotes {
56    notes: Vec<OutputNote>,
57    commitment: Digest,
58}
59
60impl OutputNotes {
61    // CONSTRUCTOR
62    // --------------------------------------------------------------------------------------------
63    /// Returns new [OutputNotes] instantiated from the provide vector of notes.
64    ///
65    /// # Errors
66    /// Returns an error if:
67    /// - The total number of notes is greater than [`MAX_OUTPUT_NOTES_PER_TX`].
68    /// - The vector of notes contains duplicates.
69    pub fn new(notes: Vec<OutputNote>) -> Result<Self, TransactionOutputError> {
70        if notes.len() > MAX_OUTPUT_NOTES_PER_TX {
71            return Err(TransactionOutputError::TooManyOutputNotes(notes.len()));
72        }
73
74        let mut seen_notes = BTreeSet::new();
75        for note in notes.iter() {
76            if !seen_notes.insert(note.id()) {
77                return Err(TransactionOutputError::DuplicateOutputNote(note.id()));
78            }
79        }
80
81        let commitment = build_output_notes_commitment(&notes);
82
83        Ok(Self { notes, commitment })
84    }
85
86    // PUBLIC ACCESSORS
87    // --------------------------------------------------------------------------------------------
88
89    /// Returns the commitment to the output notes.
90    ///
91    /// The commitment is computed as a sequential hash of (hash, metadata) tuples for the notes
92    /// created in a transaction.
93    pub fn commitment(&self) -> Digest {
94        self.commitment
95    }
96    /// Returns total number of output notes.
97    pub fn num_notes(&self) -> usize {
98        self.notes.len()
99    }
100
101    /// Returns true if this [OutputNotes] does not contain any notes.
102    pub fn is_empty(&self) -> bool {
103        self.notes.is_empty()
104    }
105
106    /// Returns a reference to the note located at the specified index.
107    pub fn get_note(&self, idx: usize) -> &OutputNote {
108        &self.notes[idx]
109    }
110
111    // ITERATORS
112    // --------------------------------------------------------------------------------------------
113
114    /// Returns an iterator over notes in this [OutputNotes].
115    pub fn iter(&self) -> impl Iterator<Item = &OutputNote> {
116        self.notes.iter()
117    }
118}
119
120// SERIALIZATION
121// ------------------------------------------------------------------------------------------------
122
123impl Serializable for OutputNotes {
124    fn write_into<W: ByteWriter>(&self, target: &mut W) {
125        // assert is OK here because we enforce max number of notes in the constructor
126        assert!(self.notes.len() <= u16::MAX.into());
127        target.write_u16(self.notes.len() as u16);
128        target.write_many(&self.notes);
129    }
130}
131
132impl Deserializable for OutputNotes {
133    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
134        let num_notes = source.read_u16()?;
135        let notes = source.read_many::<OutputNote>(num_notes.into())?;
136        Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
137    }
138}
139
140// HELPER FUNCTIONS
141// ------------------------------------------------------------------------------------------------
142
143/// Build a commitment to output notes.
144///
145/// For a non-empty list of notes, this is a sequential hash of (note_id, metadata) tuples for the
146/// notes created in a transaction. For an empty list, [EMPTY_WORD] is returned.
147fn build_output_notes_commitment(notes: &[OutputNote]) -> Digest {
148    if notes.is_empty() {
149        return Digest::default();
150    }
151
152    let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 8);
153    for note in notes.iter() {
154        elements.extend_from_slice(note.id().as_elements());
155        elements.extend_from_slice(&Word::from(note.metadata()));
156    }
157
158    Hasher::hash_elements(&elements)
159}
160
161// OUTPUT NOTE
162// ================================================================================================
163
164const FULL: u8 = 0;
165const PARTIAL: u8 = 1;
166const HEADER: u8 = 2;
167
168/// The types of note outputs supported by the transaction kernel.
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum OutputNote {
171    Full(Note),
172    Partial(PartialNote),
173    Header(NoteHeader),
174}
175
176impl OutputNote {
177    /// The assets contained in the note.
178    pub fn assets(&self) -> Option<&NoteAssets> {
179        match self {
180            OutputNote::Full(note) => Some(note.assets()),
181            OutputNote::Partial(note) => Some(note.assets()),
182            OutputNote::Header(_) => None,
183        }
184    }
185
186    /// Unique note identifier.
187    ///
188    /// This value is both an unique identifier and a commitment to the note.
189    pub fn id(&self) -> NoteId {
190        match self {
191            OutputNote::Full(note) => note.id(),
192            OutputNote::Partial(note) => note.id(),
193            OutputNote::Header(note) => note.id(),
194        }
195    }
196
197    /// Value that represents under which condition a note can be consumed.
198    ///
199    /// See [crate::note::NoteRecipient] for more details.
200    pub fn recipient_digest(&self) -> Option<Digest> {
201        match self {
202            OutputNote::Full(note) => Some(note.recipient().digest()),
203            OutputNote::Partial(note) => Some(note.recipient_digest()),
204            OutputNote::Header(_) => None,
205        }
206    }
207
208    /// Note's metadata.
209    pub fn metadata(&self) -> &NoteMetadata {
210        match self {
211            OutputNote::Full(note) => note.metadata(),
212            OutputNote::Partial(note) => note.metadata(),
213            OutputNote::Header(note) => note.metadata(),
214        }
215    }
216
217    /// Erase private note information.
218    ///
219    /// Specifically:
220    /// - Full private notes are converted into note headers.
221    /// - All partial notes are converted into note headers.
222    pub fn shrink(&self) -> Self {
223        match self {
224            OutputNote::Full(note) if note.metadata().is_private() => {
225                OutputNote::Header(*note.header())
226            },
227            OutputNote::Partial(note) => OutputNote::Header(note.into()),
228            _ => self.clone(),
229        }
230    }
231
232    /// Returns a commitment to the note and its metadata.
233    ///
234    /// > hash(NOTE_ID || NOTE_METADATA)
235    pub fn commitment(&self) -> Digest {
236        compute_note_commitment(self.id(), self.metadata())
237    }
238}
239
240// CONVERSIONS
241// ------------------------------------------------------------------------------------------------
242
243impl From<OutputNote> for NoteHeader {
244    fn from(value: OutputNote) -> Self {
245        (&value).into()
246    }
247}
248
249impl From<&OutputNote> for NoteHeader {
250    fn from(value: &OutputNote) -> Self {
251        match value {
252            OutputNote::Full(note) => note.into(),
253            OutputNote::Partial(note) => note.into(),
254            OutputNote::Header(note) => *note,
255        }
256    }
257}
258
259// SERIALIZATION
260// ------------------------------------------------------------------------------------------------
261
262impl Serializable for OutputNote {
263    fn write_into<W: ByteWriter>(&self, target: &mut W) {
264        match self {
265            OutputNote::Full(note) => {
266                target.write(FULL);
267                target.write(note);
268            },
269            OutputNote::Partial(note) => {
270                target.write(PARTIAL);
271                target.write(note);
272            },
273            OutputNote::Header(note) => {
274                target.write(HEADER);
275                target.write(note);
276            },
277        }
278    }
279}
280
281impl Deserializable for OutputNote {
282    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
283        match source.read_u8()? {
284            FULL => Ok(OutputNote::Full(Note::read_from(source)?)),
285            PARTIAL => Ok(OutputNote::Partial(PartialNote::read_from(source)?)),
286            HEADER => Ok(OutputNote::Header(NoteHeader::read_from(source)?)),
287            v => Err(DeserializationError::InvalidValue(format!("invalid note type: {v}"))),
288        }
289    }
290}