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