mithril_common/crypto_helper/cardano/
opcert.rs

1//! Module to (de)serialise, OpCert using the same structure as used in Cardano.  
2
3use 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/// Operational certificate error
20#[derive(Error, Debug, PartialEq, Eq)]
21pub enum OpCertError {
22    /// Error raised when a pool address encoding fails
23    #[error("pool address encoding error")]
24    PoolAddressEncoding,
25}
26
27/// Raw Fields of the operational certificates (without including the cold VK)
28#[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/// Raw Operational Certificate
37#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
38struct RawOpCert(RawOpCertWithoutColdVerificationKey, EdVerificationKey);
39
40/// Parsed Operational Certificate without cold verification key
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct OpCertWithoutColdVerificationKey {
43    pub(crate) kes_vk: KesPublicKey,
44    pub(crate) issue_number: u64,
45    /// KES period at which KES key is initalized
46    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/// Parsed Operational Certificate
101#[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    /// OpCert factory / test only
114    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    /// Get the KES verification key
139    pub fn get_kes_verification_key(&self) -> KesPublicKey {
140        self.opcert_without_vk.kes_vk
141    }
142
143    /// Get the issue number
144    pub fn get_issue_number(&self) -> u64 {
145        self.opcert_without_vk.issue_number
146    }
147
148    /// Get the start KES period
149    pub fn get_start_kes_period(&self) -> u64 {
150        self.opcert_without_vk.start_kes_period
151    }
152
153    /// Get the certificate signature
154    pub fn get_certificate_signature(&self) -> EdSignature {
155        self.opcert_without_vk.cert_sig
156    }
157
158    /// Get the OpCert without cold verification key
159    pub fn get_opcert_without_cold_verification_key(&self) -> OpCertWithoutColdVerificationKey {
160        self.opcert_without_vk.clone()
161    }
162
163    /// Get the cold verification key
164    pub fn get_cold_verification_key(&self) -> EdVerificationKey {
165        self.cold_vk
166    }
167
168    /// Compute message to sign
169    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    /// Validate a certificate
182    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    /// Compute protocol party id as pool id bech 32
202    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    /// Compute protocol party id as hash
211    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    /// Compute the hash of an OpCert
218    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}