miden_objects/transaction/inputs/
notes.rs

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