radix_transactions/model/preparation/
summarized_composite.rs

1use crate::internal_prelude::*;
2use radix_common::constants::*;
3
4pub enum ConcatenatedDigest {}
5
6impl ConcatenatedDigest {
7    /// For use when creating a transaction payload
8    pub fn prepare_transaction_payload<T: TuplePreparable>(
9        decoder: &mut TransactionDecoder,
10        discriminator: TransactionDiscriminator,
11        header: ExpectedHeaderKind,
12    ) -> Result<(T, Summary), PrepareError> {
13        let digest = HashAccumulator::new()
14            .concat([TRANSACTION_HASHABLE_PAYLOAD_PREFIX, discriminator as u8]);
15        T::prepare_into_concatenated_digest(
16            decoder,
17            digest,
18            header.with_discriminator(discriminator as u8),
19        )
20    }
21
22    #[deprecated = "Use prepare_from_sbor_array_value_body instead for new models"]
23    pub fn prepare_from_sbor_array_full_value<T: ArrayPreparable>(
24        decoder: &mut TransactionDecoder,
25        value_type: ValueType,
26        max_length: usize,
27    ) -> Result<(T, Summary), PrepareError> {
28        T::prepare_into_concatenated_digest(
29            decoder,
30            HashAccumulator::new(),
31            value_type,
32            max_length,
33            true,
34        )
35    }
36
37    pub fn prepare_from_sbor_array_value_body<T: ArrayPreparable>(
38        decoder: &mut TransactionDecoder,
39        value_type: ValueType,
40        max_length: usize,
41    ) -> Result<(T, Summary), PrepareError> {
42        T::prepare_into_concatenated_digest(
43            decoder,
44            HashAccumulator::new(),
45            value_type,
46            max_length,
47            false,
48        )
49    }
50
51    #[deprecated = "Use prepare_from_sbor_tuple_value_body instead for new models"]
52    pub fn prepare_from_sbor_tuple_full_value<T: TuplePreparable>(
53        decoder: &mut TransactionDecoder,
54    ) -> Result<(T, Summary), PrepareError> {
55        T::prepare_into_concatenated_digest(
56            decoder,
57            HashAccumulator::new(),
58            ExpectedTupleHeader::TupleWithValueKind,
59        )
60    }
61
62    pub fn prepare_from_sbor_tuple_value_body<T: TuplePreparable>(
63        decoder: &mut TransactionDecoder,
64    ) -> Result<(T, Summary), PrepareError> {
65        T::prepare_into_concatenated_digest(
66            decoder,
67            HashAccumulator::new(),
68            ExpectedTupleHeader::TupleNoValueKind,
69        )
70    }
71}
72
73pub trait ArrayPreparable: Sized {
74    fn prepare_into_concatenated_digest(
75        decoder: &mut TransactionDecoder,
76        accumulator: HashAccumulator,
77        value_type: ValueType,
78        max_length: usize,
79        read_value_kind: bool,
80    ) -> Result<(Self, Summary), PrepareError>;
81}
82
83impl<T: TransactionPreparableFromValueBody> ArrayPreparable for Vec<T> {
84    fn prepare_into_concatenated_digest(
85        decoder: &mut TransactionDecoder,
86        mut accumulator: HashAccumulator,
87        value_type: ValueType,
88        max_length: usize,
89        read_value_kind: bool,
90    ) -> Result<(Self, Summary), PrepareError> {
91        decoder.track_stack_depth_increase()?;
92        let length = if read_value_kind {
93            decoder.read_array_header(T::value_kind())?
94        } else {
95            decoder.read_array_header_without_value_kind(T::value_kind())?
96        };
97
98        if length > max_length {
99            return Err(PrepareError::TooManyValues {
100                value_type,
101                actual: length,
102                max: max_length,
103            });
104        }
105
106        // NOTE: We purposefully don't take the effective_length from the size of the SBOR type header
107        // This is because the SBOR value header isn't included in the hash...
108        // And we want to protect against non-determinism in the effective_length due to a different serializations of the SBOR value header.
109        // Whilst we believe the SBOR value header to currently be unique (eg we don't allow trailing bytes in the encoded size) - I'd rather not rely on that.
110        // So just assume it's 2 here (1 byte for value kind + 1 byte for length if length sufficiently short)
111        let mut effective_length = 2usize;
112        let mut total_bytes_hashed = 0usize;
113
114        let mut all_prepared: Vec<T> = Vec::with_capacity(length);
115        for _ in 0..length {
116            let prepared = T::prepare_from_value_body(decoder)?;
117            effective_length = effective_length
118                .checked_add(prepared.get_summary().effective_length)
119                .ok_or(PrepareError::LengthOverflow)?;
120            total_bytes_hashed = total_bytes_hashed
121                .checked_add(prepared.get_summary().total_bytes_hashed)
122                .ok_or(PrepareError::LengthOverflow)?;
123            accumulator = accumulator.concat(prepared.get_summary().hash);
124            all_prepared.push(prepared);
125        }
126
127        decoder.track_stack_depth_decrease()?;
128
129        total_bytes_hashed = total_bytes_hashed
130            .checked_add(accumulator.input_length())
131            .ok_or(PrepareError::LengthOverflow)?;
132
133        let summary = Summary {
134            effective_length,
135            total_bytes_hashed,
136            hash: accumulator.finalize(),
137        };
138
139        Ok((all_prepared, summary))
140    }
141}
142
143pub trait TuplePreparable: Sized {
144    fn prepare_into_concatenated_digest(
145        decoder: &mut TransactionDecoder,
146        accumulator: HashAccumulator,
147        header: ExpectedTupleHeader,
148    ) -> Result<(Self, Summary), PrepareError>;
149}
150
151pub enum ExpectedHeaderKind {
152    EnumNoValueKind,
153    EnumWithValueKind,
154    TupleNoValueKind,
155    TupleWithValueKind,
156}
157
158impl ExpectedHeaderKind {
159    pub fn with_discriminator(self, discriminator: u8) -> ExpectedTupleHeader {
160        match self {
161            Self::EnumNoValueKind => ExpectedTupleHeader::EnumNoValueKind { discriminator },
162            Self::EnumWithValueKind => ExpectedTupleHeader::EnumWithValueKind { discriminator },
163            Self::TupleNoValueKind => ExpectedTupleHeader::TupleNoValueKind,
164            Self::TupleWithValueKind => ExpectedTupleHeader::TupleWithValueKind,
165        }
166    }
167}
168
169pub enum ExpectedTupleHeader {
170    EnumNoValueKind { discriminator: u8 },
171    EnumWithValueKind { discriminator: u8 },
172    TupleWithValueKind,
173    TupleNoValueKind,
174}
175
176macro_rules! prepare_tuple {
177    ($n:tt$( $var_name:ident $type_name:ident)*) => {
178        impl<$($type_name: TransactionPreparableFromValue,)*> TuplePreparable for ($($type_name,)*) {
179            #[allow(unused_mut)]
180            fn prepare_into_concatenated_digest(decoder: &mut TransactionDecoder, mut accumulator: HashAccumulator, header: ExpectedTupleHeader) -> Result<(Self, Summary), PrepareError> {
181                decoder.track_stack_depth_increase()?;
182                decoder.read_header(header, $n)?;
183
184                // NOTE: We purposefully don't take the effective_length from the size of the SBOR type header
185                // This is because the SBOR value header isn't included in the hash...
186                // And we want to protect against non-determinism in the effective_length due to a different serializations of the SBOR value header.
187                // Whilst we believe the SBOR value header to currently be unique (eg we don't allow trailing bytes in the encoded size) - I'd rather not rely on that.
188                // So just assume it's 2 here (1 byte for value kind + 1 byte for length if length sufficiently short)
189                // ALSO this makes the length independent of whether it is an enum or tuple
190                let mut effective_length = 2usize;
191                let mut total_bytes_hashed = 0usize;
192
193                $(
194                    let $var_name = <$type_name>::prepare_from_value(decoder)?;
195                    effective_length = effective_length.checked_add($var_name.get_summary().effective_length).ok_or(PrepareError::LengthOverflow)?;
196                    total_bytes_hashed = total_bytes_hashed.checked_add($var_name.get_summary().total_bytes_hashed).ok_or(PrepareError::LengthOverflow)?;
197                    accumulator = accumulator.concat($var_name.get_summary().hash);
198                )*
199
200                decoder.track_stack_depth_decrease()?;
201
202                total_bytes_hashed = total_bytes_hashed.checked_add(accumulator.input_length()).ok_or(PrepareError::LengthOverflow)?;
203
204                let summary = Summary {
205                    effective_length,
206                    total_bytes_hashed,
207                    hash: accumulator.finalize(),
208                };
209                Ok((($($var_name,)*), summary))
210            }
211        }
212    };
213}
214
215prepare_tuple! { 0 }
216prepare_tuple! { 1 p0 T0 }
217prepare_tuple! { 2 p0 T0 p1 T1 }
218prepare_tuple! { 3 p0 T0 p1 T1 p2 T2 }
219prepare_tuple! { 4 p0 T0 p1 T1 p2 T2 p3 T3 }
220prepare_tuple! { 5 p0 T0 p1 T1 p2 T2 p3 T3 p4 T4 }
221prepare_tuple! { 6 p0 T0 p1 T1 p2 T2 p3 T3 p4 T4 p5 T5 }