miden_protocol/transaction/inputs/
notes.rs1use 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
16pub trait ToInputNoteCommitments {
26 fn nullifier(&self) -> Nullifier;
27 fn note_commitment(&self) -> Option<Word>;
28}
29
30#[derive(Debug, Clone)]
39pub struct InputNotes<T> {
40 notes: Vec<T>,
41 commitment: Word,
42}
43
44impl<T: ToInputNoteCommitments> InputNotes<T> {
45 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(¬es);
66
67 Ok(Self { notes, commitment })
68 }
69
70 pub fn new_unchecked(notes: Vec<T>) -> Self {
79 let commitment = build_input_note_commitment(¬es);
80 Self { notes, commitment }
81 }
82
83 pub fn commitment(&self) -> Word {
95 self.commitment
96 }
97
98 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 pub fn is_empty(&self) -> bool {
108 self.notes.is_empty()
109 }
110
111 pub fn get_note(&self, idx: usize) -> &T {
113 &self.notes[idx]
114 }
115
116 pub fn iter(&self) -> impl Iterator<Item = &T> {
121 self.notes.iter()
122 }
123
124 pub fn into_vec(self) -> Vec<T> {
129 self.notes
130 }
131}
132
133impl InputNotes<InputNote> {
134 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 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
192impl<T: Serializable> Serializable for InputNotes<T> {
196 fn write_into<W: ByteWriter>(&self, target: &mut W) {
197 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
212fn build_input_note_commitment<T: ToInputNoteCommitments>(notes: &[T]) -> Word {
216 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
233const AUTHENTICATED: u8 = 0;
237const UNAUTHENTICATED: u8 = 1;
238
239#[derive(Debug, Clone, PartialEq, Eq)]
241pub enum InputNote {
242 Authenticated { note: Note, proof: NoteInclusionProof },
244
245 Unauthenticated { note: Note },
248}
249
250impl InputNote {
251 pub fn authenticated(note: Note, proof: NoteInclusionProof) -> Self {
256 Self::Authenticated { note, proof }
257 }
258
259 pub fn unauthenticated(note: Note) -> Self {
261 Self::Unauthenticated { note }
262 }
263
264 pub fn id(&self) -> NoteId {
269 self.note().id()
270 }
271
272 pub fn note(&self) -> &Note {
274 match self {
275 Self::Authenticated { note, .. } => note,
276 Self::Unauthenticated { note } => note,
277 }
278 }
279
280 pub fn into_note(self) -> Note {
282 match self {
283 Self::Authenticated { note, .. } => note,
284 Self::Unauthenticated { note } => note,
285 }
286 }
287
288 pub fn proof(&self) -> Option<&NoteInclusionProof> {
290 match self {
291 Self::Authenticated { proof, .. } => Some(proof),
292 Self::Unauthenticated { .. } => None,
293 }
294 }
295
296 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
331impl 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#[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}