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