Skip to main content

mithril_common/messages/
cardano_transactions_proof.rs

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