miden_protocol/transaction/
tx_summary.rs1use alloc::vec::Vec;
2
3use crate::account::AccountDelta;
4use crate::crypto::SequentialCommit;
5use crate::errors::TransactionSummaryError;
6use crate::transaction::{InputNote, InputNotes, RawOutputNotes};
7use crate::utils::serde::{
8 ByteReader,
9 ByteWriter,
10 Deserializable,
11 DeserializationError,
12 Serializable,
13};
14use crate::{Felt, WORD_SIZE, Word};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct TransactionSummary {
30 account_delta: AccountDelta,
31 input_notes: InputNotes<InputNote>,
32 output_notes: RawOutputNotes,
33 block_commitment: Word,
34 expiration_delta: u16,
35 user_params: TransactionSummaryUserParams,
36}
37
38impl TransactionSummary {
39 const EXPIRATION_DELTA_IDX: usize = 4 * WORD_SIZE;
45
46 pub const NUM_ELEMENTS: usize =
52 Self::EXPIRATION_DELTA_IDX + 1 + TransactionSummaryUserParams::NUM_ELEMENTS;
53
54 pub fn new(
59 account_delta: AccountDelta,
60 input_notes: InputNotes<InputNote>,
61 output_notes: RawOutputNotes,
62 block_commitment: Word,
63 expiration_delta: u16,
64 user_params: TransactionSummaryUserParams,
65 ) -> Self {
66 Self {
67 account_delta,
68 input_notes,
69 output_notes,
70 block_commitment,
71 expiration_delta,
72 user_params,
73 }
74 }
75
76 pub fn account_delta(&self) -> &AccountDelta {
81 &self.account_delta
82 }
83
84 pub fn input_notes(&self) -> &InputNotes<InputNote> {
86 &self.input_notes
87 }
88
89 pub fn output_notes(&self) -> &RawOutputNotes {
91 &self.output_notes
92 }
93
94 pub fn block_commitment(&self) -> Word {
96 self.block_commitment
97 }
98
99 pub fn expiration_delta(&self) -> u16 {
101 self.expiration_delta
102 }
103
104 pub fn user_params(&self) -> TransactionSummaryUserParams {
106 self.user_params
107 }
108
109 pub fn to_elements(&self) -> Vec<Felt> {
122 <Self as SequentialCommit>::to_elements(self)
123 }
124
125 pub fn to_commitment(&self) -> Word {
129 <Self as SequentialCommit>::to_commitment(self)
130 }
131
132 pub fn try_params_from_elements(
150 elements: &[Felt],
151 ) -> Result<(u16, TransactionSummaryUserParams), TransactionSummaryError> {
152 if elements.len() != Self::NUM_ELEMENTS {
153 return Err(TransactionSummaryError::InvalidPreimageLength {
154 actual: elements.len(),
155 expected: Self::NUM_ELEMENTS,
156 });
157 }
158
159 let expiration_delta_element = elements[Self::EXPIRATION_DELTA_IDX];
160 let expiration_delta =
161 u16::try_from(expiration_delta_element.as_canonical_u64()).map_err(|_| {
162 TransactionSummaryError::ExpirationDeltaTooLarge(expiration_delta_element)
163 })?;
164
165 let user_params = elements[Self::EXPIRATION_DELTA_IDX + 1..]
166 .try_into()
167 .expect("preimage length was validated above");
168
169 Ok((expiration_delta, TransactionSummaryUserParams::new(user_params)))
170 }
171}
172
173const _: () = assert!(TransactionSummary::NUM_ELEMENTS.is_multiple_of(WORD_SIZE));
175
176impl SequentialCommit for TransactionSummary {
177 type Commitment = Word;
178
179 fn to_elements(&self) -> Vec<Felt> {
180 let mut elements = Vec::with_capacity(Self::NUM_ELEMENTS);
181 elements.extend_from_slice(self.account_delta.to_commitment().as_elements());
182 elements.extend_from_slice(self.input_notes.commitment().as_elements());
183 elements.extend_from_slice(self.output_notes.commitment().as_elements());
184 elements.extend_from_slice(self.block_commitment.as_elements());
185 elements.push(Felt::from(self.expiration_delta));
186 elements.extend_from_slice(self.user_params.as_elements());
187 elements
188 }
189}
190
191impl Serializable for TransactionSummary {
192 fn write_into<W: ByteWriter>(&self, target: &mut W) {
193 self.account_delta.write_into(target);
194 self.input_notes.write_into(target);
195 self.output_notes.write_into(target);
196 self.block_commitment.write_into(target);
197 self.expiration_delta.write_into(target);
198 self.user_params.write_into(target);
199 }
200}
201
202impl Deserializable for TransactionSummary {
203 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
204 let account_delta = source.read()?;
205 let input_notes = source.read()?;
206 let output_notes = source.read()?;
207 let block_commitment = source.read()?;
208 let expiration_delta = source.read()?;
209 let user_params = source.read()?;
210
211 Ok(Self::new(
212 account_delta,
213 input_notes,
214 output_notes,
215 block_commitment,
216 expiration_delta,
217 user_params,
218 ))
219 }
220}
221
222#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
235pub struct TransactionSummaryUserParams {
236 elements: [Felt; Self::NUM_ELEMENTS],
237}
238
239impl TransactionSummaryUserParams {
240 pub const NUM_ELEMENTS: usize = 7;
245
246 pub fn new(elements: [Felt; Self::NUM_ELEMENTS]) -> Self {
251 Self { elements }
252 }
253
254 pub fn as_elements(&self) -> &[Felt; Self::NUM_ELEMENTS] {
260 &self.elements
261 }
262}
263
264impl Serializable for TransactionSummaryUserParams {
265 fn write_into<W: ByteWriter>(&self, target: &mut W) {
266 self.elements.write_into(target);
267 }
268}
269
270impl Deserializable for TransactionSummaryUserParams {
271 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
272 Ok(Self::new(source.read()?))
273 }
274}
275
276#[cfg(test)]
280mod tests {
281 use assert_matches::assert_matches;
282
283 use super::*;
284 use crate::ONE;
285 use crate::account::{AccountId, AccountStoragePatch, AccountVaultDelta};
286 use crate::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER;
287
288 const EXPIRATION_DELTA: u16 = 42;
290 const USER_PARAMS: [u32; TransactionSummaryUserParams::NUM_ELEMENTS] = [1, 2, 3, 4, 5, 6, 7];
291
292 fn mock_summary() -> TransactionSummary {
294 let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
295 let account_delta = AccountDelta::new(
296 account_id,
297 AccountStoragePatch::new(),
298 AccountVaultDelta::default(),
299 None,
300 ONE,
301 )
302 .unwrap();
303
304 TransactionSummary::new(
305 account_delta,
306 InputNotes::new(Vec::new()).unwrap(),
307 RawOutputNotes::new(Vec::new()).unwrap(),
308 Word::from([9u32, 10, 11, 12].map(Felt::from)),
309 EXPIRATION_DELTA,
310 TransactionSummaryUserParams::new(USER_PARAMS.map(Felt::from)),
311 )
312 }
313
314 #[test]
315 fn tx_summary_params_element_roundtrip() {
316 let summary = mock_summary();
317 let elements = summary.to_elements();
318
319 assert_eq!(elements.len(), TransactionSummary::NUM_ELEMENTS);
320 assert_eq!(
321 &elements[TransactionSummary::EXPIRATION_DELTA_IDX..],
322 [
323 Felt::from(EXPIRATION_DELTA),
324 Felt::from(USER_PARAMS[0]),
325 Felt::from(USER_PARAMS[1]),
326 Felt::from(USER_PARAMS[2]),
327 Felt::from(USER_PARAMS[3]),
328 Felt::from(USER_PARAMS[4]),
329 Felt::from(USER_PARAMS[5]),
330 Felt::from(USER_PARAMS[6]),
331 ]
332 );
333
334 let (expiration_delta, user_params) =
335 TransactionSummary::try_params_from_elements(&elements).unwrap();
336 assert_eq!(expiration_delta, EXPIRATION_DELTA);
337 assert_eq!(user_params, summary.user_params());
338 }
339
340 #[test]
341 fn tx_summary_serde_roundtrip() {
342 let summary = mock_summary();
343
344 let deserialized = TransactionSummary::read_from_bytes(&summary.to_bytes()).unwrap();
345 assert_eq!(deserialized, summary);
346 }
347
348 #[test]
349 fn tx_summary_params_reject_out_of_range_expiration_delta() {
350 let mut elements = mock_summary().to_elements();
351 elements[TransactionSummary::EXPIRATION_DELTA_IDX] = Felt::from(u16::MAX as u32 + 1);
352
353 assert_matches!(
354 TransactionSummary::try_params_from_elements(&elements),
355 Err(TransactionSummaryError::ExpirationDeltaTooLarge(_))
356 );
357 }
358
359 #[test]
360 fn tx_summary_params_reject_preimage_of_wrong_length() {
361 let mut elements = mock_summary().to_elements();
362 elements.pop();
363
364 assert_matches!(
365 TransactionSummary::try_params_from_elements(&elements),
366 Err(TransactionSummaryError::InvalidPreimageLength { actual, expected })
367 if actual == TransactionSummary::NUM_ELEMENTS - 1
368 && expected == TransactionSummary::NUM_ELEMENTS
369 );
370 }
371}