mithril_common/entities/
cardano_transactions_set_proof.rs

1use crate::crypto_helper::{MKMapProof, ProtocolMkProof};
2use crate::entities::TransactionHash;
3use crate::StdResult;
4
5use super::BlockRange;
6
7cfg_test_tools! {
8    use crate::crypto_helper::{MKMap, MKTree, MKTreeNode, MKMapNode, MKTreeStorer, MKTreeStoreInMemory};
9    use crate::entities::BlockNumber;
10    use std::collections::HashMap;
11}
12
13/// A cryptographic proof of a set of Cardano transactions is included in the global Cardano transactions set
14#[derive(Clone, Debug, PartialEq)]
15pub struct CardanoTransactionsSetProof {
16    /// Hashes of the certified transactions
17    pub(crate) transactions_hashes: Vec<TransactionHash>,
18
19    /// Proof of the transactions
20    pub(crate) transactions_proof: ProtocolMkProof,
21}
22
23impl CardanoTransactionsSetProof {
24    /// CardanoTransactionsSetProof factory
25    pub fn new<T: Into<MKMapProof<BlockRange>>>(
26        transactions_hashes: Vec<TransactionHash>,
27        transactions_proof: T,
28    ) -> Self {
29        Self {
30            transactions_hashes,
31            transactions_proof: ProtocolMkProof::new(transactions_proof.into()),
32        }
33    }
34
35    /// Return the hex encoded merkle root of this proof
36    pub fn merkle_root(&self) -> String {
37        self.transactions_proof.compute_root().to_hex()
38    }
39
40    /// Get the hashes of the transactions certified by this proof
41    pub fn transactions_hashes(&self) -> &[TransactionHash] {
42        &self.transactions_hashes
43    }
44
45    /// Verify that transactions set proof is valid
46    pub fn verify(&self) -> StdResult<()> {
47        self.transactions_proof.verify()?;
48        for hash in &self.transactions_hashes {
49            self.transactions_proof.contains(&hash.to_owned().into())?;
50        }
51
52        Ok(())
53    }
54
55    cfg_test_tools! {
56        /// Retrieve a dummy proof (for test only)
57        pub fn dummy() -> Self {
58            let leaves = vec![
59                (BlockNumber(0), "tx-1".to_string()),
60                (BlockNumber(1), "tx-2".to_string()),
61                (BlockNumber(1), "tx-3".to_string()),
62                (BlockNumber(10), "tx-4".to_string()),
63                (BlockNumber(20), "tx-5".to_string()),
64                (BlockNumber(22), "tx-6".to_string()),
65            ];
66
67            Self::from_leaves::<MKTreeStoreInMemory>(&leaves).unwrap()
68        }
69
70        /// Helper to create a proof from a list of leaves
71        pub fn from_leaves<S: MKTreeStorer>(leaves: &[(BlockNumber, TransactionHash)]) -> StdResult<Self> {
72            let transactions_hashes: Vec<TransactionHash> =
73                leaves.iter().map(|(_, t)| t.into()).collect();
74            let mut transactions_by_block_ranges: HashMap<BlockRange, Vec<TransactionHash>> =
75                HashMap::new();
76            for (block_number, transaction_hash) in leaves {
77                let block_range = BlockRange::from_block_number(*block_number);
78                transactions_by_block_ranges
79                    .entry(block_range)
80                    .or_default()
81                    .push(transaction_hash.to_owned());
82            }
83            let mk_map = MKMap::<_, _, MKTreeStoreInMemory>::new(
84                transactions_by_block_ranges
85                    .into_iter()
86                    .try_fold(
87                        vec![],
88                        |mut acc, (block_range, transactions)| -> StdResult<Vec<(_, MKMapNode<_,S>)>> {
89                            acc.push((block_range, MKTree::<S>::new(&transactions)?.into()));
90                            Ok(acc)
91                        },
92                    )?
93                    .as_slice(),
94            )?;
95            let mk_leaves: Vec<MKTreeNode> = transactions_hashes
96                .iter()
97                .map(|h| h.to_owned().into())
98                .collect();
99            let mk_proof = mk_map.compute_proof(&mk_leaves)?;
100            Ok(Self::new(transactions_hashes, mk_proof))
101        }
102
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn should_verify_where_all_hashes_are_contained_in_the_proof() {
112        let leaves = vec![
113            (BlockNumber(0), "tx-1".to_string()),
114            (BlockNumber(1), "tx-2".to_string()),
115            (BlockNumber(1), "tx-3".to_string()),
116            (BlockNumber(10), "tx-4".to_string()),
117            (BlockNumber(20), "tx-5".to_string()),
118            (BlockNumber(22), "tx-6".to_string()),
119        ];
120        let proof =
121            CardanoTransactionsSetProof::from_leaves::<MKTreeStoreInMemory>(&leaves).unwrap();
122
123        proof.verify().expect("The proof should be valid");
124    }
125
126    #[test]
127    fn shouldnt_verify_where_at_least_one_hash_is_not_contained_in_the_proof() {
128        let leaves = vec![
129            (BlockNumber(0), "tx-1".to_string()),
130            (BlockNumber(1), "tx-2".to_string()),
131            (BlockNumber(1), "tx-3".to_string()),
132            (BlockNumber(10), "tx-4".to_string()),
133            (BlockNumber(20), "tx-5".to_string()),
134            (BlockNumber(22), "tx-6".to_string()),
135        ];
136        let proof =
137            CardanoTransactionsSetProof::from_leaves::<MKTreeStoreInMemory>(&leaves).unwrap();
138        let mut transactions_hashes_tampered = proof.transactions_hashes().to_vec();
139        transactions_hashes_tampered.push("tx-123".to_string());
140        let proof = CardanoTransactionsSetProof {
141            transactions_hashes: transactions_hashes_tampered,
142            ..proof
143        };
144
145        proof.verify().expect_err("The proof should be invalid");
146    }
147}