mithril_common/entities/
certificate.rs

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