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#[derive(Clone, Debug)]
12pub enum CertificateSignature {
13 GenesisSignature(ProtocolGenesisSignature),
16
17 MultiSignature(SignedEntityType, ProtocolMultiSignature),
20}
21
22#[derive(Clone)]
24pub struct Certificate {
25 pub hash: String,
29
30 pub previous_hash: String,
35
36 pub epoch: Epoch,
38
39 pub metadata: CertificateMetadata,
42
43 pub protocol_message: ProtocolMessage,
46
47 pub signed_message: String,
50
51 pub aggregate_verification_key: ProtocolAggregateVerificationKeyForConcatenation,
55
56 pub signature: CertificateSignature,
58}
59
60impl Certificate {
61 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 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 pub fn is_genesis(&self) -> bool {
111 matches!(self.signature, CertificateSignature::GenesisSignature(_))
112 }
113
114 pub fn is_chaining_to_itself(&self) -> bool {
117 self.hash == self.previous_hash
118 }
119
120 pub fn match_message(&self, message: &ProtocolMessage) -> bool {
122 message.compute_hash() == self.signed_message
123 }
124
125 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 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}