radix_transactions/model/
any_transaction.rs

1use crate::internal_prelude::*;
2
3//=============================================================================
4// TRANSACTION PAYLOAD VERSIONING
5//
6// This file aligns with REP-82 - please see the REP for details on why the
7// payloads are versioned this way.
8//=============================================================================
9
10/// Note - some of these are reserved for use in the node.
11#[derive(Copy, Clone, Debug, Eq, PartialEq, FromRepr)]
12#[repr(u8)]
13pub enum TransactionDiscriminator {
14    V1Intent = V1_INTENT,
15    V1SignedIntent = V1_SIGNED_INTENT,
16    V1Notarized = V1_NOTARIZED_TRANSACTION,
17    V1System = V1_SYSTEM_TRANSACTION,
18    V1RoundUpdate = V1_ROUND_UPDATE_TRANSACTION,
19    Ledger = LEDGER_TRANSACTION,
20    V1Flash = V1_FLASH_TRANSACTION,
21    V2TransactionIntent = V2_TRANSACTION_INTENT,
22    V2SignedTransactionIntent = V2_SIGNED_TRANSACTION_INTENT,
23    V2Subintent = V2_SUBINTENT,
24    V2Notarized = V2_NOTARIZED_TRANSACTION,
25    V2PartialTransaction = V2_PARTIAL_TRANSACTION,
26    V2SignedPartialTransaction = V2_SIGNED_PARTIAL_TRANSACTION,
27    V2PreviewTransaction = V2_PREVIEW_TRANSACTION,
28}
29
30const V1_INTENT: u8 = 1;
31const V1_SIGNED_INTENT: u8 = 2;
32const V1_NOTARIZED_TRANSACTION: u8 = 3;
33const V1_SYSTEM_TRANSACTION: u8 = 4;
34const V1_ROUND_UPDATE_TRANSACTION: u8 = 5;
35// NOTE: 6 used to be reserved for serialized preview transactions,
36//       but they have never been serialized, so 6 is free for re-use
37
38// LEDGER TRANSACTION is not versioned, and can be extended with support
39// for new versions
40const LEDGER_TRANSACTION: u8 = 7;
41const V1_FLASH_TRANSACTION: u8 = 8;
42const V2_TRANSACTION_INTENT: u8 = 9;
43const V2_SIGNED_TRANSACTION_INTENT: u8 = 10;
44const V2_SUBINTENT: u8 = 11;
45const V2_NOTARIZED_TRANSACTION: u8 = 12;
46const V2_PARTIAL_TRANSACTION: u8 = 13;
47const V2_SIGNED_PARTIAL_TRANSACTION: u8 = 14;
48const V2_PREVIEW_TRANSACTION: u8 = 15;
49
50/// An enum of a variety of different transaction payload types.
51///
52/// Running `to_payload_bytes()` on each transaction type gives the same
53/// as Manifest SBOR encoding the variant of this enum.
54///
55/// For this reason, this type might see use in the Node's transaction
56/// parse API, and in other places where we want to decode or handle an
57/// arbitrary transaction payload.
58///
59/// All the transaction types also implement `ScryptoDescribe`, primarily
60/// so that they can derive `ScryptoSborAssertion` to ensure we don't change
61/// the types accidentally.
62#[derive(Clone, Debug, Eq, PartialEq, ManifestSbor, ScryptoDescribe, ScryptoSborAssertion)]
63#[sbor(impl_variant_traits)]
64#[sbor_assert(
65    // This sum type of all payload-convertible transactions is extensible, so
66    // we use `backwards_compatible` here. But most individual transaction models
67    // should themselves be `fixed`, e.g. NotarizedTransactionV1
68    backwards_compatible(
69        bottlenose = "FILE:any_transaction_payload_schema_bottlenose.txt",
70        cuttlefish = "FILE:any_transaction_payload_schema_cuttlefish.bin"
71    ),
72    settings(allow_name_changes)
73)]
74pub enum AnyTransaction {
75    #[sbor(discriminator(V1_INTENT))]
76    TransactionIntentV1(#[sbor(flatten)] IntentV1),
77    #[sbor(discriminator(V1_SIGNED_INTENT))]
78    SignedTransactionIntentV1(#[sbor(flatten)] SignedIntentV1),
79    #[sbor(discriminator(V1_NOTARIZED_TRANSACTION))]
80    NotarizedTransactionV1(#[sbor(flatten)] NotarizedTransactionV1),
81    #[sbor(discriminator(V1_SYSTEM_TRANSACTION))]
82    SystemTransactionV1(#[sbor(flatten)] SystemTransactionV1),
83    #[sbor(discriminator(V1_ROUND_UPDATE_TRANSACTION))]
84    RoundUpdateTransactionV1(#[sbor(flatten)] RoundUpdateTransactionV1),
85    #[sbor(discriminator(LEDGER_TRANSACTION))] // Not flattened because it's an enum
86    LedgerTransaction(LedgerTransaction),
87    #[sbor(discriminator(V1_FLASH_TRANSACTION))]
88    FlashTransactionV1(#[sbor(flatten)] FlashTransactionV1),
89    #[sbor(discriminator(V2_TRANSACTION_INTENT))]
90    TransactionIntentV2(#[sbor(flatten)] TransactionIntentV2),
91    #[sbor(discriminator(V2_SIGNED_TRANSACTION_INTENT))]
92    SignedTransactionIntentV2(#[sbor(flatten)] SignedTransactionIntentV2),
93    #[sbor(discriminator(V2_SUBINTENT))]
94    SubintentV2(#[sbor(flatten)] SubintentV2),
95    #[sbor(discriminator(V2_NOTARIZED_TRANSACTION))]
96    NotarizedTransactionV2(#[sbor(flatten)] NotarizedTransactionV2),
97    #[sbor(discriminator(V2_PARTIAL_TRANSACTION))]
98    PartialTransactionV2(#[sbor(flatten)] PartialTransactionV2),
99    #[sbor(discriminator(V2_SIGNED_PARTIAL_TRANSACTION))]
100    SignedPartialTransactionV2(#[sbor(flatten)] SignedPartialTransactionV2),
101    #[sbor(discriminator(V2_PREVIEW_TRANSACTION))]
102    PreviewTransactionV2(#[sbor(flatten)] PreviewTransactionV2),
103}
104
105#[cfg(test)]
106mod tests {
107    use radix_engine_interface::blueprints::resource::FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT;
108
109    use super::*;
110    use crate::manifest::e2e::tests::print_blob;
111    use crate::model::*;
112
113    #[deprecated = "Should only be used by transaction v1, because it's less flexible than hash_encoded_sbor_value_body"]
114    fn hash_encoded_sbor_value<T: ManifestEncode>(value: T) -> Hash {
115        // Ignore the version byte
116        hash(&manifest_encode(&value).unwrap()[1..])
117    }
118
119    fn hash_encoded_sbor_value_body<T: ManifestEncode>(value: T) -> Hash {
120        // Ignore the version byte AND the value kind
121        hash(&manifest_encode(&value).unwrap()[2..])
122    }
123
124    fn hash_contatenated_hashes<H: Into<Hash>>(hashes: impl IntoIterator<Item = H>) -> Hash {
125        let concatenated_hashes: Vec<u8> = hashes
126            .into_iter()
127            .flat_map(|h| Into::<Hash>::into(h).0)
128            .collect();
129        hash(concatenated_hashes)
130    }
131
132    fn hash_from_partial_prepare(value: &impl TransactionPartialPrepare) -> Hash {
133        value
134            .prepare_partial(PreparationSettings::latest_ref())
135            .unwrap()
136            .get_summary()
137            .hash
138    }
139
140    /// This test demonstrates how the hashes and payloads are constructed in a valid user transaction.
141    /// It also provides an example payload which can be used in other implementations.
142    #[test]
143    #[allow(deprecated)] // Transaction V1 is allowed to use deprecated hashing
144    pub fn v1_user_transaction_structure() {
145        let network = NetworkDefinition::simulator();
146        let preparation_settings = PreparationSettings::babylon();
147
148        // Create key pairs
149        let sig_1_private_key = Secp256k1PrivateKey::from_u64(1).unwrap();
150        let sig_2_private_key = Ed25519PrivateKey::from_u64(2).unwrap();
151        let notary_private_key = Ed25519PrivateKey::from_u64(3).unwrap();
152
153        //===================
154        // INTENT
155        //===================
156        let header_v1 = TransactionHeaderV1 {
157            network_id: network.id,
158            start_epoch_inclusive: Epoch::of(1),
159            end_epoch_exclusive: Epoch::of(5),
160            nonce: 0,
161            notary_public_key: notary_private_key.public_key().into(),
162            notary_is_signatory: false,
163            tip_percentage: 0,
164        };
165        let expected_header_hash = hash_encoded_sbor_value(&header_v1);
166
167        let instructions = vec![InstructionV1::DropAuthZoneProofs(DropAuthZoneProofs)];
168        let expected_instructions_hash = hash_encoded_sbor_value(&instructions);
169        let instructions_v1 = InstructionsV1(instructions);
170
171        let blob1: Vec<u8> = vec![0, 1, 2, 3];
172        let blob2: Vec<u8> = vec![5, 6];
173        let expected_blobs_hash =
174            hash([hash(&blob1).0.as_slice(), hash(&blob2).0.as_slice()].concat());
175
176        let blobs_v1 = BlobsV1 {
177            blobs: vec![BlobV1(blob1), BlobV1(blob2)],
178        };
179
180        let prepared_blobs_v1 = blobs_v1.prepare_partial(&preparation_settings).unwrap();
181        assert_eq!(prepared_blobs_v1.get_summary().hash, expected_blobs_hash);
182
183        let message_v1 = MessageV1::default();
184        let expected_attachments_hash = hash_encoded_sbor_value(&message_v1);
185
186        let intent_v1 = IntentV1 {
187            header: header_v1.clone(),
188            instructions: instructions_v1.clone(),
189            blobs: blobs_v1.clone(),
190            message: message_v1.clone(),
191        };
192        let expected_intent_hash = TransactionIntentHash::from_hash(hash(
193            [
194                [
195                    TRANSACTION_HASHABLE_PAYLOAD_PREFIX,
196                    TransactionDiscriminator::V1Intent as u8,
197                ]
198                .as_slice(),
199                expected_header_hash.0.as_slice(),
200                expected_instructions_hash.0.as_slice(),
201                expected_blobs_hash.0.as_slice(),
202                expected_attachments_hash.0.as_slice(),
203            ]
204            .concat(),
205        ));
206
207        let raw_intent_payload = intent_v1.to_raw().unwrap();
208
209        println!();
210        print_blob("HC_INTENT", raw_intent_payload.as_slice());
211        print_blob("HC_INTENT_HASH", expected_intent_hash.0.as_slice());
212
213        IntentV1::from_raw(&raw_intent_payload).expect("Intent can be decoded");
214        let intent_as_versioned =
215            manifest_decode::<AnyTransaction>(raw_intent_payload.as_slice()).unwrap();
216        assert_eq!(
217            intent_as_versioned,
218            AnyTransaction::TransactionIntentV1(intent_v1.clone())
219        );
220
221        let prepared_intent =
222            PreparedIntentV1::prepare(&raw_intent_payload, &preparation_settings).unwrap();
223        assert_eq!(
224            expected_intent_hash,
225            prepared_intent.transaction_intent_hash()
226        );
227
228        let intent_hash = prepared_intent.transaction_intent_hash();
229
230        assert_eq!(
231            intent_hash.to_string(&TransactionHashBech32Encoder::for_simulator()),
232            "txid_sim16hm8cq74dyusrgy8xg6eg5ss0d3cte9hdj0dhudtzp6vvszh3vjq3amttp"
233        );
234        assert_eq!(
235            hex::encode(raw_intent_payload),
236            "4d220104210707f20a01000000000000000a05000000000000000900000000220101200720f381626e41e7027ea431bfe3009e94bdd25a746beec468948d6c3c7c5dc9a54b0100080000202201120020200207040001020307020506220000"
237        );
238
239        //===================
240        // SIGNED INTENT
241        //===================
242        let sig1 = sig_1_private_key.sign_with_public_key(&intent_hash);
243        let sig2 = sig_2_private_key.sign_with_public_key(&intent_hash);
244
245        let intent_signatures_v1 = IntentSignaturesV1 {
246            signatures: vec![IntentSignatureV1(sig1), IntentSignatureV1(sig2)],
247        };
248        let expected_intent_signatures_hash = hash_encoded_sbor_value(&intent_signatures_v1);
249
250        let signed_intent_v1 = SignedIntentV1 {
251            intent: intent_v1.clone(),
252            intent_signatures: intent_signatures_v1.clone(),
253        };
254        let expected_signed_intent_hash = SignedTransactionIntentHash::from_hash(hash(
255            [
256                [
257                    TRANSACTION_HASHABLE_PAYLOAD_PREFIX,
258                    TransactionDiscriminator::V1SignedIntent as u8,
259                ]
260                .as_slice(),
261                intent_hash.0.as_slice(),
262                expected_intent_signatures_hash.0.as_slice(),
263            ]
264            .concat(),
265        ));
266
267        let raw_signed_intent = signed_intent_v1.to_raw().unwrap();
268
269        let signed_intent_as_versioned =
270            manifest_decode::<AnyTransaction>(raw_signed_intent.as_slice()).unwrap();
271        assert_eq!(
272            signed_intent_as_versioned,
273            AnyTransaction::SignedTransactionIntentV1(signed_intent_v1.clone())
274        );
275
276        let prepared_signed_intent =
277            PreparedSignedIntentV1::prepare(&raw_signed_intent, &preparation_settings).unwrap();
278        assert_eq!(
279            expected_signed_intent_hash,
280            prepared_signed_intent.signed_transaction_intent_hash()
281        );
282        assert_eq!(
283            intent_hash,
284            prepared_signed_intent.transaction_intent_hash()
285        );
286
287        let signed_intent_hash = expected_signed_intent_hash;
288
289        assert_eq!(
290            signed_intent_hash.to_string(&TransactionHashBech32Encoder::for_simulator()),
291            "signedintent_sim1dylyaqctdlpnr8768ve6gy6mhjryd5w46scepdx50nplyk64g28qcy3zxn"
292        );
293        assert_eq!(
294            hex::encode(raw_signed_intent),
295            "4d2202022104210707f20a01000000000000000a05000000000000000900000000220101200720f381626e41e7027ea431bfe3009e94bdd25a746beec468948d6c3c7c5dc9a54b01000800002022011200202002070400010203070205062200002022020001210120074100ffb4d3532977ad5f561d73ee8febbf4330812bb43063fd61a15e59ad233a13ea2f27b8eda06af0861b18108e4dae6301363b5b243ac1518f482e27f2f32f0bb701022007207422b9887598068e32c4448a949adb290d0f4e35b9e01b0ee5f1a1e600fe26742101200740f0587aa712a637c84b0b2bc929c14cb2ccb3846c330434459205a11be5ff610cadfdbf33fa12b98d8e947f33a350a84068e710672753cdc33315c400db9c4e0f"
296        );
297
298        //======================
299        // NOTARIZED TRANSACTION
300        //======================
301        let notary_signature = notary_private_key.sign(signed_intent_hash);
302
303        let notary_signature_v1 = NotarySignatureV1(notary_signature.into());
304        let expected_notary_signature_v1_hash = hash_encoded_sbor_value(&notary_signature_v1);
305
306        let notarized_transaction_v1 = NotarizedTransactionV1 {
307            signed_intent: signed_intent_v1.clone(),
308            notary_signature: notary_signature_v1.clone(),
309        };
310        let expected_notarized_transaction_hash = NotarizedTransactionHash::from_hash(hash(
311            [
312                [
313                    TRANSACTION_HASHABLE_PAYLOAD_PREFIX,
314                    TransactionDiscriminator::V1Notarized as u8,
315                ]
316                .as_slice(),
317                signed_intent_hash.0.as_slice(),
318                expected_notary_signature_v1_hash.0.as_slice(),
319            ]
320            .concat(),
321        ));
322
323        let raw_notarized_transaction = notarized_transaction_v1.to_raw().unwrap();
324        NotarizedTransactionV1::from_raw(&raw_notarized_transaction)
325            .expect("NotarizedTransaction can be decoded");
326        let notarized_transaction_as_versioned =
327            manifest_decode::<AnyTransaction>(raw_notarized_transaction.as_slice()).unwrap();
328        assert_eq!(
329            notarized_transaction_as_versioned,
330            AnyTransaction::NotarizedTransactionV1(notarized_transaction_v1)
331        );
332
333        let prepared_notarized_transaction = PreparedNotarizedTransactionV1::prepare(
334            &raw_notarized_transaction,
335            &preparation_settings,
336        )
337        .unwrap();
338        assert_eq!(
339            expected_notarized_transaction_hash,
340            prepared_notarized_transaction.notarized_transaction_hash()
341        );
342        let notarized_transaction_hash = expected_notarized_transaction_hash;
343        assert_eq!(
344            signed_intent_hash,
345            prepared_notarized_transaction.signed_transaction_intent_hash()
346        );
347        assert_eq!(
348            intent_hash,
349            prepared_notarized_transaction.transaction_intent_hash()
350        );
351
352        assert_eq!(
353            notarized_transaction_hash.to_string(&TransactionHashBech32Encoder::for_simulator()),
354            "notarizedtransaction_sim1lhfnzp027gt7ducszxmkl02qpp5lpx25npqwxkrk2qqyhs08raksacmd94"
355        );
356        assert_eq!(
357            hex::encode(raw_notarized_transaction),
358            "4d22030221022104210707f20a01000000000000000a05000000000000000900000000220101200720f381626e41e7027ea431bfe3009e94bdd25a746beec468948d6c3c7c5dc9a54b01000800002022011200202002070400010203070205062200002022020001210120074100ffb4d3532977ad5f561d73ee8febbf4330812bb43063fd61a15e59ad233a13ea2f27b8eda06af0861b18108e4dae6301363b5b243ac1518f482e27f2f32f0bb701022007207422b9887598068e32c4448a949adb290d0f4e35b9e01b0ee5f1a1e600fe26742101200740f0587aa712a637c84b0b2bc929c14cb2ccb3846c330434459205a11be5ff610cadfdbf33fa12b98d8e947f33a350a84068e710672753cdc33315c400db9c4e0f2201012101200740321bfd17cac75d0b16fe6fd5aa9bb3e2beaf6521af4607f28815c8bd08718de8078a3fd75750354c400e1ea33cc8986853af6115bc43530cc0550ec9b2696a06"
359        );
360    }
361
362    /// This test demonstrates how the hashes and payloads are constructed in a valid user transaction.
363    /// It also provides an example payload which can be used in other implementations.
364    #[test]
365    pub fn v2_notarized_transaction_structure() {
366        let network = NetworkDefinition::simulator();
367
368        let (signed_transaction_intent, signed_transaction_intent_hash) =
369            create_signed_transaction_intent_v2(&network);
370        let (notary_signature, notary_signature_hash) =
371            create_notary_signature_v2(signed_transaction_intent_hash);
372
373        let notarized_transaction = NotarizedTransactionV2 {
374            signed_transaction_intent,
375            notary_signature,
376        };
377        let expected_hash = NotarizedTransactionHash(hash(
378            [
379                [
380                    TRANSACTION_HASHABLE_PAYLOAD_PREFIX,
381                    TransactionDiscriminator::V2Notarized as u8,
382                ]
383                .as_slice(),
384                signed_transaction_intent_hash.0.as_slice(),
385                notary_signature_hash.0.as_slice(),
386            ]
387            .concat(),
388        ));
389        let raw = notarized_transaction.to_raw().unwrap();
390
391        let prepared_transaction = notarized_transaction
392            .prepare(&PreparationSettings::latest())
393            .unwrap();
394        let actual_transaction_intent_hash = prepared_transaction.transaction_intent_hash();
395        let actual_signed_transaction_intent_hash =
396            prepared_transaction.signed_transaction_intent_hash();
397        let notarized_transaction_hash = prepared_transaction.notarized_transaction_hash();
398
399        assert_eq!(expected_hash, notarized_transaction_hash);
400        assert_eq!(
401            notarized_transaction_hash.to_string(&TransactionHashBech32Encoder::for_simulator()),
402            "notarizedtransaction_sim1qh37lkr547jgv5zfvlkq4njdhn62m2sg09k6njmkuma7u2hd4zasrmhyew",
403        );
404        assert_eq!(
405            actual_signed_transaction_intent_hash
406                .to_string(&TransactionHashBech32Encoder::for_simulator()),
407            "signedintent_sim1z2at9wmfh7pcx7ad0c4npyv3xn3mecf2gyehwd6g6w99v56ntfsq4k92yx",
408        );
409        assert_eq!(
410            actual_transaction_intent_hash
411                .to_string(&TransactionHashBech32Encoder::for_simulator()),
412            "txid_sim1v7xlgxkrk59qekpj53x8jul0lml0r4nzn3yfmv4jd5ysjewmkaust5l3t2",
413        );
414        assert_eq!(
415            hex::encode(raw.as_slice()),
416            "4d220c0221032103210322010120072009b3f25a3a1839f46ddb09b068271811f6f00a79246fb24e7a808a9e46d6075d010009000000002105210607f20a01000000000000000a0a000000000000002200002201010500000000000000000a00000000000000002020020704000102030702050622010121020c0a746578742f706c61696e2200010c0c48656c6c6f20776f726c64212020010720b37d9be9fe7362e9f01a828af77a3298758ac7d43be750575befdbd395c28918202201610209000000002100202101012105210607f20a01000000000000000a0a000000000000002200002201010500000000000000000a00000000000000002020020704000102030702050622010121020c0a746578742f706c61696e2200010c0c48656c6c6f20776f726c64212020002022016001210020220101022007204d956b5eb1147b3a80c40170e340e2918d2a9f33bdb529c54401e3ed80a4e70a2101200740e04f0e563d71ca150d900d75538d2253dff0f77d86c8ecfa4dcd25ac94de5a4ed27d76ac95c3ee8ebdcc1da52df6d1ca5f265bc1f973f631bc753e4146b3aa0c20200122010102200720c561fa9f643fe5c60113cce9db282fde2b9e5ca5fc6b6fc0d1679bb339c9f72f2101200740860417490e96c91addd5a390f5f1bcd2697535f23a947d2337b291a7b86611f56cc3aa0606ac8b8cba98381c35ef9a1f655362b18764eb90b1d8b814ec17f40e2201012101200740975a47326156a7818b4776e3e455a67c906c34eda7a9c9bb9688c77664ed9679c78aa9e33740aa1d3631b89119071a3feaf02b650799da64da7f659d107db905"
417        );
418
419        // Check that the transaction we created is actually valid...
420        prepared_transaction
421            .validate(&TransactionValidator::new_for_latest_simulator())
422            .unwrap();
423    }
424
425    fn create_notary_signature_v2(
426        hash_to_sign: SignedTransactionIntentHash,
427    ) -> (NotarySignatureV2, Hash) {
428        let notary_signature = NotarySignatureV2(
429            TransactionV2Builder::testing_default_notary()
430                .sign_without_public_key(hash_to_sign.as_hash()),
431        );
432        let expected_hash = hash_encoded_sbor_value_body(&notary_signature);
433        let actual_hash = hash_from_partial_prepare(&notary_signature);
434        assert_eq!(expected_hash, actual_hash);
435        (notary_signature, actual_hash)
436    }
437
438    fn create_signed_transaction_intent_v2(
439        network: &NetworkDefinition,
440    ) -> (SignedTransactionIntentV2, SignedTransactionIntentHash) {
441        let (transaction_intent, transaction_intent_hash, subintent_hash) =
442            create_transaction_intent_v2(network);
443        let (transaction_intent_signatures, transaction_intent_signatures_hash) =
444            create_intent_signatures_v2(vec![2313], transaction_intent_hash);
445        let (non_root_subintent_signatures, non_root_subintent_signatures_hash) =
446            create_non_root_subintent_signatures(vec![subintent_hash]);
447
448        let signed = SignedTransactionIntentV2 {
449            transaction_intent,
450            transaction_intent_signatures,
451            non_root_subintent_signatures,
452        };
453
454        let expected_hash = SignedTransactionIntentHash(hash(
455            [
456                [
457                    TRANSACTION_HASHABLE_PAYLOAD_PREFIX,
458                    TransactionDiscriminator::V2SignedTransactionIntent as u8,
459                ]
460                .as_slice(),
461                transaction_intent_hash.0.as_slice(),
462                transaction_intent_signatures_hash.0.as_slice(),
463                non_root_subintent_signatures_hash.0.as_slice(),
464            ]
465            .concat(),
466        ));
467
468        let prepared = signed.prepare(&PreparationSettings::latest()).unwrap();
469        let actual_hash = prepared.signed_transaction_intent_hash();
470        assert_eq!(
471            actual_hash.to_string(&TransactionHashBech32Encoder::for_simulator()),
472            "signedintent_sim1z2at9wmfh7pcx7ad0c4npyv3xn3mecf2gyehwd6g6w99v56ntfsq4k92yx",
473        );
474        assert_eq!(expected_hash, actual_hash);
475
476        (signed, actual_hash)
477    }
478
479    fn create_non_root_subintent_signatures(
480        subintent_hashes: Vec<SubintentHash>,
481    ) -> (NonRootSubintentSignaturesV2, Hash) {
482        let (batches, batch_hashes): (Vec<_>, Vec<_>) = subintent_hashes
483            .into_iter()
484            .enumerate()
485            .map(|(i, subintent_hash)| {
486                create_intent_signatures_v2(vec![(i * 100 + 42) as u64], subintent_hash)
487            })
488            .unzip();
489        let signature_batches = NonRootSubintentSignaturesV2 {
490            by_subintent: batches,
491        };
492        let expected_hash = hash_contatenated_hashes(batch_hashes);
493        let actual_hash = hash_from_partial_prepare(&signature_batches);
494        assert_eq!(expected_hash, actual_hash);
495        (signature_batches, expected_hash)
496    }
497
498    fn create_intent_signatures_v2(
499        key_sources: Vec<u64>,
500        intent_hash: impl Into<IntentHash>,
501    ) -> (IntentSignaturesV2, Hash) {
502        let hash_to_sign = intent_hash.into().into_hash();
503        let signatures = IntentSignaturesV2 {
504            signatures: key_sources
505                .into_iter()
506                .map(|key_source| {
507                    create_intent_signature_v1(
508                        Ed25519PrivateKey::from_u64(key_source).unwrap(),
509                        &hash_to_sign,
510                    )
511                })
512                .collect(),
513        };
514        let expected_hash = hash_encoded_sbor_value_body(&signatures);
515        let actual_hash = hash_from_partial_prepare(&signatures);
516        assert_eq!(expected_hash, actual_hash);
517        (signatures, actual_hash)
518    }
519
520    fn create_intent_signature_v1(signer: impl Signer, hash_to_sign: &Hash) -> IntentSignatureV1 {
521        let signature = signer.sign_with_public_key(hash_to_sign);
522        IntentSignatureV1(signature)
523    }
524
525    fn create_transaction_intent_v2(
526        network: &NetworkDefinition,
527    ) -> (TransactionIntentV2, TransactionIntentHash, SubintentHash) {
528        let (subintent_1, subintent_1_hash) = create_checked_childless_subintent_v2(network);
529        let (non_root_subintents, non_root_subintents_hash) =
530            create_non_root_subintents_v2(vec![subintent_1], vec![subintent_1_hash]);
531
532        let (transaction_header, transaction_header_hash) = create_transaction_header_v2();
533        let (root_intent_core, root_intent_core_hash) = create_intent_core_v2(
534            &NetworkDefinition::simulator(),
535            vec![InstructionV2::YieldToChild(YieldToChild::empty(0))],
536            vec![subintent_1_hash],
537        );
538
539        let expected_transaction_intent_hash = TransactionIntentHash(hash(
540            [
541                [
542                    TRANSACTION_HASHABLE_PAYLOAD_PREFIX,
543                    TransactionDiscriminator::V2TransactionIntent as u8,
544                ]
545                .as_slice(),
546                transaction_header_hash.as_slice(),
547                root_intent_core_hash.as_slice(),
548                non_root_subintents_hash.as_slice(),
549            ]
550            .concat(),
551        ));
552
553        let transaction_intent = TransactionIntentV2 {
554            transaction_header,
555            root_intent_core,
556            non_root_subintents,
557        };
558
559        let actual_hash = transaction_intent
560            .prepare(PreparationSettings::latest_ref())
561            .unwrap()
562            .transaction_intent_hash();
563
564        assert_eq!(expected_transaction_intent_hash, actual_hash);
565        assert_eq!(
566            expected_transaction_intent_hash
567                .to_string(&TransactionHashBech32Encoder::for_simulator()),
568            "txid_sim1v7xlgxkrk59qekpj53x8jul0lml0r4nzn3yfmv4jd5ysjewmkaust5l3t2",
569        );
570
571        (transaction_intent, actual_hash, subintent_1_hash)
572    }
573
574    fn create_transaction_header_v2() -> (TransactionHeaderV2, Hash) {
575        let transaction_header = TransactionHeaderV2 {
576            notary_public_key: TransactionV2Builder::testing_default_notary()
577                .public_key()
578                .into(),
579            notary_is_signatory: false,
580            tip_basis_points: 0,
581        };
582        let expected_hash = hash_encoded_sbor_value_body(&transaction_header);
583        let actual_hash = hash_from_partial_prepare(&transaction_header);
584        assert_eq!(expected_hash, actual_hash);
585        (transaction_header, expected_hash)
586    }
587
588    fn create_non_root_subintents_v2(
589        subintents: Vec<SubintentV2>,
590        hashes: Vec<SubintentHash>,
591    ) -> (NonRootSubintentsV2, Hash) {
592        let non_root_subintents = NonRootSubintentsV2(subintents);
593
594        let expected_hash = hash_contatenated_hashes(hashes);
595        let actual_hash = hash_from_partial_prepare(&non_root_subintents);
596        assert_eq!(expected_hash, actual_hash);
597
598        (non_root_subintents, expected_hash)
599    }
600
601    fn create_checked_childless_subintent_v2(
602        network: &NetworkDefinition,
603    ) -> (SubintentV2, SubintentHash) {
604        let (intent_core, intent_core_hash) = create_intent_core_v2(
605            network,
606            vec![InstructionV2::YieldToParent(YieldToParent::empty())],
607            vec![],
608        );
609
610        let subintent = SubintentV2 { intent_core };
611
612        let expected_subintent_hash = SubintentHash(hash(
613            [
614                [
615                    TRANSACTION_HASHABLE_PAYLOAD_PREFIX,
616                    TransactionDiscriminator::V2Subintent as u8,
617                ]
618                .as_slice(),
619                intent_core_hash.as_slice(),
620            ]
621            .concat(),
622        ));
623
624        let prepared = subintent
625            .prepare(PreparationSettings::latest_ref())
626            .unwrap();
627        let actual_subintent_hash = prepared.subintent_hash();
628        assert_eq!(expected_subintent_hash, actual_subintent_hash);
629        assert_eq!(
630            expected_subintent_hash.to_string(&TransactionHashBech32Encoder::for_simulator()),
631            "subtxid_sim1kd7eh607wd3wnuq6s290w73jnp6c437580n4q46malda89wz3yvq3cph38",
632        );
633
634        (subintent, actual_subintent_hash)
635    }
636
637    fn create_intent_core_v2(
638        network: &NetworkDefinition,
639        instructions: Vec<InstructionV2>,
640        children: Vec<SubintentHash>,
641    ) -> (IntentCoreV2, Hash) {
642        let (header, expected_header_hash) = create_intent_header_v2(network);
643        let (blobs, expected_blobs_hash) = create_blobs_v1();
644        let (instructions, expected_instructions_hash) =
645            create_subintent_instructions_v2(instructions);
646        let (message, expected_message_hash) = create_message_v2();
647        let (child_intent_constraints, expected_constraints_hash) =
648            create_child_subintent_specifiers_v2(children);
649
650        let intent_core = IntentCoreV2 {
651            header,
652            instructions,
653            blobs,
654            message,
655            children: child_intent_constraints,
656        };
657
658        let expected_hash = hash(
659            [
660                expected_header_hash.as_slice(),
661                expected_blobs_hash.as_slice(),
662                expected_message_hash.as_slice(),
663                expected_constraints_hash.as_slice(),
664                expected_instructions_hash.as_slice(),
665            ]
666            .concat(),
667        );
668        let actual_hash = hash_from_partial_prepare(&intent_core);
669        assert_eq!(expected_hash, actual_hash);
670        (intent_core, expected_hash)
671    }
672
673    fn create_intent_header_v2(network: &NetworkDefinition) -> (IntentHeaderV2, Hash) {
674        let intent_header = IntentHeaderV2 {
675            network_id: network.id,
676            start_epoch_inclusive: Epoch::of(1),
677            end_epoch_exclusive: Epoch::of(10),
678            min_proposer_timestamp_inclusive: None,
679            max_proposer_timestamp_exclusive: Some(Instant::new(0)),
680            intent_discriminator: 0,
681        };
682        let expected_hash = hash_encoded_sbor_value_body(&intent_header);
683        let actual_hash = hash_from_partial_prepare(&intent_header);
684        assert_eq!(expected_hash, actual_hash);
685        (intent_header, expected_hash)
686    }
687
688    fn create_blobs_v1() -> (BlobsV1, Hash) {
689        let blob1: Vec<u8> = vec![0, 1, 2, 3];
690        let blob2: Vec<u8> = vec![5, 6];
691        let expected_hash = hash_contatenated_hashes([hash(&blob1), hash(&blob2)]);
692
693        let blobs_v1 = BlobsV1 {
694            blobs: vec![BlobV1(blob1), BlobV1(blob2)],
695        };
696
697        let actual_hash = hash_from_partial_prepare(&blobs_v1);
698        assert_eq!(expected_hash, actual_hash);
699
700        (blobs_v1, expected_hash)
701    }
702
703    fn create_subintent_instructions_v2(
704        instructions: Vec<InstructionV2>,
705    ) -> (InstructionsV2, Hash) {
706        let instructions = InstructionsV2::from(instructions);
707        let expected_hash = hash_encoded_sbor_value_body(&instructions);
708        let actual_hash = hash_from_partial_prepare(&instructions);
709        assert_eq!(expected_hash, actual_hash);
710
711        (instructions, expected_hash)
712    }
713
714    fn create_message_v2() -> (MessageV2, Hash) {
715        let message = MessageV2::Plaintext(PlaintextMessageV1::text("Hello world!"));
716        let expected_hash = hash_encoded_sbor_value_body(&message);
717
718        let actual_hash = hash_from_partial_prepare(&message);
719        assert_eq!(expected_hash, actual_hash);
720
721        (message, expected_hash)
722    }
723
724    fn create_child_subintent_specifiers_v2(
725        children: Vec<SubintentHash>,
726    ) -> (ChildSubintentSpecifiersV2, Hash) {
727        let child_subintent_specifiers: ChildSubintentSpecifiersV2 = ChildSubintentSpecifiersV2 {
728            children: children.clone().into_iter().map(|h| h.into()).collect(),
729        };
730        let expected_hash = hash_contatenated_hashes(children);
731        let actual_hash = hash_from_partial_prepare(&child_subintent_specifiers);
732        assert_eq!(expected_hash, actual_hash);
733
734        (child_subintent_specifiers, expected_hash)
735    }
736
737    /// This test demonstrates how the hashes and payloads are constructed in a valid system transaction.
738    /// A system transaction can be embedded into the node's LedgerTransaction structure, eg as part of Genesis
739    #[test]
740    #[allow(deprecated)] // Transaction V1 is allowed to use deprecated hashing
741    pub fn v1_system_transaction_structure() {
742        let instructions = vec![InstructionV1::DropAuthZoneProofs(DropAuthZoneProofs)];
743        let expected_instructions_hash = hash_encoded_sbor_value(&instructions);
744        let instructions_v1 = InstructionsV1(instructions);
745
746        let blob1: Vec<u8> = vec![0, 1, 2, 3];
747        let blob2: Vec<u8> = vec![5, 6];
748        let expected_blobs_hash =
749            hash([hash(&blob1).0.as_slice(), hash(&blob2).0.as_slice()].concat());
750
751        let blobs_v1 = BlobsV1 {
752            blobs: vec![BlobV1(blob1), BlobV1(blob2)],
753        };
754
755        let prepared_blobs_v1 = blobs_v1
756            .prepare_partial(PreparationSettings::latest_ref())
757            .unwrap();
758        assert_eq!(prepared_blobs_v1.get_summary().hash, expected_blobs_hash);
759
760        let pre_allocated_addresses_v1 = vec![PreAllocatedAddress {
761            blueprint_id: BlueprintId::new(&RESOURCE_PACKAGE, FUNGIBLE_RESOURCE_MANAGER_BLUEPRINT),
762            address: XRD.into(),
763        }];
764        let expected_preallocated_addresses_hash =
765            hash_encoded_sbor_value(&pre_allocated_addresses_v1);
766
767        let hash_for_execution = hash("Pretend genesis transaction");
768
769        let system_transaction_v1 = SystemTransactionV1 {
770            instructions: instructions_v1.clone(),
771            blobs: blobs_v1.clone(),
772            pre_allocated_addresses: pre_allocated_addresses_v1.clone(),
773            hash_for_execution,
774        };
775        let expected_system_transaction_hash = SystemTransactionHash::from_hash(hash(
776            [
777                [
778                    TRANSACTION_HASHABLE_PAYLOAD_PREFIX,
779                    TransactionDiscriminator::V1System as u8,
780                ]
781                .as_slice(),
782                expected_instructions_hash.0.as_slice(),
783                expected_blobs_hash.0.as_slice(),
784                expected_preallocated_addresses_hash.0.as_slice(),
785                hash_for_execution.0.as_slice(),
786            ]
787            .concat(),
788        ));
789
790        let raw_system_transaction = system_transaction_v1.to_raw().unwrap();
791        SystemTransactionV1::from_raw(&raw_system_transaction)
792            .expect("SystemTransaction can be decoded");
793        let system_transaction_as_versioned =
794            manifest_decode::<AnyTransaction>(raw_system_transaction.as_slice()).unwrap();
795        assert_eq!(
796            system_transaction_as_versioned,
797            AnyTransaction::SystemTransactionV1(system_transaction_v1)
798        );
799
800        let prepared_system_transaction = PreparedSystemTransactionV1::prepare(
801            &raw_system_transaction,
802            PreparationSettings::latest_ref(),
803        )
804        .unwrap();
805
806        assert_eq!(
807            expected_system_transaction_hash,
808            prepared_system_transaction.system_transaction_hash()
809        );
810        assert_eq!(
811            expected_system_transaction_hash
812                .to_string(&TransactionHashBech32Encoder::for_simulator()),
813            "systemtransaction_sim14yf4hrcuqw9y8xrje8jr7h8y3fwnsg9y6nts2f5ru6r8s3yvgguq2da744"
814        );
815        assert_eq!(
816            hex::encode(raw_system_transaction),
817            "4d22040420220112002020020704000102030702050620210102210280000d906318c6318c61e603c64c6318c6318cf7be913d63aafbc6318c6318c60c1746756e6769626c655265736f757263654d616e6167657280005da66318c6318c61f5a61b4c6318c6318cf794aa8d295f14e6318c6318c62007207646fcb3e6a2dbf0fd4830933c54928d3e8dafaf9f704afdae56336fc67aae0d"
818        );
819    }
820}