mithril_common/messages/
cardano_transactions_proof.rs

1use crate::StdError;
2use crate::entities::{
3    BlockNumber, CardanoTransactionsSetProof, ProtocolMessage, ProtocolMessagePartKey,
4    TransactionHash,
5};
6use crate::messages::CardanoTransactionsSetProofMessagePart;
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10#[cfg(target_family = "wasm")]
11use wasm_bindgen::prelude::*;
12
13/// A cryptographic proof for a set of Cardano transactions
14#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
15#[cfg_attr(
16    target_family = "wasm",
17    wasm_bindgen(getter_with_clone, js_name = "CardanoTransactionsProofs")
18)]
19pub struct CardanoTransactionsProofsMessage {
20    /// Hash of the certificate that validate this proof merkle root
21    pub certificate_hash: String,
22
23    /// Transactions that have been certified
24    pub certified_transactions: Vec<CardanoTransactionsSetProofMessagePart>,
25
26    /// Transactions that could not be certified
27    pub non_certified_transactions: Vec<TransactionHash>,
28
29    /// Latest block number that has been certified
30    pub latest_block_number: BlockNumber,
31}
32
33#[cfg_attr(
34    target_family = "wasm",
35    wasm_bindgen(js_class = "CardanoTransactionsProofs")
36)]
37impl CardanoTransactionsProofsMessage {
38    /// Transactions that have been certified
39    #[cfg_attr(target_family = "wasm", wasm_bindgen(getter))]
40    pub fn transactions_hashes(&self) -> Vec<TransactionHash> {
41        self.certified_transactions
42            .iter()
43            .flat_map(|ct| ct.transactions_hashes.clone())
44            .collect::<Vec<_>>()
45    }
46}
47
48/// Set of transactions verified by [CardanoTransactionsProofsMessage::verify].
49///
50/// Can be used to reconstruct part of a [ProtocolMessage] in order to check that
51/// it is indeed signed by a certificate.
52#[derive(Debug, Clone, PartialEq)]
53pub struct VerifiedCardanoTransactions {
54    certificate_hash: String,
55    merkle_root: String,
56    certified_transactions: Vec<TransactionHash>,
57    latest_block_number: BlockNumber,
58}
59
60impl VerifiedCardanoTransactions {
61    /// Hash of the certificate that signs this struct Merkle root.
62    pub fn certificate_hash(&self) -> &str {
63        &self.certificate_hash
64    }
65
66    /// Hashes of the certified transactions
67    pub fn certified_transactions(&self) -> &[TransactionHash] {
68        &self.certified_transactions
69    }
70
71    /// Fill the given [ProtocolMessage] with the data associated with this
72    /// verified transactions set.
73    pub fn fill_protocol_message(&self, message: &mut ProtocolMessage) {
74        message.set_message_part(
75            ProtocolMessagePartKey::CardanoTransactionsMerkleRoot,
76            self.merkle_root.clone(),
77        );
78
79        message.set_message_part(
80            ProtocolMessagePartKey::LatestBlockNumber,
81            self.latest_block_number.to_string(),
82        );
83    }
84}
85
86/// Error encountered or produced by the [cardano transaction proof verification][CardanoTransactionsProofsMessage::verify].
87#[derive(Error, Debug)]
88pub enum VerifyCardanoTransactionsProofsError {
89    /// The verification of an individual [CardanoTransactionsSetProofMessagePart] failed.
90    #[error("Invalid set proof for transactions hashes: {transactions_hashes:?}")]
91    InvalidSetProof {
92        /// Hashes of the invalid transactions
93        transactions_hashes: Vec<TransactionHash>,
94        /// Error source
95        source: StdError,
96    },
97
98    /// No certified transactions set proof to verify
99    #[error("There's no certified transaction to verify")]
100    NoCertifiedTransaction,
101
102    /// Not all certified transactions set proof have the same merkle root.
103    ///
104    /// This is problematic because all the set proof should be generated from the same
105    /// merkle tree which root is signed in the [certificate][crate::entities::Certificate].
106    #[error("All certified transactions set proofs must share the same Merkle root")]
107    NonMatchingMerkleRoot,
108
109    /// An individual [CardanoTransactionsSetProofMessagePart] could not be converted to a
110    /// [CardanoTransactionsProofsMessage] for verification.
111    #[error("Malformed data or unknown Cardano Set Proof format")]
112    MalformedData(#[source] StdError),
113}
114
115impl CardanoTransactionsProofsMessage {
116    /// Create a new `CardanoTransactionsProofsMessage`
117    pub fn new(
118        certificate_hash: &str,
119        certified_transactions: Vec<CardanoTransactionsSetProofMessagePart>,
120        non_certified_transactions: Vec<TransactionHash>,
121        latest_block_number: BlockNumber,
122    ) -> Self {
123        Self {
124            certificate_hash: certificate_hash.to_string(),
125            certified_transactions,
126            non_certified_transactions,
127            latest_block_number,
128        }
129    }
130
131    /// Verify that all the certified transactions proofs are valid
132    ///
133    /// The following checks will be executed:
134    ///
135    /// 1 - Check that each Merkle proof is valid
136    ///
137    /// 2 - Check that all proofs share the same Merkle root
138    ///
139    /// 3 - Assert that there's at least one certified transaction
140    ///
141    /// If every check is okay, the hex encoded Merkle root of the proof will be returned.
142    pub fn verify(
143        &self,
144    ) -> Result<VerifiedCardanoTransactions, VerifyCardanoTransactionsProofsError> {
145        let mut merkle_root = None;
146
147        for certified_transaction in &self.certified_transactions {
148            let certified_transaction: CardanoTransactionsSetProof = certified_transaction
149                .clone()
150                .try_into()
151                .map_err(VerifyCardanoTransactionsProofsError::MalformedData)?;
152            certified_transaction.verify().map_err(|e| {
153                VerifyCardanoTransactionsProofsError::InvalidSetProof {
154                    transactions_hashes: certified_transaction.transactions_hashes().to_vec(),
155                    source: e,
156                }
157            })?;
158
159            let tx_merkle_root = Some(certified_transaction.merkle_root());
160
161            if merkle_root.is_none() {
162                merkle_root = tx_merkle_root;
163            } else if merkle_root != tx_merkle_root {
164                return Err(VerifyCardanoTransactionsProofsError::NonMatchingMerkleRoot);
165            }
166        }
167
168        Ok(VerifiedCardanoTransactions {
169            certificate_hash: self.certificate_hash.clone(),
170            merkle_root: merkle_root
171                .ok_or(VerifyCardanoTransactionsProofsError::NoCertifiedTransaction)?,
172            certified_transactions: self
173                .certified_transactions
174                .iter()
175                .flat_map(|c| c.transactions_hashes.clone())
176                .collect(),
177            latest_block_number: self.latest_block_number,
178        })
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use std::sync::Arc;
185
186    use crate::crypto_helper::{MKMap, MKMapNode, MKProof, MKTreeStoreInMemory};
187    use crate::entities::{BlockNumber, BlockRange, CardanoTransaction, SlotNumber};
188    use crate::signable_builder::{
189        CardanoTransactionsSignableBuilder, MockBlockRangeRootRetriever, MockTransactionsImporter,
190        SignableBuilder,
191    };
192    use crate::test::crypto_helper::MKProofTestExtension;
193    use crate::test::double::Dummy;
194    use crate::test::entities_extensions::CardanoTransactionsSetProofTestExtension;
195
196    use super::*;
197
198    #[test]
199    fn verify_malformed_proofs_fail() {
200        let txs_proofs = CardanoTransactionsProofsMessage::new(
201            "whatever",
202            vec![CardanoTransactionsSetProofMessagePart {
203                transactions_hashes: vec![],
204                proof: "invalid".to_string(),
205            }],
206            vec![],
207            BlockNumber(99999),
208        );
209
210        let error = txs_proofs
211            .verify()
212            .expect_err("Malformed txs proofs should fail to verify itself");
213        assert!(
214            matches!(
215                error,
216                VerifyCardanoTransactionsProofsError::MalformedData(_)
217            ),
218            "Expected 'MalformedData' error but got '{error:?}'"
219        );
220    }
221
222    #[test]
223    fn verify_no_certified_transaction_fail() {
224        let txs_proofs =
225            CardanoTransactionsProofsMessage::new("whatever", vec![], vec![], BlockNumber(99999));
226
227        let error = txs_proofs
228            .verify()
229            .expect_err("Proofs without certified transactions should fail to verify itself");
230        assert!(
231            matches!(
232                error,
233                VerifyCardanoTransactionsProofsError::NoCertifiedTransaction
234            ),
235            "Expected 'NoCertifiedTransactions' error but got '{error:?}'"
236        );
237    }
238
239    #[test]
240    fn verify_valid_proofs() {
241        let set_proof = CardanoTransactionsSetProof::dummy();
242        let expected = VerifiedCardanoTransactions {
243            certificate_hash: "whatever".to_string(),
244            merkle_root: set_proof.merkle_root(),
245            certified_transactions: set_proof.transactions_hashes().to_vec(),
246            latest_block_number: BlockNumber(99999),
247        };
248        let txs_proofs = CardanoTransactionsProofsMessage::new(
249            "whatever",
250            vec![set_proof.try_into().unwrap()],
251            vec![],
252            BlockNumber(99999),
253        );
254
255        let verified_txs = txs_proofs.verify().expect("Valid txs proofs should verify itself");
256
257        assert_eq!(expected, verified_txs);
258    }
259
260    #[test]
261    fn verify_invalid_proofs() {
262        let set_proof = CardanoTransactionsSetProof::new(
263            vec!["invalid1".to_string()],
264            MKProof::from_leaves(&["invalid2"]).unwrap(),
265        );
266        let txs_proofs = CardanoTransactionsProofsMessage::new(
267            "whatever",
268            vec![set_proof.try_into().unwrap()],
269            vec![],
270            BlockNumber(99999),
271        );
272
273        let error = txs_proofs
274            .verify()
275            .expect_err("Invalid txs proofs should fail to verify itself");
276
277        assert!(
278            matches!(
279                error,
280                VerifyCardanoTransactionsProofsError::InvalidSetProof { .. },
281            ),
282            "Expected 'InvalidSetProof' error but got '{error:?}'"
283        );
284    }
285
286    #[test]
287    fn verify_valid_proof_with_different_merkle_root_fail() {
288        let set_proofs = vec![
289            CardanoTransactionsSetProof::new(
290                vec!["tx-1".to_string()],
291                MKProof::from_leaves(&["tx-1"]).unwrap(),
292            ),
293            CardanoTransactionsSetProof::new(
294                vec!["tx-2".to_string()],
295                MKProof::from_leaves(&["tx-2"]).unwrap(),
296            ),
297        ];
298        let txs_proofs = CardanoTransactionsProofsMessage::new(
299            "whatever",
300            set_proofs.into_iter().map(|p| p.try_into().unwrap()).collect(),
301            vec![],
302            BlockNumber(99999),
303        );
304
305        let error = txs_proofs
306            .verify()
307            .expect_err("Txs proofs with non matching merkle root should fail to verify itself");
308
309        assert!(
310            matches!(
311                error,
312                VerifyCardanoTransactionsProofsError::NonMatchingMerkleRoot,
313            ),
314            "Expected 'NonMatchingMerkleRoot' error but got '{error:?}'"
315        );
316    }
317
318    #[tokio::test]
319    async fn verify_hashes_from_verified_cardano_transaction_and_from_signable_builder_are_equals()
320    {
321        let transactions = vec![
322            CardanoTransaction::new("tx-hash-123", BlockNumber(10), SlotNumber(1), "block_hash"),
323            CardanoTransaction::new("tx-hash-456", BlockNumber(20), SlotNumber(2), "block_hash"),
324        ];
325
326        assert_eq!(
327            from_verified_cardano_transaction(&transactions, 99999).compute_hash(),
328            from_signable_builder(&transactions, BlockNumber(99999))
329                .await
330                .compute_hash()
331        );
332
333        assert_ne!(
334            from_verified_cardano_transaction(&transactions, 99999).compute_hash(),
335            from_signable_builder(&transactions, BlockNumber(123456))
336                .await
337                .compute_hash()
338        );
339    }
340
341    fn from_verified_cardano_transaction(
342        transactions: &[CardanoTransaction],
343        block_number: u64,
344    ) -> ProtocolMessage {
345        let set_proof = CardanoTransactionsSetProof::from_leaves::<MKTreeStoreInMemory>(
346            transactions
347                .iter()
348                .map(|t| (t.block_number, t.transaction_hash.clone()))
349                .collect::<Vec<_>>()
350                .as_slice(),
351        )
352        .unwrap();
353
354        let verified_transactions_fake = VerifiedCardanoTransactions {
355            certificate_hash: "whatever".to_string(),
356            merkle_root: set_proof.merkle_root(),
357            certified_transactions: set_proof.transactions_hashes().to_vec(),
358            latest_block_number: BlockNumber(block_number),
359        };
360
361        let mut message = ProtocolMessage::new();
362        verified_transactions_fake.fill_protocol_message(&mut message);
363
364        message
365    }
366
367    async fn from_signable_builder(
368        transactions: &[CardanoTransaction],
369        block_number: BlockNumber,
370    ) -> ProtocolMessage {
371        let mut transaction_importer = MockTransactionsImporter::new();
372        transaction_importer.expect_import().return_once(move |_| Ok(()));
373        let mut block_range_root_retriever = MockBlockRangeRootRetriever::new();
374
375        let transactions_imported = transactions.to_vec();
376        block_range_root_retriever
377            .expect_compute_merkle_map_from_block_range_roots()
378            .return_once(move |_| {
379                MKMap::<
380                        BlockRange,
381                        MKMapNode<BlockRange, MKTreeStoreInMemory>,
382                        MKTreeStoreInMemory,
383                    >::new_from_iter(transactions_imported.into_iter().map(
384                        |tx| {
385                            (
386                                BlockRange::from_block_number(tx.block_number),
387                                MKMapNode::TreeNode(tx.transaction_hash.clone().into()),
388                            )
389                        },
390                    ))
391            });
392        let cardano_transaction_signable_builder = CardanoTransactionsSignableBuilder::new(
393            Arc::new(transaction_importer),
394            Arc::new(block_range_root_retriever),
395        );
396        cardano_transaction_signable_builder
397            .compute_protocol_message(block_number)
398            .await
399            .unwrap()
400    }
401}