miden_objects/transaction/
outputs.rs1use alloc::{collections::BTreeSet, string::ToString, vec::Vec};
2use core::fmt::Debug;
3
4use crate::{
5 Digest, Felt, Hasher, MAX_OUTPUT_NOTES_PER_TX, TransactionOutputError, Word,
6 account::AccountHeader,
7 block::BlockNumber,
8 note::{
9 Note, NoteAssets, NoteHeader, NoteId, NoteMetadata, PartialNote, compute_note_commitment,
10 },
11 utils::serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
12};
13#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct TransactionOutputs {
19 pub account: AccountHeader,
21 pub output_notes: OutputNotes,
23 pub expiration_block_num: BlockNumber,
25}
26
27impl Serializable for TransactionOutputs {
28 fn write_into<W: ByteWriter>(&self, target: &mut W) {
29 self.account.write_into(target);
30 self.output_notes.write_into(target);
31 self.expiration_block_num.write_into(target);
32 }
33}
34
35impl Deserializable for TransactionOutputs {
36 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
37 let account = AccountHeader::read_from(source)?;
38 let output_notes = OutputNotes::read_from(source)?;
39 let expiration_block_num = BlockNumber::read_from(source)?;
40
41 Ok(Self {
42 account,
43 output_notes,
44 expiration_block_num,
45 })
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct OutputNotes {
56 notes: Vec<OutputNote>,
57 commitment: Digest,
58}
59
60impl OutputNotes {
61 pub fn new(notes: Vec<OutputNote>) -> Result<Self, TransactionOutputError> {
70 if notes.len() > MAX_OUTPUT_NOTES_PER_TX {
71 return Err(TransactionOutputError::TooManyOutputNotes(notes.len()));
72 }
73
74 let mut seen_notes = BTreeSet::new();
75 for note in notes.iter() {
76 if !seen_notes.insert(note.id()) {
77 return Err(TransactionOutputError::DuplicateOutputNote(note.id()));
78 }
79 }
80
81 let commitment = build_output_notes_commitment(¬es);
82
83 Ok(Self { notes, commitment })
84 }
85
86 pub fn commitment(&self) -> Digest {
94 self.commitment
95 }
96 pub fn num_notes(&self) -> usize {
98 self.notes.len()
99 }
100
101 pub fn is_empty(&self) -> bool {
103 self.notes.is_empty()
104 }
105
106 pub fn get_note(&self, idx: usize) -> &OutputNote {
108 &self.notes[idx]
109 }
110
111 pub fn iter(&self) -> impl Iterator<Item = &OutputNote> {
116 self.notes.iter()
117 }
118}
119
120impl Serializable for OutputNotes {
124 fn write_into<W: ByteWriter>(&self, target: &mut W) {
125 assert!(self.notes.len() <= u16::MAX.into());
127 target.write_u16(self.notes.len() as u16);
128 target.write_many(&self.notes);
129 }
130}
131
132impl Deserializable for OutputNotes {
133 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
134 let num_notes = source.read_u16()?;
135 let notes = source.read_many::<OutputNote>(num_notes.into())?;
136 Self::new(notes).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
137 }
138}
139
140fn build_output_notes_commitment(notes: &[OutputNote]) -> Digest {
148 if notes.is_empty() {
149 return Digest::default();
150 }
151
152 let mut elements: Vec<Felt> = Vec::with_capacity(notes.len() * 8);
153 for note in notes.iter() {
154 elements.extend_from_slice(note.id().as_elements());
155 elements.extend_from_slice(&Word::from(note.metadata()));
156 }
157
158 Hasher::hash_elements(&elements)
159}
160
161const FULL: u8 = 0;
165const PARTIAL: u8 = 1;
166const HEADER: u8 = 2;
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum OutputNote {
171 Full(Note),
172 Partial(PartialNote),
173 Header(NoteHeader),
174}
175
176impl OutputNote {
177 pub fn assets(&self) -> Option<&NoteAssets> {
179 match self {
180 OutputNote::Full(note) => Some(note.assets()),
181 OutputNote::Partial(note) => Some(note.assets()),
182 OutputNote::Header(_) => None,
183 }
184 }
185
186 pub fn id(&self) -> NoteId {
190 match self {
191 OutputNote::Full(note) => note.id(),
192 OutputNote::Partial(note) => note.id(),
193 OutputNote::Header(note) => note.id(),
194 }
195 }
196
197 pub fn recipient_digest(&self) -> Option<Digest> {
201 match self {
202 OutputNote::Full(note) => Some(note.recipient().digest()),
203 OutputNote::Partial(note) => Some(note.recipient_digest()),
204 OutputNote::Header(_) => None,
205 }
206 }
207
208 pub fn metadata(&self) -> &NoteMetadata {
210 match self {
211 OutputNote::Full(note) => note.metadata(),
212 OutputNote::Partial(note) => note.metadata(),
213 OutputNote::Header(note) => note.metadata(),
214 }
215 }
216
217 pub fn shrink(&self) -> Self {
223 match self {
224 OutputNote::Full(note) if note.metadata().is_private() => {
225 OutputNote::Header(*note.header())
226 },
227 OutputNote::Partial(note) => OutputNote::Header(note.into()),
228 _ => self.clone(),
229 }
230 }
231
232 pub fn commitment(&self) -> Digest {
236 compute_note_commitment(self.id(), self.metadata())
237 }
238}
239
240impl From<OutputNote> for NoteHeader {
244 fn from(value: OutputNote) -> Self {
245 (&value).into()
246 }
247}
248
249impl From<&OutputNote> for NoteHeader {
250 fn from(value: &OutputNote) -> Self {
251 match value {
252 OutputNote::Full(note) => note.into(),
253 OutputNote::Partial(note) => note.into(),
254 OutputNote::Header(note) => *note,
255 }
256 }
257}
258
259impl Serializable for OutputNote {
263 fn write_into<W: ByteWriter>(&self, target: &mut W) {
264 match self {
265 OutputNote::Full(note) => {
266 target.write(FULL);
267 target.write(note);
268 },
269 OutputNote::Partial(note) => {
270 target.write(PARTIAL);
271 target.write(note);
272 },
273 OutputNote::Header(note) => {
274 target.write(HEADER);
275 target.write(note);
276 },
277 }
278 }
279}
280
281impl Deserializable for OutputNote {
282 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
283 match source.read_u8()? {
284 FULL => Ok(OutputNote::Full(Note::read_from(source)?)),
285 PARTIAL => Ok(OutputNote::Partial(PartialNote::read_from(source)?)),
286 HEADER => Ok(OutputNote::Header(NoteHeader::read_from(source)?)),
287 v => Err(DeserializationError::InvalidValue(format!("invalid note type: {v}"))),
288 }
289 }
290}