mithril_common/messages/
certificate_list.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::fmt::{Debug, Formatter};
4
5use crate::entities::{
6    Epoch, ProtocolMessage, ProtocolParameters, ProtocolVersion, SignedEntityType,
7};
8
9/// Message structure of a certificate list
10pub type CertificateListMessage = Vec<CertificateListItemMessage>;
11
12/// CertificateListItemMessage represents the metadata associated to a CertificateListItemMessage
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
14pub struct CertificateListItemMessageMetadata {
15    /// Cardano network
16    /// part of METADATA(p,n)
17    pub network: String,
18
19    /// Protocol Version (semver)
20    /// Useful to achieve backward compatibility of the certificates (including of the multi signature)
21    /// part of METADATA(p,n)
22    #[serde(rename = "version")]
23    pub protocol_version: ProtocolVersion,
24
25    /// Protocol parameters
26    /// part of METADATA(p,n)
27    #[serde(rename = "parameters")]
28    pub protocol_parameters: ProtocolParameters,
29
30    /// Date and time when the certificate was initiated
31    /// Represents the time at which the single signatures registration is opened
32    /// part of METADATA(p,n)
33    pub initiated_at: DateTime<Utc>,
34
35    /// Date and time when the certificate was sealed
36    /// Represents the time at which the quorum of single signatures was reached so that they were aggregated into a multi signature
37    /// part of METADATA(p,n)
38    pub sealed_at: DateTime<Utc>,
39
40    /// The number of signers that contributed to the certificate
41    /// part of METADATA(p,n)
42    pub total_signers: usize,
43}
44
45/// Message structure of a certificate list item
46#[derive(Clone, PartialEq, Serialize, Deserialize)]
47pub struct CertificateListItemMessage {
48    /// Hash of the current certificate
49    /// Computed from the other fields of the certificate
50    /// aka H(Cp,n))
51    pub hash: String,
52
53    /// Hash of the previous certificate in the chain
54    /// This is either the hash of the first certificate of the epoch in the chain
55    /// Or the first certificate of the previous epoch in the chain (if the certificate is the first of its epoch)
56    /// aka H(FC(n))
57    pub previous_hash: String,
58
59    /// Epoch of the Cardano chain
60    pub epoch: Epoch,
61
62    /// The signed entity type of the message.
63    /// aka BEACON(p,n)
64    pub signed_entity_type: SignedEntityType,
65
66    /// Certificate metadata
67    /// aka METADATA(p,n)
68    pub metadata: CertificateListItemMessageMetadata,
69
70    /// Structured message that is used to create the signed message
71    /// aka MSG(p,n) U AVK(n-1)
72    pub protocol_message: ProtocolMessage,
73
74    /// Message that is signed by the signers
75    /// aka H(MSG(p,n) || AVK(n-1))
76    pub signed_message: String,
77
78    /// Aggregate verification key
79    /// The AVK used to sign during the current epoch
80    /// aka AVK(n-2)
81    pub aggregate_verification_key: String,
82}
83
84impl Debug for CertificateListItemMessage {
85    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
86        let should_be_exhaustive = f.alternate();
87        let mut debug = f.debug_struct("Certificate");
88        debug
89            .field("hash", &self.hash)
90            .field("previous_hash", &self.previous_hash)
91            .field("epoch", &format_args!("{:?}", self.epoch))
92            .field(
93                "signed_entity_type",
94                &format_args!("{:?}", self.signed_entity_type),
95            )
96            .field("metadata", &format_args!("{:?}", self.metadata))
97            .field(
98                "protocol_message",
99                &format_args!("{:?}", self.protocol_message),
100            )
101            .field("signed_message", &self.signed_message);
102
103        match should_be_exhaustive {
104            true => debug
105                .field(
106                    "aggregate_verification_key",
107                    &self.aggregate_verification_key,
108                )
109                .finish(),
110            false => debug.finish_non_exhaustive(),
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use crate::entities::{CardanoDbBeacon, ProtocolMessagePartKey};
118
119    use super::*;
120
121    const CURRENT_JSON: &str = r#"[{
122            "hash": "hash",
123            "previous_hash": "previous_hash",
124            "epoch": 10,
125            "signed_entity_type": {
126                "CardanoImmutableFilesFull": {
127                    "epoch": 10,
128                    "immutable_file_number": 1728
129                }
130            },
131            "metadata": {
132                "network": "testnet",
133                "version": "0.1.0",
134                "parameters": {
135                    "k": 1000,
136                    "m": 100,
137                    "phi_f": 0.123
138                },
139                "initiated_at": "2024-02-12T13:11:47Z",
140                "sealed_at": "2024-02-12T13:12:57Z",
141                "total_signers": 2
142            },
143            "protocol_message": {
144                "message_parts": {
145                    "snapshot_digest": "snapshot-digest-123",
146                    "next_aggregate_verification_key": "next-avk-123"
147                }
148            },
149            "signed_message": "signed_message",
150            "aggregate_verification_key": "aggregate_verification_key"
151        }]"#;
152
153    fn golden_current_message() -> CertificateListItemMessage {
154        let mut protocol_message = ProtocolMessage::new();
155        protocol_message.set_message_part(
156            ProtocolMessagePartKey::SnapshotDigest,
157            "snapshot-digest-123".to_string(),
158        );
159        protocol_message.set_message_part(
160            ProtocolMessagePartKey::NextAggregateVerificationKey,
161            "next-avk-123".to_string(),
162        );
163        let epoch = Epoch(10);
164
165        CertificateListItemMessage {
166            hash: "hash".to_string(),
167            previous_hash: "previous_hash".to_string(),
168            epoch,
169            signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(
170                *epoch, 1728,
171            )),
172            metadata: CertificateListItemMessageMetadata {
173                network: "testnet".to_string(),
174                protocol_version: "0.1.0".to_string(),
175                protocol_parameters: ProtocolParameters::new(1000, 100, 0.123),
176                initiated_at: DateTime::parse_from_rfc3339("2024-02-12T13:11:47Z")
177                    .unwrap()
178                    .with_timezone(&Utc),
179                sealed_at: DateTime::parse_from_rfc3339("2024-02-12T13:12:57Z")
180                    .unwrap()
181                    .with_timezone(&Utc),
182                total_signers: 2,
183            },
184            protocol_message: protocol_message.clone(),
185            signed_message: "signed_message".to_string(),
186            aggregate_verification_key: "aggregate_verification_key".to_string(),
187        }
188    }
189
190    #[test]
191    fn test_current_json_deserialized_into_current_message() {
192        let json = CURRENT_JSON;
193        let message: CertificateListMessage = serde_json::from_str(json).unwrap();
194
195        assert_eq!(vec![golden_current_message()], message);
196    }
197}