mithril_aggregator/tools/
genesis.rs

1use anyhow::Context;
2use std::{
3    fs::File,
4    io::{Write, prelude::*},
5    path::{Path, PathBuf},
6    sync::Arc,
7};
8
9use mithril_common::{
10    CardanoNetwork, StdResult,
11    certificate_chain::{CertificateGenesisProducer, CertificateVerifier},
12    crypto_helper::{
13        ProtocolAggregateVerificationKey, ProtocolGenesisSecretKey, ProtocolGenesisSignature,
14        ProtocolGenesisSigner, ProtocolGenesisVerificationKey,
15    },
16    entities::{Epoch, ProtocolParameters},
17    protocol::SignerBuilder,
18};
19
20use crate::{
21    database::repository::CertificateRepository,
22    dependency_injection::GenesisCommandDependenciesContainer,
23};
24
25pub struct GenesisTools {
26    network: CardanoNetwork,
27    epoch: Epoch,
28    genesis_avk: ProtocolAggregateVerificationKey,
29    genesis_protocol_parameters: ProtocolParameters,
30    certificate_verifier: Arc<dyn CertificateVerifier>,
31    certificate_repository: Arc<CertificateRepository>,
32}
33
34impl GenesisTools {
35    pub fn new(
36        network: CardanoNetwork,
37        epoch: Epoch,
38        genesis_avk: ProtocolAggregateVerificationKey,
39        genesis_protocol_parameters: ProtocolParameters,
40        certificate_verifier: Arc<dyn CertificateVerifier>,
41        certificate_repository: Arc<CertificateRepository>,
42    ) -> Self {
43        Self {
44            network,
45            epoch,
46            genesis_avk,
47            genesis_protocol_parameters,
48            certificate_verifier,
49            certificate_repository,
50        }
51    }
52
53    pub async fn from_dependencies(
54        dependencies: GenesisCommandDependenciesContainer,
55    ) -> StdResult<Self> {
56        let epoch = dependencies
57            .chain_observer
58            .get_current_epoch()
59            .await?
60            .with_context(|| "Chain observer can not retrieve current epoch")?;
61        let certificate_verifier = dependencies.certificate_verifier.clone();
62        let certificate_repository = dependencies.certificate_repository.clone();
63        let protocol_parameters_retriever = dependencies.protocol_parameters_retriever.clone();
64        let genesis_avk_epoch = epoch.offset_to_next_signer_retrieval_epoch();
65        let genesis_protocol_parameters = protocol_parameters_retriever
66            .get_protocol_parameters(epoch.offset_to_signer_retrieval_epoch()?)
67            .await?
68            .with_context(|| {
69                format!("Missing protocol parameters for epoch {genesis_avk_epoch}")
70            })?;
71        let genesis_signers = dependencies
72            .verification_key_store
73            .get_signers(genesis_avk_epoch)
74            .await?
75            .with_context(|| format!("Missing signers for epoch {genesis_avk_epoch}"))?;
76
77        let protocol_multi_signer =
78            SignerBuilder::new(&genesis_signers, &genesis_protocol_parameters)
79                .with_context(|| "Could not build a multi signer to compute the genesis avk")?
80                .build_multi_signer();
81        let genesis_avk = protocol_multi_signer.compute_aggregate_verification_key();
82
83        Ok(Self::new(
84            dependencies.network,
85            epoch,
86            genesis_avk,
87            genesis_protocol_parameters,
88            certificate_verifier,
89            certificate_repository,
90        ))
91    }
92
93    /// Export AVK of the genesis stake distribution to a payload file
94    pub fn export_payload_to_sign(&self, target_path: &Path) -> StdResult<()> {
95        let mut target_file = File::create(target_path)?;
96        let protocol_message = CertificateGenesisProducer::create_genesis_protocol_message(
97            &self.genesis_protocol_parameters,
98            &self.genesis_avk,
99            &self.epoch,
100        )?;
101        target_file.write_all(protocol_message.compute_hash().as_bytes())?;
102        Ok(())
103    }
104
105    /// Import signature of the AVK of the genesis stake distribution from a file
106    pub async fn import_payload_signature(
107        &self,
108        signed_payload_path: &Path,
109        genesis_verification_key: &ProtocolGenesisVerificationKey,
110    ) -> StdResult<()> {
111        let mut signed_payload_file = File::open(signed_payload_path).unwrap();
112        let mut signed_payload_buffer = Vec::new();
113        signed_payload_file.read_to_end(&mut signed_payload_buffer)?;
114        let genesis_signature = ProtocolGenesisSignature::from_bytes(&signed_payload_buffer)?;
115
116        self.create_and_save_genesis_certificate(genesis_signature, genesis_verification_key)
117            .await
118    }
119
120    /// Automatic bootstrap of the genesis certificate (test only)
121    pub async fn bootstrap_test_genesis_certificate(
122        &self,
123        genesis_signer: ProtocolGenesisSigner,
124    ) -> StdResult<()> {
125        let genesis_verification_key = &genesis_signer.verification_key();
126        let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer)));
127        let genesis_protocol_message = CertificateGenesisProducer::create_genesis_protocol_message(
128            &self.genesis_protocol_parameters,
129            &self.genesis_avk,
130            &self.epoch,
131        )?;
132        let genesis_signature =
133            genesis_producer.sign_genesis_protocol_message(genesis_protocol_message)?;
134        self.create_and_save_genesis_certificate(genesis_signature, genesis_verification_key)
135            .await
136    }
137
138    /// Sign the genesis certificate
139    pub async fn sign_genesis_certificate(
140        to_sign_payload_path: &Path,
141        target_signed_payload_path: &Path,
142        genesis_secret_key_path: &Path,
143    ) -> StdResult<()> {
144        let genesis_secret_key =
145            ProtocolGenesisSecretKey::read_json_hex_from_file(genesis_secret_key_path)?;
146        let genesis_signer = ProtocolGenesisSigner::from_secret_key(genesis_secret_key);
147
148        let mut to_sign_payload_file = File::open(to_sign_payload_path).unwrap();
149        let mut to_sign_payload_buffer = Vec::new();
150        to_sign_payload_file.read_to_end(&mut to_sign_payload_buffer)?;
151
152        let genesis_signature = genesis_signer.sign(&to_sign_payload_buffer);
153        let signed_payload = genesis_signature.to_bytes();
154
155        let mut target_signed_payload_file = File::create(target_signed_payload_path)?;
156        target_signed_payload_file.write_all(&signed_payload)?;
157
158        Ok(())
159    }
160
161    async fn create_and_save_genesis_certificate(
162        &self,
163        genesis_signature: ProtocolGenesisSignature,
164        genesis_verification_key: &ProtocolGenesisVerificationKey,
165    ) -> StdResult<()> {
166        let genesis_certificate = CertificateGenesisProducer::create_genesis_certificate(
167            self.genesis_protocol_parameters.clone(),
168            self.network,
169            self.epoch,
170            self.genesis_avk.clone(),
171            genesis_signature,
172        )?;
173        self.certificate_verifier
174            .verify_genesis_certificate(&genesis_certificate, genesis_verification_key)
175            .await?;
176        self.certificate_repository
177            .create_certificate(genesis_certificate.clone())
178            .await
179            .with_context(|| {
180                format!(
181                    "Genesis tool can not create certificate with genesis signature: '{genesis_signature:?}'"
182                )
183            })?;
184        Ok(())
185    }
186
187    /// Export the genesis keypair to a folder and returns the paths to the files (secret key, verification_key)
188    pub fn create_and_save_genesis_keypair(keypair_path: &Path) -> StdResult<(PathBuf, PathBuf)> {
189        let genesis_signer = ProtocolGenesisSigner::create_non_deterministic_signer();
190        let genesis_secret_key_path = keypair_path.join("genesis.sk");
191        genesis_signer
192            .secret_key()
193            .write_json_hex_to_file(&genesis_secret_key_path)?;
194        let genesis_verification_key_path = keypair_path.join("genesis.vk");
195        genesis_signer
196            .verification_key()
197            .write_json_hex_to_file(&genesis_verification_key_path)?;
198
199        Ok((genesis_secret_key_path, genesis_verification_key_path))
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use mithril_common::{
206        certificate_chain::MithrilCertificateVerifier,
207        crypto_helper::{
208            ProtocolGenesisSecretKey, ProtocolGenesisSigner, ProtocolGenesisVerificationKey,
209            ProtocolGenesisVerifier,
210        },
211        test::{TempDir, builder::MithrilFixtureBuilder, double::fake_data},
212    };
213    use std::{fs::read_to_string, path::PathBuf};
214
215    use crate::database::test_helper::main_db_connection;
216    use crate::test::TestLogger;
217
218    use super::*;
219
220    fn get_temp_dir(dir_name: &str) -> PathBuf {
221        TempDir::create("genesis", dir_name)
222    }
223
224    fn create_fake_genesis_avk() -> ProtocolAggregateVerificationKey {
225        let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
226
227        fixture.compute_avk()
228    }
229
230    fn build_tools(
231        genesis_signer: &ProtocolGenesisSigner,
232    ) -> (
233        GenesisTools,
234        Arc<CertificateRepository>,
235        Arc<ProtocolGenesisVerifier>,
236        Arc<dyn CertificateVerifier>,
237    ) {
238        let connection = main_db_connection().unwrap();
239        let certificate_store = Arc::new(CertificateRepository::new(Arc::new(connection)));
240        let certificate_verifier = Arc::new(MithrilCertificateVerifier::new(
241            TestLogger::stdout(),
242            certificate_store.clone(),
243        ));
244        let genesis_avk = create_fake_genesis_avk();
245        let genesis_verifier = Arc::new(genesis_signer.create_verifier());
246        let genesis_tools = GenesisTools::new(
247            fake_data::network(),
248            Epoch(10),
249            genesis_avk,
250            fake_data::protocol_parameters(),
251            certificate_verifier.clone(),
252            certificate_store.clone(),
253        );
254
255        (
256            genesis_tools,
257            certificate_store,
258            genesis_verifier,
259            certificate_verifier,
260        )
261    }
262
263    #[tokio::test]
264    async fn export_sign_then_import_genesis_payload() {
265        let test_dir = get_temp_dir("export_payload_to_sign");
266        let payload_path = test_dir.join("payload.txt");
267        let signed_payload_path = test_dir.join("payload-signed.txt");
268        let (genesis_secret_key_path, _) = GenesisTools::create_and_save_genesis_keypair(&test_dir)
269            .expect("exporting the keypair should not fail");
270        let genesis_secret_key = ProtocolGenesisSecretKey::from_json_hex(
271            &read_to_string(&genesis_secret_key_path)
272                .expect("reading genesis secret key file should not fail"),
273        )
274        .expect("parsing genesis secret key should not fail");
275        let genesis_signer = ProtocolGenesisSigner::from_secret_key(genesis_secret_key);
276        let (genesis_tools, certificate_store, genesis_verifier, certificate_verifier) =
277            build_tools(&genesis_signer);
278
279        genesis_tools
280            .export_payload_to_sign(&payload_path)
281            .expect("export_payload_to_sign should not fail");
282        GenesisTools::sign_genesis_certificate(
283            &payload_path,
284            &signed_payload_path,
285            &genesis_secret_key_path,
286        )
287        .await
288        .expect("sign_genesis_certificate should not fail");
289        genesis_tools
290            .import_payload_signature(
291                &signed_payload_path,
292                &genesis_verifier.to_verification_key(),
293            )
294            .await
295            .expect("import_payload_signature should not fail");
296
297        let last_certificates = certificate_store.get_latest_certificates(10).await.unwrap();
298
299        assert_eq!(1, last_certificates.len());
300        certificate_verifier
301            .verify_genesis_certificate(
302                &last_certificates[0],
303                &genesis_verifier.to_verification_key(),
304            )
305            .await
306            .expect(
307                "verify_genesis_certificate should successfully validate the genesis certificate",
308            );
309    }
310
311    #[tokio::test]
312    async fn bootstrap_test_genesis_certificate_works() {
313        let genesis_signer = ProtocolGenesisSigner::create_deterministic_signer();
314        let (genesis_tools, certificate_store, genesis_verifier, certificate_verifier) =
315            build_tools(&genesis_signer);
316
317        genesis_tools
318            .bootstrap_test_genesis_certificate(genesis_signer)
319            .await
320            .expect("bootstrap test genesis certificate should not fail");
321
322        let last_certificates = certificate_store.get_latest_certificates(10).await.unwrap();
323
324        assert_eq!(1, last_certificates.len());
325        certificate_verifier
326            .verify_genesis_certificate(
327                &last_certificates[0],
328                &genesis_verifier.to_verification_key(),
329            )
330            .await
331            .expect(
332                "verify_genesis_certificate should successfully validate the genesis certificate",
333            );
334    }
335
336    #[test]
337    fn test_create_and_save_genesis_keypair() {
338        let temp_dir = get_temp_dir("test_create_and_save_genesis_keypair");
339        let (genesis_secret_key_path, genesis_verification_key_path) =
340            GenesisTools::create_and_save_genesis_keypair(&temp_dir)
341                .expect("Failed to create and save genesis keypair");
342        let genesis_secret_key = ProtocolGenesisSecretKey::from_json_hex(
343            &read_to_string(&genesis_secret_key_path)
344                .expect("Failed to read genesis secret key file"),
345        )
346        .expect("Failed to parse genesis secret key");
347        let genesis_verification_key = ProtocolGenesisVerificationKey::from_json_hex(
348            &read_to_string(&genesis_verification_key_path)
349                .expect("Failed to read genesis verification key file"),
350        )
351        .expect("Failed to parse genesis verification key");
352        let genesis_verifier =
353            ProtocolGenesisSigner::from_secret_key(genesis_secret_key).create_verifier();
354
355        let expected_genesis_verification_key = genesis_verifier.to_verification_key();
356        assert_eq!(expected_genesis_verification_key, genesis_verification_key);
357    }
358}