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#[derive(Clone, Debug)]
11pub enum CertificateSignature {
12 GenesisSignature(ProtocolGenesisSignature),
15
16 MultiSignature(SignedEntityType, ProtocolMultiSignature),
19}
20
21#[derive(Clone)]
23pub struct Certificate {
24 pub hash: String,
28
29 pub previous_hash: String,
34
35 pub epoch: Epoch,
37
38 pub metadata: CertificateMetadata,
41
42 pub protocol_message: ProtocolMessage,
45
46 pub signed_message: String,
49
50 pub aggregate_verification_key: ProtocolAggregateVerificationKey,
54
55 pub signature: CertificateSignature,
57}
58
59impl Certificate {
60 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 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 pub fn is_genesis(&self) -> bool {
112 matches!(self.signature, CertificateSignature::GenesisSignature(_))
113 }
114
115 pub fn is_chaining_to_itself(&self) -> bool {
118 self.hash == self.previous_hash
119 }
120
121 pub fn match_message(&self, message: &ProtocolMessage) -> bool {
123 message.compute_hash() == self.signed_message
124 }
125
126 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}