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