mithril_common/messages/
cardano_transactions_proof.rs

1use crate::entities::{
2    BlockNumber, CardanoTransactionsSetProof, ProtocolMessage, ProtocolMessagePartKey,
3    TransactionHash,
4};
5use crate::messages::CardanoTransactionsSetProofMessagePart;
6use crate::StdError;
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 crate::crypto_helper::MKProof;
185
186    use super::*;
187
188    #[test]
189    fn verify_malformed_proofs_fail() {
190        let txs_proofs = CardanoTransactionsProofsMessage::new(
191            "whatever",
192            vec![CardanoTransactionsSetProofMessagePart {
193                transactions_hashes: vec![],
194                proof: "invalid".to_string(),
195            }],
196            vec![],
197            BlockNumber(99999),
198        );
199
200        let error = txs_proofs
201            .verify()
202            .expect_err("Malformed txs proofs should fail to verify itself");
203        assert!(
204            matches!(
205                error,
206                VerifyCardanoTransactionsProofsError::MalformedData(_)
207            ),
208            "Expected 'MalformedData' error but got '{:?}'",
209            error
210        );
211    }
212
213    #[test]
214    fn verify_no_certified_transaction_fail() {
215        let txs_proofs =
216            CardanoTransactionsProofsMessage::new("whatever", vec![], vec![], BlockNumber(99999));
217
218        let error = txs_proofs
219            .verify()
220            .expect_err("Proofs without certified transactions should fail to verify itself");
221        assert!(
222            matches!(
223                error,
224                VerifyCardanoTransactionsProofsError::NoCertifiedTransaction
225            ),
226            "Expected 'NoCertifiedTransactions' error but got '{:?}'",
227            error
228        );
229    }
230
231    #[test]
232    fn verify_valid_proofs() {
233        let set_proof = CardanoTransactionsSetProof::dummy();
234        let expected = VerifiedCardanoTransactions {
235            certificate_hash: "whatever".to_string(),
236            merkle_root: set_proof.merkle_root(),
237            certified_transactions: set_proof.transactions_hashes().to_vec(),
238            latest_block_number: BlockNumber(99999),
239        };
240        let txs_proofs = CardanoTransactionsProofsMessage::new(
241            "whatever",
242            vec![set_proof.try_into().unwrap()],
243            vec![],
244            BlockNumber(99999),
245        );
246
247        let verified_txs = txs_proofs
248            .verify()
249            .expect("Valid txs proofs should verify itself");
250
251        assert_eq!(expected, verified_txs);
252    }
253
254    #[test]
255    fn verify_invalid_proofs() {
256        let set_proof = CardanoTransactionsSetProof::new(
257            vec!["invalid1".to_string()],
258            MKProof::from_leaves(&["invalid2"]).unwrap(),
259        );
260        let txs_proofs = CardanoTransactionsProofsMessage::new(
261            "whatever",
262            vec![set_proof.try_into().unwrap()],
263            vec![],
264            BlockNumber(99999),
265        );
266
267        let error = txs_proofs
268            .verify()
269            .expect_err("Invalid txs proofs should fail to verify itself");
270
271        assert!(
272            matches!(
273                error,
274                VerifyCardanoTransactionsProofsError::InvalidSetProof { .. },
275            ),
276            "Expected 'InvalidSetProof' error but got '{:?}'",
277            error
278        );
279    }
280
281    #[test]
282    fn verify_valid_proof_with_different_merkle_root_fail() {
283        let set_proofs = vec![
284            CardanoTransactionsSetProof::new(
285                vec!["tx-1".to_string()],
286                MKProof::from_leaves(&["tx-1"]).unwrap(),
287            ),
288            CardanoTransactionsSetProof::new(
289                vec!["tx-2".to_string()],
290                MKProof::from_leaves(&["tx-2"]).unwrap(),
291            ),
292        ];
293        let txs_proofs = CardanoTransactionsProofsMessage::new(
294            "whatever",
295            set_proofs
296                .into_iter()
297                .map(|p| p.try_into().unwrap())
298                .collect(),
299            vec![],
300            BlockNumber(99999),
301        );
302
303        let error = txs_proofs
304            .verify()
305            .expect_err("Txs proofs with non matching merkle root should fail to verify itself");
306
307        assert!(
308            matches!(
309                error,
310                VerifyCardanoTransactionsProofsError::NonMatchingMerkleRoot,
311            ),
312            "Expected 'NonMatchingMerkleRoot' error but got '{:?}'",
313            error
314        );
315    }
316
317    #[cfg(feature = "fs")]
318    mod fs_only {
319        use crate::crypto_helper::{MKMap, MKMapNode, MKTreeStoreInMemory};
320        use crate::entities::{BlockNumber, BlockRange, CardanoTransaction, SlotNumber};
321        use crate::signable_builder::{
322            CardanoTransactionsSignableBuilder, MockBlockRangeRootRetriever,
323            MockTransactionsImporter, SignableBuilder,
324        };
325        use std::sync::Arc;
326
327        use super::*;
328
329        #[tokio::test]
330        async fn verify_hashes_from_verified_cardano_transaction_and_from_signable_builder_are_equals(
331        ) {
332            let transactions = vec![
333                CardanoTransaction::new(
334                    "tx-hash-123",
335                    BlockNumber(10),
336                    SlotNumber(1),
337                    "block_hash",
338                ),
339                CardanoTransaction::new(
340                    "tx-hash-456",
341                    BlockNumber(20),
342                    SlotNumber(2),
343                    "block_hash",
344                ),
345            ];
346
347            assert_eq!(
348                from_verified_cardano_transaction(&transactions, 99999).compute_hash(),
349                from_signable_builder(&transactions, BlockNumber(99999))
350                    .await
351                    .compute_hash()
352            );
353
354            assert_ne!(
355                from_verified_cardano_transaction(&transactions, 99999).compute_hash(),
356                from_signable_builder(&transactions, BlockNumber(123456))
357                    .await
358                    .compute_hash()
359            );
360        }
361
362        fn from_verified_cardano_transaction(
363            transactions: &[CardanoTransaction],
364            block_number: u64,
365        ) -> ProtocolMessage {
366            let set_proof = CardanoTransactionsSetProof::from_leaves::<MKTreeStoreInMemory>(
367                transactions
368                    .iter()
369                    .map(|t| (t.block_number, t.transaction_hash.clone()))
370                    .collect::<Vec<_>>()
371                    .as_slice(),
372            )
373            .unwrap();
374
375            let verified_transactions_fake = VerifiedCardanoTransactions {
376                certificate_hash: "whatever".to_string(),
377                merkle_root: set_proof.merkle_root(),
378                certified_transactions: set_proof.transactions_hashes().to_vec(),
379                latest_block_number: BlockNumber(block_number),
380            };
381
382            let mut message = ProtocolMessage::new();
383            verified_transactions_fake.fill_protocol_message(&mut message);
384
385            message
386        }
387
388        async fn from_signable_builder(
389            transactions: &[CardanoTransaction],
390            block_number: BlockNumber,
391        ) -> ProtocolMessage {
392            let mut transaction_importer = MockTransactionsImporter::new();
393            transaction_importer
394                .expect_import()
395                .return_once(move |_| Ok(()));
396            let mut block_range_root_retriever = MockBlockRangeRootRetriever::new();
397
398            let transactions_imported = transactions.to_vec();
399            block_range_root_retriever
400                .expect_compute_merkle_map_from_block_range_roots()
401                .return_once(move |_| {
402                    MKMap::<
403                        BlockRange,
404                        MKMapNode<BlockRange, MKTreeStoreInMemory>,
405                        MKTreeStoreInMemory,
406                    >::new_from_iter(transactions_imported.into_iter().map(
407                        |tx| {
408                            (
409                                BlockRange::from_block_number(tx.block_number),
410                                MKMapNode::TreeNode(tx.transaction_hash.clone().into()),
411                            )
412                        },
413                    ))
414                });
415            let cardano_transaction_signable_builder = CardanoTransactionsSignableBuilder::new(
416                Arc::new(transaction_importer),
417                Arc::new(block_range_root_retriever),
418            );
419            cardano_transaction_signable_builder
420                .compute_protocol_message(block_number)
421                .await
422                .unwrap()
423        }
424    }
425}