mithril_common/crypto_helper/cardano/
opcert.rs1use 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#[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 RawFields(
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(RawFields, EdVerificationKey);
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct OpCert {
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 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 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 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 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 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 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 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}