miden_objects/transaction/inputs/
notes.rs1use 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
10pub trait ToInputNoteCommitments {
20 fn nullifier(&self) -> Nullifier;
21 fn note_commitment(&self) -> Option<Digest>;
22}
23
24#[derive(Debug, Clone)]
33pub struct InputNotes<T> {
34 notes: Vec<T>,
35 commitment: Digest,
36}
37
38impl<T: ToInputNoteCommitments> InputNotes<T> {
39 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(¬es);
60
61 Ok(Self { notes, commitment })
62 }
63
64 pub fn new_unchecked(notes: Vec<T>) -> Self {
73 let commitment = build_input_note_commitment(¬es);
74 Self { notes, commitment }
75 }
76
77 pub fn commitment(&self) -> Digest {
89 self.commitment
90 }
91
92 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 pub fn is_empty(&self) -> bool {
102 self.notes.is_empty()
103 }
104
105 pub fn get_note(&self, idx: usize) -> &T {
107 &self.notes[idx]
108 }
109
110 pub fn iter(&self) -> impl Iterator<Item = &T> {
115 self.notes.iter()
116 }
117
118 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
162impl<T: Serializable> Serializable for InputNotes<T> {
166 fn write_into<W: ByteWriter>(&self, target: &mut W) {
167 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
182fn build_input_note_commitment<T: ToInputNoteCommitments>(notes: &[T]) -> Digest {
186 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
204const AUTHENTICATED: u8 = 0;
208const UNAUTHENTICATED: u8 = 1;
209
210#[derive(Debug, Clone, PartialEq, Eq)]
212pub enum InputNote {
213 Authenticated { note: Note, proof: NoteInclusionProof },
215
216 Unauthenticated { note: Note },
219}
220
221impl InputNote {
222 pub fn authenticated(note: Note, proof: NoteInclusionProof) -> Self {
227 Self::Authenticated { note, proof }
228 }
229
230 pub fn unauthenticated(note: Note) -> Self {
232 Self::Unauthenticated { note }
233 }
234
235 pub fn id(&self) -> NoteId {
240 self.note().id()
241 }
242
243 pub fn note(&self) -> &Note {
245 match self {
246 Self::Authenticated { note, .. } => note,
247 Self::Unauthenticated { note } => note,
248 }
249 }
250
251 pub fn proof(&self) -> Option<&NoteInclusionProof> {
253 match self {
254 Self::Authenticated { proof, .. } => Some(proof),
255 Self::Unauthenticated { .. } => None,
256 }
257 }
258
259 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
288impl 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#[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}