pallas_traverse/
hashes.rs

1use crate::{ComputeHash, OriginalHash};
2use pallas_codec::utils::KeepRaw;
3use pallas_crypto::{
4    hash::{Hash, Hasher},
5    key::ed25519::PublicKey,
6};
7use pallas_primitives::{alonzo, babbage, byron, conway};
8
9impl ComputeHash<32> for byron::EbbHead {
10    fn compute_hash(&self) -> Hash<32> {
11        // hash expects to have a prefix for the type of block
12        Hasher::<256>::hash_cbor(&(0, self))
13    }
14}
15
16impl OriginalHash<32> for KeepRaw<'_, byron::EbbHead> {
17    fn original_hash(&self) -> Hash<32> {
18        // hash expects to have a prefix for the type of block
19        Hasher::<256>::hash_cbor(&(0, self))
20    }
21}
22
23impl ComputeHash<32> for byron::BlockHead {
24    fn compute_hash(&self) -> Hash<32> {
25        // hash expects to have a prefix for the type of block
26        Hasher::<256>::hash_cbor(&(1, self))
27    }
28}
29
30impl OriginalHash<32> for KeepRaw<'_, byron::BlockHead> {
31    fn original_hash(&self) -> Hash<32> {
32        // hash expects to have a prefix for the type of block
33        Hasher::<256>::hash_cbor(&(1, self))
34    }
35}
36
37impl ComputeHash<32> for byron::Tx {
38    fn compute_hash(&self) -> Hash<32> {
39        Hasher::<256>::hash_cbor(self)
40    }
41}
42
43impl OriginalHash<32> for KeepRaw<'_, byron::Tx> {
44    fn original_hash(&self) -> Hash<32> {
45        Hasher::<256>::hash(self.raw_cbor())
46    }
47}
48
49impl ComputeHash<32> for alonzo::Header {
50    fn compute_hash(&self) -> pallas_crypto::hash::Hash<32> {
51        Hasher::<256>::hash_cbor(self)
52    }
53}
54
55impl OriginalHash<32> for KeepRaw<'_, alonzo::MintedHeader<'_>> {
56    fn original_hash(&self) -> pallas_crypto::hash::Hash<32> {
57        Hasher::<256>::hash(self.raw_cbor())
58    }
59}
60
61impl OriginalHash<32> for KeepRaw<'_, babbage::MintedHeader<'_>> {
62    fn original_hash(&self) -> pallas_crypto::hash::Hash<32> {
63        Hasher::<256>::hash(self.raw_cbor())
64    }
65}
66
67impl ComputeHash<32> for alonzo::AuxiliaryData {
68    fn compute_hash(&self) -> pallas_crypto::hash::Hash<32> {
69        Hasher::<256>::hash_cbor(self)
70    }
71}
72
73impl ComputeHash<28> for alonzo::NativeScript {
74    fn compute_hash(&self) -> Hash<28> {
75        Hasher::<224>::hash_tagged_cbor(self, 0)
76    }
77}
78
79impl OriginalHash<28> for KeepRaw<'_, alonzo::NativeScript> {
80    fn original_hash(&self) -> Hash<28> {
81        Hasher::<224>::hash_tagged(self.raw_cbor(), 0)
82    }
83}
84
85impl<const VERSION: usize> ComputeHash<28> for alonzo::PlutusScript<VERSION> {
86    fn compute_hash(&self) -> Hash<28> {
87        Hasher::<224>::hash_tagged(&self.0, VERSION as u8)
88    }
89}
90
91impl ComputeHash<32> for alonzo::PlutusData {
92    fn compute_hash(&self) -> Hash<32> {
93        Hasher::<256>::hash_cbor(self)
94    }
95}
96
97impl OriginalHash<32> for KeepRaw<'_, alonzo::PlutusData> {
98    fn original_hash(&self) -> Hash<32> {
99        Hasher::<256>::hash(self.raw_cbor())
100    }
101}
102
103impl ComputeHash<32> for alonzo::TransactionBody {
104    fn compute_hash(&self) -> Hash<32> {
105        Hasher::<256>::hash_cbor(self)
106    }
107}
108
109impl OriginalHash<32> for KeepRaw<'_, alonzo::TransactionBody> {
110    fn original_hash(&self) -> pallas_crypto::hash::Hash<32> {
111        Hasher::<256>::hash(self.raw_cbor())
112    }
113}
114
115impl ComputeHash<32> for babbage::Header {
116    fn compute_hash(&self) -> pallas_crypto::hash::Hash<32> {
117        Hasher::<256>::hash_cbor(self)
118    }
119}
120
121impl OriginalHash<32> for KeepRaw<'_, babbage::Header> {
122    fn original_hash(&self) -> pallas_crypto::hash::Hash<32> {
123        Hasher::<256>::hash(self.raw_cbor())
124    }
125}
126
127impl ComputeHash<32> for babbage::TransactionBody {
128    fn compute_hash(&self) -> Hash<32> {
129        Hasher::<256>::hash_cbor(self)
130    }
131}
132
133impl OriginalHash<32> for KeepRaw<'_, babbage::TransactionBody> {
134    fn original_hash(&self) -> pallas_crypto::hash::Hash<32> {
135        Hasher::<256>::hash(self.raw_cbor())
136    }
137}
138
139impl OriginalHash<32> for KeepRaw<'_, babbage::MintedTransactionBody<'_>> {
140    fn original_hash(&self) -> pallas_crypto::hash::Hash<32> {
141        Hasher::<256>::hash(self.raw_cbor())
142    }
143}
144
145impl ComputeHash<32> for babbage::DatumOption {
146    fn compute_hash(&self) -> Hash<32> {
147        match self {
148            babbage::DatumOption::Hash(hash) => *hash,
149            babbage::DatumOption::Data(data) => data.compute_hash(),
150        }
151    }
152}
153
154// conway
155
156impl ComputeHash<32> for conway::TransactionBody {
157    fn compute_hash(&self) -> Hash<32> {
158        Hasher::<256>::hash_cbor(self)
159    }
160}
161
162impl OriginalHash<32> for KeepRaw<'_, conway::TransactionBody> {
163    fn original_hash(&self) -> pallas_crypto::hash::Hash<32> {
164        Hasher::<256>::hash(self.raw_cbor())
165    }
166}
167
168impl OriginalHash<32> for KeepRaw<'_, conway::MintedTransactionBody<'_>> {
169    fn original_hash(&self) -> pallas_crypto::hash::Hash<32> {
170        Hasher::<256>::hash(self.raw_cbor())
171    }
172}
173
174impl ComputeHash<28> for PublicKey {
175    fn compute_hash(&self) -> Hash<28> {
176        Hasher::<224>::hash(&Into::<[u8; PublicKey::SIZE]>::into(*self))
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use crate::{Era, MultiEraTx};
183
184    use super::{ComputeHash, OriginalHash};
185    use pallas_codec::utils::{Int, MaybeIndefArray};
186    use pallas_codec::{minicbor, utils::Bytes};
187    use pallas_crypto::hash::Hash;
188    use pallas_crypto::key::ed25519::PublicKey;
189    use pallas_primitives::babbage::MintedDatumOption;
190    use pallas_primitives::{alonzo, babbage, byron};
191    use std::str::FromStr;
192
193    #[test]
194    fn byron_transaction_hash_works() {
195        type BlockWrapper<'b> = (u16, byron::MintedBlock<'b>);
196
197        // TODO: expand this test to include more test blocks
198        let block_str = include_str!("../../test_data/byron1.block");
199
200        let block_bytes = hex::decode(block_str).expect("bad block file");
201        let (_, block_model): BlockWrapper =
202            minicbor::decode(&block_bytes[..]).expect("error decoding cbor for file");
203
204        let computed_hash = block_model.header.original_hash();
205
206        assert_eq!(
207            hex::encode(computed_hash),
208            "5c196e7394ace0449ba5a51c919369699b13896e97432894b4f0354dce8670b6"
209        )
210    }
211
212    #[test]
213    fn alonzo_transaction_hash_works() {
214        type BlockWrapper<'b> = (u16, alonzo::MintedBlock<'b>);
215
216        // TODO: expand this test to include more test blocks
217        let block_str = include_str!("../../test_data/alonzo1.block");
218
219        let block_bytes = hex::decode(block_str).expect("bad block file");
220        let (_, block_model): BlockWrapper =
221            minicbor::decode(&block_bytes[..]).expect("error decoding cbor for file");
222
223        let valid_hashes = [
224            "8ae0cd531635579a9b52b954a840782d12235251fb1451e5c699e864c677514a",
225            "bb5bb4e1c09c02aa199c60e9f330102912e3ef977bb73ecfd8f790945c6091d4",
226            "8cdd88042ddb6c800714fb1469fb1a1a93152aae3c87a81f2a3016f2ee5c664a",
227            "10add6bdaa7ade06466bdd768456e756709090846b58bf473f240c484db517fa",
228            "8838f5ab27894a6543255aeaec086f7b3405a6db6e7457a541409cdbbf0cd474",
229        ];
230
231        for (tx_idx, tx) in block_model.transaction_bodies.iter().enumerate() {
232            let original_hash = tx.original_hash();
233            let expected_hash = valid_hashes[tx_idx];
234            assert_eq!(hex::encode(original_hash), expected_hash)
235        }
236    }
237
238    #[test]
239    fn babbage_transaction_hash_works() {
240        type BlockWrapper<'b> = (u16, babbage::MintedBlock<'b>);
241
242        // TODO: expand this test to include more test blocks
243        let block_idx = 1;
244        let block_str = include_str!("../../test_data/babbage1.block");
245
246        let block_bytes =
247            hex::decode(block_str).unwrap_or_else(|_| panic!("bad block file {block_idx}"));
248        let (_, block_model): BlockWrapper = minicbor::decode(&block_bytes[..])
249            .unwrap_or_else(|_| panic!("error decoding cbor for file {block_idx}"));
250
251        let valid_hashes = ["3fad302595665b004971a6b76909854a39a0a7ecdbff3692f37b77ae37dbe882"];
252
253        for (tx_idx, tx) in block_model.transaction_bodies.iter().enumerate() {
254            let original_hash = tx.original_hash();
255            let expected_hash = valid_hashes[tx_idx];
256            assert_eq!(hex::encode(original_hash), expected_hash)
257        }
258    }
259
260    #[test]
261    fn native_script_hashes_as_cardano_cli() {
262        // construct an arbitrary script to use as example
263        let ns = alonzo::NativeScript::ScriptAll(vec![
264            alonzo::NativeScript::ScriptPubkey(
265                Hash::<28>::from_str("4d04380dcb9fbad5aff8e2f4e19394ef4e5e11b37932838f01984a12")
266                    .unwrap(),
267            ),
268            alonzo::NativeScript::InvalidBefore(112500819),
269        ]);
270
271        // hash that we assume correct since it was generated through the cardano-cli
272        let cardano_cli_output = "d6a8ced01ecdfbb26c90850010a06fbc20a7c23632fc92f531667f36";
273
274        assert_eq!(
275            ns.compute_hash(),
276            Hash::<28>::from_str(cardano_cli_output).unwrap()
277        )
278    }
279
280    #[test]
281    fn plutus_data_hashes_as_cardano_cli() {
282        // construct an arbitrary complex datum to use as example
283        let pd = alonzo::PlutusData::Constr(alonzo::Constr::<alonzo::PlutusData> {
284            tag: 1280,
285            any_constructor: None,
286            fields: MaybeIndefArray::Indef(vec![
287                alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(4))),
288                alonzo::PlutusData::Constr(alonzo::Constr::<alonzo::PlutusData> {
289                    tag: 124,
290                    any_constructor: None,
291                    fields: MaybeIndefArray::Indef(vec![
292                        alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(-4))),
293                        alonzo::PlutusData::Constr(alonzo::Constr::<alonzo::PlutusData> {
294                            tag: 102,
295                            any_constructor: Some(453),
296                            fields: MaybeIndefArray::Indef(vec![
297                                alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(2))),
298                                alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(3434))),
299                            ]),
300                        }),
301                        alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(-11828293))),
302                    ]),
303                }),
304                alonzo::PlutusData::BigInt(alonzo::BigInt::Int(Int::from(11828293))),
305            ]),
306        });
307
308        // if you need to try this out in the cardano-cli, uncomment this line to see
309        // the json representation of the above struct:
310        // println!("{}", crate::ToCanonicalJson::to_json(&pd));
311
312        // hash that we assume correct since it was generated through the cardano-cli
313        let cardano_cli_output = "d9bc0eb6ac664286155f70d720cafd2af16277fbd9014a930997431a2ffbe554";
314
315        assert_eq!(
316            pd.compute_hash(),
317            Hash::<32>::from_str(cardano_cli_output).unwrap()
318        )
319    }
320
321    #[test]
322    fn plutus_v1_script_hashes_as_cardano_cli() {
323        let bytecode_hex = include_str!("../../test_data/jpgstore.plutus");
324        let bytecode = hex::decode(bytecode_hex).unwrap();
325        let script = alonzo::PlutusScript::<1>(Bytes::from(bytecode));
326
327        let generated = script.compute_hash().to_string();
328
329        assert_eq!(
330            generated,
331            // this is the payment script hash from the address:
332            // addr1w999n67e86jn6xal07pzxtrmqynspgx0fwmcmpua4wc6yzsxpljz3
333            "4a59ebd93ea53d1bbf7f82232c7b012700a0cf4bb78d879dabb1a20a"
334        );
335    }
336
337    #[test]
338    fn plutus_v2_script_hashes_as_cardano_cli() {
339        let bytecode_hex = include_str!("../../test_data/v2script.plutus");
340        let bytecode = hex::decode(bytecode_hex).unwrap();
341        let script = babbage::PlutusScript::<2>(Bytes::from(bytecode));
342
343        let generated = script.compute_hash().to_string();
344
345        assert_eq!(
346            generated,
347            // script bytes and script hash from
348            // https://preview.cexplorer.io/script/2616f3e9edb51f98ef04dbaefd042b5c731e86616e8e9172c63c39be
349            "2616f3e9edb51f98ef04dbaefd042b5c731e86616e8e9172c63c39be"
350        );
351    }
352
353    #[test]
354    fn tx_wits_plutus_v1_script_hashes_as_cli() {
355        let tx_bytecode_hex = include_str!("../../test_data/scriptwit.tx");
356        let bytecode = hex::decode(tx_bytecode_hex).unwrap();
357        let tx = MultiEraTx::decode_for_era(Era::Babbage, &bytecode).unwrap();
358
359        let generated = tx
360            .plutus_v1_scripts()
361            .first()
362            .unwrap()
363            .compute_hash()
364            .to_string();
365
366        assert_eq!(
367            generated,
368            "62bdc3d04d04376d516d31664944b25ce3affa76d17f8b5e1279b49d"
369        );
370    }
371
372    #[test]
373    fn test_witness_datum_hash_respects_original_cbor() {
374        let expected = [
375            "54ad3c112d58e8946480e21d6a35b2a215d1a9a8f540c13714ded86e4b0b6aea",
376            "831a557bc2948e1b8c9f5e8e594d62299abff4eb1a11dc19da38bfaf9f2da407",
377            "923918e403bf43c34b4ef6b48eb2ee04babed17320d8d1b9ff9ad086e86f44ec",
378            "b0ea85f16a443da7f60704a427923ae1d89a7dc2d6621d805d9dd441431ed700",
379            "c695868b4bfbf4c95714e707c69da1823bcf8cfc7c4b14b92c3645d4e1943be3",
380            "ed33125018c5cbc9ae1b242a3ff8f3db2e108e4a63866d0b5238a34502c723ed",
381        ];
382
383        let tx_hex = include_str!("../../test_data/babbage1.tx");
384        let tx_bytes = hex::decode(tx_hex).unwrap();
385        let tx = MultiEraTx::decode_for_era(Era::Babbage, &tx_bytes).unwrap();
386        let data = tx.plutus_data();
387
388        for (datum, expected_hash) in data.iter().zip(expected) {
389            assert_eq!(datum.original_hash().to_string(), expected_hash);
390        }
391    }
392
393    #[test]
394    fn test_inline_datum_hash_respects_original_cbor() {
395        let expected = "7607117edd3189347a2898defbb9042e9ea3bf094466718cdaf65f7f9bfeefdb";
396
397        let tx_hex = include_str!("../../test_data/babbage2.tx");
398        let tx_bytes = hex::decode(tx_hex).unwrap();
399        let tx = MultiEraTx::decode_for_era(Era::Babbage, &tx_bytes).unwrap();
400
401        for output in tx.outputs() {
402            if let Some(MintedDatumOption::Data(datum)) = output.datum() {
403                assert_eq!(datum.original_hash().to_string(), expected);
404            }
405        }
406    }
407
408    #[test]
409    fn test_public_key_hash() {
410        let key: [u8; 32] =
411            hex::decode("2354bc4e1ae230e3a9047b568848fdd4bccd8d9aa60e6d1426baa730908e662d")
412                .unwrap()
413                .try_into()
414                .unwrap();
415        let pk = PublicKey::from(key);
416
417        assert_eq!(
418            pk.compute_hash().to_vec(),
419            hex::decode("2b6b3949d380fea6cb1c1cf88490ea40b2c1ce87717df7869cb1c38e").unwrap()
420        )
421    }
422}