miden_objects/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::utils::serde::{
7 ByteReader,
8 ByteWriter,
9 Deserializable,
10 DeserializationError,
11 Serializable,
12};
13use crate::{Felt, Hasher, MAX_INPUT_NOTES_PER_TX, Word};
14
15pub trait ToInputNoteCommitments {
25 fn nullifier(&self) -> Nullifier;
26 fn note_commitment(&self) -> Option<Word>;
27}
28
29#[derive(Debug, Clone)]
38pub struct InputNotes<T> {
39 notes: Vec<T>,
40 commitment: Word,
41}
42
43impl<T: ToInputNoteCommitments> InputNotes<T> {
44 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(¬es);
65
66 Ok(Self { notes, commitment })
67 }
68
69 pub fn new_unchecked(notes: Vec<T>) -> Self {
78 let commitment = build_input_note_commitment(¬es);
79 Self { notes, commitment }
80 }
81
82 pub fn commitment(&self) -> Word {
94 self.commitment
95 }
96
97 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 pub fn is_empty(&self) -> bool {
107 self.notes.is_empty()
108 }
109
110 pub fn get_note(&self, idx: usize) -> &T {
112 &self.notes[idx]
113 }
114
115 pub fn iter(&self) -> impl Iterator<Item = &T> {
120 self.notes.iter()
121 }
122
123 pub fn into_vec(self) -> Vec<T> {
128 self.notes
129 }
130}
131
132impl InputNotes<InputNote> {
133 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
185impl<T: Serializable> Serializable for InputNotes<T> {
189 fn write_into<W: ByteWriter>(&self, target: &mut W) {
190 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
205fn build_input_note_commitment<T: ToInputNoteCommitments>(notes: &[T]) -> Word {
209 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
226const AUTHENTICATED: u8 = 0;
230const UNAUTHENTICATED: u8 = 1;
231
232#[derive(Debug, Clone, PartialEq, Eq)]
234pub enum InputNote {
235 Authenticated { note: Note, proof: NoteInclusionProof },
237
238 Unauthenticated { note: Note },
241}
242
243impl InputNote {
244 pub fn authenticated(note: Note, proof: NoteInclusionProof) -> Self {
249 Self::Authenticated { note, proof }
250 }
251
252 pub fn unauthenticated(note: Note) -> Self {
254 Self::Unauthenticated { note }
255 }
256
257 pub fn id(&self) -> NoteId {
262 self.note().id()
263 }
264
265 pub fn note(&self) -> &Note {
267 match self {
268 Self::Authenticated { note, .. } => note,
269 Self::Unauthenticated { note } => note,
270 }
271 }
272
273 pub fn into_note(self) -> Note {
275 match self {
276 Self::Authenticated { note, .. } => note,
277 Self::Unauthenticated { note } => note,
278 }
279 }
280
281 pub fn proof(&self) -> Option<&NoteInclusionProof> {
283 match self {
284 Self::Authenticated { proof, .. } => Some(proof),
285 Self::Unauthenticated { .. } => None,
286 }
287 }
288
289 pub fn location(&self) -> Option<&NoteLocation> {
291 self.proof().map(|proof| proof.location())
292 }
293}
294
295impl From<Vec<Note>> for InputNotes<InputNote> {
296 fn from(notes: Vec<Note>) -> Self {
297 Self::new_unchecked(notes.into_iter().map(InputNote::unauthenticated).collect::<Vec<_>>())
298 }
299}
300
301impl ToInputNoteCommitments for InputNote {
302 fn nullifier(&self) -> Nullifier {
303 self.note().nullifier()
304 }
305
306 fn note_commitment(&self) -> Option<Word> {
307 match self {
308 InputNote::Authenticated { .. } => None,
309 InputNote::Unauthenticated { note } => Some(note.commitment()),
310 }
311 }
312}
313
314impl ToInputNoteCommitments for &InputNote {
315 fn nullifier(&self) -> Nullifier {
316 (*self).nullifier()
317 }
318
319 fn note_commitment(&self) -> Option<Word> {
320 (*self).note_commitment()
321 }
322}
323
324impl Serializable for InputNote {
328 fn write_into<W: ByteWriter>(&self, target: &mut W) {
329 match self {
330 Self::Authenticated { note, proof } => {
331 target.write(AUTHENTICATED);
332 target.write(note);
333 target.write(proof);
334 },
335 Self::Unauthenticated { note } => {
336 target.write(UNAUTHENTICATED);
337 target.write(note);
338 },
339 }
340 }
341}
342
343impl Deserializable for InputNote {
344 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
345 match source.read_u8()? {
346 AUTHENTICATED => {
347 let note = Note::read_from(source)?;
348 let proof = NoteInclusionProof::read_from(source)?;
349 Ok(Self::Authenticated { note, proof })
350 },
351 UNAUTHENTICATED => {
352 let note = Note::read_from(source)?;
353 Ok(Self::Unauthenticated { note })
354 },
355 v => Err(DeserializationError::InvalidValue(format!("invalid input note type: {v}"))),
356 }
357 }
358}
359
360#[cfg(test)]
364mod input_notes_tests {
365 use assert_matches::assert_matches;
366 use miden_core::Word;
367
368 use super::InputNotes;
369 use crate::TransactionInputError;
370 use crate::note::Note;
371 use crate::transaction::InputNote;
372
373 #[test]
374 fn test_duplicate_input_notes() -> anyhow::Result<()> {
375 let mock_note = Note::mock_noop(Word::empty());
376 let mock_note_nullifier = mock_note.nullifier();
377 let mock_note_clone = mock_note.clone();
378
379 let error = InputNotes::new(vec![
380 InputNote::Unauthenticated { note: mock_note },
381 InputNote::Unauthenticated { note: mock_note_clone },
382 ])
383 .expect_err("input notes creation should fail");
384
385 assert_matches!(error, TransactionInputError::DuplicateInputNote(nullifier) if nullifier == mock_note_nullifier);
386
387 Ok(())
388 }
389}