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::{encode_bech32, ProtocolPartyId};
6
7use blake2::{digest::consts::U28, Blake2b, Digest};
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 RawFields(
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(RawFields, EdVerificationKey);
39
40/// Parsed Operational Certificate
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct OpCert {
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    pub(crate) cold_vk: EdVerificationKey,
49}
50
51impl SerDeShelleyFileFormat for OpCert {
52    const TYPE: &'static str = "NodeOperationalCertificate";
53    const DESCRIPTION: &'static str = "";
54}
55
56impl OpCert {
57    /// OpCert factory / test only
58    pub fn new(
59        kes_vk: KesPublicKey,
60        issue_number: u64,
61        start_kes_period: u64,
62        cold_secret_key: EdSecretKey,
63    ) -> Self {
64        let cold_vk: EdVerificationKey = cold_secret_key.verifying_key();
65        let cert_sig = cold_secret_key.sign(&Self::compute_message_to_sign(
66            &kes_vk,
67            issue_number,
68            start_kes_period,
69        ));
70
71        Self {
72            kes_vk,
73            issue_number,
74            start_kes_period,
75            cert_sig,
76            cold_vk,
77        }
78    }
79
80    /// Compute message to sign
81    pub(crate) fn compute_message_to_sign(
82        kes_vk: &KesPublicKey,
83        issue_number: u64,
84        start_kes_period: u64,
85    ) -> [u8; 48] {
86        let mut msg = [0u8; 48];
87        msg[..32].copy_from_slice(kes_vk.as_bytes());
88        msg[32..40].copy_from_slice(&issue_number.to_be_bytes());
89        msg[40..48].copy_from_slice(&start_kes_period.to_be_bytes());
90        msg
91    }
92
93    /// Validate a certificate
94    pub fn validate(&self) -> Result<(), ProtocolRegistrationErrorWrapper> {
95        if self
96            .cold_vk
97            .verify(
98                &Self::compute_message_to_sign(
99                    &self.kes_vk,
100                    self.issue_number,
101                    self.start_kes_period,
102                ),
103                &self.cert_sig,
104            )
105            .is_ok()
106        {
107            return Ok(());
108        }
109
110        Err(ProtocolRegistrationErrorWrapper::OpCertInvalid)
111    }
112
113    /// Compute protocol party id as pool id bech 32
114    pub fn compute_protocol_party_id(&self) -> Result<ProtocolPartyId, OpCertError> {
115        let mut hasher = Blake2b::<U28>::new();
116        hasher.update(self.cold_vk.as_bytes());
117        let mut pool_id = [0u8; 28];
118        pool_id.copy_from_slice(hasher.finalize().as_bytes());
119        encode_bech32("pool", &pool_id).map_err(|_| OpCertError::PoolAddressEncoding)
120    }
121
122    /// Compute protocol party id as hash
123    pub fn compute_protocol_party_id_as_hash(&self) -> String {
124        let mut hasher = Blake2b::<U28>::new();
125        hasher.update(self.cold_vk.as_bytes());
126        hex::encode(hasher.finalize())
127    }
128
129    /// Compute the hash of an OpCert
130    pub fn compute_hash(&self) -> String {
131        let mut hasher = Sha256::new();
132        hasher.update(self.kes_vk.as_bytes());
133        hasher.update(self.issue_number.to_be_bytes());
134        hasher.update(self.start_kes_period.to_be_bytes());
135        hasher.update(self.cert_sig.to_bytes());
136        hasher.update(self.cold_vk.as_bytes());
137        hex::encode(hasher.finalize())
138    }
139}
140
141impl Serialize for OpCert {
142    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143    where
144        S: Serializer,
145    {
146        let raw_cert = RawOpCert(
147            RawFields(
148                self.kes_vk.as_bytes().to_vec(),
149                self.issue_number,
150                self.start_kes_period,
151                self.cert_sig.to_bytes().to_vec(),
152            ),
153            self.cold_vk,
154        );
155
156        raw_cert.serialize(serializer)
157    }
158}
159
160impl<'de> Deserialize<'de> for OpCert {
161    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162    where
163        D: Deserializer<'de>,
164    {
165        let raw_cert = RawOpCert::deserialize(deserializer)?;
166        Ok(Self {
167            kes_vk: KesPublicKey::from_bytes(&raw_cert.0 .0)
168                .map_err(|_| Error::custom("KES vk serialisation error"))?,
169            issue_number: raw_cert.0 .1,
170            start_kes_period: raw_cert.0 .2,
171            cert_sig: EdSignature::from_slice(&raw_cert.0 .3)
172                .map_err(|_| Error::custom("ed25519 signature serialisation error"))?,
173            cold_vk: raw_cert.1,
174        })
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use crate::crypto_helper::cardano::ColdKeyGenerator;
182    use crate::test_utils::TempDir;
183
184    use kes_summed_ed25519::{kes::Sum6Kes, traits::KesSk};
185    use std::path::PathBuf;
186
187    fn setup_temp_directory(test_name: &str) -> PathBuf {
188        TempDir::create("mithril_cardano_opcert", test_name)
189    }
190
191    #[test]
192    fn test_vector_opcert() {
193        let temp_dir = setup_temp_directory("test_vector_opcert");
194        let keypair = ColdKeyGenerator::create_deterministic_keypair([0u8; 32]);
195        let mut dummy_key_buffer = [0u8; Sum6Kes::SIZE + 4];
196        let mut dummy_seed = [0u8; 32];
197        let (_, kes_verification_key) = Sum6Kes::keygen(&mut dummy_key_buffer, &mut dummy_seed);
198        let operational_certificate = OpCert::new(kes_verification_key, 0, 0, keypair);
199        assert!(operational_certificate.validate().is_ok());
200
201        let operation_certificate_file = temp_dir.join("node.cert");
202        operational_certificate
203            .to_file(&operation_certificate_file)
204            .expect("operational certificate file export should not fail");
205
206        let operational_certificate: OpCert = OpCert::from_file(&operation_certificate_file)
207            .expect("operational certificate file import should not fail");
208        assert!(operational_certificate.validate().is_ok());
209
210        let party_id = operational_certificate
211            .compute_protocol_party_id()
212            .expect("compute protocol party_id should not fail");
213        assert_eq!(
214            "pool1mxyec46067n3querj9cxkk0g0zlag93pf3ya9vuyr3wgkq2e6t7".to_string(),
215            party_id
216        );
217
218        let party_id_as_hash = operational_certificate.compute_protocol_party_id_as_hash();
219        assert_eq!(
220            "d9899c574fd7a710732391706b59e878bfd416214c49d2b3841c5c8b".to_string(),
221            party_id_as_hash
222        );
223    }
224}