Skip to main content

miden_protocol/transaction/inputs/
notes.rs

1use alloc::collections::BTreeSet;
2use alloc::vec::Vec;
3
4use super::TransactionInputError;
5use crate::note::{Note, NoteId, NoteInclusionProof, NoteLocation, Nullifier};
6use crate::transaction::InputNoteCommitment;
7use crate::utils::serde::{
8    ByteReader,
9    ByteWriter,
10    Deserializable,
11    DeserializationError,
12    Serializable,
13};
14use crate::{Felt, Hasher, MAX_INPUT_NOTES_PER_TX, Word};
15
16// TO INPUT NOTE COMMITMENT
17// ================================================================================================
18
19/// Specifies the data used by the transaction kernel to commit to a note.
20///
21/// The commitment is composed of:
22///
23/// - nullifier, which prevents double spend and provides unlinkability.
24/// - an optional note commitment, which allows for delayed note authentication.
25pub trait ToInputNoteCommitments {
26    fn nullifier(&self) -> Nullifier;
27    fn note_commitment(&self) -> Option<Word>;
28}
29
30// INPUT NOTES
31// ================================================================================================
32
33/// Input notes for a transaction, empty if the transaction does not consume notes.
34///
35/// This structure is generic over `T`, so it can be used to create the input notes for transaction
36/// execution, which require the note's details to run the transaction kernel, and the input notes
37/// for proof verification, which require only the commitment data.
38#[derive(Debug, Clone)]
39pub struct InputNotes<T> {
40    notes: Vec<T>,
41    commitment: Word,
42}
43
44impl<T: ToInputNoteCommitments> InputNotes<T> {
45    // CONSTRUCTOR
46    // --------------------------------------------------------------------------------------------
47    /// Returns new [InputNotes] instantiated from the provided vector of notes.
48    ///
49    /// # Errors
50    /// Returns an error if:
51    /// - The total number of notes is greater than [`MAX_INPUT_NOTES_PER_TX`].
52    /// - The vector of notes contains duplicates.
53    pub fn new(notes: Vec<T>) -> Result<Self, TransactionInputError> {
54        if notes.len() > MAX_INPUT_NOTES_PER_TX {
55            return Err(TransactionInputError::TooManyInputNotes(notes.len()));
56        }
57
58        let mut seen_notes = BTreeSet::new();
59        for note in notes.iter() {
60            if !seen_notes.insert(note.nullifier().as_word()) {
61                return Err(TransactionInputError::DuplicateInputNote(note.nullifier()));
62            }
63        }
64
65        let commitment = build_input_note_commitment(&notes);
66
67        Ok(Self { notes, commitment })
68    }
69
70    /// Returns new [`InputNotes`] instantiated from the provided vector of notes without checking
71    /// their validity.
72    ///
73    /// This is exposed for use in transaction batches, but should generally not be used.
74    ///
75    /// # Warning
76    ///
77    /// This does not run the checks from [`InputNotes::new`], so the latter should be preferred.
78    pub fn new_unchecked(notes: Vec<T>) -> Self {
79        let commitment = build_input_note_commitment(&notes);
80        Self { notes, commitment }
81    }
82
83    // PUBLIC ACCESSORS
84    // --------------------------------------------------------------------------------------------
85
86    /// Returns a sequential hash of nullifiers for all notes.
87    ///
88    /// For non empty lists the commitment is defined as:
89    ///
90    /// > hash(nullifier_0 || noteid0_or_zero || nullifier_1 || noteid1_or_zero || .. || nullifier_n
91    /// > || noteidn_or_zero)
92    ///
93    /// Otherwise defined as ZERO for empty lists.
94    pub fn commitment(&self) -> Word {
95        self.commitment
96    }
97
98    /// Returns total number of input notes.
99    pub fn num_notes(&self) -> u16 {
100        self.notes
101            .len()
102            .try_into()
103            .expect("by construction, number of notes fits into u16")
104    }
105
106    /// Returns true if this [InputNotes] does not contain any notes.
107    pub fn is_empty(&self) -> bool {
108        self.notes.is_empty()
109    }
110
111    /// Returns a reference to the note located at the specified index.
112    pub fn get_note(&self, idx: usize) -> &T {
113        &self.notes[idx]
114    }
115
116    // ITERATORS
117    // --------------------------------------------------------------------------------------------
118
119    /// Returns an iterator over notes in this [InputNotes].
120    pub fn iter(&self) -> impl Iterator<Item = &T> {
121        self.notes.iter()
122    }
123
124    // CONVERSIONS
125    // --------------------------------------------------------------------------------------------
126
127    /// Converts self into a vector of input notes.
128    pub fn into_vec(self) -> Vec<T> {
129        self.notes
130    }
131}
132
133impl InputNotes<InputNote> {
134    /// Returns new [`InputNotes`] instantiated from the provided vector of [notes](Note).
135    ///
136    /// This constructor internally converts the provided notes into the
137    /// [`InputNote::Unauthenticated`], which are then used in the [`Self::new`] constructor.
138    ///
139    /// # Errors
140    /// Returns an error if:
141    /// - The total number of notes is greater than [`MAX_INPUT_NOTES_PER_TX`].
142    /// - The vector of notes contains duplicates.
143    pub fn from_unauthenticated_notes(notes: Vec<Note>) -> Result<Self, TransactionInputError> {
144        let input_note_vec =
145            notes.into_iter().map(|note| InputNote::Unauthenticated { note }).collect();
146
147        Self::new(input_note_vec)
148    }
149
150    /// Returns a vector of input note commitments based on the input notes.
151    pub fn to_commitments(&self) -> InputNotes<InputNoteCommitment> {
152        let notes = self.notes.iter().map(InputNoteCommitment::from).collect();
153        InputNotes::<InputNoteCommitment>::new_unchecked(notes)
154    }
155}
156
157impl<T> IntoIterator for InputNotes<T> {
158    type Item = T;
159    type IntoIter = alloc::vec::IntoIter<Self::Item>;
160
161    fn into_iter(self) -> Self::IntoIter {
162        self.notes.into_iter()
163    }
164}
165
166impl<'a, T> IntoIterator for &'a InputNotes<T> {
167    type Item = &'a T;
168    type IntoIter = alloc::slice::Iter<'a, T>;
169
170    fn into_iter(self) -> alloc::slice::Iter<'a, T> {
171        self.notes.iter()
172    }
173}
174
175impl<T: PartialEq> PartialEq for InputNotes<T> {
176    fn eq(&self, other: &Self) -> bool {
177        self.notes == other.notes
178    }
179}
180
181impl<T: Eq> Eq for InputNotes<T> {}
182
183impl<T: ToInputNoteCommitments> Default for InputNotes<T> {
184    fn default() -> Self {
185        Self {
186            notes: Vec::new(),
187            commitment: build_input_note_commitment::<T>(&[]),
188        }
189    }
190}
191
192// SERIALIZATION
193// ------------------------------------------------------------------------------------------------
194
195impl<T: Serializable> Serializable for InputNotes<T> {
196    fn write_into<W: ByteWriter>(&self, target: &mut W) {
197        // assert is OK here because we enforce max number of notes in the constructor
198        assert!(self.notes.len() <= u16::MAX.into());
199        target.write_u16(self.notes.len() as u16);
200        target.write_many(&self.notes);
201    }
202}
203
204impl<T: Deserializable + ToInputNoteCommitments> Deserializable for InputNotes<T> {
205    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
206        let num_notes = source.read_u16()?;
207        let notes = source.read_many::<T>(num_notes.into())?;
208        Self::new(notes).map_err(|err| DeserializationError::InvalidValue(format!("{err}")))
209    }
210}
211
212// HELPER FUNCTIONS
213// ------------------------------------------------------------------------------------------------
214
215fn build_input_note_commitment<T: ToInputNoteCommitments>(notes: &[T]) -> Word {
216    // Note: This implementation must be kept in sync with the kernel's `process_input_notes_data`
217    if notes.is_empty() {
218        return Word::empty();
219    }
220
221    let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 2);
222    for commitment_data in notes {
223        let nullifier = commitment_data.nullifier();
224        let empty_word_or_note_commitment =
225            &commitment_data.note_commitment().map_or(Word::empty(), |note_id| note_id);
226
227        elements.extend_from_slice(nullifier.as_elements());
228        elements.extend_from_slice(empty_word_or_note_commitment.as_elements());
229    }
230    Hasher::hash_elements(&elements)
231}
232
233// INPUT NOTE
234// ================================================================================================
235
236const AUTHENTICATED: u8 = 0;
237const UNAUTHENTICATED: u8 = 1;
238
239/// An input note for a transaction.
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub enum InputNote {
242    /// Input notes whose existences in the chain is verified by the transaction kernel.
243    Authenticated { note: Note, proof: NoteInclusionProof },
244
245    /// Input notes whose existence in the chain is not verified by the transaction kernel, but
246    /// instead is delegated to the protocol kernels.
247    Unauthenticated { note: Note },
248}
249
250impl InputNote {
251    // CONSTRUCTORS
252    // -------------------------------------------------------------------------------------------
253
254    /// Returns an authenticated [InputNote].
255    pub fn authenticated(note: Note, proof: NoteInclusionProof) -> Self {
256        Self::Authenticated { note, proof }
257    }
258
259    /// Returns an unauthenticated [InputNote].
260    pub fn unauthenticated(note: Note) -> Self {
261        Self::Unauthenticated { note }
262    }
263
264    // ACCESSORS
265    // -------------------------------------------------------------------------------------------
266
267    /// Returns the ID of the note.
268    pub fn id(&self) -> NoteId {
269        self.note().id()
270    }
271
272    /// Returns a reference to the underlying note.
273    pub fn note(&self) -> &Note {
274        match self {
275            Self::Authenticated { note, .. } => note,
276            Self::Unauthenticated { note } => note,
277        }
278    }
279
280    /// Consumes the [`InputNote`] an converts it to a [`Note`].
281    pub fn into_note(self) -> Note {
282        match self {
283            Self::Authenticated { note, .. } => note,
284            Self::Unauthenticated { note } => note,
285        }
286    }
287
288    /// Returns a reference to the inclusion proof of the note.
289    pub fn proof(&self) -> Option<&NoteInclusionProof> {
290        match self {
291            Self::Authenticated { proof, .. } => Some(proof),
292            Self::Unauthenticated { .. } => None,
293        }
294    }
295
296    /// Returns a reference to the location of the note.
297    pub fn location(&self) -> Option<&NoteLocation> {
298        self.proof().map(|proof| proof.location())
299    }
300}
301
302impl From<Vec<Note>> for InputNotes<InputNote> {
303    fn from(notes: Vec<Note>) -> Self {
304        Self::new_unchecked(notes.into_iter().map(InputNote::unauthenticated).collect::<Vec<_>>())
305    }
306}
307
308impl ToInputNoteCommitments for InputNote {
309    fn nullifier(&self) -> Nullifier {
310        self.note().nullifier()
311    }
312
313    fn note_commitment(&self) -> Option<Word> {
314        match self {
315            InputNote::Authenticated { .. } => None,
316            InputNote::Unauthenticated { note } => Some(note.commitment()),
317        }
318    }
319}
320
321impl ToInputNoteCommitments for &InputNote {
322    fn nullifier(&self) -> Nullifier {
323        (*self).nullifier()
324    }
325
326    fn note_commitment(&self) -> Option<Word> {
327        (*self).note_commitment()
328    }
329}
330
331// SERIALIZATION
332// ------------------------------------------------------------------------------------------------
333
334impl Serializable for InputNote {
335    fn write_into<W: ByteWriter>(&self, target: &mut W) {
336        match self {
337            Self::Authenticated { note, proof } => {
338                target.write(AUTHENTICATED);
339                target.write(note);
340                target.write(proof);
341            },
342            Self::Unauthenticated { note } => {
343                target.write(UNAUTHENTICATED);
344                target.write(note);
345            },
346        }
347    }
348}
349
350impl Deserializable for InputNote {
351    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
352        match source.read_u8()? {
353            AUTHENTICATED => {
354                let note = Note::read_from(source)?;
355                let proof = NoteInclusionProof::read_from(source)?;
356                Ok(Self::Authenticated { note, proof })
357            },
358            UNAUTHENTICATED => {
359                let note = Note::read_from(source)?;
360                Ok(Self::Unauthenticated { note })
361            },
362            v => Err(DeserializationError::InvalidValue(format!("invalid input note type: {v}"))),
363        }
364    }
365}
366
367// TESTS
368// ================================================================================================
369
370#[cfg(test)]
371mod input_notes_tests {
372    use assert_matches::assert_matches;
373    use miden_core::Word;
374
375    use super::InputNotes;
376    use crate::errors::TransactionInputError;
377    use crate::note::Note;
378    use crate::transaction::InputNote;
379
380    #[test]
381    fn test_duplicate_input_notes() -> anyhow::Result<()> {
382        let mock_note = Note::mock_noop(Word::empty());
383        let mock_note_nullifier = mock_note.nullifier();
384        let mock_note_clone = mock_note.clone();
385
386        let error = InputNotes::new(vec![
387            InputNote::Unauthenticated { note: mock_note },
388            InputNote::Unauthenticated { note: mock_note_clone },
389        ])
390        .expect_err("input notes creation should fail");
391
392        assert_matches!(error, TransactionInputError::DuplicateInputNote(nullifier) if nullifier == mock_note_nullifier);
393
394        Ok(())
395    }
396}