miden_objects/transaction/
outputs.rs1use alloc::collections::BTreeSet;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4use core::fmt::Debug;
5
6use crate::account::AccountHeader;
7use crate::asset::FungibleAsset;
8use crate::block::BlockNumber;
9use crate::note::{
10 Note,
11 NoteAssets,
12 NoteHeader,
13 NoteId,
14 NoteMetadata,
15 NoteRecipient,
16 PartialNote,
17 compute_note_commitment,
18};
19use crate::utils::serde::{
20 ByteReader,
21 ByteWriter,
22 Deserializable,
23 DeserializationError,
24 Serializable,
25};
26use crate::{Felt, Hasher, MAX_OUTPUT_NOTES_PER_TX, TransactionOutputError, Word};
27
28#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct TransactionOutputs {
34 pub account: AccountHeader,
36 pub account_delta_commitment: Word,
38 pub output_notes: OutputNotes,
40 pub fee: FungibleAsset,
42 pub expiration_block_num: BlockNumber,
44}
45
46impl Serializable for TransactionOutputs {
47 fn write_into<W: ByteWriter>(&self, target: &mut W) {
48 self.account.write_into(target);
49 self.account_delta_commitment.write_into(target);
50 self.output_notes.write_into(target);
51 self.fee.write_into(target);
52 self.expiration_block_num.write_into(target);
53 }
54}
55
56impl Deserializable for TransactionOutputs {
57 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
58 let account = AccountHeader::read_from(source)?;
59 let account_delta_commitment = Word::read_from(source)?;
60 let output_notes = OutputNotes::read_from(source)?;
61 let fee = FungibleAsset::read_from(source)?;
62 let expiration_block_num = BlockNumber::read_from(source)?;
63
64 Ok(Self {
65 account,
66 account_delta_commitment,
67 output_notes,
68 fee,
69 expiration_block_num,
70 })
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct OutputNotes {
81 notes: Vec<OutputNote>,
82 commitment: Word,
83}
84
85impl OutputNotes {
86 pub fn new(notes: Vec<OutputNote>) -> Result<Self, TransactionOutputError> {
95 if notes.len() > MAX_OUTPUT_NOTES_PER_TX {
96 return Err(TransactionOutputError::TooManyOutputNotes(notes.len()));
97 }
98
99 let mut seen_notes = BTreeSet::new();
100 for note in notes.iter() {
101 if !seen_notes.insert(note.id()) {
102 return Err(TransactionOutputError::DuplicateOutputNote(note.id()));
103 }
104 }
105
106 let commitment = build_output_notes_commitment(¬es);
107
108 Ok(Self { notes, commitment })
109 }
110
111 pub fn commitment(&self) -> Word {
119 self.commitment
120 }
121 pub fn num_notes(&self) -> usize {
123 self.notes.len()
124 }
125
126 pub fn is_empty(&self) -> bool {
128 self.notes.is_empty()
129 }
130
131 pub fn get_note(&self, idx: usize) -> &OutputNote {
133 &self.notes[idx]
134 }
135
136 pub fn iter(&self) -> impl Iterator<Item = &OutputNote> {
141 self.notes.iter()
142 }
143}
144
145impl Serializable for OutputNotes {
149 fn write_into<W: ByteWriter>(&self, target: &mut W) {
150 assert!(self.notes.len() <= u16::MAX.into());
152 target.write_u16(self.notes.len() as u16);
153 target.write_many(&self.notes);
154 }
155}
156
157impl Deserializable for OutputNotes {
158 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
159 let num_notes = source.read_u16()?;
160 let notes = source.read_many::<OutputNote>(num_notes.into())?;
161 Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
162 }
163}
164
165const FULL: u8 = 0;
169const PARTIAL: u8 = 1;
170const HEADER: u8 = 2;
171
172#[derive(Debug, Clone, PartialEq, Eq)]
174pub enum OutputNote {
175 Full(Note),
176 Partial(PartialNote),
177 Header(NoteHeader),
178}
179
180impl OutputNote {
181 pub fn assets(&self) -> Option<&NoteAssets> {
183 match self {
184 OutputNote::Full(note) => Some(note.assets()),
185 OutputNote::Partial(note) => Some(note.assets()),
186 OutputNote::Header(_) => None,
187 }
188 }
189
190 pub fn id(&self) -> NoteId {
194 match self {
195 OutputNote::Full(note) => note.id(),
196 OutputNote::Partial(note) => note.id(),
197 OutputNote::Header(note) => note.id(),
198 }
199 }
200
201 pub fn recipient(&self) -> Option<&NoteRecipient> {
206 match self {
207 OutputNote::Full(note) => Some(note.recipient()),
208 OutputNote::Partial(_) => None,
209 OutputNote::Header(_) => None,
210 }
211 }
212
213 pub fn recipient_digest(&self) -> Option<Word> {
219 match self {
220 OutputNote::Full(note) => Some(note.recipient().digest()),
221 OutputNote::Partial(note) => Some(note.recipient_digest()),
222 OutputNote::Header(_) => None,
223 }
224 }
225
226 pub fn metadata(&self) -> &NoteMetadata {
228 match self {
229 OutputNote::Full(note) => note.metadata(),
230 OutputNote::Partial(note) => note.metadata(),
231 OutputNote::Header(note) => note.metadata(),
232 }
233 }
234
235 pub fn shrink(&self) -> Self {
241 match self {
242 OutputNote::Full(note) if note.metadata().is_private() => {
243 OutputNote::Header(*note.header())
244 },
245 OutputNote::Partial(note) => OutputNote::Header(note.into()),
246 _ => self.clone(),
247 }
248 }
249
250 pub fn commitment(&self) -> Word {
254 compute_note_commitment(self.id(), self.metadata())
255 }
256}
257
258impl From<OutputNote> for NoteHeader {
262 fn from(value: OutputNote) -> Self {
263 (&value).into()
264 }
265}
266
267impl From<&OutputNote> for NoteHeader {
268 fn from(value: &OutputNote) -> Self {
269 match value {
270 OutputNote::Full(note) => note.into(),
271 OutputNote::Partial(note) => note.into(),
272 OutputNote::Header(note) => *note,
273 }
274 }
275}
276
277impl Serializable for OutputNote {
281 fn write_into<W: ByteWriter>(&self, target: &mut W) {
282 match self {
283 OutputNote::Full(note) => {
284 target.write(FULL);
285 target.write(note);
286 },
287 OutputNote::Partial(note) => {
288 target.write(PARTIAL);
289 target.write(note);
290 },
291 OutputNote::Header(note) => {
292 target.write(HEADER);
293 target.write(note);
294 },
295 }
296 }
297}
298
299impl Deserializable for OutputNote {
300 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
301 match source.read_u8()? {
302 FULL => Ok(OutputNote::Full(Note::read_from(source)?)),
303 PARTIAL => Ok(OutputNote::Partial(PartialNote::read_from(source)?)),
304 HEADER => Ok(OutputNote::Header(NoteHeader::read_from(source)?)),
305 v => Err(DeserializationError::InvalidValue(format!("invalid note type: {v}"))),
306 }
307 }
308}
309
310fn build_output_notes_commitment(notes: &[OutputNote]) -> Word {
318 if notes.is_empty() {
319 return Word::empty();
320 }
321
322 let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 8);
323 for note in notes.iter() {
324 elements.extend_from_slice(note.id().as_elements());
325 elements.extend_from_slice(Word::from(note.metadata()).as_elements());
326 }
327
328 Hasher::hash_elements(&elements)
329}
330
331#[cfg(test)]
335mod output_notes_tests {
336 use assert_matches::assert_matches;
337
338 use super::OutputNotes;
339 use crate::note::Note;
340 use crate::transaction::OutputNote;
341 use crate::{TransactionOutputError, Word};
342
343 #[test]
344 fn test_duplicate_output_notes() -> anyhow::Result<()> {
345 let mock_note = Note::mock_noop(Word::empty());
346 let mock_note_id = mock_note.id();
347 let mock_note_clone = mock_note.clone();
348
349 let error =
350 OutputNotes::new(vec![OutputNote::Full(mock_note), OutputNote::Full(mock_note_clone)])
351 .expect_err("input notes creation should fail");
352
353 assert_matches!(error, TransactionOutputError::DuplicateOutputNote(note_id) if note_id == mock_note_id);
354
355 Ok(())
356 }
357}