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(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}