Skip to main content

mithril_common/crypto_helper/cardano/
key_certification.rs

1//! API for mithril key certification.
2//! Includes the wrappers for Initializer and KeyRegistration, and ProtocolRegistrationErrorWrapper.
3//! These wrappers allows keeping mithril-stm agnostic to Cardano, while providing some
4//! guarantees that mithril-stm will not be misused in the context of Cardano.  
5
6use std::{collections::HashMap, sync::Arc};
7
8use anyhow::anyhow;
9use kes_summed_ed25519::kes::Sum6KesSig;
10use rand_core::{CryptoRng, RngCore};
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14use mithril_stm::{
15    ClosedKeyRegistration, Initializer, KeyRegistration, MembershipDigest, MithrilMembershipDigest,
16    Parameters, RegisterError, Signer, Stake, VerificationKeyProofOfPossession,
17};
18
19use crate::{
20    StdError, StdResult,
21    crypto_helper::{
22        KesEvolutions, KesPeriod, ProtocolOpCert,
23        cardano::{KesSigner, KesVerifier, KesVerifierStandard},
24        types::{
25            ProtocolParameters, ProtocolPartyId, ProtocolSignerVerificationKey,
26            ProtocolSignerVerificationKeySignature, ProtocolStakeDistribution,
27        },
28    },
29};
30
31// Protocol types alias
32type D = MithrilMembershipDigest;
33
34/// New registration error
35#[derive(Error, Debug)]
36pub enum ProtocolRegistrationErrorWrapper {
37    /// Error raised when a party id is needed but not provided
38    ///
39    /// Used only for testing when SPO pool id is not certified
40    #[error("missing party id")]
41    PartyIdMissing,
42
43    /// Error raised when a party id is not available in the Cardano_stake distribution
44    #[error("party id does not exist in the stake distribution")]
45    PartyIdNonExisting,
46
47    /// Error raised when the operational certificate is missing
48    #[error("missing operational certificate")]
49    OpCertMissing,
50
51    /// Error raised when an operational certificate is invalid
52    #[error("invalid operational certificate")]
53    OpCertInvalid,
54
55    /// Error raised when a KES Signature verification fails
56    #[error("KES signature verification error: KesEvolutions={0}, StartKesPeriod={1}")]
57    KesSignatureInvalid(KesEvolutions, KesPeriod),
58
59    /// Error raised when a KES Signature is needed but not provided
60    #[error("missing KES signature")]
61    KesSignatureMissing,
62
63    /// Error raised when a KES Period is needed but not provided
64    #[error("missing KES period")]
65    KesPeriodMissing,
66
67    /// Error raised when a pool address encoding fails
68    #[error("pool address encoding error")]
69    PoolAddressEncoding,
70
71    /// Error raised when a core registration error occurs
72    #[error("core registration error")]
73    CoreRegister(#[source] RegisterError),
74}
75
76/// New initializer error
77#[derive(Error, Debug)]
78pub enum ProtocolInitializerErrorWrapper {
79    /// Error raised when the underlying protocol initializer fails
80    #[error("protocol initializer error")]
81    ProtocolInitializer(#[source] StdError),
82
83    /// Error raised when a KES update error occurs
84    #[error("KES key cannot be updated for evolution {0}")]
85    KesUpdate(KesPeriod),
86
87    /// Period of key file does not match with period provided by user
88    #[error("Period of key file, {0}, does not match with period provided by user, {1}")]
89    KesMismatch(KesPeriod, KesPeriod),
90}
91
92/// Wrapper structure for [MithrilStm:Initializer](mithril_stm::stm::Initializer).
93/// It now obtains a KES signature over the Mithril key. This allows the signers prove
94/// their correct identity with respect to a Cardano PoolID.
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct StmInitializerWrapper {
97    /// The Initializer
98    stm_initializer: Initializer,
99
100    /// The KES signature over the Mithril key
101    ///
102    /// None is used only for testing when SPO pool id is not certified
103    kes_signature: Option<Sum6KesSig>,
104}
105
106impl StmInitializerWrapper {
107    /// Builds an `Initializer` that is ready to register with the key registration service.
108    /// This function generates the signing and verification key with a PoP, signs the verification
109    /// key with a provided KES signer implementation, and initializes the structure.
110    pub fn setup<R: RngCore + CryptoRng>(
111        params: Parameters,
112        kes_signer: Option<Arc<dyn KesSigner>>,
113        current_kes_period: Option<KesPeriod>,
114        stake: Stake,
115        rng: &mut R,
116    ) -> StdResult<Self> {
117        let stm_initializer = Initializer::new(params, stake, rng);
118        let kes_signature = if let Some(kes_signer) = kes_signer {
119            let (signature, _op_cert) = kes_signer.sign(
120                &stm_initializer.get_verification_key_proof_of_possession().to_bytes(),
121                current_kes_period.unwrap_or_default(),
122            )?;
123
124            Some(signature)
125        } else {
126            println!(
127                "WARNING: Non certified signer registration by providing only a Pool Id is decommissioned and must be used for tests only!"
128            );
129            None
130        };
131
132        Ok(Self {
133            stm_initializer,
134            kes_signature,
135        })
136    }
137
138    /// Extract the verification key.
139    pub fn verification_key(&self) -> VerificationKeyProofOfPossession {
140        self.stm_initializer.get_verification_key_proof_of_possession()
141    }
142
143    /// Extract the verification key signature.
144    pub fn verification_key_signature(&self) -> Option<ProtocolSignerVerificationKeySignature> {
145        self.kes_signature.map(|k| k.into())
146    }
147
148    /// Extract the protocol parameters of the initializer
149    pub fn get_protocol_parameters(&self) -> ProtocolParameters {
150        self.stm_initializer.params
151    }
152
153    /// Extract the stake of the party
154    pub fn get_stake(&self) -> Stake {
155        self.stm_initializer.stake
156    }
157
158    /// Build the `avk` for the given list of parties.
159    ///
160    /// Note that if this Initializer was modified *between* the last call to `register`,
161    /// then the resulting `Signer` may not be able to produce valid signatures.
162    ///
163    /// Returns a `StmSignerWrapper` specialized to
164    /// * this `StmSignerWrapper`'s ID and current stake
165    /// * this `StmSignerWrapper`'s parameter valuation
166    /// * the `avk` as built from the current registered parties (according to the registration service)
167    /// * the current total stake (according to the registration service)
168    /// # Error
169    /// This function fails if the initializer is not registered.
170    pub fn new_signer(self, closed_reg: ClosedKeyRegistration<D>) -> StdResult<Signer<D>> {
171        self.stm_initializer.create_signer(closed_reg)
172    }
173
174    /// Convert to bytes
175    /// # Layout
176    /// * StmInitialiser
177    /// * KesSignature
178    pub fn to_bytes(&self) -> Vec<u8> {
179        let mut out = Vec::new();
180        out.extend_from_slice(&self.stm_initializer.to_bytes());
181        if let Some(kes_signature) = &self.kes_signature {
182            out.extend_from_slice(&kes_signature.to_bytes());
183        }
184
185        out
186    }
187
188    /// Convert a slice of bytes to an `StmInitializerWrapper`
189    /// # Error
190    /// The function fails if the given string of bytes is not of required size.
191    pub fn from_bytes(bytes: &[u8]) -> StdResult<Self> {
192        let stm_initializer =
193            Initializer::from_bytes(bytes.get(..256).ok_or(RegisterError::SerializationError)?)?;
194        let bytes = bytes.get(256..).ok_or(RegisterError::SerializationError)?;
195        let kes_signature = if bytes.is_empty() {
196            None
197        } else {
198            Some(Sum6KesSig::from_bytes(bytes).map_err(|_| RegisterError::SerializationError)?)
199        };
200
201        Ok(Self {
202            stm_initializer,
203            kes_signature,
204        })
205    }
206}
207
208/// Wrapper structure for [MithrilStm:KeyRegistration](mithril_stm::key_reg::KeyRegistration).
209/// The wrapper not only contains a map between `Mithril vkey <-> Stake`, but also
210/// a map `PoolID <-> Stake`. This information is recovered from the node state, and
211/// is used to verify the identity of a Mithril signer. Furthermore, the `register` function
212/// of the wrapper forces the registrar to check that the KES signature over the Mithril key
213/// is valid with respect to the PoolID.
214#[derive(Debug, Clone)]
215pub struct KeyRegWrapper {
216    kes_verifier: Arc<dyn KesVerifier>,
217    stm_key_reg: KeyRegistration,
218    stake_distribution: HashMap<ProtocolPartyId, Stake>,
219}
220
221impl KeyRegWrapper {
222    /// New Initialisation function. We temporarily keep the other init function,
223    /// but we should eventually transition to only use this one.
224    pub fn init(stake_dist: &ProtocolStakeDistribution) -> Self {
225        Self {
226            kes_verifier: Arc::new(KesVerifierStandard),
227            stm_key_reg: KeyRegistration::init(),
228            stake_distribution: HashMap::from_iter(stake_dist.to_vec()),
229        }
230    }
231
232    /// Register a new party. For a successful registration, the registrar needs to
233    /// provide the OpCert (in cbor form), the cold VK, a KES signature, and a
234    /// Mithril key (with its corresponding Proof of Possession).
235    ///
236    /// kes_evolutions: The number of evolutions since the start KES period of the operational certificate.
237    pub fn register(
238        &mut self,
239        party_id: Option<ProtocolPartyId>, // Used for only for testing when SPO pool id is not certified
240        opcert: Option<ProtocolOpCert>, // Used for only for testing when SPO pool id is not certified
241        kes_sig: Option<ProtocolSignerVerificationKeySignature>, // Used for only for testing when SPO pool id is not certified
242        kes_evolutions: Option<KesEvolutions>,
243        pk: ProtocolSignerVerificationKey,
244    ) -> StdResult<ProtocolPartyId> {
245        let pool_id_bech32: ProtocolPartyId = if let Some(opcert) = opcert {
246            let signature = kes_sig.ok_or(ProtocolRegistrationErrorWrapper::KesSignatureMissing)?;
247            let kes_evolutions =
248                kes_evolutions.ok_or(ProtocolRegistrationErrorWrapper::KesPeriodMissing)?;
249            if self
250                .kes_verifier
251                .verify(&pk.to_bytes(), &signature, &opcert, kes_evolutions)
252                .is_ok()
253            {
254                opcert
255                    .compute_protocol_party_id()
256                    .map_err(|_| ProtocolRegistrationErrorWrapper::PoolAddressEncoding)?
257            } else {
258                return Err(anyhow!(
259                    ProtocolRegistrationErrorWrapper::KesSignatureInvalid(
260                        kes_evolutions,
261                        opcert.get_start_kes_period(),
262                    )
263                ));
264            }
265        } else {
266            if cfg!(not(feature = "allow_skip_signer_certification")) {
267                Err(ProtocolRegistrationErrorWrapper::OpCertMissing)?
268            }
269            party_id.ok_or(ProtocolRegistrationErrorWrapper::PartyIdMissing)?
270        };
271
272        if let Some(&stake) = self.stake_distribution.get(&pool_id_bech32) {
273            self.stm_key_reg.register(stake, pk.into())?;
274            return Ok(pool_id_bech32);
275        }
276        Err(anyhow!(
277            ProtocolRegistrationErrorWrapper::PartyIdNonExisting
278        ))
279    }
280
281    /// Finalize the key registration.
282    /// This function disables `ClosedKeyRegistration::register`, consumes the instance of `self`, and returns a `ClosedKeyRegistration`.
283    pub fn close<D: MembershipDigest>(self) -> ClosedKeyRegistration<D> {
284        self.stm_key_reg.close()
285    }
286}
287
288mod test_extensions {
289    use crate::test::crypto_helper::ProtocolInitializerTestExtension;
290
291    use super::*;
292
293    impl ProtocolInitializerTestExtension for StmInitializerWrapper {
294        fn override_protocol_parameters(&mut self, protocol_parameters: &ProtocolParameters) {
295            self.stm_initializer.params = protocol_parameters.to_owned();
296        }
297    }
298}
299
300#[cfg(test)]
301mod test {
302    use crate::crypto_helper::cardano::kes::KesSignerStandard;
303    use crate::crypto_helper::{OpCert, SerDeShelleyFileFormat};
304    use crate::test::crypto_helper::{
305        KesCryptographicMaterialForTest, KesPartyIndexForTest, create_kes_cryptographic_material,
306    };
307
308    use rand_chacha::ChaCha20Rng;
309    use rand_core::SeedableRng;
310
311    use super::*;
312
313    #[test]
314    fn test_vector_key_reg() {
315        let params = Parameters {
316            m: 5,
317            k: 5,
318            phi_f: 1.0,
319        };
320        let mut rng = ChaCha20Rng::from_seed([0u8; 32]);
321        let KesCryptographicMaterialForTest {
322            party_id: party_id_1,
323            operational_certificate_file: operational_certificate_file_1,
324            kes_secret_key_file: kes_secret_key_file_1,
325        } = create_kes_cryptographic_material(
326            1 as KesPartyIndexForTest,
327            KesPeriod(0),
328            "test_vector_key_reg",
329        );
330        let KesCryptographicMaterialForTest {
331            party_id: party_id_2,
332            operational_certificate_file: operational_certificate_file_2,
333            kes_secret_key_file: kes_secret_key_file_2,
334        } = create_kes_cryptographic_material(
335            2 as KesPartyIndexForTest,
336            KesPeriod(0),
337            "test_vector_key_reg",
338        );
339
340        let mut key_reg = KeyRegWrapper::init(&vec![(party_id_1, 10), (party_id_2, 3)]);
341
342        let initializer_1 = StmInitializerWrapper::setup(
343            params,
344            Some(Arc::new(KesSignerStandard::new(
345                kes_secret_key_file_1,
346                operational_certificate_file_1.clone(),
347            ))),
348            Some(KesPeriod(0)),
349            10,
350            &mut rng,
351        )
352        .unwrap();
353
354        let opcert1 = OpCert::from_file(operational_certificate_file_1)
355            .expect("opcert deserialization should not fail")
356            .into();
357
358        let key_registration_1 = key_reg.register(
359            None,
360            Some(opcert1),
361            initializer_1.verification_key_signature(),
362            Some(KesEvolutions(0)),
363            initializer_1
364                .stm_initializer
365                .get_verification_key_proof_of_possession()
366                .into(),
367        );
368        assert!(key_registration_1.is_ok());
369
370        let initializer_2 = StmInitializerWrapper::setup(
371            params,
372            Some(Arc::new(KesSignerStandard::new(
373                kes_secret_key_file_2,
374                operational_certificate_file_2.clone(),
375            ))),
376            Some(KesPeriod(0)),
377            10,
378            &mut rng,
379        )
380        .unwrap();
381
382        let opcert2 = OpCert::from_file(operational_certificate_file_2)
383            .expect("opcert deserialization should not fail")
384            .into();
385
386        let key_registration_2 = key_reg.register(
387            None,
388            Some(opcert2),
389            initializer_2.verification_key_signature(),
390            Some(KesEvolutions(0)),
391            initializer_2
392                .stm_initializer
393                .get_verification_key_proof_of_possession()
394                .into(),
395        );
396        assert!(key_registration_2.is_ok())
397    }
398
399    const GOLDEN_STM_INITIALIZER_WRAPPER_JSON: &str = r#"
400    {
401        "stm_initializer": {
402            "stake": 9497432569,
403            "params": {
404                "m": 20973,
405                "k": 2422,
406                "phi_f": 0.2
407            },
408            "sk": [49, 181, 118, 110, 190, 161, 107, 218, 165, 20, 147, 129, 193, 79, 160, 0, 37, 23, 102, 223, 88, 174, 208, 70, 97, 79, 174, 51, 28, 0, 192, 210],
409            "pk": {
410                "vk": [173, 149, 133, 21, 100, 254, 36, 74, 165, 174, 56, 9, 145, 190, 48, 14, 12, 193, 243, 3, 200, 148, 221, 124, 170, 143, 89, 5, 168, 0, 226, 125, 61, 181, 190, 80, 62, 199, 99, 161, 117, 49, 65, 34, 81, 96, 34, 81, 2, 235, 173, 57, 58, 128, 49, 22, 242, 42, 30, 137, 6, 51, 77, 57, 142, 192, 140, 161, 206, 206, 213, 114, 156, 191, 127, 167, 167, 9, 39, 29, 97, 166, 134, 76, 55, 179, 72, 29, 41, 251, 14, 71, 89, 181, 31, 115],
411                "pop": [171, 0, 214, 91, 37, 208, 228, 71, 228, 31, 138, 0, 237, 175, 24, 45, 160, 117, 14, 210, 23, 46, 235, 83, 45, 9, 58, 207, 18, 36, 31, 160, 252, 111, 69, 102, 248, 205, 46, 71, 24, 38, 41, 77, 29, 129, 95, 16, 136, 114, 250, 44, 230, 184, 222, 122, 120, 58, 249, 103, 48, 121, 141, 244, 243, 26, 252, 60, 230, 64, 75, 3, 86, 107, 198, 198, 117, 242, 107, 104, 219, 209, 211, 255, 174, 203, 43, 141, 34, 146, 25, 181, 212, 38, 194, 99]
412            }
413        },
414        "kes_signature": {
415            "sigma": {
416                "sigma": {
417                    "sigma": {
418                        "sigma": {
419                            "sigma": {
420                                "sigma": [71, 225, 146, 98, 81, 62, 28, 21, 7, 157, 88, 4, 226, 126, 27, 133, 146, 171, 216, 170, 77, 17, 38, 146, 98, 202, 35, 87, 166, 162, 25, 207, 105, 174, 48, 225, 152, 68, 19, 109, 72, 241, 69, 111, 22, 214, 72, 20, 81, 56, 181, 104, 69, 121, 173, 194, 37, 60, 16, 155, 86, 99, 253, 7],
421                                "lhs_pk": [
422                                    91, 82, 235, 39, 167, 29, 141, 253, 163, 163, 55, 185, 162, 191, 52, 8, 245, 7, 104, 22, 182, 239, 133, 138, 131, 15, 233, 116, 147, 251, 182, 140],
423                                    "rhs_pk": [189, 26, 9, 118, 59, 34, 225, 34, 104, 202, 192, 7, 66, 150, 137, 75, 106, 7, 22, 234, 42, 94, 139, 65, 241, 65, 1, 190, 153, 16, 221, 87]
424                                    },
425                            "lhs_pk": [206, 50, 185, 93, 20, 234, 100, 168, 163, 125, 95, 201, 162, 104, 35, 2, 205, 41, 180, 73, 107, 140, 79, 182, 173, 17, 172, 49, 51, 85, 180, 5],
426                            "rhs_pk": [68, 40, 90, 110, 254, 68, 87, 12, 19, 21, 252, 197, 69, 255, 33, 172, 140, 70, 79, 39, 71, 217, 12, 254, 82, 125, 123, 148, 221, 217, 141, 194]
427                        },
428                        "lhs_pk": [155, 2, 30, 71, 52, 89, 112, 247, 108, 177, 144, 212, 206, 254, 87, 126, 180, 207, 146, 223, 164, 246, 178, 62, 148, 96, 39, 136, 106, 36, 253, 56],
429                        "rhs_pk": [155, 140, 124, 154, 235, 97, 51, 77, 208, 24, 45, 219, 199, 232, 222, 26, 160, 62, 38, 253, 121, 241, 219, 233, 36, 50, 60, 182, 127, 255, 132, 245]
430                    },
431                    "lhs_pk": [172, 176, 18, 228, 203, 85, 44, 151, 221, 13, 91, 250, 67, 232, 114, 16, 251, 13, 115, 233, 214, 194, 102, 199, 200, 124, 30, 190, 143, 18, 85, 75],
432                    "rhs_pk": [100, 192, 98, 123, 150, 116, 55, 42, 207, 44, 181, 31, 203, 65, 237, 13, 55, 246, 185, 211, 149, 245, 245, 219, 183, 41, 237, 253, 128, 231, 161, 226]
433                },
434                "lhs_pk": [112, 16, 177, 142, 158, 1, 36, 210, 87, 165, 5, 195, 199, 61, 13, 195, 219, 26, 231, 103, 163, 223, 54, 16, 106, 0, 252, 69, 242, 31, 210, 167],
435                "rhs_pk": [15, 246, 81, 72, 172, 15, 170, 235, 10, 64, 229, 233, 169, 140, 179, 209, 244, 183, 3, 59, 2, 252, 233, 229, 13, 190, 196, 208, 109, 30, 73, 113]
436            },
437            "lhs_pk": [114, 238, 75, 184, 228, 147, 37, 72, 134, 65, 139, 64, 81, 114, 157, 148, 197, 108, 80, 89, 30, 235, 75, 108, 193, 53, 185, 15, 57, 61, 181, 119],
438            "rhs_pk": [82, 28, 113, 114, 168, 192, 222, 110, 96, 15, 28, 179, 164, 180, 76, 87, 254, 72, 48, 154, 167, 102, 220, 74, 76, 136, 45, 105, 243, 87, 165, 212]
439        }
440    }
441    "#;
442
443    #[test]
444    fn golden_initializer_deserialization() {
445        let _: StmInitializerWrapper = serde_json::from_str(GOLDEN_STM_INITIALIZER_WRAPPER_JSON)
446            .expect("Deserializing a StmInitializerWrapper should not fail");
447    }
448
449    #[test]
450    fn test_initializer_wrapper_conversions() {
451        let stm_initializer_wrapper_json = GOLDEN_STM_INITIALIZER_WRAPPER_JSON;
452
453        let stm_initializer_wrapper_from_json: StmInitializerWrapper =
454            serde_json::from_str(stm_initializer_wrapper_json)
455                .expect("Deserializing a StmInitializerWrapper should not fail");
456        let stm_initializer_wrapper_from_json_to_json =
457            serde_json::to_string(&stm_initializer_wrapper_from_json)
458                .expect("Serializing a StmInitializerWrapper to json should not fail");
459
460        let stm_initializer_wrapper_from_bytes =
461            StmInitializerWrapper::from_bytes(&stm_initializer_wrapper_from_json.to_bytes())
462                .expect("Deserializing a StmInitializerWrapper from bytes should not fail");
463        let stm_initializer_wrapper_from_bytes_to_json =
464            serde_json::to_string(&stm_initializer_wrapper_from_bytes)
465                .expect("Serializing a StmInitializerWrapper to json should not fail");
466
467        assert_eq!(
468            stm_initializer_wrapper_from_json_to_json,
469            stm_initializer_wrapper_from_bytes_to_json
470        );
471
472        let mut stm_initializer_wrapper_from_json = stm_initializer_wrapper_from_json;
473        stm_initializer_wrapper_from_json.kes_signature = None;
474
475        let stm_initializer_wrapper_from_bytes =
476            StmInitializerWrapper::from_bytes(&stm_initializer_wrapper_from_json.to_bytes())
477                .expect("Deserializing a StmInitializerWrapper from bytes should not fail");
478        assert_eq!(None, stm_initializer_wrapper_from_bytes.kes_signature);
479    }
480}