mithril_common/entities/
certificate.rs

1use crate::crypto_helper::{
2    ProtocolAggregateVerificationKey, ProtocolGenesisSignature, ProtocolMultiSignature,
3};
4use crate::entities::{CertificateMetadata, Epoch, ProtocolMessage, SignedEntityType};
5use std::fmt::{Debug, Formatter};
6
7use sha2::{Digest, Sha256};
8
9/// The signature of a [Certificate]
10#[derive(Clone, Debug)]
11pub enum CertificateSignature {
12    /// Genesis signature created from the original stake distribution
13    /// aka GENESIS_SIG(AVK(-1))
14    GenesisSignature(ProtocolGenesisSignature),
15
16    /// STM multi signature created from a quorum of single signatures from the signers
17    /// aka (BEACON(p,n), MULTI_SIG(H(MSG(p,n) || AVK(n-1))))
18    MultiSignature(SignedEntityType, ProtocolMultiSignature),
19}
20
21/// Certificate represents a Mithril certificate embedding a Mithril STM multisignature
22#[derive(Clone)]
23pub struct Certificate {
24    /// Hash of the current certificate
25    /// Computed from the other fields of the certificate
26    /// aka H(Cp,n))
27    pub hash: String,
28
29    /// Hash of the previous certificate in the chain
30    /// This is either the hash of the first certificate of the epoch in the chain
31    /// Or the first certificate of the previous epoch in the chain (if the certificate is the first of its epoch)
32    /// aka H(FC(n))
33    pub previous_hash: String,
34
35    /// Cardano chain epoch number
36    pub epoch: Epoch,
37
38    /// Certificate metadata
39    /// aka METADATA(p,n)
40    pub metadata: CertificateMetadata,
41
42    /// Structured message that is used to create the signed message
43    /// aka MSG(p,n) U AVK(n-1)
44    pub protocol_message: ProtocolMessage,
45
46    /// Message that is signed by the signers
47    /// aka H(MSG(p,n) || AVK(n-1))
48    pub signed_message: String,
49
50    /// Aggregate verification key
51    /// The AVK used to sign during the current epoch
52    /// aka AVK(n-2)
53    pub aggregate_verification_key: ProtocolAggregateVerificationKey,
54
55    /// Certificate signature
56    pub signature: CertificateSignature,
57}
58
59impl Certificate {
60    /// Certificate factory
61    pub fn new<T: Into<String>>(
62        previous_hash: T,
63        epoch: Epoch,
64        metadata: CertificateMetadata,
65        protocol_message: ProtocolMessage,
66        aggregate_verification_key: ProtocolAggregateVerificationKey,
67        signature: CertificateSignature,
68    ) -> Certificate {
69        let signed_message = protocol_message.compute_hash();
70        let mut certificate = Certificate {
71            hash: "".to_string(),
72            previous_hash: previous_hash.into(),
73            epoch,
74            metadata,
75            protocol_message,
76            signed_message,
77            aggregate_verification_key,
78            signature,
79        };
80        certificate.hash = certificate.compute_hash();
81        certificate
82    }
83
84    /// Computes the hash of a Certificate
85    pub fn compute_hash(&self) -> String {
86        let mut hasher = Sha256::new();
87        hasher.update(self.previous_hash.as_bytes());
88        hasher.update(self.epoch.to_be_bytes());
89        hasher.update(self.metadata.compute_hash().as_bytes());
90        hasher.update(self.protocol_message.compute_hash().as_bytes());
91        hasher.update(self.signed_message.as_bytes());
92        hasher.update(self.aggregate_verification_key.to_json_hex().unwrap().as_bytes());
93        match &self.signature {
94            CertificateSignature::GenesisSignature(signature) => {
95                hasher.update(signature.to_bytes_hex().unwrap());
96            }
97            CertificateSignature::MultiSignature(signed_entity_type, signature) => {
98                signed_entity_type.feed_hash(&mut hasher);
99                hasher.update(signature.to_json_hex().unwrap());
100            }
101        };
102        hex::encode(hasher.finalize())
103    }
104
105    /// Tell if the certificate is a genesis certificate
106    pub fn is_genesis(&self) -> bool {
107        matches!(self.signature, CertificateSignature::GenesisSignature(_))
108    }
109
110    /// Return true if the certificate is chaining into itself (meaning that its hash and previous
111    /// hash are equal).
112    pub fn is_chaining_to_itself(&self) -> bool {
113        self.hash == self.previous_hash
114    }
115
116    /// Check that the certificate signed message match the given protocol message.
117    pub fn match_message(&self, message: &ProtocolMessage) -> bool {
118        message.compute_hash() == self.signed_message
119    }
120
121    /// Get the certificate signed entity type.
122    pub fn signed_entity_type(&self) -> SignedEntityType {
123        match &self.signature {
124            CertificateSignature::GenesisSignature(_) => SignedEntityType::genesis(self.epoch),
125            CertificateSignature::MultiSignature(entity_type, _) => entity_type.clone(),
126        }
127    }
128}
129
130impl PartialEq for Certificate {
131    fn eq(&self, other: &Self) -> bool {
132        self.epoch.eq(&other.epoch) && self.hash.eq(&other.hash)
133    }
134}
135
136impl Debug for Certificate {
137    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
138        let should_be_exhaustive = f.alternate();
139        let mut debug = f.debug_struct("Certificate");
140        debug
141            .field("hash", &self.hash)
142            .field("previous_hash", &self.previous_hash)
143            .field("epoch", &format_args!("{:?}", self.epoch))
144            .field("metadata", &format_args!("{:?}", self.metadata))
145            .field(
146                "protocol_message",
147                &format_args!("{:?}", self.protocol_message),
148            )
149            .field("signed_message", &self.signed_message);
150
151        match should_be_exhaustive {
152            true => debug
153                .field(
154                    "aggregate_verification_key",
155                    &format_args!("{:?}", self.aggregate_verification_key.to_json_hex()),
156                )
157                .field("signature", &format_args!("{:?}", self.signature))
158                .finish(),
159            false => debug.finish_non_exhaustive(),
160        }
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use chrono::{DateTime, Duration, Utc};
167
168    use crate::entities::SignedEntityType::CardanoStakeDistribution;
169    use crate::{
170        entities::{
171            ProtocolMessagePartKey, ProtocolParameters,
172            certificate_metadata::StakeDistributionParty,
173        },
174        test::double::fake_keys,
175    };
176
177    use super::*;
178
179    fn get_parties() -> Vec<StakeDistributionParty> {
180        vec![
181            StakeDistributionParty {
182                party_id: "1".to_string(),
183                stake: 10,
184            },
185            StakeDistributionParty {
186                party_id: "2".to_string(),
187                stake: 20,
188            },
189        ]
190    }
191
192    fn get_protocol_message() -> ProtocolMessage {
193        let mut protocol_message = ProtocolMessage::new();
194        protocol_message.set_message_part(
195            ProtocolMessagePartKey::SnapshotDigest,
196            "snapshot-digest-123".to_string(),
197        );
198        protocol_message.set_message_part(
199            ProtocolMessagePartKey::NextAggregateVerificationKey,
200            fake_keys::aggregate_verification_key()[1].to_owned(),
201        );
202
203        protocol_message
204    }
205
206    #[test]
207    fn test_certificate_compute_hash() {
208        const HASH_EXPECTED: &str =
209            "5e341ceeb91cc9957fca96d586e0ff2c66a8d6ec6b90bf84929c060c717094da";
210
211        let initiated_at = DateTime::parse_from_rfc3339("2024-02-12T13:11:47.0123043Z")
212            .unwrap()
213            .with_timezone(&Utc);
214        let sealed_at = initiated_at + Duration::try_seconds(100).unwrap();
215        let signed_entity_type = SignedEntityType::MithrilStakeDistribution(Epoch(10));
216
217        let certificate = Certificate::new(
218            "previous_hash".to_string(),
219            Epoch(10),
220            CertificateMetadata::new(
221                "testnet",
222                "0.1.0",
223                ProtocolParameters::new(1000, 100, 0.123),
224                initiated_at,
225                sealed_at,
226                get_parties(),
227            ),
228            get_protocol_message(),
229            fake_keys::aggregate_verification_key()[0].try_into().unwrap(),
230            CertificateSignature::MultiSignature(
231                signed_entity_type.clone(),
232                fake_keys::multi_signature()[0].try_into().unwrap(),
233            ),
234        );
235
236        assert_eq!(HASH_EXPECTED, certificate.compute_hash());
237
238        assert_ne!(
239            HASH_EXPECTED,
240            Certificate {
241                previous_hash: "previous_hash-modified".to_string(),
242                ..certificate.clone()
243            }
244            .compute_hash(),
245        );
246
247        assert_ne!(
248            HASH_EXPECTED,
249            Certificate {
250                epoch: certificate.epoch + 10,
251                ..certificate.clone()
252            }
253            .compute_hash(),
254        );
255
256        assert_ne!(
257            HASH_EXPECTED,
258            Certificate {
259                metadata: CertificateMetadata {
260                    protocol_version: "0.1.0-modified".to_string(),
261                    ..certificate.metadata.clone()
262                },
263                ..certificate.clone()
264            }
265            .compute_hash(),
266        );
267
268        assert_ne!(
269            HASH_EXPECTED,
270            Certificate {
271                protocol_message: {
272                    let mut protocol_message_modified = certificate.protocol_message.clone();
273                    protocol_message_modified.set_message_part(
274                        ProtocolMessagePartKey::NextAggregateVerificationKey,
275                        fake_keys::aggregate_verification_key()[2].into(),
276                    );
277
278                    protocol_message_modified
279                },
280                ..certificate.clone()
281            }
282            .compute_hash(),
283        );
284
285        assert_ne!(
286            HASH_EXPECTED,
287            Certificate {
288                aggregate_verification_key: fake_keys::aggregate_verification_key()[2]
289                    .try_into()
290                    .unwrap(),
291                ..certificate.clone()
292            }
293            .compute_hash(),
294        );
295
296        assert_ne!(
297            HASH_EXPECTED,
298            Certificate {
299                signature: CertificateSignature::MultiSignature(
300                    CardanoStakeDistribution(Epoch(100)),
301                    fake_keys::multi_signature()[0].try_into().unwrap()
302                ),
303                ..certificate.clone()
304            }
305            .compute_hash(),
306        );
307
308        assert_ne!(
309            HASH_EXPECTED,
310            Certificate {
311                signature: CertificateSignature::MultiSignature(
312                    signed_entity_type,
313                    fake_keys::multi_signature()[1].try_into().unwrap()
314                ),
315                ..certificate.clone()
316            }
317            .compute_hash(),
318        );
319    }
320
321    #[test]
322    fn test_genesis_certificate_compute_hash() {
323        const HASH_EXPECTED: &str =
324            "6160fca853402c0ea89a0a9ceb5d97462ffd81c558c53feef01dcc0827f5bd19";
325
326        let initiated_at = DateTime::parse_from_rfc3339("2024-02-12T13:11:47.0123043Z")
327            .unwrap()
328            .with_timezone(&Utc);
329        let sealed_at = initiated_at + Duration::try_seconds(100).unwrap();
330
331        let genesis_certificate = Certificate::new(
332            "previous_hash",
333            Epoch(10),
334            CertificateMetadata::new(
335                "testnet",
336                "0.1.0".to_string(),
337                ProtocolParameters::new(1000, 100, 0.123),
338                initiated_at,
339                sealed_at,
340                get_parties(),
341            ),
342            get_protocol_message(),
343            fake_keys::aggregate_verification_key()[1].try_into().unwrap(),
344            CertificateSignature::GenesisSignature(
345                fake_keys::genesis_signature()[0].try_into().unwrap(),
346            ),
347        );
348
349        assert_eq!(HASH_EXPECTED, genesis_certificate.compute_hash());
350
351        assert_ne!(
352            HASH_EXPECTED,
353            Certificate {
354                signature: CertificateSignature::GenesisSignature(
355                    fake_keys::genesis_signature()[1].try_into().unwrap()
356                ),
357                ..genesis_certificate.clone()
358            }
359            .compute_hash(),
360        );
361    }
362}