mithril_common/crypto_helper/cardano/
opcert.rs

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