mithril_common/crypto_helper/cardano/
opcert.rs1use super::SerDeShelleyFileFormat;
4use crate::crypto_helper::cardano::ProtocolRegistrationErrorWrapper;
5use crate::crypto_helper::{ProtocolPartyId, encode_bech32};
6
7use blake2::{Blake2b, Digest, digest::consts::U28};
8use ed25519_dalek::{
9 Signature as EdSignature, Signer, SigningKey as EdSecretKey, Verifier,
10 VerifyingKey as EdVerificationKey,
11};
12use kes_summed_ed25519::PublicKey as KesPublicKey;
13use nom::AsBytes;
14use serde::de::Error;
15use serde::{Deserialize, Deserializer, Serialize, Serializer};
16use sha2::Sha256;
17use thiserror::Error;
18
19#[derive(Error, Debug, PartialEq, Eq)]
21pub enum OpCertError {
22 #[error("pool address encoding error")]
24 PoolAddressEncoding,
25}
26
27#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
29struct RawOpCertWithoutColdVerificationKey(
30 #[serde(with = "serde_bytes")] Vec<u8>,
31 u64,
32 u64,
33 #[serde(with = "serde_bytes")] Vec<u8>,
34);
35
36#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
38struct RawOpCert(RawOpCertWithoutColdVerificationKey, EdVerificationKey);
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct OpCertWithoutColdVerificationKey {
43 pub(crate) kes_vk: KesPublicKey,
44 pub(crate) issue_number: u64,
45 pub start_kes_period: u64,
47 pub(crate) cert_sig: EdSignature,
48}
49
50impl SerDeShelleyFileFormat for OpCertWithoutColdVerificationKey {
51 const TYPE: &'static str = "NodeOperationalCertificateWithoutColdVerificationKey";
52 const DESCRIPTION: &'static str = "";
53}
54
55impl Serialize for OpCertWithoutColdVerificationKey {
56 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
57 where
58 S: Serializer,
59 {
60 let raw_cert = RawOpCertWithoutColdVerificationKey(
61 self.kes_vk.as_bytes().to_vec(),
62 self.issue_number,
63 self.start_kes_period,
64 self.cert_sig.to_bytes().to_vec(),
65 );
66
67 raw_cert.serialize(serializer)
68 }
69}
70
71impl<'de> Deserialize<'de> for OpCertWithoutColdVerificationKey {
72 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73 where
74 D: Deserializer<'de>,
75 {
76 let raw_cert = RawOpCertWithoutColdVerificationKey::deserialize(deserializer)?;
77
78 Ok(Self {
79 kes_vk: KesPublicKey::from_bytes(&raw_cert.0)
80 .map_err(|_| Error::custom("KES vk serialisation error"))?,
81 issue_number: raw_cert.1,
82 start_kes_period: raw_cert.2,
83 cert_sig: EdSignature::from_slice(&raw_cert.3)
84 .map_err(|_| Error::custom("ed25519 signature serialisation error"))?,
85 })
86 }
87}
88
89impl From<&OpCertWithoutColdVerificationKey> for RawOpCertWithoutColdVerificationKey {
90 fn from(opcert: &OpCertWithoutColdVerificationKey) -> Self {
91 RawOpCertWithoutColdVerificationKey(
92 opcert.kes_vk.as_bytes().to_vec(),
93 opcert.issue_number,
94 opcert.start_kes_period,
95 opcert.cert_sig.to_bytes().to_vec(),
96 )
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct OpCert {
103 pub(crate) opcert_without_vk: OpCertWithoutColdVerificationKey,
104 pub(crate) cold_vk: EdVerificationKey,
105}
106
107impl SerDeShelleyFileFormat for OpCert {
108 const TYPE: &'static str = "NodeOperationalCertificate";
109 const DESCRIPTION: &'static str = "";
110}
111
112impl OpCert {
113 pub fn new(
115 kes_vk: KesPublicKey,
116 issue_number: u64,
117 start_kes_period: u64,
118 cold_secret_key: EdSecretKey,
119 ) -> Self {
120 let cold_vk: EdVerificationKey = cold_secret_key.verifying_key();
121 let cert_sig = cold_secret_key.sign(&Self::compute_message_to_sign(
122 &kes_vk,
123 issue_number,
124 start_kes_period,
125 ));
126
127 Self {
128 opcert_without_vk: OpCertWithoutColdVerificationKey {
129 kes_vk,
130 issue_number,
131 start_kes_period,
132 cert_sig,
133 },
134 cold_vk,
135 }
136 }
137
138 pub fn get_kes_verification_key(&self) -> KesPublicKey {
140 self.opcert_without_vk.kes_vk
141 }
142
143 pub fn get_issue_number(&self) -> u64 {
145 self.opcert_without_vk.issue_number
146 }
147
148 pub fn get_start_kes_period(&self) -> u64 {
150 self.opcert_without_vk.start_kes_period
151 }
152
153 pub fn get_certificate_signature(&self) -> EdSignature {
155 self.opcert_without_vk.cert_sig
156 }
157
158 pub fn get_opcert_without_cold_verification_key(&self) -> OpCertWithoutColdVerificationKey {
160 self.opcert_without_vk.clone()
161 }
162
163 pub fn get_cold_verification_key(&self) -> EdVerificationKey {
165 self.cold_vk
166 }
167
168 pub(crate) fn compute_message_to_sign(
170 kes_vk: &KesPublicKey,
171 issue_number: u64,
172 start_kes_period: u64,
173 ) -> [u8; 48] {
174 let mut msg = [0u8; 48];
175 msg[..32].copy_from_slice(kes_vk.as_bytes());
176 msg[32..40].copy_from_slice(&issue_number.to_be_bytes());
177 msg[40..48].copy_from_slice(&start_kes_period.to_be_bytes());
178 msg
179 }
180
181 pub fn validate(&self) -> Result<(), ProtocolRegistrationErrorWrapper> {
183 if self
184 .cold_vk
185 .verify(
186 &Self::compute_message_to_sign(
187 &self.opcert_without_vk.kes_vk,
188 self.opcert_without_vk.issue_number,
189 self.opcert_without_vk.start_kes_period,
190 ),
191 &self.opcert_without_vk.cert_sig,
192 )
193 .is_ok()
194 {
195 return Ok(());
196 }
197
198 Err(ProtocolRegistrationErrorWrapper::OpCertInvalid)
199 }
200
201 pub fn compute_protocol_party_id(&self) -> Result<ProtocolPartyId, OpCertError> {
203 let mut hasher = Blake2b::<U28>::new();
204 hasher.update(self.cold_vk.as_bytes());
205 let mut pool_id = [0u8; 28];
206 pool_id.copy_from_slice(hasher.finalize().as_bytes());
207 encode_bech32("pool", &pool_id).map_err(|_| OpCertError::PoolAddressEncoding)
208 }
209
210 pub fn compute_protocol_party_id_as_hash(&self) -> String {
212 let mut hasher = Blake2b::<U28>::new();
213 hasher.update(self.cold_vk.as_bytes());
214 hex::encode(hasher.finalize())
215 }
216
217 pub fn compute_hash(&self) -> String {
219 let mut hasher = Sha256::new();
220 hasher.update(self.opcert_without_vk.kes_vk.as_bytes());
221 hasher.update(self.opcert_without_vk.issue_number.to_be_bytes());
222 hasher.update(self.opcert_without_vk.start_kes_period.to_be_bytes());
223 hasher.update(self.opcert_without_vk.cert_sig.to_bytes());
224 hasher.update(self.cold_vk.as_bytes());
225 hex::encode(hasher.finalize())
226 }
227}
228
229impl Serialize for OpCert {
230 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
231 where
232 S: Serializer,
233 {
234 let raw_opcert_without_vk: RawOpCertWithoutColdVerificationKey =
235 (&self.opcert_without_vk).into();
236 let raw_cert = RawOpCert(raw_opcert_without_vk, self.cold_vk);
237
238 raw_cert.serialize(serializer)
239 }
240}
241
242impl<'de> Deserialize<'de> for OpCert {
243 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
244 where
245 D: Deserializer<'de>,
246 {
247 let raw_cert = RawOpCert::deserialize(deserializer)?;
248 let raw_opcert_without_vk = &raw_cert.0;
249
250 Ok(Self {
251 opcert_without_vk: OpCertWithoutColdVerificationKey {
252 kes_vk: KesPublicKey::from_bytes(&raw_opcert_without_vk.0)
253 .map_err(|_| Error::custom("KES vk serialisation error"))?,
254 issue_number: raw_opcert_without_vk.1,
255 start_kes_period: raw_opcert_without_vk.2,
256 cert_sig: EdSignature::from_slice(&raw_opcert_without_vk.3)
257 .map_err(|_| Error::custom("ed25519 signature serialisation error"))?,
258 },
259 cold_vk: raw_cert.1,
260 })
261 }
262}
263
264impl From<(OpCertWithoutColdVerificationKey, EdVerificationKey)> for OpCert {
265 fn from(
266 (opcert_without_vk, cold_vk): (OpCertWithoutColdVerificationKey, EdVerificationKey),
267 ) -> Self {
268 Self {
269 opcert_without_vk,
270 cold_vk,
271 }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use crate::crypto_helper::cardano::ColdKeyGenerator;
279 use crate::test::TempDir;
280
281 use kes_summed_ed25519::{kes::Sum6Kes, traits::KesSk};
282 use std::path::PathBuf;
283
284 fn setup_temp_directory(test_name: &str) -> PathBuf {
285 TempDir::create("mithril_cardano_opcert", test_name)
286 }
287
288 #[test]
289 fn test_vector_opcert() {
290 let temp_dir = setup_temp_directory("test_vector_opcert");
291 let keypair = ColdKeyGenerator::create_deterministic_keypair([0u8; 32]);
292 let mut dummy_key_buffer = [0u8; Sum6Kes::SIZE + 4];
293 let mut dummy_seed = [0u8; 32];
294 let (_, kes_verification_key) = Sum6Kes::keygen(&mut dummy_key_buffer, &mut dummy_seed);
295 let operational_certificate = OpCert::new(kes_verification_key, 0, 0, keypair);
296 assert!(operational_certificate.validate().is_ok());
297
298 let operation_certificate_file = temp_dir.join("node.cert");
299 operational_certificate
300 .to_file(&operation_certificate_file)
301 .expect("operational certificate file export should not fail");
302
303 let operational_certificate: OpCert = OpCert::from_file(&operation_certificate_file)
304 .expect("operational certificate file import should not fail");
305 assert!(operational_certificate.validate().is_ok());
306
307 let party_id = operational_certificate
308 .compute_protocol_party_id()
309 .expect("compute protocol party_id should not fail");
310 assert_eq!(
311 "pool1mxyec46067n3querj9cxkk0g0zlag93pf3ya9vuyr3wgkq2e6t7".to_string(),
312 party_id
313 );
314
315 let party_id_as_hash = operational_certificate.compute_protocol_party_id_as_hash();
316 assert_eq!(
317 "d9899c574fd7a710732391706b59e878bfd416214c49d2b3841c5c8b".to_string(),
318 party_id_as_hash
319 );
320
321 let operational_certificate_bytes_without_cold_vk = operational_certificate
322 .get_opcert_without_cold_verification_key()
323 .to_cbor_bytes()
324 .expect("compute CBOR bytes should not fail");
325 assert_eq!(
326 "845820e650d7531509bb6cffd7998c28c68e4ec8fa621a0952206ea11eb03fcd7dcb2900005840d4abce27da05ff03c1342cc6ab53135072e1babf9cc05492f59f1ff009f70457aaa862c7158b13be0cfb41d7a91a562589bc110eb2cdaf5d2756048abbea5f05",
327 hex::encode(operational_certificate_bytes_without_cold_vk)
328 );
329 }
330}