use alloc::vec::Vec;
use crate::account::AccountDelta;
use crate::crypto::SequentialCommit;
use crate::errors::TransactionSummaryError;
use crate::transaction::{InputNote, InputNotes, RawOutputNotes};
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{Felt, WORD_SIZE, Word};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransactionSummary {
account_delta: AccountDelta,
input_notes: InputNotes<InputNote>,
output_notes: RawOutputNotes,
block_commitment: Word,
expiration_delta: u16,
user_params: TransactionSummaryUserParams,
}
impl TransactionSummary {
const EXPIRATION_DELTA_IDX: usize = 4 * WORD_SIZE;
pub const NUM_ELEMENTS: usize =
Self::EXPIRATION_DELTA_IDX + 1 + TransactionSummaryUserParams::NUM_ELEMENTS;
pub fn new(
account_delta: AccountDelta,
input_notes: InputNotes<InputNote>,
output_notes: RawOutputNotes,
block_commitment: Word,
expiration_delta: u16,
user_params: TransactionSummaryUserParams,
) -> Self {
Self {
account_delta,
input_notes,
output_notes,
block_commitment,
expiration_delta,
user_params,
}
}
pub fn account_delta(&self) -> &AccountDelta {
&self.account_delta
}
pub fn input_notes(&self) -> &InputNotes<InputNote> {
&self.input_notes
}
pub fn output_notes(&self) -> &RawOutputNotes {
&self.output_notes
}
pub fn block_commitment(&self) -> Word {
self.block_commitment
}
pub fn expiration_delta(&self) -> u16 {
self.expiration_delta
}
pub fn user_params(&self) -> TransactionSummaryUserParams {
self.user_params
}
pub fn to_elements(&self) -> Vec<Felt> {
<Self as SequentialCommit>::to_elements(self)
}
pub fn to_commitment(&self) -> Word {
<Self as SequentialCommit>::to_commitment(self)
}
pub fn try_params_from_elements(
elements: &[Felt],
) -> Result<(u16, TransactionSummaryUserParams), TransactionSummaryError> {
if elements.len() != Self::NUM_ELEMENTS {
return Err(TransactionSummaryError::InvalidPreimageLength {
actual: elements.len(),
expected: Self::NUM_ELEMENTS,
});
}
let expiration_delta_element = elements[Self::EXPIRATION_DELTA_IDX];
let expiration_delta =
u16::try_from(expiration_delta_element.as_canonical_u64()).map_err(|_| {
TransactionSummaryError::ExpirationDeltaTooLarge(expiration_delta_element)
})?;
let user_params = elements[Self::EXPIRATION_DELTA_IDX + 1..]
.try_into()
.expect("preimage length was validated above");
Ok((expiration_delta, TransactionSummaryUserParams::new(user_params)))
}
}
const _: () = assert!(TransactionSummary::NUM_ELEMENTS.is_multiple_of(WORD_SIZE));
impl SequentialCommit for TransactionSummary {
type Commitment = Word;
fn to_elements(&self) -> Vec<Felt> {
let mut elements = Vec::with_capacity(Self::NUM_ELEMENTS);
elements.extend_from_slice(self.account_delta.to_commitment().as_elements());
elements.extend_from_slice(self.input_notes.commitment().as_elements());
elements.extend_from_slice(self.output_notes.commitment().as_elements());
elements.extend_from_slice(self.block_commitment.as_elements());
elements.push(Felt::from(self.expiration_delta));
elements.extend_from_slice(self.user_params.as_elements());
elements
}
}
impl Serializable for TransactionSummary {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.account_delta.write_into(target);
self.input_notes.write_into(target);
self.output_notes.write_into(target);
self.block_commitment.write_into(target);
self.expiration_delta.write_into(target);
self.user_params.write_into(target);
}
}
impl Deserializable for TransactionSummary {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let account_delta = source.read()?;
let input_notes = source.read()?;
let output_notes = source.read()?;
let block_commitment = source.read()?;
let expiration_delta = source.read()?;
let user_params = source.read()?;
Ok(Self::new(
account_delta,
input_notes,
output_notes,
block_commitment,
expiration_delta,
user_params,
))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TransactionSummaryUserParams {
elements: [Felt; Self::NUM_ELEMENTS],
}
impl TransactionSummaryUserParams {
pub const NUM_ELEMENTS: usize = 7;
pub fn new(elements: [Felt; Self::NUM_ELEMENTS]) -> Self {
Self { elements }
}
pub fn as_elements(&self) -> &[Felt; Self::NUM_ELEMENTS] {
&self.elements
}
}
impl Serializable for TransactionSummaryUserParams {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.elements.write_into(target);
}
}
impl Deserializable for TransactionSummaryUserParams {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
Ok(Self::new(source.read()?))
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::*;
use crate::ONE;
use crate::account::{AccountId, AccountStoragePatch, AccountVaultDelta};
use crate::testing::account_id::ACCOUNT_ID_PRIVATE_SENDER;
const EXPIRATION_DELTA: u16 = 42;
const USER_PARAMS: [u32; TransactionSummaryUserParams::NUM_ELEMENTS] = [1, 2, 3, 4, 5, 6, 7];
fn mock_summary() -> TransactionSummary {
let account_id = AccountId::try_from(ACCOUNT_ID_PRIVATE_SENDER).unwrap();
let account_delta = AccountDelta::new(
account_id,
AccountStoragePatch::new(),
AccountVaultDelta::default(),
None,
ONE,
)
.unwrap();
TransactionSummary::new(
account_delta,
InputNotes::new(Vec::new()).unwrap(),
RawOutputNotes::new(Vec::new()).unwrap(),
Word::from([9u32, 10, 11, 12].map(Felt::from)),
EXPIRATION_DELTA,
TransactionSummaryUserParams::new(USER_PARAMS.map(Felt::from)),
)
}
#[test]
fn tx_summary_params_element_roundtrip() {
let summary = mock_summary();
let elements = summary.to_elements();
assert_eq!(elements.len(), TransactionSummary::NUM_ELEMENTS);
assert_eq!(
&elements[TransactionSummary::EXPIRATION_DELTA_IDX..],
[
Felt::from(EXPIRATION_DELTA),
Felt::from(USER_PARAMS[0]),
Felt::from(USER_PARAMS[1]),
Felt::from(USER_PARAMS[2]),
Felt::from(USER_PARAMS[3]),
Felt::from(USER_PARAMS[4]),
Felt::from(USER_PARAMS[5]),
Felt::from(USER_PARAMS[6]),
]
);
let (expiration_delta, user_params) =
TransactionSummary::try_params_from_elements(&elements).unwrap();
assert_eq!(expiration_delta, EXPIRATION_DELTA);
assert_eq!(user_params, summary.user_params());
}
#[test]
fn tx_summary_serde_roundtrip() {
let summary = mock_summary();
let deserialized = TransactionSummary::read_from_bytes(&summary.to_bytes()).unwrap();
assert_eq!(deserialized, summary);
}
#[test]
fn tx_summary_params_reject_out_of_range_expiration_delta() {
let mut elements = mock_summary().to_elements();
elements[TransactionSummary::EXPIRATION_DELTA_IDX] = Felt::from(u16::MAX as u32 + 1);
assert_matches!(
TransactionSummary::try_params_from_elements(&elements),
Err(TransactionSummaryError::ExpirationDeltaTooLarge(_))
);
}
#[test]
fn tx_summary_params_reject_preimage_of_wrong_length() {
let mut elements = mock_summary().to_elements();
elements.pop();
assert_matches!(
TransactionSummary::try_params_from_elements(&elements),
Err(TransactionSummaryError::InvalidPreimageLength { actual, expected })
if actual == TransactionSummary::NUM_ELEMENTS - 1
&& expected == TransactionSummary::NUM_ELEMENTS
);
}
}