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