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(
93            self.aggregate_verification_key
94                .to_json_hex()
95                .unwrap()
96                .as_bytes(),
97        );
98        match &self.signature {
99            CertificateSignature::GenesisSignature(signature) => {
100                hasher.update(signature.to_bytes_hex());
101            }
102            CertificateSignature::MultiSignature(signed_entity_type, signature) => {
103                signed_entity_type.feed_hash(&mut hasher);
104                hasher.update(signature.to_json_hex().unwrap());
105            }
106        };
107        hex::encode(hasher.finalize())
108    }
109
110    /// Tell if the certificate is a genesis certificate
111    pub fn is_genesis(&self) -> bool {
112        matches!(self.signature, CertificateSignature::GenesisSignature(_))
113    }
114
115    /// Return true if the certificate is chaining into itself (meaning that its hash and previous
116    /// hash are equal).
117    pub fn is_chaining_to_itself(&self) -> bool {
118        self.hash == self.previous_hash
119    }
120
121    /// Check that the certificate signed message match the given protocol message.
122    pub fn match_message(&self, message: &ProtocolMessage) -> bool {
123        message.compute_hash() == self.signed_message
124    }
125
126    /// Get the certificate signed entity type.
127    pub fn signed_entity_type(&self) -> SignedEntityType {
128        match &self.signature {
129            CertificateSignature::GenesisSignature(_) => SignedEntityType::genesis(self.epoch),
130            CertificateSignature::MultiSignature(entity_type, _) => entity_type.clone(),
131        }
132    }
133}
134
135impl PartialEq for Certificate {
136    fn eq(&self, other: &Self) -> bool {
137        self.epoch.eq(&other.epoch) && self.hash.eq(&other.hash)
138    }
139}
140
141impl Debug for Certificate {
142    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
143        let should_be_exhaustive = f.alternate();
144        let mut debug = f.debug_struct("Certificate");
145        debug
146            .field("hash", &self.hash)
147            .field("previous_hash", &self.previous_hash)
148            .field("epoch", &format_args!("{:?}", self.epoch))
149            .field("metadata", &format_args!("{:?}", self.metadata))
150            .field(
151                "protocol_message",
152                &format_args!("{:?}", self.protocol_message),
153            )
154            .field("signed_message", &self.signed_message);
155
156        match should_be_exhaustive {
157            true => debug
158                .field(
159                    "aggregate_verification_key",
160                    &format_args!("{:?}", self.aggregate_verification_key.to_json_hex()),
161                )
162                .field("signature", &format_args!("{:?}", self.signature))
163                .finish(),
164            false => debug.finish_non_exhaustive(),
165        }
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use crate::entities::SignedEntityType::CardanoStakeDistribution;
173    use crate::{
174        entities::{
175            certificate_metadata::StakeDistributionParty, ProtocolMessagePartKey,
176            ProtocolParameters,
177        },
178        test_utils::fake_keys,
179    };
180    use chrono::{DateTime, Duration, Utc};
181
182    fn get_parties() -> Vec<StakeDistributionParty> {
183        vec![
184            StakeDistributionParty {
185                party_id: "1".to_string(),
186                stake: 10,
187            },
188            StakeDistributionParty {
189                party_id: "2".to_string(),
190                stake: 20,
191            },
192        ]
193    }
194
195    fn get_protocol_message() -> ProtocolMessage {
196        let mut protocol_message = ProtocolMessage::new();
197        protocol_message.set_message_part(
198            ProtocolMessagePartKey::SnapshotDigest,
199            "snapshot-digest-123".to_string(),
200        );
201        protocol_message.set_message_part(
202            ProtocolMessagePartKey::NextAggregateVerificationKey,
203            fake_keys::aggregate_verification_key()[1].to_owned(),
204        );
205
206        protocol_message
207    }
208
209    #[test]
210    fn test_certificate_compute_hash() {
211        const HASH_EXPECTED: &str =
212            "5e341ceeb91cc9957fca96d586e0ff2c66a8d6ec6b90bf84929c060c717094da";
213
214        let initiated_at = DateTime::parse_from_rfc3339("2024-02-12T13:11:47.0123043Z")
215            .unwrap()
216            .with_timezone(&Utc);
217        let sealed_at = initiated_at + Duration::try_seconds(100).unwrap();
218        let signed_entity_type = SignedEntityType::MithrilStakeDistribution(Epoch(10));
219
220        let certificate = Certificate::new(
221            "previous_hash".to_string(),
222            Epoch(10),
223            CertificateMetadata::new(
224                "testnet",
225                "0.1.0",
226                ProtocolParameters::new(1000, 100, 0.123),
227                initiated_at,
228                sealed_at,
229                get_parties(),
230            ),
231            get_protocol_message(),
232            fake_keys::aggregate_verification_key()[0]
233                .try_into()
234                .unwrap(),
235            CertificateSignature::MultiSignature(
236                signed_entity_type.clone(),
237                fake_keys::multi_signature()[0].try_into().unwrap(),
238            ),
239        );
240
241        assert_eq!(HASH_EXPECTED, certificate.compute_hash());
242
243        assert_ne!(
244            HASH_EXPECTED,
245            Certificate {
246                previous_hash: "previous_hash-modified".to_string(),
247                ..certificate.clone()
248            }
249            .compute_hash(),
250        );
251
252        assert_ne!(
253            HASH_EXPECTED,
254            Certificate {
255                epoch: certificate.epoch + 10,
256                ..certificate.clone()
257            }
258            .compute_hash(),
259        );
260
261        assert_ne!(
262            HASH_EXPECTED,
263            Certificate {
264                metadata: CertificateMetadata {
265                    protocol_version: "0.1.0-modified".to_string(),
266                    ..certificate.metadata.clone()
267                },
268                ..certificate.clone()
269            }
270            .compute_hash(),
271        );
272
273        assert_ne!(
274            HASH_EXPECTED,
275            Certificate {
276                protocol_message: {
277                    let mut protocol_message_modified = certificate.protocol_message.clone();
278                    protocol_message_modified.set_message_part(
279                        ProtocolMessagePartKey::NextAggregateVerificationKey,
280                        fake_keys::aggregate_verification_key()[2].into(),
281                    );
282
283                    protocol_message_modified
284                },
285                ..certificate.clone()
286            }
287            .compute_hash(),
288        );
289
290        assert_ne!(
291            HASH_EXPECTED,
292            Certificate {
293                aggregate_verification_key: fake_keys::aggregate_verification_key()[2]
294                    .try_into()
295                    .unwrap(),
296                ..certificate.clone()
297            }
298            .compute_hash(),
299        );
300
301        assert_ne!(
302            HASH_EXPECTED,
303            Certificate {
304                signature: CertificateSignature::MultiSignature(
305                    CardanoStakeDistribution(Epoch(100)),
306                    fake_keys::multi_signature()[0].try_into().unwrap()
307                ),
308                ..certificate.clone()
309            }
310            .compute_hash(),
311        );
312
313        assert_ne!(
314            HASH_EXPECTED,
315            Certificate {
316                signature: CertificateSignature::MultiSignature(
317                    signed_entity_type,
318                    fake_keys::multi_signature()[1].try_into().unwrap()
319                ),
320                ..certificate.clone()
321            }
322            .compute_hash(),
323        );
324    }
325
326    #[test]
327    fn test_genesis_certificate_compute_hash() {
328        const HASH_EXPECTED: &str =
329            "6160fca853402c0ea89a0a9ceb5d97462ffd81c558c53feef01dcc0827f5bd19";
330
331        let initiated_at = DateTime::parse_from_rfc3339("2024-02-12T13:11:47.0123043Z")
332            .unwrap()
333            .with_timezone(&Utc);
334        let sealed_at = initiated_at + Duration::try_seconds(100).unwrap();
335
336        let genesis_certificate = Certificate::new(
337            "previous_hash",
338            Epoch(10),
339            CertificateMetadata::new(
340                "testnet",
341                "0.1.0".to_string(),
342                ProtocolParameters::new(1000, 100, 0.123),
343                initiated_at,
344                sealed_at,
345                get_parties(),
346            ),
347            get_protocol_message(),
348            fake_keys::aggregate_verification_key()[1]
349                .try_into()
350                .unwrap(),
351            CertificateSignature::GenesisSignature(
352                fake_keys::genesis_signature()[0].try_into().unwrap(),
353            ),
354        );
355
356        assert_eq!(HASH_EXPECTED, genesis_certificate.compute_hash());
357
358        assert_ne!(
359            HASH_EXPECTED,
360            Certificate {
361                signature: CertificateSignature::GenesisSignature(
362                    fake_keys::genesis_signature()[1].try_into().unwrap()
363                ),
364                ..genesis_certificate.clone()
365            }
366            .compute_hash(),
367        );
368    }
369}