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 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
318impl 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#[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}