mithril_aggregator/tools/
genesis.rs1use 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 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 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 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 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 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}