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