mithril_common/messages/
certificate.rs

1use std::fmt::{Debug, Formatter};
2
3use anyhow::Context;
4use serde::{Deserialize, Serialize};
5
6use crate::StdError;
7use crate::entities::{
8    Certificate, CertificateMetadata, CertificateSignature, Epoch, ProtocolMessage,
9    SignedEntityType,
10};
11use crate::messages::CertificateMetadataMessagePart;
12#[cfg(any(test, feature = "test_tools"))]
13use crate::{entities::ProtocolMessagePartKey, test_utils::fake_keys};
14
15/// Message structure of a certificate
16#[derive(Clone, PartialEq, Serialize, Deserialize)]
17pub struct CertificateMessage {
18    /// Hash of the current certificate
19    /// Computed from the other fields of the certificate
20    /// aka H(Cp,n))
21    pub hash: String,
22
23    /// Hash of the previous certificate in the chain
24    /// This is either the hash of the first certificate of the epoch in the chain
25    /// Or the first certificate of the previous epoch in the chain (if the certificate is the first of its epoch)
26    /// aka H(FC(n))
27    pub previous_hash: String,
28
29    /// Epoch of the Cardano chain
30    pub epoch: Epoch,
31
32    /// The signed entity type of the message.
33    /// aka BEACON(p,n)
34    pub signed_entity_type: SignedEntityType,
35
36    /// Certificate metadata
37    /// aka METADATA(p,n)
38    pub metadata: CertificateMetadataMessagePart,
39
40    /// Structured message that is used to create the signed message
41    /// aka MSG(p,n) U AVK(n-1)
42    pub protocol_message: ProtocolMessage,
43
44    /// Message that is signed by the signers
45    /// aka H(MSG(p,n) || AVK(n-1))
46    pub signed_message: String,
47
48    /// Aggregate verification key
49    /// The AVK used to sign during the current epoch
50    /// aka AVK(n-2)
51    pub aggregate_verification_key: String,
52
53    /// STM multi signature created from a quorum of single signatures from the signers
54    /// aka MULTI_SIG(H(MSG(p,n) || AVK(n-1)))
55    pub multi_signature: String,
56
57    /// Genesis signature created from the original stake distribution
58    /// aka GENESIS_SIG(AVK(-1))
59    pub genesis_signature: String,
60}
61
62impl CertificateMessage {
63    cfg_test_tools! {
64        /// Return a dummy test entity (test-only).
65        pub fn dummy() -> Self {
66            let mut protocol_message = ProtocolMessage::new();
67            protocol_message.set_message_part(
68                ProtocolMessagePartKey::SnapshotDigest,
69                "snapshot-digest-123".to_string(),
70            );
71            protocol_message.set_message_part(
72                ProtocolMessagePartKey::NextAggregateVerificationKey,
73                fake_keys::aggregate_verification_key()[1].to_owned(),
74            );
75            let epoch = Epoch(10);
76
77            Self {
78                hash: "hash".to_string(),
79                previous_hash: "previous_hash".to_string(),
80                epoch,
81                signed_entity_type: SignedEntityType::MithrilStakeDistribution(epoch),
82                metadata: CertificateMetadataMessagePart::dummy(),
83                protocol_message: protocol_message.clone(),
84                signed_message: "signed_message".to_string(),
85                aggregate_verification_key: fake_keys::aggregate_verification_key()[0].to_owned(),
86                multi_signature: fake_keys::multi_signature()[0].to_owned(),
87                genesis_signature: String::new(),
88            }
89        }
90    }
91
92    /// Check that the certificate signed message match the given protocol message.
93    pub fn match_message(&self, message: &ProtocolMessage) -> bool {
94        message.compute_hash() == self.signed_message
95    }
96}
97
98impl Debug for CertificateMessage {
99    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
100        let should_be_exhaustive = f.alternate();
101        let mut debug = f.debug_struct("Certificate");
102        debug
103            .field("hash", &self.hash)
104            .field("previous_hash", &self.previous_hash)
105            .field("epoch", &format_args!("{:?}", self.epoch))
106            .field(
107                "signed_entity_type",
108                &format_args!("{:?}", self.signed_entity_type),
109            )
110            .field("metadata", &format_args!("{:?}", self.metadata))
111            .field(
112                "protocol_message",
113                &format_args!("{:?}", self.protocol_message),
114            )
115            .field("signed_message", &self.signed_message);
116
117        match should_be_exhaustive {
118            true => debug
119                .field(
120                    "aggregate_verification_key",
121                    &self.aggregate_verification_key,
122                )
123                .field("multi_signature", &self.multi_signature)
124                .field("genesis_signature", &self.genesis_signature)
125                .finish(),
126            false => debug.finish_non_exhaustive(),
127        }
128    }
129}
130
131impl TryFrom<CertificateMessage> for Certificate {
132    type Error = StdError;
133
134    fn try_from(certificate_message: CertificateMessage) -> Result<Self, Self::Error> {
135        let metadata = CertificateMetadata {
136            network: certificate_message.metadata.network,
137            protocol_version: certificate_message.metadata.protocol_version,
138            protocol_parameters: certificate_message.metadata.protocol_parameters,
139            initiated_at: certificate_message.metadata.initiated_at,
140            sealed_at: certificate_message.metadata.sealed_at,
141            signers: certificate_message.metadata.signers,
142        };
143
144        let certificate = Certificate {
145            hash: certificate_message.hash,
146            previous_hash: certificate_message.previous_hash,
147            epoch: certificate_message.epoch,
148            metadata,
149            protocol_message: certificate_message.protocol_message,
150            signed_message: certificate_message.signed_message,
151            aggregate_verification_key: certificate_message
152                .aggregate_verification_key
153                .try_into()
154                .with_context(|| {
155                "Can not convert message to certificate: can not decode the aggregate verification key"
156            })?,
157            signature: if certificate_message.genesis_signature.is_empty() {
158                CertificateSignature::MultiSignature(
159                    certificate_message.signed_entity_type,
160                    certificate_message
161                        .multi_signature
162                        .try_into()
163                        .with_context(|| {
164                            "Can not convert message to certificate: can not decode the multi-signature"
165                        })?,
166                )
167            } else {
168                CertificateSignature::GenesisSignature(
169                    certificate_message
170                        .genesis_signature
171                        .try_into()
172                        .with_context(|| {
173                            "Can not convert message to certificate: can not decode the genesis signature"
174                        })?,
175                )
176            },
177        };
178
179        Ok(certificate)
180    }
181}
182
183impl TryFrom<Certificate> for CertificateMessage {
184    type Error = StdError;
185
186    fn try_from(certificate: Certificate) -> Result<Self, Self::Error> {
187        let signed_entity_type = certificate.signed_entity_type();
188        let metadata = CertificateMetadataMessagePart {
189            network: certificate.metadata.network,
190            protocol_version: certificate.metadata.protocol_version,
191            protocol_parameters: certificate.metadata.protocol_parameters,
192            initiated_at: certificate.metadata.initiated_at,
193            sealed_at: certificate.metadata.sealed_at,
194            signers: certificate.metadata.signers,
195        };
196
197        let (multi_signature, genesis_signature) = match certificate.signature {
198            CertificateSignature::GenesisSignature(signature) => (
199                String::new(),
200                signature.to_bytes_hex().with_context(|| {
201                    "Can not convert certificate to message: can not encode the genesis signature"
202                })?,
203            ),
204            CertificateSignature::MultiSignature(_, signature) => (
205                signature.to_json_hex().with_context(|| {
206                    "Can not convert certificate to message: can not encode the multi-signature"
207                })?,
208                String::new(),
209            ),
210        };
211
212        let message = CertificateMessage {
213            hash: certificate.hash,
214            previous_hash: certificate.previous_hash,
215            epoch: certificate.epoch,
216            signed_entity_type,
217            metadata,
218            protocol_message: certificate.protocol_message,
219            signed_message: certificate.signed_message,
220            aggregate_verification_key: certificate
221                .aggregate_verification_key
222                .to_json_hex()
223                .with_context(|| {
224                    "Can not convert certificate to message: can not encode aggregate verification key"
225                })?,
226            multi_signature,
227            genesis_signature,
228        };
229
230        Ok(message)
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use chrono::{DateTime, Utc};
237
238    use crate::entities::{CardanoDbBeacon, ProtocolParameters, StakeDistributionParty};
239
240    use super::*;
241
242    fn golden_certificate_message() -> CertificateMessage {
243        CertificateMessage {
244            hash: "hash".to_string(),
245            previous_hash: "previous_hash".to_string(),
246            epoch: Epoch(10),
247            signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(
248                *Epoch(10),
249                1728,
250            )),
251            metadata: CertificateMetadataMessagePart {
252                network: "testnet".to_string(),
253                protocol_version: "0.1.0".to_string(),
254                protocol_parameters: ProtocolParameters::new(1000, 100, 0.123),
255                initiated_at: DateTime::parse_from_rfc3339("2024-02-12T13:11:47Z")
256                    .unwrap()
257                    .with_timezone(&Utc),
258                sealed_at: DateTime::parse_from_rfc3339("2024-02-12T13:12:57Z")
259                    .unwrap()
260                    .with_timezone(&Utc),
261                signers: vec![
262                    StakeDistributionParty {
263                        party_id: "1".to_string(),
264                        stake: 10,
265                    },
266                    StakeDistributionParty {
267                        party_id: "2".to_string(),
268                        stake: 20,
269                    },
270                ],
271            },
272            protocol_message: {
273                let mut protocol_message = ProtocolMessage::new();
274                protocol_message.set_message_part(
275                    ProtocolMessagePartKey::SnapshotDigest,
276                    "snapshot-digest-123".to_string(),
277                );
278                protocol_message.set_message_part(
279                    ProtocolMessagePartKey::NextAggregateVerificationKey,
280                    "next-avk-123".to_string(),
281                );
282
283                protocol_message
284            },
285            signed_message: "signed_message".to_string(),
286            aggregate_verification_key: "aggregate_verification_key".to_string(),
287            multi_signature: "multi_signature".to_string(),
288            genesis_signature: "genesis_signature".to_string(),
289        }
290    }
291
292    mod golden_json_serialization {
293
294        use super::*;
295
296        const CURRENT_JSON: &str = r#"{
297            "hash": "hash",
298            "previous_hash": "previous_hash",
299            "epoch": 10,
300            "signed_entity_type": {
301                "CardanoImmutableFilesFull": {
302                    "epoch": 10,
303                    "immutable_file_number": 1728
304                }
305            },
306            "metadata": {
307                "network": "testnet",
308                "version": "0.1.0",
309                "parameters": {
310                    "k": 1000,
311                    "m": 100,
312                    "phi_f": 0.123
313                },
314            "initiated_at": "2024-02-12T13:11:47Z",
315            "sealed_at": "2024-02-12T13:12:57Z",
316                "signers": [
317                    {
318                        "party_id": "1",
319                        "verification_key": "7b22766b223a5b3134332c3136312c3235352c34382c37382c35372c3230342c3232302c32352c3232312c3136342c3235322c3234382c31342c35362c3132362c3138362c3133352c3232382c3138382c3134352c3138312c35322c3230302c39372c39392c3231332c34362c302c3139392c3139332c38392c3138372c38382c32392c3133352c3137332c3234342c38362c33362c38332c35342c36372c3136342c362c3133372c39342c37322c362c3130352c3132382c3132382c39332c34382c3137362c31312c342c3234362c3133382c34382c3138302c3133332c39302c3134322c3139322c32342c3139332c3131312c3134322c33312c37362c3131312c3131302c3233342c3135332c39302c3230382c3139322c33312c3132342c39352c3130322c34392c3135382c39392c35322c3232302c3136352c39342c3235312c36382c36392c3132312c31362c3232342c3139345d2c22706f70223a5b3136382c35302c3233332c3139332c31352c3133362c36352c37322c3132332c3134382c3132392c3137362c33382c3139382c3230392c34372c32382c3230342c3137362c3134342c35372c3235312c34322c32382c36362c37362c38392c39372c3135382c36332c35342c3139382c3139342c3137362c3133352c3232312c31342c3138352c3139372c3232352c3230322c39382c3234332c37342c3233332c3232352c3134332c3135312c3134372c3137372c3137302c3131372c36362c3136352c36362c36322c33332c3231362c3233322c37352c36382c3131342c3139352c32322c3130302c36352c34342c3139382c342c3136362c3130322c3233332c3235332c3234302c35392c3137352c36302c3131372c3134322c3131342c3134302c3132322c31372c38372c3131302c3138372c312c31372c31302c3139352c3135342c31332c3234392c38362c35342c3232365d7d",
320                        "stake": 10
321                    },
322                    {
323                        "party_id": "2",
324                        "verification_key": "7b22766b223a5b3134352c35362c3137352c33322c3132322c3138372c3231342c3232362c3235312c3134382c38382c392c312c3130332c3135392c3134362c38302c3136362c3130372c3234332c3235312c3233362c34312c32382c3131312c3132382c3230372c3136342c3133322c3134372c3232382c38332c3234362c3232382c3137302c36382c38392c37382c36302c32382c3132332c3133302c38382c3233342c33382c39372c34322c36352c312c3130302c35332c31382c37382c3133312c382c36312c3132322c3133312c3233382c38342c3233332c3232332c3135342c3131382c3131382c37332c32382c32372c3130312c37382c38302c3233332c3132332c3230362c3232302c3137342c3133342c3230352c37312c3131302c3131322c3138302c39372c39382c302c3131332c36392c3134352c3233312c3136382c34332c3137332c3137322c35362c3130342c3230385d2c22706f70223a5b3133372c3231342c37352c37352c3134342c3136312c3133372c37392c39342c3134302c3138312c34372c33312c38312c3231332c33312c3137312c3231362c32342c3137342c37382c3234382c3133302c37352c3235352c31312c3134352c3132342c36312c38302c3139302c32372c3231362c3130352c3130362c3234382c39312c3134332c3230342c3130322c3230332c3136322c37362c3130372c31352c35322c36312c38322c3134362c3133302c3132342c37342c382c33342c3136342c3138372c3230332c38322c36342c3130382c3139312c3138352c3138382c37372c3132322c352c3234362c3235352c3130322c3131392c3234372c3139392c3131372c36372c3234312c3134332c32392c3136382c36372c39342c3135312c37382c3132392c3133312c33302c3130312c3137332c31302c36392c36382c3137352c39382c33372c3233392c3139342c32395d7d",
325                        "stake": 20
326                    }
327                ]
328            },
329            "protocol_message": {
330                "message_parts": {
331                    "snapshot_digest": "snapshot-digest-123",
332                    "next_aggregate_verification_key": "next-avk-123"
333                }
334            },
335            "signed_message": "signed_message",
336            "aggregate_verification_key": "aggregate_verification_key",
337            "multi_signature": "multi_signature",
338            "genesis_signature": "genesis_signature"
339        }"#;
340
341        fn golden_current_message() -> CertificateMessage {
342            golden_certificate_message()
343        }
344
345        #[test]
346        fn test_current_json_deserialized_into_current_message() {
347            let json = CURRENT_JSON;
348            let message: CertificateMessage = serde_json::from_str(json).unwrap();
349
350            assert_eq!(golden_current_message(), message);
351        }
352    }
353
354    mod golden_protocol_key_encodings {
355        use super::*;
356
357        mod standard_certificate {
358            use super::*;
359
360            fn golden_message_with_json_hex_encoding() -> CertificateMessage {
361                CertificateMessage {
362                    aggregate_verification_key: "7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b3234312c3235352c35332c3133352c3231322c3134322c33372c3131342c3133302c3131372c3135342c3230382c34392c3134352c31362c3132382c3230392c37352c3137392c32392c35392c3136352c3134352c3235302c34372c332c3233312c3134302c3137382c35302c3231322c3131345d2c226e725f6c6561766573223a342c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a33303337393438363730323339327d".to_string(),
363                    multi_signature: "7b227369676e617475726573223a5b5b7b227369676d61223a5b3135302c3132312c3230322c3133322c33362c34392c3230342c3137332c35392c3130322c3130382c36362c32322c3230342c3130372c3235302c36352c3136372c3230302c3233372c32312c31372c37382c3233382c34332c3232372c3234382c38392c3136362c3232322c3134352c36382c33312c3134322c3231302c3232342c3139322c3233342c38362c3134372c36362c37302c3132332c33332c39382c37372c3138382c3136375d2c22696e6465786573223a5b352c31332c32322c33312c37312c37355d2c227369676e65725f696e646578223a307d2c5b5b3135302c37362c3234362c3133302c3130352c3136372c3138372c3230372c39382c3132332c3134382c3133322c3132342c3234372c33372c3133342c32332c3137322c312c3138352c3133302c3235312c3138312c38302c36382c3137342c3131362c3139302c3231372c37312c33342c33372c3134302c3139342c3234342c3138342c3136322c3136392c3137302c37322c3139312c3138372c3232392c3136362c34372c3139362c3133392c3233332c372c38372c3232352c392c3139332c37332c3138312c3233352c35342c3135322c312c3133382c34382c3130332c36392c3230392c35322c34302c31372c32312c3134372c37332c3232352c37302c392c3233342c3233362c342c37312c33382c38392c3232352c32342c3131362c392c3133302c3139352c3139362c3233312c3133312c3230332c37372c39372c3230322c36332c3132382c3132332c3230335d2c313030393439373632393034365d5d5d2c2262617463685f70726f6f66223a7b2276616c756573223a5b5b3130312c3230302c3136392c3231322c3135312c3133352c35362c35312c3232312c3138392c3138352c3230322c3232362c3132312c3138332c36382c3135372c3132352c32342c3232332c3135312c38392c3235342c32372c32332c372c3230392c32312c3136372c3234332c322c3131345d2c5b3138352c3134312c3139392c362c3131342c3134342c3235352c37312c3138302c36342c3135332c33322c37362c372c3234392c3137342c3134312c3230302c3131382c3231312c302c31392c3232352c3134392c3133372c33362c3134312c35302c3134382c38312c3137322c3139325d5d2c22696e6469636573223a5b305d2c22686173686572223a6e756c6c7d7d".to_string(),
364                    genesis_signature: "".to_string(),
365                    ..golden_certificate_message()
366                }
367            }
368
369            fn golden_message_with_bytes_hex_encoding() -> CertificateMessage {
370                CertificateMessage {
371                    aggregate_verification_key: "20f1ff3587d48e257282759ad031911080d14bb31d3ba591fa2f03e78cb232d47204fd386b8346a11b0000".to_string(),
372                    multi_signature: "00000000000000000100000000000000d8964cf68269a7bbcf627b94847cf7258617ac01b982fbb55044ae74bed94722258cc2f4b8a2a9aa48bfbbe5a62fc48be90757e109c149b5eb3698018a306745d1342811159349e14609eaec04472659e118740982c3c4e783cb4d61ca3f807bcb000000eb0abf617600000000000000060000000000000005000000000000000d0000000000000016000000000000001f0000000000000047000000000000004b9679ca842431ccad3b666c4216cc6bfa41a7c8ed15114eee2be3f859a6de91441f8ed2e0c0ea569342467b21624dbca700000000000000000000000000000002000000000000000165c8a9d497873833ddbdb9cae279b7449d7d18df9759fe1b1707d115a7f30272b98dc7067290ff47b44099204c07f9ae8dc876d30013e19589248d329451acc00000000000000000".to_string(),
373                    genesis_signature: "".to_string(),
374                    ..golden_certificate_message()
375                }
376            }
377
378            #[test]
379            fn restorations_from_json_hex_and_bytes_hex_give_same_certificate() {
380                let certificate_from_json_hex: Certificate =
381                    golden_message_with_json_hex_encoding().try_into().unwrap();
382                let certificate_from_bytes_hex: Certificate =
383                    golden_message_with_bytes_hex_encoding().try_into().unwrap();
384
385                assert_eq!(certificate_from_json_hex, certificate_from_bytes_hex);
386            }
387        }
388
389        mod genesis_certificate {
390            use super::*;
391
392            fn golden_message_with_bytes_hex_encoding() -> CertificateMessage {
393                CertificateMessage {
394                    aggregate_verification_key: "20f1ff3587d48e257282759ad031911080d14bb31d3ba591fa2f03e78cb232d47204fd386b8346a11b0000".to_string(),
395                    multi_signature: "".to_string(),
396                    genesis_signature: "c21f77fb812a8111b547c2145d765f854ca224b17e883d6483b668a8c4d095fd893efd2a2ba1d41da9f49d82bf02d8ee603791998b64436000e49184c000170b".to_string(),
397                    ..golden_certificate_message()
398                }
399            }
400
401            #[test]
402            fn restorations_from_bytes_hex_succeeds() {
403                let _certificate_from_bytes_hex: Certificate =
404                    golden_message_with_bytes_hex_encoding().try_into().unwrap();
405            }
406        }
407    }
408}