mithril_aggregator/tools/
genesis.rs

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