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(aggregate_verification_key.to_owned().into())
137    }
138}
139
140impl PartialEq for Certificate {
141    fn eq(&self, other: &Self) -> bool {
142        self.epoch.eq(&other.epoch) && self.hash.eq(&other.hash)
143    }
144}
145
146impl Debug for Certificate {
147    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
148        let should_be_exhaustive = f.alternate();
149        let mut debug = f.debug_struct("Certificate");
150        debug
151            .field("hash", &self.hash)
152            .field("previous_hash", &self.previous_hash)
153            .field("epoch", &format_args!("{:?}", self.epoch))
154            .field("metadata", &format_args!("{:?}", self.metadata))
155            .field(
156                "protocol_message",
157                &format_args!("{:?}", self.protocol_message),
158            )
159            .field("signed_message", &self.signed_message);
160
161        match should_be_exhaustive {
162            true => debug
163                .field(
164                    "aggregate_verification_key",
165                    &format_args!("{:?}", self.aggregate_verification_key.to_json_hex()),
166                )
167                .field("signature", &format_args!("{:?}", self.signature))
168                .finish(),
169            false => debug.finish_non_exhaustive(),
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use chrono::{DateTime, Duration, Utc};
177
178    use crate::entities::SignedEntityType::CardanoStakeDistribution;
179    use crate::{
180        entities::{
181            ProtocolMessagePartKey, ProtocolParameters,
182            certificate_metadata::StakeDistributionParty,
183        },
184        test::double::fake_keys,
185    };
186
187    use super::*;
188
189    fn get_parties() -> Vec<StakeDistributionParty> {
190        vec![
191            StakeDistributionParty {
192                party_id: "1".to_string(),
193                stake: 10,
194            },
195            StakeDistributionParty {
196                party_id: "2".to_string(),
197                stake: 20,
198            },
199        ]
200    }
201
202    fn get_protocol_message() -> ProtocolMessage {
203        let mut protocol_message = ProtocolMessage::new();
204        protocol_message.set_message_part(
205            ProtocolMessagePartKey::SnapshotDigest,
206            "snapshot-digest-123".to_string(),
207        );
208        protocol_message.set_message_part(
209            ProtocolMessagePartKey::NextAggregateVerificationKey,
210            fake_keys::aggregate_verification_key_for_concatenation()[1].to_owned(),
211        );
212
213        protocol_message
214    }
215
216    #[test]
217    fn test_certificate_compute_hash() {
218        const HASH_EXPECTED: &str =
219            "5e341ceeb91cc9957fca96d586e0ff2c66a8d6ec6b90bf84929c060c717094da";
220
221        let initiated_at = DateTime::parse_from_rfc3339("2024-02-12T13:11:47.0123043Z")
222            .unwrap()
223            .with_timezone(&Utc);
224        let sealed_at = initiated_at + Duration::try_seconds(100).unwrap();
225        let signed_entity_type = SignedEntityType::MithrilStakeDistribution(Epoch(10));
226
227        let certificate = Certificate::new(
228            "previous_hash".to_string(),
229            Epoch(10),
230            CertificateMetadata::new(
231                "testnet",
232                "0.1.0",
233                ProtocolParameters::new(1000, 100, 0.123),
234                initiated_at,
235                sealed_at,
236                get_parties(),
237            ),
238            get_protocol_message(),
239            ProtocolAggregateVerificationKey::new(
240                ProtocolAggregateVerificationKeyForConcatenation::try_from(
241                    fake_keys::aggregate_verification_key_for_concatenation()[0],
242                )
243                .unwrap()
244                .into(),
245            ),
246            CertificateSignature::MultiSignature(
247                signed_entity_type.clone(),
248                fake_keys::multi_signature()[0].try_into().unwrap(),
249            ),
250        );
251
252        assert_eq!(HASH_EXPECTED, certificate.compute_hash());
253
254        assert_ne!(
255            HASH_EXPECTED,
256            Certificate {
257                previous_hash: "previous_hash-modified".to_string(),
258                ..certificate.clone()
259            }
260            .compute_hash(),
261        );
262
263        assert_ne!(
264            HASH_EXPECTED,
265            Certificate {
266                epoch: certificate.epoch + 10,
267                ..certificate.clone()
268            }
269            .compute_hash(),
270        );
271
272        assert_ne!(
273            HASH_EXPECTED,
274            Certificate {
275                metadata: CertificateMetadata {
276                    protocol_version: "0.1.0-modified".to_string(),
277                    ..certificate.metadata.clone()
278                },
279                ..certificate.clone()
280            }
281            .compute_hash(),
282        );
283
284        assert_ne!(
285            HASH_EXPECTED,
286            Certificate {
287                protocol_message: {
288                    let mut protocol_message_modified = certificate.protocol_message.clone();
289                    protocol_message_modified.set_message_part(
290                        ProtocolMessagePartKey::NextAggregateVerificationKey,
291                        fake_keys::aggregate_verification_key_for_concatenation()[2].into(),
292                    );
293
294                    protocol_message_modified
295                },
296                ..certificate.clone()
297            }
298            .compute_hash(),
299        );
300
301        assert_ne!(
302            HASH_EXPECTED,
303            Certificate {
304                aggregate_verification_key:
305                    fake_keys::aggregate_verification_key_for_concatenation()[2]
306                        .try_into()
307                        .unwrap(),
308                ..certificate.clone()
309            }
310            .compute_hash(),
311        );
312
313        assert_ne!(
314            HASH_EXPECTED,
315            Certificate {
316                signature: CertificateSignature::MultiSignature(
317                    CardanoStakeDistribution(Epoch(100)),
318                    fake_keys::multi_signature()[0].try_into().unwrap()
319                ),
320                ..certificate.clone()
321            }
322            .compute_hash(),
323        );
324
325        assert_ne!(
326            HASH_EXPECTED,
327            Certificate {
328                signature: CertificateSignature::MultiSignature(
329                    signed_entity_type,
330                    fake_keys::multi_signature()[1].try_into().unwrap()
331                ),
332                ..certificate.clone()
333            }
334            .compute_hash(),
335        );
336    }
337
338    #[test]
339    fn test_genesis_certificate_compute_hash() {
340        const HASH_EXPECTED: &str =
341            "6160fca853402c0ea89a0a9ceb5d97462ffd81c558c53feef01dcc0827f5bd19";
342
343        let initiated_at = DateTime::parse_from_rfc3339("2024-02-12T13:11:47.0123043Z")
344            .unwrap()
345            .with_timezone(&Utc);
346        let sealed_at = initiated_at + Duration::try_seconds(100).unwrap();
347
348        let genesis_certificate = Certificate::new(
349            "previous_hash",
350            Epoch(10),
351            CertificateMetadata::new(
352                "testnet",
353                "0.1.0".to_string(),
354                ProtocolParameters::new(1000, 100, 0.123),
355                initiated_at,
356                sealed_at,
357                get_parties(),
358            ),
359            get_protocol_message(),
360            ProtocolAggregateVerificationKey::new(
361                ProtocolAggregateVerificationKeyForConcatenation::try_from(
362                    fake_keys::aggregate_verification_key_for_concatenation()[1],
363                )
364                .unwrap()
365                .into(),
366            ),
367            CertificateSignature::GenesisSignature(
368                fake_keys::genesis_signature()[0].try_into().unwrap(),
369            ),
370        );
371
372        assert_eq!(HASH_EXPECTED, genesis_certificate.compute_hash());
373
374        assert_ne!(
375            HASH_EXPECTED,
376            Certificate {
377                signature: CertificateSignature::GenesisSignature(
378                    fake_keys::genesis_signature()[1].try_into().unwrap()
379                ),
380                ..genesis_certificate.clone()
381            }
382            .compute_hash(),
383        );
384    }
385}