From 852dd120dc3087c4988bbf9dba446c7bcd7725fc Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 6 Mar 2026 17:24:19 +0100 Subject: [PATCH 01/19] feat(stm): add SNARK aggregate verification key to protocol layer --- mithril-stm/src/lib.rs | 5 +- .../proof_system/halo2_snark/aggregate_key.rs | 176 +++++++++++++++++- .../src/proof_system/halo2_snark/clerk.rs | 116 ++++++++++++ .../proof_system/halo2_snark/eligibility.rs | 11 +- .../src/proof_system/halo2_snark/mod.rs | 4 +- mithril-stm/src/proof_system/mod.rs | 5 +- .../aggregate_signature/aggregate_key.rs | 24 ++- .../src/protocol/aggregate_signature/clerk.rs | 33 +++- .../src/protocol/key_registration/mod.rs | 1 - .../src/protocol/key_registration/register.rs | 8 + mithril-stm/src/protocol/mod.rs | 5 +- 11 files changed, 351 insertions(+), 37 deletions(-) create mode 100644 mithril-stm/src/proof_system/halo2_snark/clerk.rs diff --git a/mithril-stm/src/lib.rs b/mithril-stm/src/lib.rs index 9aa4019112d..0949d76e9e9 100644 --- a/mithril-stm/src/lib.rs +++ b/mithril-stm/src/lib.rs @@ -154,6 +154,9 @@ use hash::poseidon::MidnightPoseidonDigest; #[cfg(feature = "benchmark-internals")] pub use hash::poseidon::MidnightPoseidonDigest; +#[cfg(feature = "future_snark")] +pub use proof_system::AggregateVerificationKeyForSnark; + #[cfg(feature = "future_snark")] pub use protocol::{RegistrationEntryForSnark, VerificationKeyForSnark}; @@ -186,7 +189,7 @@ pub type LotteryTargetValue = crate::signature_scheme::BaseFieldElement; pub trait MembershipDigest: Clone { type ConcatenationHash: Digest + FixedOutput + Clone + Debug + Send + Sync; #[cfg(feature = "future_snark")] - type SnarkHash: Digest + FixedOutput + Clone + Debug + Send + Sync + Eq + PartialEq; + type SnarkHash: Digest + FixedOutput + Clone + Debug + Send + Sync; } /// Default Mithril Membership Digest diff --git a/mithril-stm/src/proof_system/halo2_snark/aggregate_key.rs b/mithril-stm/src/proof_system/halo2_snark/aggregate_key.rs index 5e228a200fc..8a1b4880f8c 100644 --- a/mithril-stm/src/proof_system/halo2_snark/aggregate_key.rs +++ b/mithril-stm/src/proof_system/halo2_snark/aggregate_key.rs @@ -1,15 +1,19 @@ use serde::{Deserialize, Serialize}; use crate::{ - ClosedKeyRegistration, MembershipDigest, - membership_commitment::{MerkleTreeCommitment, MerkleTreeSnarkLeaf}, - protocol::RegistrationEntryForSnark, + ClosedKeyRegistration, MembershipDigest, RegistrationEntryForSnark, Stake, StmResult, + membership_commitment::{MerkleTreeCommitment, MerkleTreeError, MerkleTreeSnarkLeaf}, }; -/// Aggregate verification key of the snark proof system. -#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +/// Aggregate verification key for the SNARK proof system. +/// +/// This key embeds the Merkle tree commitment over the SNARK registration entries +/// (Schnorr verification keys and lottery target values), along with the total +/// registered stake. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct AggregateVerificationKeyForSnark { merkle_tree_commitment: MerkleTreeCommitment, + total_stake: Stake, } impl AggregateVerificationKeyForSnark { @@ -19,15 +23,169 @@ impl AggregateVerificationKeyForSnark { ) -> &MerkleTreeCommitment { &self.merkle_tree_commitment } + + /// Get the total stake. + pub fn get_total_stake(&self) -> Stake { + self.total_stake + } + + /// Serialize the aggregate verification key for SNARK to bytes. + /// + /// Layout: `merkle_tree_commitment || total_stake (8 bytes BE)` + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend(self.merkle_tree_commitment.to_bytes()); + bytes.extend(self.total_stake.to_be_bytes()); + + bytes + } + + /// Deserialize the aggregate verification key for SNARK from bytes. + pub fn from_bytes(bytes: &[u8]) -> StmResult { + if bytes.len() < 8 { + return Err(MerkleTreeError::SerializationError.into()); + } + + let commitment_end = bytes.len() - 8; + let merkle_tree_commitment = MerkleTreeCommitment::from_bytes( + bytes + .get(..commitment_end) + .ok_or(MerkleTreeError::SerializationError)?, + )?; + + let mut u64_bytes = [0u8; 8]; + u64_bytes.copy_from_slice( + bytes + .get(commitment_end..commitment_end + 8) + .ok_or(MerkleTreeError::SerializationError)?, + ); + let total_stake = u64::from_be_bytes(u64_bytes); + + Ok(Self { + merkle_tree_commitment, + total_stake, + }) + } } +impl PartialEq for AggregateVerificationKeyForSnark { + fn eq(&self, other: &Self) -> bool { + self.merkle_tree_commitment.root == other.merkle_tree_commitment.root + && self.total_stake == other.total_stake + } +} + +impl Eq for AggregateVerificationKeyForSnark {} + impl From<&ClosedKeyRegistration> for AggregateVerificationKeyForSnark { - fn from(reg: &ClosedKeyRegistration) -> Self { - let key_registration_commitment_for_snark = - reg.to_merkle_tree::(); + fn from(registration: &ClosedKeyRegistration) -> Self { Self { - merkle_tree_commitment: key_registration_commitment_for_snark + merkle_tree_commitment: registration + .to_merkle_tree::() .to_merkle_tree_commitment(), + total_stake: registration.total_stake, + } + } +} + +#[cfg(test)] +mod tests { + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use crate::{ + Initializer, KeyRegistration, MithrilMembershipDigest, Parameters, RegistrationEntry, + proof_system::AggregateVerificationKeyForSnark, + proof_system::halo2_snark::clerk::SnarkClerk, + }; + + type D = MithrilMembershipDigest; + + fn setup_closed_registration( + number_of_parties: u64, + ) -> (Parameters, crate::ClosedKeyRegistration) { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let parameters = Parameters { + m: 10, + k: 5, + phi_f: 0.8, + }; + + let mut key_registration = KeyRegistration::initialize(); + for stake in 1..=number_of_parties { + let initializer = Initializer::new(parameters, stake, &mut rng); + let entry = RegistrationEntry::new( + initializer.get_verification_key_proof_of_possession_for_concatenation(), + initializer.stake, + #[cfg(feature = "future_snark")] + initializer.schnorr_verification_key, + ) + .unwrap(); + key_registration.register_by_entry(&entry).unwrap(); + } + + let closed_registration = key_registration.close_registration(¶meters).unwrap(); + (parameters, closed_registration) + } + + mod golden { + use super::*; + + const GOLDEN_BYTES: &[u8; 40] = &[ + 44, 84, 216, 246, 141, 120, 242, 182, 103, 85, 253, 105, 87, 28, 199, 233, 121, 66, 21, + 104, 195, 7, 166, 38, 168, 15, 50, 78, 108, 149, 244, 92, 0, 0, 0, 0, 0, 0, 0, 3, + ]; + + fn golden_value() -> AggregateVerificationKeyForSnark { + let (_parameters, closed_registration) = setup_closed_registration(2); + let clerk = SnarkClerk::new_clerk_from_closed_key_registration(&closed_registration); + + clerk.compute_aggregate_verification_key_for_snark() + } + + #[test] + fn golden_conversions() { + let value = AggregateVerificationKeyForSnark::::from_bytes(GOLDEN_BYTES) + .expect("This from bytes should not fail"); + assert_eq!(golden_value(), value); + + let serialized = AggregateVerificationKeyForSnark::::to_bytes(&value); + let golden_serialized = + AggregateVerificationKeyForSnark::::to_bytes(&golden_value()); + assert_eq!(golden_serialized, serialized); + } + } + + mod golden_json { + use super::*; + + const GOLDEN_JSON: &str = r#" + { + "merkle_tree_commitment":{ + "root":[44,84,216,246,141,120,242,182,103,85,253,105,87,28,199,233,121,66,21,104,195,7,166,38,168,15,50,78,108,149,244,92], + "hasher":null + }, + "total_stake":3 + } + "#; + + fn golden_value() -> AggregateVerificationKeyForSnark { + let (_parameters, closed_registration) = setup_closed_registration(2); + let clerk = SnarkClerk::new_clerk_from_closed_key_registration(&closed_registration); + + clerk.compute_aggregate_verification_key_for_snark() + } + + #[test] + fn golden_conversions() { + let value: AggregateVerificationKeyForSnark = serde_json::from_str(GOLDEN_JSON) + .expect("This JSON deserialization should not fail"); + + let serialized = + serde_json::to_string(&value).expect("This JSON serialization should not fail"); + let golden_serialized = serde_json::to_string(&golden_value()) + .expect("This JSON serialization should not fail"); + assert_eq!(golden_serialized, serialized); } } } diff --git a/mithril-stm/src/proof_system/halo2_snark/clerk.rs b/mithril-stm/src/proof_system/halo2_snark/clerk.rs new file mode 100644 index 00000000000..be8e58fa9e6 --- /dev/null +++ b/mithril-stm/src/proof_system/halo2_snark/clerk.rs @@ -0,0 +1,116 @@ +use crate::{ + ClosedKeyRegistration, MembershipDigest, Signer, proof_system::AggregateVerificationKeyForSnark, +}; + +/// Clerk for managing the SNARK proof system. +/// +/// Responsible for computing the SNARK aggregate verification key from +/// a closed key registration. This is the SNARK counterpart of the +/// `ConcatenationClerk`. +#[derive(Debug, Clone)] +pub struct SnarkClerk { + /// The closed key registration associated with this clerk. + pub(crate) closed_key_registration: ClosedKeyRegistration, +} + +impl SnarkClerk { + /// Create a new `SnarkClerk` from a closed registration instance. + pub fn new_clerk_from_closed_key_registration( + closed_key_registration: &ClosedKeyRegistration, + ) -> Self { + Self { + closed_key_registration: closed_key_registration.clone(), + } + } + + /// Create a `SnarkClerk` from a signer. + pub fn new_clerk_from_signer(signer: &Signer) -> Self { + Self { + closed_key_registration: signer.closed_key_registration.clone(), + } + } + + /// Compute the SNARK aggregate verification key from the closed registration. + pub fn compute_aggregate_verification_key_for_snark( + &self, + ) -> AggregateVerificationKeyForSnark { + AggregateVerificationKeyForSnark::from(&self.closed_key_registration) + } +} + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use crate::{ + Initializer, KeyRegistration, MithrilMembershipDigest, Parameters, RegistrationEntry, + }; + + use super::*; + + type D = MithrilMembershipDigest; + + proptest! { + #![proptest_config(ProptestConfig::with_cases(50))] + + #[test] + fn compute_snark_avk( + seed in any::<[u8; 32]>(), + number_of_parties in 1_usize..10, + m in 1_u64..20, + k in 1_u64..10, + phi_f in 0.1_f64..1.0, + ) { + let parameters = Parameters { m, k, phi_f }; + let mut rng = ChaCha20Rng::from_seed(seed); + + let mut key_registration = KeyRegistration::initialize(); + let mut initializers = Vec::new(); + + for i in 0..number_of_parties { + let stake = (i as u64 + 1) * 10; + let initializer = Initializer::new(parameters, stake, &mut rng); + let entry = RegistrationEntry::new( + initializer.get_verification_key_proof_of_possession_for_concatenation(), + initializer.stake, + #[cfg(feature = "future_snark")] + initializer.schnorr_verification_key, + ) + .unwrap(); + key_registration.register_by_entry(&entry).unwrap(); + initializers.push(initializer); + } + + let closed_registration = key_registration.close_registration(¶meters).unwrap(); + + let signers: Vec<_> = initializers + .into_iter() + .map(|init| init.try_create_signer::(&closed_registration).unwrap()) + .collect(); + + let clerk_from_registration = + SnarkClerk::new_clerk_from_closed_key_registration(&closed_registration); + let clerk_from_signer = SnarkClerk::new_clerk_from_signer::(&signers[0]); + + let avk_from_registration: AggregateVerificationKeyForSnark = + clerk_from_registration.compute_aggregate_verification_key_for_snark(); + let avk_from_signer: AggregateVerificationKeyForSnark = + clerk_from_signer.compute_aggregate_verification_key_for_snark(); + + let expected_total_stake: u64 = (1..=number_of_parties as u64).map(|i| i * 10).sum(); + prop_assert_eq!(avk_from_registration.get_total_stake(), expected_total_stake); + prop_assert_eq!(&avk_from_registration, &avk_from_signer); + + let bytes = avk_from_registration.to_bytes(); + let deserialized = AggregateVerificationKeyForSnark::::from_bytes(&bytes) + .expect("deserialization should succeed"); + prop_assert_eq!(&avk_from_registration, &deserialized); + + let avk_second: AggregateVerificationKeyForSnark = + clerk_from_registration.compute_aggregate_verification_key_for_snark(); + prop_assert_eq!(&avk_from_registration, &avk_second); + } + } +} diff --git a/mithril-stm/src/proof_system/halo2_snark/eligibility.rs b/mithril-stm/src/proof_system/halo2_snark/eligibility.rs index 603f8d9e197..a1678417158 100644 --- a/mithril-stm/src/proof_system/halo2_snark/eligibility.rs +++ b/mithril-stm/src/proof_system/halo2_snark/eligibility.rs @@ -2,6 +2,12 @@ use anyhow::anyhow; use crate::{PhiFValue, RegisterError}; +#[cfg(feature = "future_snark")] +use crate::{ + LotteryIndex, LotteryTargetValue, SignatureError, StmResult, UniqueSchnorrSignature, + signature_scheme::{BaseFieldElement, DOMAIN_SEPARATION_TAG_LOTTERY, compute_poseidon_digest}, +}; + cfg_num_integer! { use num_bigint::BigInt; use num_integer::Integer; @@ -9,10 +15,7 @@ cfg_num_integer! { use num_traits::{Num, One}; #[cfg(feature = "future_snark")] - use crate::{ - LotteryIndex, LotteryTargetValue, SignatureError, Stake, StmResult, UniqueSchnorrSignature, - signature_scheme::{BaseFieldElement, compute_poseidon_digest, DOMAIN_SEPARATION_TAG_LOTTERY}, - }; + use crate::Stake; /// Modulus of the Jubjub Base Field as a hexadecimal number const JUBJUB_BASE_FIELD_MODULUS: &str = "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"; diff --git a/mithril-stm/src/proof_system/halo2_snark/mod.rs b/mithril-stm/src/proof_system/halo2_snark/mod.rs index 54271366c25..ec81483fa2b 100644 --- a/mithril-stm/src/proof_system/halo2_snark/mod.rs +++ b/mithril-stm/src/proof_system/halo2_snark/mod.rs @@ -1,10 +1,12 @@ mod aggregate_key; +mod clerk; mod eligibility; mod message; mod signer; mod single_signature; -pub(crate) use aggregate_key::AggregateVerificationKeyForSnark; +pub use aggregate_key::AggregateVerificationKeyForSnark; +pub(crate) use clerk::SnarkClerk; pub(crate) use eligibility::{ compute_target_value_for_snark_lottery, compute_winning_lottery_indices, }; diff --git a/mithril-stm/src/proof_system/mod.rs b/mithril-stm/src/proof_system/mod.rs index 6bcb5f76437..4d7de1ce57d 100644 --- a/mithril-stm/src/proof_system/mod.rs +++ b/mithril-stm/src/proof_system/mod.rs @@ -25,8 +25,9 @@ pub use concatenation::{ }; pub(crate) use concatenation::{ConcatenationProofSigner, SingleSignatureForConcatenation}; +#[cfg(feature = "future_snark")] +pub use halo2_snark::AggregateVerificationKeyForSnark; #[cfg(feature = "future_snark")] pub(crate) use halo2_snark::{ - AggregateVerificationKeyForSnark, SingleSignatureForSnark, SnarkProofSigner, - compute_target_value_for_snark_lottery, + SingleSignatureForSnark, SnarkClerk, SnarkProofSigner, compute_target_value_for_snark_lottery, }; diff --git a/mithril-stm/src/protocol/aggregate_signature/aggregate_key.rs b/mithril-stm/src/protocol/aggregate_signature/aggregate_key.rs index cf0d5b487bd..7514d068a44 100644 --- a/mithril-stm/src/protocol/aggregate_signature/aggregate_key.rs +++ b/mithril-stm/src/protocol/aggregate_signature/aggregate_key.rs @@ -5,18 +5,22 @@ use crate::{ #[cfg(feature = "future_snark")] use crate::proof_system::AggregateVerificationKeyForSnark; -/// Aggregate verification key -#[derive(Debug, Clone, Eq, PartialEq)] +/// Aggregate verification key combining both the concatenation and SNARK proof systems. +/// +/// Holds the concatenation aggregate verification key used in the current Mithril protocol, +/// and optionally the SNARK aggregate verification key when the `future_snark` feature is +/// enabled. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct AggregateVerificationKey { /// Concatenation aggregate verification key. concatenation_aggregate_verification_key: AggregateVerificationKeyForConcatenation, - /// Snark aggregate verification key. + /// SNARK aggregate verification key (when `future_snark` feature is enabled). #[cfg(feature = "future_snark")] snark_aggregate_verification_key: Option>, } impl AggregateVerificationKey { - /// Creates a new aggregate verification key + /// Create a new aggregate verification key. pub fn new( concatenation_aggregate_verification_key: AggregateVerificationKeyForConcatenation, #[cfg(feature = "future_snark")] snark_aggregate_verification_key: Option< @@ -30,14 +34,14 @@ impl AggregateVerificationKey { } } - /// Returns the concatenation aggregate verification key + /// Returns the concatenation aggregate verification key. pub fn to_concatenation_aggregate_verification_key( &self, ) -> &AggregateVerificationKeyForConcatenation { &self.concatenation_aggregate_verification_key } - /// Returns the snark aggregate verification key + /// Returns the SNARK aggregate verification key, if present. #[cfg(feature = "future_snark")] pub fn to_snark_aggregate_verification_key( &self, @@ -47,12 +51,14 @@ impl AggregateVerificationKey { } impl From<&ClosedKeyRegistration> for AggregateVerificationKey { - fn from(reg: &ClosedKeyRegistration) -> Self { + fn from(registration: &ClosedKeyRegistration) -> Self { AggregateVerificationKey { concatenation_aggregate_verification_key: - AggregateVerificationKeyForConcatenation::from(reg), + AggregateVerificationKeyForConcatenation::from(registration), #[cfg(feature = "future_snark")] - snark_aggregate_verification_key: Some(AggregateVerificationKeyForSnark::from(reg)), + snark_aggregate_verification_key: registration + .has_snark_verification_keys() + .then(|| AggregateVerificationKeyForSnark::from(registration)), } } } diff --git a/mithril-stm/src/protocol/aggregate_signature/clerk.rs b/mithril-stm/src/protocol/aggregate_signature/clerk.rs index 0f7f3435a2c..333d5a3c969 100644 --- a/mithril-stm/src/protocol/aggregate_signature/clerk.rs +++ b/mithril-stm/src/protocol/aggregate_signature/clerk.rs @@ -10,15 +10,24 @@ use crate::{ proof_system::{ConcatenationClerk, ConcatenationProof}, }; +#[cfg(feature = "future_snark")] +use crate::proof_system::SnarkClerk; + use super::{AggregateSignature, AggregateSignatureType}; #[cfg(feature = "future_snark")] use super::AggregationError; /// Clerk for aggregate signatures. +/// +/// Manages both the concatenation proof clerk and, when the `future_snark` +/// feature is enabled, the SNARK proof clerk. Provides methods for signature +/// aggregation and aggregate verification key computation. #[derive(Debug, Clone)] pub struct Clerk { concatenation_proof_clerk: ConcatenationClerk, + #[cfg(feature = "future_snark")] + snark_proof_clerk: Option, phantom_data: PhantomData, } @@ -27,6 +36,11 @@ impl Clerk { pub fn new_clerk_from_signer(signer: &Signer) -> Self { Self { concatenation_proof_clerk: ConcatenationClerk::new_clerk_from_signer(signer), + #[cfg(feature = "future_snark")] + snark_proof_clerk: signer + .closed_key_registration + .has_snark_verification_keys() + .then(|| SnarkClerk::new_clerk_from_signer(signer)), phantom_data: PhantomData, } } @@ -34,15 +48,21 @@ impl Clerk { /// Create a Clerk from a closed key registration. pub fn new_clerk_from_closed_key_registration( parameters: &Parameters, - closed_reg: &ClosedKeyRegistration, + closed_registration: &ClosedKeyRegistration, ) -> Self { Self { concatenation_proof_clerk: ConcatenationClerk::new_clerk_from_closed_key_registration( - parameters, closed_reg, + parameters, + closed_registration, ), + #[cfg(feature = "future_snark")] + snark_proof_clerk: closed_registration + .has_snark_verification_keys() + .then(|| SnarkClerk::new_clerk_from_closed_key_registration(closed_registration)), phantom_data: PhantomData, } } + /// Aggregate a set of signatures with a given proof type. pub fn aggregate_signatures_with_type( &self, @@ -72,16 +92,15 @@ impl Clerk { &self.concatenation_proof_clerk } - /// Compute the aggregate verification key. - /// It computes only the concatenation aggregate verification key for now. - // TODO: Replace None with the actual SNARK verification key when implementing - // SNARK aggregation primitives. + /// Compute the aggregate verification key covering both proof systems. pub fn compute_aggregate_verification_key(&self) -> AggregateVerificationKey { AggregateVerificationKey::new( self.concatenation_proof_clerk .compute_aggregate_verification_key_for_concatenation(), #[cfg(feature = "future_snark")] - None, + self.snark_proof_clerk + .as_ref() + .map(|clerk| clerk.compute_aggregate_verification_key_for_snark()), ) } diff --git a/mithril-stm/src/protocol/key_registration/mod.rs b/mithril-stm/src/protocol/key_registration/mod.rs index 674c7326bab..730522fc977 100644 --- a/mithril-stm/src/protocol/key_registration/mod.rs +++ b/mithril-stm/src/protocol/key_registration/mod.rs @@ -10,6 +10,5 @@ pub use closed_registration_entry::ClosedRegistrationEntry; pub use concatenation_registration_entry::RegistrationEntryForConcatenation; pub use register::{ClosedKeyRegistration, KeyRegistration}; pub use registration_entry::RegistrationEntry; - #[cfg(feature = "future_snark")] pub use snark_registration_entry::RegistrationEntryForSnark; diff --git a/mithril-stm/src/protocol/key_registration/register.rs b/mithril-stm/src/protocol/key_registration/register.rs index 7b48bf773c0..593950ce915 100644 --- a/mithril-stm/src/protocol/key_registration/register.rs +++ b/mithril-stm/src/protocol/key_registration/register.rs @@ -123,6 +123,14 @@ impl ClosedKeyRegistration { .map(|s| s as u64) } + /// Check if any registration entry has a SNARK verification key. + #[cfg(feature = "future_snark")] + pub fn has_snark_verification_keys(&self) -> bool { + self.closed_registration_entries + .iter() + .any(|entry| entry.get_verification_key_for_snark().is_some()) + } + /// Get the closed registration entry for a given signer index. pub fn get_registration_entry_for_index( &self, diff --git a/mithril-stm/src/protocol/mod.rs b/mithril-stm/src/protocol/mod.rs index 23339257cab..dc644b99fbd 100644 --- a/mithril-stm/src/protocol/mod.rs +++ b/mithril-stm/src/protocol/mod.rs @@ -10,6 +10,8 @@ pub use aggregate_signature::{ AggregationError, Clerk, }; pub use error::RegisterError; +#[cfg(feature = "future_snark")] +pub use key_registration::RegistrationEntryForSnark; pub use key_registration::{ ClosedKeyRegistration, ClosedRegistrationEntry, KeyRegistration, RegistrationEntry, RegistrationEntryForConcatenation, @@ -18,9 +20,6 @@ pub use parameters::Parameters; pub use participant::{Initializer, Signer}; pub use single_signature::{SignatureError, SingleSignature, SingleSignatureWithRegisteredParty}; -#[cfg(feature = "future_snark")] -pub use key_registration::RegistrationEntryForSnark; - /// Wrapper of the Concatenation proof Verification key with proof of possession pub type VerificationKeyProofOfPossessionForConcatenation = crate::signature_scheme::BlsVerificationKeyProofOfPossession; From 17f72f7e5ca22a7f771a39357cba78a3809524d3 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 6 Mar 2026 17:24:34 +0100 Subject: [PATCH 02/19] feat(common): wire SNARK AVK into crypto helper types and protocol message --- .../src/crypto_helper/codec/binary.rs | 18 ++- .../src/crypto_helper/types/wrappers.rs | 14 ++- mithril-common/src/entities/certificate.rs | 110 +++++++++++++++--- .../src/entities/protocol_message.rs | 24 ++++ 4 files changed, 150 insertions(+), 16 deletions(-) diff --git a/mithril-common/src/crypto_helper/codec/binary.rs b/mithril-common/src/crypto_helper/codec/binary.rs index 8cfacc7c930..4fc314635aa 100644 --- a/mithril-common/src/crypto_helper/codec/binary.rs +++ b/mithril-common/src/crypto_helper/codec/binary.rs @@ -30,13 +30,13 @@ pub trait TryFromBytes: Sized { mod binary_mithril_stm { - #[cfg(feature = "future_snark")] - use mithril_stm::VerificationKeyForSnark; use mithril_stm::{ AggregateSignature, AggregateVerificationKeyForConcatenation, Initializer, MithrilMembershipDigest, Parameters, SingleSignature, SingleSignatureWithRegisteredParty, VerificationKeyForConcatenation, VerificationKeyProofOfPossessionForConcatenation, }; + #[cfg(feature = "future_snark")] + use mithril_stm::{AggregateVerificationKeyForSnark, VerificationKeyForSnark}; use super::*; @@ -141,6 +141,20 @@ mod binary_mithril_stm { } } + #[cfg(feature = "future_snark")] + impl TryToBytes for AggregateVerificationKeyForSnark { + fn to_bytes_vec(&self) -> StdResult> { + Ok(self.to_bytes()) + } + } + + #[cfg(feature = "future_snark")] + impl TryFromBytes for AggregateVerificationKeyForSnark { + fn try_from_bytes(bytes: &[u8]) -> StdResult { + Self::from_bytes(bytes) + } + } + impl TryToBytes for Initializer { fn to_bytes_vec(&self) -> StdResult> { Ok(self.to_bytes().to_vec()) diff --git a/mithril-common/src/crypto_helper/types/wrappers.rs b/mithril-common/src/crypto_helper/types/wrappers.rs index e00aa6d616b..6f9db99aaef 100644 --- a/mithril-common/src/crypto_helper/types/wrappers.rs +++ b/mithril-common/src/crypto_helper/types/wrappers.rs @@ -1,5 +1,7 @@ use kes_summed_ed25519::kes::Sum6KesSig; #[cfg(feature = "future_snark")] +use mithril_stm::AggregateVerificationKeyForSnark; +#[cfg(feature = "future_snark")] use mithril_stm::VerificationKeyForSnark; use mithril_stm::{ AggregateSignature, AggregateVerificationKey, AggregateVerificationKeyForConcatenation, @@ -44,6 +46,11 @@ pub type ProtocolAggregateVerificationKey = AggregateVerificationKey>; +/// Wrapper of [MithrilStm:AggregateVerificationKeyForSnark](struct@AggregateVerificationKeyForSnark). +#[cfg(feature = "future_snark")] +pub type ProtocolAggregateVerificationKeyForSnark = + ProtocolKey>; + /// Wrapper of [MKProof] to add serialization utilities. pub type ProtocolMkProof = ProtocolKey>; @@ -58,5 +65,10 @@ impl_codec_and_type_conversions_for_protocol_key!( #[cfg(feature = "future_snark")] impl_codec_and_type_conversions_for_protocol_key!( - bytes_hex_codec => VerificationKeyForSnark + json_hex_codec => VerificationKeyForSnark +); + +#[cfg(feature = "future_snark")] +impl_codec_and_type_conversions_for_protocol_key!( + bytes_hex_codec => AggregateVerificationKeyForSnark ); diff --git a/mithril-common/src/entities/certificate.rs b/mithril-common/src/entities/certificate.rs index 3f841a46140..aca018df6db 100644 --- a/mithril-common/src/entities/certificate.rs +++ b/mithril-common/src/entities/certificate.rs @@ -1,11 +1,16 @@ +use std::fmt::{Debug, Formatter}; + +use sha2::{Digest, Sha256}; + +use mithril_stm::AggregateSignatureType; + +#[cfg(feature = "future_snark")] +use crate::crypto_helper::ProtocolAggregateVerificationKeyForSnark; use crate::crypto_helper::{ ProtocolAggregateVerificationKey, ProtocolAggregateVerificationKeyForConcatenation, ProtocolGenesisSignature, ProtocolMultiSignature, }; use crate::entities::{CertificateMetadata, Epoch, ProtocolMessage, SignedEntityType}; -use std::fmt::{Debug, Formatter}; - -use sha2::{Digest, Sha256}; /// The signature of a [Certificate] #[derive(Clone, Debug)] @@ -19,6 +24,20 @@ pub enum CertificateSignature { MultiSignature(SignedEntityType, ProtocolMultiSignature), } +impl CertificateSignature { + /// Return the aggregate signature type of the certificate signature. + /// + /// Returns `None` for genesis certificates as they do not carry a multi-signature. + pub fn aggregate_signature_type(&self) -> Option { + match self { + CertificateSignature::GenesisSignature(_) => None, + CertificateSignature::MultiSignature(_, multi_signature) => { + Some((&multi_signature.key).into()) + } + } + } +} + /// Certificate represents a Mithril certificate embedding a Mithril STM multisignature #[derive(Clone)] pub struct Certificate { @@ -48,11 +67,17 @@ pub struct Certificate { /// aka H(MSG(p,n) || AVK(n-1)) pub signed_message: String, - /// Aggregate verification key - /// The AVK used to sign during the current epoch + /// Aggregate verification key for Concatenation + /// The AVK used to sign for Concatenation during the current epoch /// aka AVK(n-2) pub aggregate_verification_key: ProtocolAggregateVerificationKeyForConcatenation, + /// Aggregate verification key for SNARK + /// The AVK used to sign for SNARK during the current epoch + /// aka AVKS(n-2) + #[cfg(feature = "future_snark")] + pub aggregate_verification_key_snark: Option, + /// Certificate signature pub signature: CertificateSignature, } @@ -68,6 +93,12 @@ impl Certificate { signature: CertificateSignature, ) -> Certificate { let signed_message = protocol_message.compute_hash(); + + #[cfg(feature = "future_snark")] + let aggregate_verification_key_snark = aggregate_verification_key + .to_snark_aggregate_verification_key() + .map(|avk| avk.to_owned().into()); + let mut certificate = Certificate { hash: "".to_string(), previous_hash: previous_hash.into(), @@ -79,6 +110,8 @@ impl Certificate { .to_concatenation_aggregate_verification_key() .to_owned() .into(), + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark, signature, }; certificate.hash = certificate.compute_hash(); @@ -106,6 +139,16 @@ impl Certificate { hex::encode(hasher.finalize()) } + /// Strip the SNARK aggregate verification key from the certificate and recompute its hash. + /// + /// Used during Pythagoras era to ensure SNARK AVK is not included in certificates + /// even when the `future_snark` feature is compiled in. + #[cfg(feature = "future_snark")] + pub fn strip_snark_aggregate_verification_key(&mut self) { + self.aggregate_verification_key_snark = None; + self.hash = self.compute_hash(); + } + /// Tell if the certificate is a genesis certificate pub fn is_genesis(&self) -> bool { matches!(self.signature, CertificateSignature::GenesisSignature(_)) @@ -132,11 +175,16 @@ impl Certificate { /// Create the aggregate verification key from the certificate. pub fn create_aggregate_verification_key(&self) -> ProtocolAggregateVerificationKey { - let aggregate_verification_key = &self.aggregate_verification_key; + let aggregate_verification_key_for_concatenation = &self.aggregate_verification_key; + #[cfg(feature = "future_snark")] + let snark_aggregate_verification_key = self + .aggregate_verification_key_snark + .as_ref() + .map(|avk| avk.to_owned().into()); ProtocolAggregateVerificationKey::new( - aggregate_verification_key.to_owned().into(), + aggregate_verification_key_for_concatenation.to_owned().into(), #[cfg(feature = "future_snark")] - None, + snark_aggregate_verification_key, ) } } @@ -163,13 +211,25 @@ impl Debug for Certificate { .field("signed_message", &self.signed_message); match should_be_exhaustive { - true => debug - .field( + true => { + debug.field( "aggregate_verification_key", &format_args!("{:?}", self.aggregate_verification_key.to_json_hex()), - ) - .field("signature", &format_args!("{:?}", self.signature)) - .finish(), + ); + #[cfg(feature = "future_snark")] + debug.field( + "aggregate_verification_key_snark", + &format_args!( + "{:?}", + self.aggregate_verification_key_snark + .as_ref() + .map(|avk| avk.to_bytes_hex()) + ), + ); + debug + .field("signature", &format_args!("{:?}", self.signature)) + .finish() + } false => debug.finish_non_exhaustive(), } } @@ -341,6 +401,30 @@ mod tests { ); } + #[cfg(feature = "future_snark")] + #[test] + fn snark_aggregate_verification_key_does_not_affect_certificate_hash() { + use crate::test::builder::MithrilFixtureBuilder; + + let fixture = MithrilFixtureBuilder::default().with_signers(3).build(); + let certificate = fixture.create_genesis_certificate("testnet", Epoch(1)); + let original_hash = certificate.compute_hash(); + + assert!( + certificate.aggregate_verification_key_snark.is_some(), + "Certificate should have a SNARK AVK when future_snark is enabled" + ); + + let mut certificate_without_snark_avk = certificate; + certificate_without_snark_avk.aggregate_verification_key_snark = None; + + assert_eq!( + original_hash, + certificate_without_snark_avk.compute_hash(), + "SNARK AVK should not affect the certificate hash for backward compatibility" + ); + } + #[test] fn test_genesis_certificate_compute_hash() { const HASH_EXPECTED: &str = diff --git a/mithril-common/src/entities/protocol_message.rs b/mithril-common/src/entities/protocol_message.rs index a727681142f..26fd9330162 100644 --- a/mithril-common/src/entities/protocol_message.rs +++ b/mithril-common/src/entities/protocol_message.rs @@ -52,6 +52,13 @@ pub enum ProtocolMessagePartKey { /// The ProtocolMessage part key associated to the Cardano database Merkle root #[serde(rename = "cardano_database_merkle_root")] CardanoDatabaseMerkleRoot, + + /// The ProtocolMessage part key associated to the Next epoch SNARK aggregate verification key + /// + /// The SNARK AVK that will be allowed to be used to sign during the next epoch + /// aka AVKS(n-1) + #[serde(rename = "next_aggregate_verification_key_snark")] + NextSnarkAggregateVerificationKey, } impl Display for ProtocolMessagePartKey { @@ -71,6 +78,9 @@ impl Display for ProtocolMessagePartKey { write!(f, "cardano_stake_distribution_merkle_root") } Self::CardanoDatabaseMerkleRoot => write!(f, "cardano_database_merkle_root"), + Self::NextSnarkAggregateVerificationKey => { + write!(f, "next_aggregate_verification_key_snark") + } } } } @@ -239,6 +249,20 @@ mod tests { assert_ne!(hash_before_change, protocol_message_modified.compute_hash()); } + #[test] + fn test_protocol_message_compute_hash_include_next_snark_aggregate_verification_key() { + let protocol_message = ProtocolMessage::new(); + let hash_before_change = protocol_message.compute_hash(); + + let mut protocol_message_modified = protocol_message.clone(); + protocol_message_modified.set_message_part( + ProtocolMessagePartKey::NextSnarkAggregateVerificationKey, + "next-snark-avk-456".to_string(), + ); + + assert_ne!(hash_before_change, protocol_message_modified.compute_hash()); + } + #[test] fn test_protocol_message_compute_hash_include_next_protocol_parameters() { let protocol_message = build_protocol_message_reference(); From 8acf1c83897b5471e81eaecc90ee739407b5b737 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 6 Mar 2026 17:24:38 +0100 Subject: [PATCH 03/19] feat(common): add SNARK AVK to genesis certificate and certificate verifier --- .../certificate_chain/certificate_genesis.rs | 53 ++- .../certificate_chain/certificate_verifier.rs | 314 ++++++++++++++++-- mithril-common/src/messages/certificate.rs | 96 +++++- 3 files changed, 432 insertions(+), 31 deletions(-) diff --git a/mithril-common/src/certificate_chain/certificate_genesis.rs b/mithril-common/src/certificate_chain/certificate_genesis.rs index c7f242813b0..514b98706e5 100644 --- a/mithril-common/src/certificate_chain/certificate_genesis.rs +++ b/mithril-common/src/certificate_chain/certificate_genesis.rs @@ -18,6 +18,9 @@ use crate::{ protocol::ToMessage, }; +#[cfg(feature = "future_snark")] +use crate::crypto_helper::ProtocolAggregateVerificationKeyForSnark; + /// [CertificateGenesisProducer] related errors. #[derive(Error, Debug)] pub enum CertificateGenesisProducerError { @@ -46,12 +49,34 @@ impl CertificateGenesisProducer { ) -> StdResult { let genesis_aggregate_verification_key_for_concatenation = ProtocolKey::new(genesis_avk.to_concatenation_aggregate_verification_key().to_owned()); - let genesis_avk = genesis_aggregate_verification_key_for_concatenation.to_json_hex()?; + let genesis_concatenation_avk = + genesis_aggregate_verification_key_for_concatenation.to_json_hex()?; let mut protocol_message = ProtocolMessage::new(); protocol_message.set_message_part( ProtocolMessagePartKey::NextAggregateVerificationKey, - genesis_avk, + genesis_concatenation_avk, ); + + #[cfg(feature = "future_snark")] + { + match genesis_avk.to_snark_aggregate_verification_key() { + Some(snark_avk) => { + let genesis_snark_avk: ProtocolAggregateVerificationKeyForSnark = + ProtocolKey::new(snark_avk.to_owned()); + protocol_message.set_message_part( + ProtocolMessagePartKey::NextSnarkAggregateVerificationKey, + genesis_snark_avk.to_bytes_hex()?, + ); + } + None => { + eprintln!( + "WARNING: SNARK aggregate verification key is unavailable, \ + genesis certificate will not include SNARK AVK" + ); + } + } + } + protocol_message.set_message_part( ProtocolMessagePartKey::NextProtocolParameters, genesis_protocol_parameters.compute_hash(), @@ -148,4 +173,28 @@ mod tests { Some(&expected_genesis_epoch) ); } + + #[cfg(feature = "future_snark")] + #[test] + fn genesis_protocol_message_includes_snark_aggregate_verification_key() { + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let genesis_protocol_parameters = fixture.protocol_parameters(); + let genesis_avk = fixture.compute_aggregate_verification_key(); + let genesis_epoch = Epoch(123); + let protocol_message = CertificateGenesisProducer::create_genesis_protocol_message( + &genesis_protocol_parameters, + &genesis_avk, + &genesis_epoch, + ) + .unwrap(); + + let expected_snark_avk_value = fixture + .compute_and_encode_snark_aggregate_verification_key() + .expect("SNARK AVK should be available"); + assert_eq!( + protocol_message + .get_message_part(&ProtocolMessagePartKey::NextSnarkAggregateVerificationKey), + Some(&expected_snark_avk_value) + ); + } } diff --git a/mithril-common/src/certificate_chain/certificate_verifier.rs b/mithril-common/src/certificate_chain/certificate_verifier.rs index 77d5bf2ee29..fc41f85bbbb 100644 --- a/mithril-common/src/certificate_chain/certificate_verifier.rs +++ b/mithril-common/src/certificate_chain/certificate_verifier.rs @@ -7,17 +7,22 @@ use slog::{Logger, debug}; use std::sync::Arc; use thiserror::Error; -use super::CertificateRetriever; +use mithril_stm::AggregateSignatureType; + use crate::StdResult; +#[cfg(feature = "future_snark")] +use crate::crypto_helper::ProtocolAggregateVerificationKeyForSnark; use crate::crypto_helper::{ - ProtocolAggregateVerificationKey, ProtocolGenesisError, ProtocolGenesisVerificationKey, - ProtocolMultiSignature, + ProtocolAggregateVerificationKey, ProtocolAggregateVerificationKeyForConcatenation, + ProtocolGenesisError, ProtocolGenesisVerificationKey, ProtocolMultiSignature, }; use crate::entities::{ Certificate, CertificateSignature, ProtocolMessagePartKey, ProtocolParameters, }; use crate::logging::LoggerExtensions; +use super::CertificateRetriever; + #[cfg(test)] use mockall::automock; @@ -253,6 +258,31 @@ impl MithrilCertificateVerifier { &self, certificate: &Certificate, previous_certificate: &Certificate, + ) -> StdResult<()> { + let aggregate_signature_type = certificate + .signature + .aggregate_signature_type() + .ok_or(CertificateVerifierError::InvalidStandardCertificateProvided)?; + + match aggregate_signature_type { + AggregateSignatureType::Concatenation => self + .verify_concatenation_aggregate_verification_key_chaining( + certificate, + previous_certificate, + ), + #[cfg(feature = "future_snark")] + AggregateSignatureType::Future => self + .verify_snark_aggregate_verification_key_chaining( + certificate, + previous_certificate, + ), + } + } + + fn verify_concatenation_aggregate_verification_key_chaining( + &self, + certificate: &Certificate, + previous_certificate: &Certificate, ) -> StdResult<()> { let previous_certificate_has_same_epoch = previous_certificate.epoch == certificate.epoch; let certificate_has_valid_aggregate_verification_key = @@ -260,24 +290,18 @@ impl MithrilCertificateVerifier { previous_certificate.aggregate_verification_key == certificate.aggregate_verification_key } else { - match &previous_certificate + previous_certificate .protocol_message .get_message_part(&ProtocolMessagePartKey::NextAggregateVerificationKey) - { - Some(previous_certificate_next_aggregate_verification_key) => { - **previous_certificate_next_aggregate_verification_key - == certificate - .aggregate_verification_key - .to_json_hex() - .with_context(|| { - format!( - "aggregate verification key to string conversion error for certificate: `{}`", - certificate.hash - ) - })? - } - None => false, - } + .and_then(|encoded_next_avk| { + ProtocolAggregateVerificationKeyForConcatenation::try_from( + encoded_next_avk.as_str(), + ) + .ok() + }) + .is_some_and(|decoded_next_avk| { + decoded_next_avk == certificate.aggregate_verification_key + }) }; if !certificate_has_valid_aggregate_verification_key { debug!( @@ -292,6 +316,50 @@ impl MithrilCertificateVerifier { Ok(()) } + #[cfg(feature = "future_snark")] + fn verify_snark_aggregate_verification_key_chaining( + &self, + certificate: &Certificate, + previous_certificate: &Certificate, + ) -> StdResult<()> { + let previous_certificate_has_same_epoch = previous_certificate.epoch == certificate.epoch; + let certificate_has_valid_snark_avk = if previous_certificate_has_same_epoch { + match ( + &certificate.aggregate_verification_key_snark, + &previous_certificate.aggregate_verification_key_snark, + ) { + (Some(current), Some(previous)) => current == previous, + _ => false, + } + } else { + previous_certificate + .protocol_message + .get_message_part(&ProtocolMessagePartKey::NextSnarkAggregateVerificationKey) + .and_then(|encoded_next_snark_avk| { + ProtocolAggregateVerificationKeyForSnark::try_from( + encoded_next_snark_avk.as_str(), + ) + .ok() + }) + .is_some_and(|decoded_next_snark_avk| { + certificate.aggregate_verification_key_snark.as_ref().is_some_and( + |current_snark_avk| *current_snark_avk == decoded_next_snark_avk, + ) + }) + }; + if !certificate_has_valid_snark_avk { + debug!( + self.logger, + "Previous certificate {:#?}", previous_certificate + ); + return Err(anyhow!( + CertificateVerifierError::CertificateChainAVKUnmatch + )); + } + + Ok(()) + } + fn verify_protocol_parameters_chaining( &self, certificate: &Certificate, @@ -1140,4 +1208,212 @@ mod tests { error ) } + + #[cfg(feature = "future_snark")] + mod snark_avk_chaining { + use super::*; + + use crate::crypto_helper::ProtocolMembershipDigest; + + use mithril_stm::AggregateSignature; + + fn with_snark_proof_type(mut certificate: Certificate) -> Certificate { + if let CertificateSignature::MultiSignature(entity_type, _) = + certificate.signature.clone() + { + let future_signature: AggregateSignature = + AggregateSignature::Future; + certificate.signature = + CertificateSignature::MultiSignature(entity_type, future_signature.into()); + certificate.hash = certificate.compute_hash(); + } else { + panic!("Certificate signature should be a multi signature"); + } + certificate + } + + #[test] + fn snark_avk_chaining_succeeds_with_different_epochs() { + let (total_certificates, certificates_per_epoch) = (5, 1); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let mut certificate = with_snark_proof_type(fake_certificates[0].clone()); + let previous_certificate = fake_certificates[1].clone(); + certificate.previous_hash.clone_from(&previous_certificate.hash); + certificate.hash = certificate.compute_hash(); + + verifier + .verify_snark_aggregate_verification_key_chaining( + &certificate, + &previous_certificate, + ) + .expect("SNARK AVK chaining verification should not fail"); + } + + #[test] + fn snark_avk_chaining_succeeds_with_same_epoch() { + let (total_certificates, certificates_per_epoch) = (5, 2); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let certificate = with_snark_proof_type(fake_certificates[0].clone()); + let previous_certificate = fake_certificates[1].clone(); + + verifier + .verify_snark_aggregate_verification_key_chaining( + &certificate, + &previous_certificate, + ) + .expect("SNARK AVK chaining verification should not fail"); + } + + #[test] + fn snark_avk_chaining_fails_with_same_epoch_when_current_has_snark_avk_but_previous_does_not() + { + let (total_certificates, certificates_per_epoch) = (5, 2); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let certificate = with_snark_proof_type(fake_certificates[0].clone()); + let mut previous_certificate = fake_certificates[1].clone(); + previous_certificate.aggregate_verification_key_snark = None; + + let error = verifier + .verify_snark_aggregate_verification_key_chaining( + &certificate, + &previous_certificate, + ) + .expect_err("SNARK AVK chaining verification should fail"); + + assert_error_matches!(CertificateVerifierError::CertificateChainAVKUnmatch, error) + } + + #[test] + fn snark_avk_chaining_fails_with_same_epoch_when_previous_has_snark_avk_but_current_does_not() + { + let (total_certificates, certificates_per_epoch) = (5, 2); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let mut certificate = with_snark_proof_type(fake_certificates[0].clone()); + certificate.aggregate_verification_key_snark = None; + certificate.hash = certificate.compute_hash(); + let previous_certificate = fake_certificates[1].clone(); + + let error = verifier + .verify_snark_aggregate_verification_key_chaining( + &certificate, + &previous_certificate, + ) + .expect_err("SNARK AVK chaining verification should fail"); + + assert_error_matches!(CertificateVerifierError::CertificateChainAVKUnmatch, error) + } + + #[test] + fn snark_avk_chaining_fails_with_same_epoch_when_both_lack_snark_avk() { + let (total_certificates, certificates_per_epoch) = (5, 2); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let mut certificate = with_snark_proof_type(fake_certificates[0].clone()); + certificate.aggregate_verification_key_snark = None; + certificate.hash = certificate.compute_hash(); + let mut previous_certificate = fake_certificates[1].clone(); + previous_certificate.aggregate_verification_key_snark = None; + + let error = verifier + .verify_snark_aggregate_verification_key_chaining( + &certificate, + &previous_certificate, + ) + .expect_err("SNARK AVK chaining verification should fail"); + + assert_error_matches!(CertificateVerifierError::CertificateChainAVKUnmatch, error) + } + + #[test] + fn snark_avk_chaining_fails_when_next_snark_avk_is_tampered() { + let (total_certificates, certificates_per_epoch) = (5, 1); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let certificate = with_snark_proof_type(fake_certificates[0].clone()); + let mut previous_certificate = fake_certificates[1].clone(); + previous_certificate.protocol_message.set_message_part( + ProtocolMessagePartKey::NextSnarkAggregateVerificationKey, + "tampered-snark-avk".to_string(), + ); + + let error = verifier + .verify_snark_aggregate_verification_key_chaining( + &certificate, + &previous_certificate, + ) + .expect_err("SNARK AVK chaining verification should fail"); + + assert_error_matches!(CertificateVerifierError::CertificateChainAVKUnmatch, error) + } + + #[test] + fn snark_avk_chaining_fails_when_next_snark_avk_is_missing() { + let (total_certificates, certificates_per_epoch) = (5, 1); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let certificate = with_snark_proof_type(fake_certificates[0].clone()); + let mut previous_certificate = fake_certificates[1].clone(); + previous_certificate + .protocol_message + .message_parts + .remove(&ProtocolMessagePartKey::NextSnarkAggregateVerificationKey); + + let error = verifier + .verify_snark_aggregate_verification_key_chaining( + &certificate, + &previous_certificate, + ) + .expect_err("SNARK AVK chaining verification should fail"); + + assert_error_matches!(CertificateVerifierError::CertificateChainAVKUnmatch, error) + } + + #[test] + fn avk_chaining_dispatches_to_snark_when_current_is_future_and_previous_is_concatenation() { + let (total_certificates, certificates_per_epoch) = (5, 1); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let mut certificate = with_snark_proof_type(fake_certificates[0].clone()); + let previous_certificate = fake_certificates[1].clone(); + certificate.previous_hash.clone_from(&previous_certificate.hash); + certificate.hash = certificate.compute_hash(); + + verifier + .verify_aggregate_verification_key_chaining(&certificate, &previous_certificate) + .expect( + "AVK chaining from concatenation to Future should succeed via SNARK dispatch", + ); + } + + #[test] + fn snark_avk_chaining_succeeds_when_previous_is_genesis_certificate() { + let (total_certificates, certificates_per_epoch) = (5, 1); + let fake_certificates = + setup_certificate_chain(total_certificates, certificates_per_epoch); + let verifier = MockDependencyInjector::new().build_certificate_verifier(); + let genesis_certificate = fake_certificates.genesis_certificate().clone(); + let mut certificate = with_snark_proof_type(fake_certificates[3].clone()); + certificate.previous_hash.clone_from(&genesis_certificate.hash); + certificate.hash = certificate.compute_hash(); + + verifier + .verify_snark_aggregate_verification_key_chaining( + &certificate, + &genesis_certificate, + ) + .expect("SNARK AVK chaining from genesis to SNARK certificate should succeed"); + } + } } diff --git a/mithril-common/src/messages/certificate.rs b/mithril-common/src/messages/certificate.rs index 6871b652d05..c624c8c81f4 100644 --- a/mithril-common/src/messages/certificate.rs +++ b/mithril-common/src/messages/certificate.rs @@ -43,11 +43,18 @@ pub struct CertificateMessage { /// aka H(MSG(p,n) || AVK(n-1)) pub signed_message: String, - /// Aggregate verification key - /// The AVK used to sign during the current epoch + /// Aggregate verification key for Concatenation + /// The AVK used to sign for Concatenation during the current epoch /// aka AVK(n-2) pub aggregate_verification_key: String, + /// Aggregate verification key for SNARK + /// The AVK used to sign for SNARK during the current epoch + /// aka AVKS(n-2) + #[cfg(feature = "future_snark")] + #[serde(skip_serializing_if = "Option::is_none", default)] + pub aggregate_verification_key_snark: Option, + /// STM multi signature created from a quorum of single signatures from the signers /// aka MULTI_SIG(H(MSG(p,n) || AVK(n-1))) pub multi_signature: String, @@ -84,14 +91,21 @@ impl Debug for CertificateMessage { .field("signed_message", &self.signed_message); match should_be_exhaustive { - true => debug - .field( + true => { + debug.field( "aggregate_verification_key", &self.aggregate_verification_key, - ) - .field("multi_signature", &self.multi_signature) - .field("genesis_signature", &self.genesis_signature) - .finish(), + ); + #[cfg(feature = "future_snark")] + debug.field( + "aggregate_verification_key_snark", + &self.aggregate_verification_key_snark, + ); + debug + .field("multi_signature", &self.multi_signature) + .field("genesis_signature", &self.genesis_signature) + .finish() + } false => debug.finish_non_exhaustive(), } } @@ -121,7 +135,15 @@ impl TryFrom for Certificate { .aggregate_verification_key .try_into() .with_context(|| { - "Can not convert message to certificate: can not decode the aggregate verification key" + "Can not convert message to certificate: can not decode the aggregate verification key for Concatenation" + })?, + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: certificate_message + .aggregate_verification_key_snark + .map(|avk| avk.try_into()) + .transpose() + .with_context(|| { + "Can not convert message to certificate: can not decode the aggregate verification key for SNARK" })?, signature: if certificate_message.genesis_signature.is_empty() { CertificateSignature::MultiSignature( @@ -190,7 +212,15 @@ impl TryFrom for CertificateMessage { .aggregate_verification_key .to_json_hex() .with_context(|| { - "Can not convert certificate to message: can not encode aggregate verification key" + "Can not convert certificate to message: can not encode aggregate verification key for Concatenation" + })?, + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: certificate + .aggregate_verification_key_snark + .map(|avk| avk.to_bytes_hex()) + .transpose() + .with_context(|| { + "Can not convert certificate to message: can not encode aggregate verification key for SNARK" })?, multi_signature, genesis_signature, @@ -255,6 +285,8 @@ mod tests { }, signed_message: "signed_message".to_string(), aggregate_verification_key: "aggregate_verification_key".to_string(), + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: None, multi_signature: "multi_signature".to_string(), genesis_signature: "genesis_signature".to_string(), } @@ -375,5 +407,49 @@ mod tests { golden_message_with_bytes_hex_encoding().try_into().unwrap(); } } + + #[cfg(feature = "future_snark")] + mod certificate_with_snark_avk { + use super::*; + + fn golden_message_with_snark_avk() -> CertificateMessage { + CertificateMessage { + aggregate_verification_key: "00000000000000000404036cb79141a645faca33405ae82d67388a663fd1f55116781006608cccd2370000000000000006".to_string(), + aggregate_verification_key_snark: Some("22184a6a150661134dc2a5f2aa241fce222cecabe3ec3a6aad67f5bbe187be670000000000000003".to_string()), + multi_signature: "".to_string(), + genesis_signature: "c21f77fb812a8111b547c2145d765f854ca224b17e883d6483b668a8c4d095fd893efd2a2ba1d41da9f49d82bf02d8ee603791998b64436000e49184c000170b".to_string(), + ..golden_certificate_message() + } + } + + #[test] + fn restorations_from_bytes_hex_succeeds_with_snark_avk() { + let _certificate: Certificate = golden_message_with_snark_avk().try_into().unwrap(); + } + + #[test] + fn json_round_trip_preserves_snark_avk() { + let message = golden_message_with_snark_avk(); + let json = serde_json::to_string(&message).unwrap(); + let deserialized: CertificateMessage = serde_json::from_str(&json).unwrap(); + + assert_eq!(message, deserialized); + assert!( + json.contains("aggregate_verification_key_snark"), + "JSON should contain the SNARK AVK field" + ); + } + + #[test] + fn json_without_snark_avk_deserializes_to_none() { + let mut message = golden_message_with_snark_avk(); + message.aggregate_verification_key_snark = None; + let json = serde_json::to_string(&message).unwrap(); + + let deserialized: CertificateMessage = serde_json::from_str(&json).unwrap(); + + assert_eq!(deserialized.aggregate_verification_key_snark, None); + } + } } } From 7b4846e41264a1c274c1f8cdc94f5ca11f63b18b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 6 Mar 2026 17:24:43 +0100 Subject: [PATCH 04/19] feat(common): add SNARK AVK to signable builder and test infrastructure --- .../src/signable_builder/interface.rs | 5 ++++ .../signable_builder_service.rs | 18 ++++++++++++++ .../test/builder/certificate_chain_builder.rs | 24 +++++++++++++++++++ .../src/test/builder/mithril_fixture.rs | 20 +++++++++++++++- mithril-common/src/test/double/dummies.rs | 2 ++ mithril-common/src/test/double/fake_data.rs | 2 ++ 6 files changed, 70 insertions(+), 1 deletion(-) diff --git a/mithril-common/src/signable_builder/interface.rs b/mithril-common/src/signable_builder/interface.rs index 03268d18041..14c7c624e70 100644 --- a/mithril-common/src/signable_builder/interface.rs +++ b/mithril-common/src/signable_builder/interface.rs @@ -43,6 +43,11 @@ pub trait SignableSeedBuilder: Send + Sync { &self, ) -> StdResult; + /// Compute next aggregate verification key for SNARK protocol message part value + async fn compute_next_aggregate_verification_key_for_snark( + &self, + ) -> StdResult>; + /// Compute next protocol parameters protocol message part value async fn compute_next_protocol_parameters(&self) -> StdResult; diff --git a/mithril-common/src/signable_builder/signable_builder_service.rs b/mithril-common/src/signable_builder/signable_builder_service.rs index 5da83eaf136..6dcceba88c6 100644 --- a/mithril-common/src/signable_builder/signable_builder_service.rs +++ b/mithril-common/src/signable_builder/signable_builder_service.rs @@ -150,6 +150,18 @@ impl MithrilSignableBuilderService { next_aggregate_verification_key, ); + #[cfg(feature = "future_snark")] + if let Some(next_snark_aggregate_verification_key) = self + .seed_signable_builder + .compute_next_aggregate_verification_key_for_snark() + .await? + { + protocol_message.set_message_part( + ProtocolMessagePartKey::NextSnarkAggregateVerificationKey, + next_snark_aggregate_verification_key, + ); + } + let next_protocol_parameters = self.seed_signable_builder.compute_next_protocol_parameters().await?; protocol_message.set_message_part( @@ -251,6 +263,12 @@ mod tests { .expect_compute_next_aggregate_verification_key_for_concatenation() .once() .return_once(move || Ok("next-avk-123".to_string())); + #[cfg(feature = "future_snark")] + mock_container + .mock_signable_seed_builder + .expect_compute_next_aggregate_verification_key_for_snark() + .once() + .return_once(move || Ok(Some("next-snark-avk-123".to_string()))); mock_container .mock_signable_seed_builder .expect_compute_next_protocol_parameters() diff --git a/mithril-common/src/test/builder/certificate_chain_builder.rs b/mithril-common/src/test/builder/certificate_chain_builder.rs index ef34ae392f9..7a879696767 100644 --- a/mithril-common/src/test/builder/certificate_chain_builder.rs +++ b/mithril-common/src/test/builder/certificate_chain_builder.rs @@ -73,6 +73,18 @@ impl<'a> CertificateChainBuilderContext<'a> { self.next_fixture .compute_and_encode_concatenation_aggregate_verification_key(), ); + + #[cfg(feature = "future_snark")] + if let Some(snark_avk) = self + .next_fixture + .compute_and_encode_snark_aggregate_verification_key() + { + protocol_message.set_message_part( + ProtocolMessagePartKey::NextSnarkAggregateVerificationKey, + snark_avk, + ); + } + protocol_message.set_message_part( ProtocolMessagePartKey::NextProtocolParameters, self.next_fixture.protocol_parameters().compute_hash(), @@ -467,6 +479,10 @@ impl<'a> CertificateChainBuilder<'a> { .to_concatenation_aggregate_verification_key() .to_owned() .into(), + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: avk + .to_snark_aggregate_verification_key() + .map(|snark_avk| snark_avk.to_owned().into()), previous_hash: "".to_string(), protocol_message, signed_message, @@ -708,6 +724,14 @@ mod test { ProtocolMessagePartKey::NextAggregateVerificationKey, expected_next_avk_part_value, ); + #[cfg(feature = "future_snark")] + if let Some(snark_avk) = next_fixture.compute_and_encode_snark_aggregate_verification_key() + { + expected_protocol_message.set_message_part( + ProtocolMessagePartKey::NextSnarkAggregateVerificationKey, + snark_avk, + ); + } expected_protocol_message.set_message_part( ProtocolMessagePartKey::NextProtocolParameters, expected_next_protocol_parameters_part_value, diff --git a/mithril-common/src/test/builder/mithril_fixture.rs b/mithril-common/src/test/builder/mithril_fixture.rs index 69c940c9f06..93e8baf5a44 100644 --- a/mithril-common/src/test/builder/mithril_fixture.rs +++ b/mithril-common/src/test/builder/mithril_fixture.rs @@ -8,7 +8,8 @@ use std::{ #[cfg(feature = "future_snark")] use crate::crypto_helper::{ - ProtocolSignerVerificationKeyForSnark, ProtocolSignerVerificationKeySignatureForSnark, + ProtocolKey, ProtocolSignerVerificationKeyForSnark, + ProtocolSignerVerificationKeySignatureForSnark, }; use crate::{ StdResult, @@ -205,6 +206,23 @@ impl MithrilFixture { aggregate_verification_key.to_json_hex().unwrap() } + /// Compute the SNARK Aggregate Verification Key for this fixture, if available. + #[cfg(feature = "future_snark")] + pub fn compute_snark_aggregate_verification_key( + &self, + ) -> Option { + self.compute_aggregate_verification_key() + .to_snark_aggregate_verification_key() + .map(|key| ProtocolKey::new(key.to_owned())) + } + + /// Compute the SNARK Aggregate Verification Key for this fixture and returns it as a hex-encoded string. + #[cfg(feature = "future_snark")] + pub fn compute_and_encode_snark_aggregate_verification_key(&self) -> Option { + self.compute_snark_aggregate_verification_key() + .map(|avk| avk.to_bytes_hex().unwrap()) + } + /// Create a genesis certificate using the fixture signers for the given beacon pub fn create_genesis_certificate>( &self, diff --git a/mithril-common/src/test/double/dummies.rs b/mithril-common/src/test/double/dummies.rs index 3fdf623a867..c502bf720bb 100644 --- a/mithril-common/src/test/double/dummies.rs +++ b/mithril-common/src/test/double/dummies.rs @@ -455,6 +455,8 @@ mod messages { signed_message: "signed_message".to_string(), aggregate_verification_key: fake_keys::aggregate_verification_key_for_concatenation()[0].to_owned(), + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: None, multi_signature: fake_keys::multi_signature()[0].to_owned(), genesis_signature: String::new(), } diff --git a/mithril-common/src/test/double/fake_data.rs b/mithril-common/src/test/double/fake_data.rs index 474684177cb..2a6e5b4a259 100644 --- a/mithril-common/src/test/double/fake_data.rs +++ b/mithril-common/src/test/double/fake_data.rs @@ -140,6 +140,8 @@ pub fn certificate>(certificate_hash: T) -> entities::Certificat protocol_message, signed_message: "".to_string(), aggregate_verification_key, + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: None, signature: CertificateSignature::MultiSignature( SignedEntityType::CardanoImmutableFilesFull(beacon), multi_signature, From e4308b2c800523784f11c18e9b7c23845ea8f4b3 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 6 Mar 2026 17:24:49 +0100 Subject: [PATCH 05/19] feat(aggregator): add SNARK AVK persistence and era-gated certificate creation --- mithril-aggregator/src/database/migration.rs | 8 + .../database/query/certificate/conditions.rs | 13 +- .../src/database/record/certificate.rs | 138 ++++++++++++++++-- .../repository/certificate_repository.rs | 91 +++++++++++- .../signable_builder/signable_seed_builder.rs | 94 +++++++++++- mithril-common/src/entities/certificate.rs | 10 -- 6 files changed, 327 insertions(+), 27 deletions(-) diff --git a/mithril-aggregator/src/database/migration.rs b/mithril-aggregator/src/database/migration.rs index 1d289986cb8..9fe404a2c2e 100644 --- a/mithril-aggregator/src/database/migration.rs +++ b/mithril-aggregator/src/database/migration.rs @@ -302,5 +302,13 @@ alter table signer_registration add column verification_key_for_snark text; alter table signer_registration add column verification_key_signature_for_snark text; "#, ), + // Migration 41 + // Add `aggregate_verification_key_snark` column to `certificate` table. + SqlMigration::new( + 41, + r#" +alter table certificate add column aggregate_verification_key_snark text; + "#, + ), ] } diff --git a/mithril-aggregator/src/database/query/certificate/conditions.rs b/mithril-aggregator/src/database/query/certificate/conditions.rs index c6687ffc7f5..b5c7a34cea0 100644 --- a/mithril-aggregator/src/database/query/certificate/conditions.rs +++ b/mithril-aggregator/src/database/query/certificate/conditions.rs @@ -14,6 +14,7 @@ pub(super) fn insert_many(certificates_records: Vec) -> Where message, \ signature, \ aggregate_verification_key, \ + aggregate_verification_key_snark, \ epoch, \ network, \ signed_entity_type_id, \ @@ -25,7 +26,7 @@ pub(super) fn insert_many(certificates_records: Vec) -> Where initiated_at, \ sealed_at)"; let values_columns: Vec<&str> = repeat_n( - "(?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*)", + "(?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*, ?*)", certificates_records.len(), ) .collect(); @@ -33,6 +34,15 @@ pub(super) fn insert_many(certificates_records: Vec) -> Where let values: Vec = certificates_records .into_iter() .flat_map(|certificate_record| { + #[cfg(feature = "future_snark")] + let aggregate_verification_key_snark = + match certificate_record.aggregate_verification_key_snark { + Some(key) => Value::String(key), + None => Value::Null, + }; + #[cfg(not(feature = "future_snark"))] + let aggregate_verification_key_snark = Value::Null; + vec![ Value::String(certificate_record.certificate_id), match certificate_record.parent_certificate_id { @@ -42,6 +52,7 @@ pub(super) fn insert_many(certificates_records: Vec) -> Where Value::String(certificate_record.message), Value::String(certificate_record.signature), Value::String(certificate_record.aggregate_verification_key), + aggregate_verification_key_snark, Value::Integer(certificate_record.epoch.try_into().unwrap()), Value::String(certificate_record.network), Value::Integer(certificate_record.signed_entity_type.index() as i64), diff --git a/mithril-aggregator/src/database/record/certificate.rs b/mithril-aggregator/src/database/record/certificate.rs index f20416de275..f0a473a7e1b 100644 --- a/mithril-aggregator/src/database/record/certificate.rs +++ b/mithril-aggregator/src/database/record/certificate.rs @@ -3,8 +3,8 @@ use chrono::{DateTime, Utc}; use mithril_common::StdError; use mithril_common::entities::{ Certificate, CertificateMetadata, CertificateSignature, Epoch, - HexEncodedAggregateVerificationKey, HexEncodedKey, ProtocolMessage, ProtocolParameters, - ProtocolVersion, SignedEntityType, StakeDistributionParty, + HexEncodedAggregateVerificationKey, HexEncodedKey, HexEncodedVerificationKeyForSnark, + ProtocolMessage, ProtocolParameters, ProtocolVersion, SignedEntityType, StakeDistributionParty, }; use mithril_common::messages::{ CertificateListItemMessage, CertificateListItemMessageMetadata, CertificateMessage, @@ -40,6 +40,9 @@ pub struct CertificateRecord { /// Note: used only if signature is a multi-signature pub aggregate_verification_key: HexEncodedAggregateVerificationKey, + /// Aggregate verification key for SNARK + pub aggregate_verification_key_snark: Option, + /// Epoch of creation of the certificate. pub epoch: Epoch, @@ -109,6 +112,7 @@ impl CertificateRecord { aggregate_verification_key: fake_keys::aggregate_verification_key_for_concatenation() [0] .to_owned(), + aggregate_verification_key_snark: None, epoch, network: fake_data::network().to_string(), signed_entity_type, @@ -142,12 +146,22 @@ impl TryFrom for CertificateRecord { } }; + #[cfg(feature = "future_snark")] + let aggregate_verification_key_snark = other + .aggregate_verification_key_snark + .as_ref() + .map(|avk| avk.to_bytes_hex()) + .transpose()?; + #[cfg(not(feature = "future_snark"))] + let aggregate_verification_key_snark: Option = None; + let certificate_record = CertificateRecord { certificate_id: other.hash, parent_certificate_id, message: other.signed_message, signature, aggregate_verification_key: other.aggregate_verification_key.to_json_hex()?, + aggregate_verification_key_snark, epoch: other.epoch, network: other.metadata.network, signed_entity_type, @@ -189,6 +203,14 @@ impl TryFrom for Certificate { ), }; + #[cfg(feature = "future_snark")] + let aggregate_verification_key_snark = other + .aggregate_verification_key_snark + .map(|hex| hex.as_str().try_into()) + .transpose()?; + #[cfg(not(feature = "future_snark"))] + let _ = other.aggregate_verification_key_snark; + let certificate = Certificate { hash: other.certificate_id, previous_hash, @@ -197,6 +219,8 @@ impl TryFrom for Certificate { signed_message: other.protocol_message.compute_hash(), protocol_message: other.protocol_message, aggregate_verification_key: other.aggregate_verification_key.try_into()?, + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark, signature, }; @@ -219,6 +243,8 @@ impl From for CertificateMessage { } else { (value.signature, String::new()) }; + #[cfg(not(feature = "future_snark"))] + let _ = value.aggregate_verification_key_snark; CertificateMessage { hash: value.certificate_id, @@ -229,6 +255,8 @@ impl From for CertificateMessage { protocol_message: value.protocol_message, signed_message: value.message, aggregate_verification_key: value.aggregate_verification_key, + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: value.aggregate_verification_key_snark, multi_signature, genesis_signature, } @@ -269,16 +297,18 @@ impl SqLiteEntity for CertificateRecord { let message = row.read::<&str, _>(2).to_string(); let signature = row.read::<&str, _>(3).to_string(); let aggregate_verification_key = row.read::<&str, _>(4).to_string(); - let epoch_int = row.read::(5); - let network = row.read::<&str, _>(6).to_string(); - let signed_entity_type_id = row.read::(7); - let signed_entity_beacon_string = Hydrator::read_signed_entity_beacon_column(&row, 8); - let protocol_version = row.read::<&str, _>(9).to_string(); - let protocol_parameters_string = row.read::<&str, _>(10); - let protocol_message_string = row.read::<&str, _>(11); - let signers_string = row.read::<&str, _>(12); - let initiated_at = row.read::<&str, _>(13); - let sealed_at = row.read::<&str, _>(14); + let aggregate_verification_key_snark: Option = + row.read::, _>(5).map(|s| s.to_owned()); + let epoch_int = row.read::(6); + let network = row.read::<&str, _>(7).to_string(); + let signed_entity_type_id = row.read::(8); + let signed_entity_beacon_string = Hydrator::read_signed_entity_beacon_column(&row, 9); + let protocol_version = row.read::<&str, _>(10).to_string(); + let protocol_parameters_string = row.read::<&str, _>(11); + let protocol_message_string = row.read::<&str, _>(12); + let signers_string = row.read::<&str, _>(13); + let initiated_at = row.read::<&str, _>(14); + let sealed_at = row.read::<&str, _>(15); let certificate_record = Self { certificate_id, @@ -286,6 +316,7 @@ impl SqLiteEntity for CertificateRecord { message, signature, aggregate_verification_key, + aggregate_verification_key_snark, epoch: Epoch(epoch_int.try_into().map_err(|e| { HydrationError::InvalidData(format!( "Could not cast i64 ({epoch_int}) to u64. Error: '{e}'" @@ -356,6 +387,11 @@ impl SqLiteEntity for CertificateRecord { "{:certificate:}.aggregate_verification_key", "text", ); + projection.add_field( + "aggregate_verification_key_snark", + "{:certificate:}.aggregate_verification_key_snark", + "text", + ); projection.add_field("epoch", "{:certificate:}.epoch", "integer"); projection.add_field("network", "{:certificate:}.network", "text"); projection.add_field( @@ -419,4 +455,82 @@ mod tests { assert_eq!(expected_hash, &certificate.hash); } + + #[cfg(feature = "future_snark")] + mod snark_aggregate_verification_key { + use super::*; + + #[test] + fn certificate_to_record_preserves_snark_aggregate_verification_key() { + let chain = setup_certificate_chain(5, 2); + let certificate = chain + .certificates_chained + .iter() + .find(|c| c.aggregate_verification_key_snark.is_some()) + .expect("At least one certificate should have a SNARK AVK"); + + let record: CertificateRecord = certificate.clone().try_into().unwrap(); + + assert!( + record.aggregate_verification_key_snark.is_some(), + "CertificateRecord should preserve SNARK AVK from Certificate" + ); + } + + #[test] + fn record_to_certificate_preserves_snark_aggregate_verification_key() { + let chain = setup_certificate_chain(5, 2); + let original_certificate = chain + .certificates_chained + .iter() + .find(|c| c.aggregate_verification_key_snark.is_some()) + .expect("At least one certificate should have a SNARK AVK"); + + let record: CertificateRecord = original_certificate.clone().try_into().unwrap(); + let restored_certificate: Certificate = record.try_into().unwrap(); + + assert_eq!( + original_certificate.aggregate_verification_key_snark, + restored_certificate.aggregate_verification_key_snark, + ); + } + + #[test] + fn certificate_to_record_roundtrip_with_none_snark_aggregate_verification_key() { + let chain = setup_certificate_chain(5, 2); + let mut certificate = chain + .certificates_chained + .first() + .expect("Chain should have at least one certificate") + .clone(); + certificate.aggregate_verification_key_snark = None; + certificate.hash = certificate.compute_hash(); + + let record: CertificateRecord = certificate.clone().try_into().unwrap(); + assert!(record.aggregate_verification_key_snark.is_none()); + + let restored: Certificate = record.try_into().unwrap(); + assert_eq!( + certificate.aggregate_verification_key_snark, + restored.aggregate_verification_key_snark, + ); + } + + #[test] + fn certificate_message_preserves_snark_aggregate_verification_key() { + let chain = setup_certificate_chain(5, 2); + let certificate = chain + .certificates_chained + .iter() + .find(|c| c.aggregate_verification_key_snark.is_some()) + .expect("At least one certificate should have a SNARK AVK"); + + let record: CertificateRecord = certificate.clone().try_into().unwrap(); + let expected_snark_avk = record.aggregate_verification_key_snark.clone(); + + let message: CertificateMessage = record.into(); + + assert_eq!(expected_snark_avk, message.aggregate_verification_key_snark,); + } + } } diff --git a/mithril-aggregator/src/database/repository/certificate_repository.rs b/mithril-aggregator/src/database/repository/certificate_repository.rs index 13fd968346d..b323a88a3ab 100644 --- a/mithril-aggregator/src/database/repository/certificate_repository.rs +++ b/mithril-aggregator/src/database/repository/certificate_repository.rs @@ -210,7 +210,8 @@ mod tests { "stake":1009497432569 }]', '2023-06-23T08:37:49.066Z', - '2023-06-23T08:37:49.066Z' + '2023-06-23T08:37:49.066Z', + null ); -- multi-signature certificate @@ -240,13 +241,72 @@ mod tests { "stake":1009497432569 }]', '2023-03-16T01:51:00.880Z', - '2023-03-16T02:07:22.145Z' + '2023-03-16T02:07:22.145Z', + null ); "#, ) .unwrap(); } + fn insert_golden_certificate_with_snark_aggregate_verification_key( + connection: &ConnectionThreadSafe, + snark_avk: &str, + ) { + connection + .execute(format!( + r#" + -- genesis certificate with SNARK AVK + insert into certificate + values( + 'bfb4efbd48d58f7677ddb7d5fe5b5b9e998e8ca549cbf7583873bdccfc70f194', + null, + '08420665c56dcf6981b7d8b64b5a584e148edbf7638f466cb36b278ce962439c', + 'b7944ddc7d728812f8e68abc93b668a84876e9867b97648bc937b20debdff15a8415470ee709599d1a12a50ac5a57a3a4955cf19307d04955fcad6931c3b9505', + '7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b37372c3230382c3138392c3138372c37362c3136322c36382c3233382c3134342c31372c3131342c3137352c36302c3136352c3230322c3134362c3139342c31332c37332c3233392c3233372c3232322c3136392c3230362c352c3130392c3132332c35322c3235342c39382c3133312c37395d2c226e725f6c6561766573223a332c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a32383439323639303636317d', + 241, + 'preview', + 0, + 241, + '0.1.0', + '{{"k":2422,"m":20973,"phi_f":0.2}}', + '{{"message_parts":{{ + "next_aggregate_verification_key":"7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b37372c3230382c3138392c3138372c37362c3136322c36382c3233382c3134342c31372c3131342c3137352c36302c3136352c3230322c3134362c3139342c31332c37332c3233392c3233372c3232322c3136392c3230362c352c3130392c3132332c35322c3235342c39382c3133312c37395d2c226e725f6c6561766573223a332c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a32383439323639303636317d" + }}}}', + '[{{"party_id":"pool1vapqexnsx6hvc588yyysxpjecf3k43hcr5mvhmstutuvy085xpa","verification_key":"7b22766b223a5b3133382c33322c3133382c3135322c3134362c3235352c3130382c3139302c37302c34322c3132362c3137322c31392c3135312c3133392c3133392c3235352c33352c3134312c38322c3138372c33372c3133332c3235322c3139322c302c32362c32342c3134342c372c3235332c3136362c3135312c3139332c392c3230392c3131392c3230302c3134312c34312c38302c342c3231372c3132322c3132302c3235332c3230382c3131312c362c37382c3234362c3134362c3131382c352c3235312c31392c3234332c3138342c3233382c3139352c39392c3235312c3135312c342c39342c3133382c3234362c33362c33372c34382c3133362c3130302c3233352c3134312c3232382c392c39362c3131332c35392c3137352c3130322c3232392c39352c39332c3134332c3137312c3130302c32302c3133362c36372c33302c3133312c3135332c32362c35372c3132385d2c22706f70223a5b3137342c3233302c33382c3138312c3131332c38332c372c34332c3130312c38392c3133372c3133302c37302c3135382c3235342c31342c31362c36372c38332c362c3234322c39312c3136372c34352c3232392c3139382c3130312c37302c3232382c36312c3138302c3132302c3130332c3232302c3231312c3134362c3136322c37302c33382c3230352c3139312c3235322c3138342c3235322c39362c3134382c3130322c3133362c3136362c34322c3137382c3133352c3130302c33312c38392c3233342c3135392c3131382c33382c3133392c31362c3134342c3132382c3134382c3132382c3139312c31382c34382c38392c3136352c35342c3134362c36332c3136302c3138362c3139362c31392c3137312c3136302c31342c39322c35382c3232312c3138352c3132392c382c3133322c35352c3231382c3235302c39352c32312c3235302c3135312c36352c3231395d7d","verification_key_signature":"7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a5b35342c35372c32332c3234302c3234342c3130352c3139322c3138312c3130362c3232312c3132302c3139382c3136392c3134372c3233362c34382c32342c35382c3233352c31332c36302c31352c3231382c33312c34352c3135322c3133302c3230382c36392c38312c34372c3135302c3234352c3234332c32352c39342c3134382c3136322c39322c3136392c3131352c37382c31352c38382c3139382c38342c3233322c3138342c3135372c3139352c35342c3136352c33352c382c3232342c3130312c3138392c38372c32392c3131342c3133322c33382c3132322c31305d2c226c68735f706b223a5b3139322c3135342c3230322c3233342c36352c3234332c3132392c3230302c3131382c3137352c3131342c3233352c3232322c3235342c3134322c3232332c3137372c3233342c31352c31382c34312c31362c38382c38352c37322c3130372c33322c3134382c33352c35312c3132352c34355d2c227268735f706b223a5b3137342c39352c3132342c31382c36322c3135312c3137302c3136382c3232332c36362c3132322c36312c3234322c3130372c3132352c3137372c3137302c3132332c35382c3231362c3137362c392c3234302c3131382c3131302c35362c3232372c3230302c3131322c3130352c32392c3230385d7d2c226c68735f706b223a5b36392c3138322c39392c382c34302c39332c3130382c3233312c382c312c3235322c3131302c3132322c37332c3133302c3230372c3231332c3137312c3130352c3232322c31352c3134322c3230362c3137392c33382c3132302c39322c362c32302c3133352c3130382c3138335d2c227268735f706b223a5b33342c36372c3134302c3132392c3231352c36392c3136302c3135362c3230302c31302c3232362c35382c3132322c36342c33382c3135362c3230362c3230362c302c3137382c3132302c3139332c362c3135332c3131322c3130392c3135372c3131322c3132322c3133372c3233372c38355d7d2c226c68735f706b223a5b37332c3131342c3136352c3137312c34322c3131372c3139322c3139342c3137342c32302c38312c392c3230392c31392c3134352c3233302c3233302c3130392c34382c3135302c31332c3232392c3139322c35342c3138362c3137372c32382c3133362c31352c3230342c3231342c3132305d2c227268735f706b223a5b3139302c32322c3131312c38362c38322c3138362c3231372c3134312c302c3136382c3130382c3230362c3130392c33342c33342c37382c3134342c3230322c3135362c3138372c3134302c3136302c32302c3233342c3138382c3234382c3131322c3131312c3136372c3234352c3138342c3137355d7d2c226c68735f706b223a5b3234382c33382c3134372c3234382c3138382c3136372c31332c3136362c3234352c31312c3234322c35332c37382c3138332c32352c31322c3136312c3235322c3130372c3231302c3234312c39352c3232302c3232362c3133392c3133302c39322c39392c3132382c3138342c33352c3233395d2c227268735f706b223a5b3134352c3136382c3130362c3137332c32342c3230302c39362c3231352c3131302c36382c3132382c3130322c3134372c3138342c31382c3230392c3138342c38392c3137322c3132372c35352c3232372c3133382c34312c38372c34312c3138382c35352c3131352c3133332c3134372c38315d7d2c226c68735f706b223a5b3137342c3130352c3131342c3132352c3131342c34392c33332c3135332c3234302c3131332c3136312c35372c34322c3233362c3137352c31382c37382c39382c33332c3230322c33382c3132312c3135372c3234332c3233382c39392c3135312c36362c35362c3134392c3135322c32345d2c227268735f706b223a5b3235322c3136382c3131372c3137302c3235302c3134332c3137302c35342c3130352c33312c3231392c3234392c3130342c33362c3133352c34382c3231362c372c3233332c3133372c3132342c3137302c3134372c3131332c35392c3233372c3234312c3132352c3231302c3233342c3234312c3130335d7d","operational_certificate":"5b5b5b3131322c39352c34322c39372c382c3235322c31382c3231342c31392c3231382c3231372c3234322c3233302c3138372c3234302c3133392c31342c3135382c3137392c3234392c3231312c36332c3132332c342c32362c3132362c3132312c3234372c302c35372c31362c3136315d2c312c37312c5b3132392c3234382c3133342c3132342c3230372c3130332c3233312c37302c3130372c32382c3134322c3134312c38362c3234392c3230352c31312c33392c3232382c3130382c3132322c3233312c3138322c3132372c3130312c3234352c33332c3135322c3233342c35342c36372c3138312c39362c3137372c3234362c32382c322c3235322c3130382c35392c3231352c3232372c3230392c3131382c3130352c3135342c37312c36332c3134352c3132372c3137352c3133382c3131352c39362c3233352c3131382c31322c3234302c3232352c3130392c3130382c3231322c3232392c35372c31305d5d2c5b33302c3138312c32302c37382c33392c3232332c352c3133372c3134312c3138392c372c3132372c34352c3232372c3230362c3135372c39352c3131352c36312c3132382c3135392c3135362c34332c3132372c302c34302c3134332c3138332c3233302c32352c39312c3137305d5d","kes_period":22,"stake":1009497432569}}]', + '2023-06-23T08:37:49.066Z', + '2023-06-23T08:37:49.066Z', + '{snark_avk}' + ); + + -- multi-signature certificate with SNARK AVK + insert into certificate + values( + '9a86b602d1eda6d3a48967e63f5b35885368795669d9293014e1c289ee0defa7', + '3997f18bbbe706a77fbf464101a3e6c6476a9d1dd2e10f2ed614f028713b8f11', + '33975e636d019513d93e9182e6a5e38092909620cd4b650e06a03e2c4cf2e65a', + '7b227369676e617475726573223a5b5b7b227369676d61223a5b3138342c3133342c38392c3137382c3234312c3232362c34372c34372c34312c36382c3136392c36352c38362c3136302c39322c362c3130382c33382c39322c3134332c3131372c3231382c33382c39342c3131332c3232372c3133332c3231302c3131332c3134312c31382c3139322c3133332c3230312c3231382c3233392c33342c3231322c39302c382c34302c3132302c3233342c3136382c3135332c3137372c3133322c34335d2c22696e6465786573223a5b312c382c31322c31342c31372c32332c32382c33332c33392c38382c39332c39382c3131342c3131352c3131372c3132372c3133322c3133342c3133362c3133392c3134302c3134312c3135302c3135372c3136332c3136342c3137322c3137372c3137382c3138312c3139302c3139312c3139322c3230302c3230312c3230332c3230342c3231352c3231362c3231392c3233322c3233342c3233372c3235302c3235312c3235352c3235362c3236322c3236352c3236362c3237372c3238302c3238342c3238392c3239372c3330302c3331312c3332302c3332312c3332382c3333332c3333342c3333372c3334322c3334332c3334342c3335342c3335372c3336302c3336392c3337352c3337362c3338362c3339342c3339372c3339382c3339392c3430312c3430322c3430352c3431302c3431352c3431372c3432302c3432372c3433302c3433362c3434312c3435302c3435392c3436352c3436362c3437322c3437342c3438322c3438352c3438382c3438392c3439312c3530342c3531302c3531342c3531362c3531372c3532312c3532322c3532342c3532382c3533302c3534342c3534392c3535302c3535312c3535322c3535372c3536322c3536382c3537342c3537392c3538322c3538352c3538382c3538392c3539342c3630392c3631382c3632312c3632342c3632392c3633312c3633352c3633392c3634302c3634312c3634322c3634362c3634372c3635302c3635372c3636342c3637332c3637352c3637362c3638312c3638342c3638372c3730312c3730322c3731352c3731382c3732352c3732392c3733302c3733362c3733382c3734322c3736312c3736372c3737312c3737322c3737342c3737382c3738392c3739312c3830362c3831332c3832332c3832372c3833342c3833382c3833392c3834352c3834382c3835322c3835352c3835362c3836352c3836372c3837302c3837312c3837322c3837342c3838332c3838352c3839302c3839372c3839392c3930312c3930332c3930352c3931362c3931382c3932322c3933342c3933362c3933382c3934342c3934362c3934392c3935352c3935382c3936382c3937302c3937342c3937362c3938382c3938392c3939322c3939352c3939382c3939395d2c227369676e65725f696e646578223a307d2c5b3138322c38362c3134352c3135362c31342c3130382c3135362c35392c3137372c31342c3134322c3133382c33382c3231332c3138322c3234342c3134302c3133362c3232322c3234312c3137372c3233302c3231332c3233302c3131342c3232352c39302c3133372c3230342c302c3234342c3131312c32362c3131372c3131312c32342c38392c3133332c3136372c3233342c3131332c37372c31312c34322c32322c3232322c3130312c3131302c3234352c3136352c35342c36302c33302c3131332c3132302c3133372c3137372c3138342c32312c3233312c3135302c3232332c36302c3134302c39302c36332c35372c3132362c3231332c3232322c352c3137322c3231362c3137352c39382c3231332c3133392c3137342c3231322c3234332c35302c34332c3234382c3233332c3138382c33392c3231352c382c3233342c35392c31362c36382c3133312c3233352c3233312c3231302c37305d5d5d2c2262617463685f70726f6f66223a7b2276616c756573223a5b5d2c22696e6469636573223a5b305d2c22686173686572223a6e756c6c7d7d', + '7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b3134302c31332c3135352c3134312c3136332c372c38362c3232372c34372c31392c3138302c3132372c3139362c3130382c3137312c3135382c3134302c37372c3137352c3135392c3133362c3139332c3130382c34322c3134322c3234342c38352c3131362c3235322c3135362c3233352c35305d2c226e725f6c6561766573223a312c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a313030393439373433323536397d', + 142, + 'preview', + 2, + '{{"epoch":142,"immutable_file_number":2838}}', + '0.1.0', + '{{"k":2422,"m":20973,"phi_f":0.2}}', + '{{"message_parts":{{ + "snapshot_digest":"cfed71151e42f8208b841531dc95477f10db25083db5eb9759e745155e83ca7c", + "next_aggregate_verification_key":"7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b3132322c3131322c3131302c37332c3131352c3130302c33352c3131322c37312c3130372c3139392c3139322c3131352c37382c32312c38322c3131362c3136312c35312c34332c3233342c3134332c3139382c3138352c33342c3233302c3131332c3234352c3136392c3137332c3136322c37315d2c226e725f6c6561766573223a322c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a323031383939353036313631357d" + }}}}', + '[{{"party_id":"pool1vapqexnsx6hvc588yyysxpjecf3k43hcr5mvhmstutuvy085xpa","verification_key":"7b22766b223a5b3133382c33322c3133382c3135322c3134362c3235352c3130382c3139302c37302c34322c3132362c3137322c31392c3135312c3133392c3133392c3235352c33352c3134312c38322c3138372c33372c3133332c3235322c3139322c302c32362c32342c3134342c372c3235332c3136362c3135312c3139332c392c3230392c3131392c3230302c3134312c34312c38302c342c3231372c3132322c3132302c3235332c3230382c3131312c362c37382c3234362c3134362c3131382c352c3235312c31392c3234332c3138342c3233382c3139352c39392c3235312c3135312c342c39342c3133382c3234362c33362c33372c34382c3133362c3130302c3233352c3134312c3232382c392c39362c3131332c35392c3137352c3130322c3232392c39352c39332c3134332c3137312c3130302c32302c3133362c36372c33302c3133312c3135332c32362c35372c3132385d2c22706f70223a5b3137342c3233302c33382c3138312c3131332c38332c372c34332c3130312c38392c3133372c3133302c37302c3135382c3235342c31342c31362c36372c38332c362c3234322c39312c3136372c34352c3232392c3139382c3130312c37302c3232382c36312c3138302c3132302c3130332c3232302c3231312c3134362c3136322c37302c33382c3230352c3139312c3235322c3138342c3235322c39362c3134382c3130322c3133362c3136362c34322c3137382c3133352c3130302c33312c38392c3233342c3135392c3131382c33382c3133392c31362c3134342c3132382c3134382c3132382c3139312c31382c34382c38392c3136352c35342c3134362c36332c3136302c3138362c3139362c31392c3137312c3136302c31342c39322c35382c3232312c3138352c3132392c382c3133322c35352c3231382c3235302c39352c32312c3235302c3135312c36352c3231395d7d","verification_key_signature":"7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a5b35342c35372c32332c3234302c3234342c3130352c3139322c3138312c3130362c3232312c3132302c3139382c3136392c3134372c3233362c34382c32342c35382c3233352c31332c36302c31352c3231382c33312c34352c3135322c3133302c3230382c36392c38312c34372c3135302c3234352c3234332c32352c39342c3134382c3136322c39322c3136392c3131352c37382c31352c38382c3139382c38342c3233322c3138342c3135372c3139352c35342c3136352c33352c382c3232342c3130312c3138392c38372c32392c3131342c3133322c33382c3132322c31305d2c226c68735f706b223a5b3139322c3135342c3230322c3233342c36352c3234332c3132392c3230302c3131382c3137352c3131342c3233352c3232322c3235342c3134322c3232332c3137372c3233342c31352c31382c34312c31362c38382c38352c37322c3130372c33322c3134382c33352c35312c3132352c34355d2c227268735f706b223a5b3137342c39352c3132342c31382c36322c3135312c3137302c3136382c3232332c36362c3132322c36312c3234322c3130372c3132352c3137372c3137302c3132332c35382c3231362c3137362c392c3234302c3131382c3131302c35362c3232372c3230302c3131322c3130352c32392c3230385d7d2c226c68735f706b223a5b36392c3138322c39392c382c34302c39332c3130382c3233312c382c312c3235322c3131302c3132322c37332c3133302c3230372c3231332c3137312c3130352c3232322c31352c3134322c3230362c3137392c33382c3132302c39322c362c32302c3133352c3130382c3138335d2c227268735f706b223a5b33342c36372c3134302c3132392c3231352c36392c3136302c3135362c3230302c31302c3232362c35382c3132322c36342c33382c3135362c3230362c3230362c302c3137382c3132302c3139332c362c3135332c3131322c3130392c3135372c3131322c3132322c3133372c3233372c38355d7d2c226c68735f706b223a5b37332c3131342c3136352c3137312c34322c3131372c3139322c3139342c3137342c32302c38312c392c3230392c31392c3134352c3233302c3233302c3130392c34382c3135302c31332c3232392c3139322c35342c3138362c3137372c32382c3133362c31352c3230342c3231342c3132305d2c227268735f706b223a5b3139302c32322c3131312c38362c38322c3138362c3231372c3134312c302c3136382c3130382c3230362c3130392c33342c33342c37382c3134342c3230322c3135362c3138372c3134302c3136302c32302c3233342c3138382c3234382c3131322c3131312c3136372c3234352c3138342c3137355d7d2c226c68735f706b223a5b3234382c33382c3134372c3234382c3138382c3136372c31332c3136362c3234352c31312c3234322c35332c37382c3138332c32352c31322c3136312c3235322c3130372c3231302c3234312c39352c3232302c3232362c3133392c3133302c39322c39392c3132382c3138342c33352c3233395d2c227268735f706b223a5b3134352c3136382c3130362c3137332c32342c3230302c39362c3231352c3131302c36382c3132382c3130322c3134372c3138342c31382c3230392c3138342c38392c3137322c3132372c35352c3232372c3133382c34312c38372c34312c3138382c35352c3131352c3133332c3134372c38315d7d2c226c68735f706b223a5b3137342c3130352c3131342c3132352c3131342c34392c33332c3135332c3234302c3131332c3136312c35372c34322c3233362c3137352c31382c37382c39382c33332c3230322c33382c3132312c3135372c3234332c3233382c39392c3135312c36362c35362c3134392c3135322c32345d2c227268735f706b223a5b3235322c3136382c3131372c3137302c3235302c3134332c3137302c35342c3130352c33312c3231392c3234392c3130342c33362c3133352c34382c3231362c372c3233332c3133372c3132342c3137302c3134372c3131332c35392c3233372c3234312c3132352c3231302c3233342c3234312c3130335d7d","operational_certificate":"5b5b5b3131322c39352c34322c39372c382c3235322c31382c3231342c31392c3231382c3231372c3234322c3233302c3138372c3234302c3133392c31342c3135382c3137392c3234392c3231312c36332c3132332c342c32362c3132362c3132312c3234372c302c35372c31362c3136315d2c312c37312c5b3132392c3234382c3133342c3132342c3230372c3130332c3233312c37302c3130372c32382c3134322c3134312c38362c3234392c3230352c31312c33392c3232382c3130382c3132322c3233312c3138322c3132372c3130312c3234352c33332c3135322c3233342c35342c36372c3138312c39362c3137372c3234362c32382c322c3235322c3130382c35392c3231352c3232372c3230392c3131382c3130352c3135342c37312c36332c3134352c3132372c3137352c3133382c3131352c39362c3233352c3131382c31322c3234302c3232352c3130392c3130382c3231322c3232392c35372c31305d5d2c5b33302c3138312c32302c37382c33392c3232332c352c3133372c3134312c3138392c372c3132372c34352c3232372c3230362c3135372c39352c3131352c36312c3132382c3135392c3135362c34332c3132372c302c34302c3134332c3138332c3233302c32352c39312c3137305d5d","kes_period":22,"stake":1009497432569}}]', + '2023-03-16T01:51:00.880Z', + '2023-03-16T02:07:22.145Z', + '{snark_avk}' + ); + "# + )) + .unwrap(); + } + #[tokio::test] async fn test_golden_master() { let connection = main_db_connection().unwrap(); @@ -259,6 +319,33 @@ mod tests { .expect("Getting Golden certificates should not fail"); assert_eq!(certificate_records.len(), 2); + for record in &certificate_records { + assert!( + record.aggregate_verification_key_snark.is_none(), + "Legacy golden certificates should have no SNARK AVK" + ); + } + } + + #[tokio::test] + async fn test_golden_master_with_snark_aggregate_verification_key() { + let connection = main_db_connection().unwrap(); + let snark_avk = "abcdef0123456789"; + insert_golden_certificate_with_snark_aggregate_verification_key(&connection, snark_avk); + + let repository = CertificateRepository::new(Arc::new(connection)); + let certificate_records = repository + .get_latest_certificates::(usize::MAX) + .await + .expect("Getting Golden certificates should not fail"); + + assert_eq!(certificate_records.len(), 2); + for record in &certificate_records { + assert_eq!( + record.aggregate_verification_key_snark.as_deref(), + Some(snark_avk), + ); + } } #[tokio::test] diff --git a/mithril-aggregator/src/services/signable_builder/signable_seed_builder.rs b/mithril-aggregator/src/services/signable_builder/signable_seed_builder.rs index 380c2251b49..f0459e81623 100644 --- a/mithril-aggregator/src/services/signable_builder/signable_seed_builder.rs +++ b/mithril-aggregator/src/services/signable_builder/signable_seed_builder.rs @@ -13,6 +13,9 @@ use mithril_common::{ signable_builder::SignableSeedBuilder, }; +#[cfg(feature = "future_snark")] +use mithril_common::entities::SupportedEra; + use crate::services::EpochService; /// SignableSeedBuilder aggregator implementation @@ -46,6 +49,37 @@ impl SignableSeedBuilder for AggregatorSignableSeedBuilder { Ok(next_aggregate_verification_key) } + async fn compute_next_aggregate_verification_key_for_snark( + &self, + ) -> StdResult> { + #[cfg(feature = "future_snark")] + { + let epoch_service = self.epoch_service.read().await; + if epoch_service.mithril_era()? == SupportedEra::Pythagoras { + return Ok(None); + } + + let snark_avk = (*epoch_service) + .next_aggregate_verification_key()? + .to_snark_aggregate_verification_key() + .ok_or_else(|| { + anyhow::anyhow!( + "SNARK aggregate verification key is unavailable during Lagrange era" + ) + })?; + let next_aggregate_verification_key = ProtocolKey::new(snark_avk.to_owned()) + .to_bytes_hex() + .with_context(|| "convert next snark avk to bytes hex failure")?; + + Ok(Some(next_aggregate_verification_key)) + } + + #[cfg(not(feature = "future_snark"))] + { + Ok(None) + } + } + async fn compute_next_protocol_parameters(&self) -> StdResult { let epoch_service = self.epoch_service.read().await; let next_protocol_parameters = epoch_service.next_protocol_parameters()?.compute_hash(); @@ -64,7 +98,7 @@ impl SignableSeedBuilder for AggregatorSignableSeedBuilder { #[cfg(test)] mod tests { use mithril_common::{ - entities::Epoch, + entities::{Epoch, SupportedEra}, test::{ builder::{MithrilFixture, MithrilFixtureBuilder}, double::Dummy, @@ -75,10 +109,11 @@ mod tests { use super::*; - fn build_signable_builder_service( + fn build_signable_builder_service_for_era( epoch: Epoch, fixture: &MithrilFixture, next_fixture: &MithrilFixture, + mithril_era: SupportedEra, ) -> AggregatorSignableSeedBuilder { let epoch_service = Arc::new(RwLock::new( FakeEpochServiceBuilder { @@ -96,6 +131,7 @@ mod tests { }, current_signers_with_stake: fixture.signers_with_stake(), next_signers_with_stake: next_fixture.signers_with_stake(), + mithril_era, ..FakeEpochServiceBuilder::dummy(epoch) } .build(), @@ -104,6 +140,14 @@ mod tests { AggregatorSignableSeedBuilder::new(epoch_service) } + fn build_signable_builder_service( + epoch: Epoch, + fixture: &MithrilFixture, + next_fixture: &MithrilFixture, + ) -> AggregatorSignableSeedBuilder { + build_signable_builder_service_for_era(epoch, fixture, next_fixture, SupportedEra::dummy()) + } + #[tokio::test] async fn test_compute_next_aggregate_verification_key_protocol_message_value() { let epoch = Epoch(5); @@ -124,6 +168,52 @@ mod tests { ); } + #[cfg(feature = "future_snark")] + #[tokio::test] + async fn compute_next_snark_avk_returns_none_during_pythagoras_era() { + let epoch = Epoch(5); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build(); + let signable_seed_builder = build_signable_builder_service_for_era( + epoch, + &fixture, + &next_fixture, + SupportedEra::Pythagoras, + ); + + let result = signable_seed_builder + .compute_next_aggregate_verification_key_for_snark() + .await + .unwrap(); + + assert!( + result.is_none(), + "SNARK AVK should not be computed during Pythagoras era" + ); + } + + #[cfg(feature = "future_snark")] + #[tokio::test] + async fn compute_next_snark_avk_returns_value_during_lagrange_era() { + let epoch = Epoch(5); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build(); + let signable_seed_builder = build_signable_builder_service_for_era( + epoch, + &fixture, + &next_fixture, + SupportedEra::Lagrange, + ); + let expected_snark_avk = next_fixture.compute_and_encode_snark_aggregate_verification_key(); + + let result = signable_seed_builder + .compute_next_aggregate_verification_key_for_snark() + .await + .unwrap(); + + assert_eq!(result, expected_snark_avk); + } + #[tokio::test] async fn test_compute_next_protocol_parameters_protocol_message_value() { let epoch = Epoch(5); diff --git a/mithril-common/src/entities/certificate.rs b/mithril-common/src/entities/certificate.rs index aca018df6db..03764f7e486 100644 --- a/mithril-common/src/entities/certificate.rs +++ b/mithril-common/src/entities/certificate.rs @@ -139,16 +139,6 @@ impl Certificate { hex::encode(hasher.finalize()) } - /// Strip the SNARK aggregate verification key from the certificate and recompute its hash. - /// - /// Used during Pythagoras era to ensure SNARK AVK is not included in certificates - /// even when the `future_snark` feature is compiled in. - #[cfg(feature = "future_snark")] - pub fn strip_snark_aggregate_verification_key(&mut self) { - self.aggregate_verification_key_snark = None; - self.hash = self.compute_hash(); - } - /// Tell if the certificate is a genesis certificate pub fn is_genesis(&self) -> bool { matches!(self.signature, CertificateSignature::GenesisSignature(_)) From 52c55de3f3ddef414903d26a19660e392c31b095 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 6 Mar 2026 17:24:56 +0100 Subject: [PATCH 06/19] feat(signer): add SNARK AVK computation with era gating --- .../src/dependency_injection/builder.rs | 1 + mithril-signer/src/runtime/runner.rs | 1 + mithril-signer/src/services/epoch_service.rs | 36 ++++- .../signable_builder/signable_seed_builder.rs | 136 ++++++++++++++++++ mithril-signer/src/services/single_signer.rs | 24 +++- .../test_extensions/state_machine_tester.rs | 1 + 6 files changed, 191 insertions(+), 8 deletions(-) diff --git a/mithril-signer/src/dependency_injection/builder.rs b/mithril-signer/src/dependency_injection/builder.rs index 5bb2d2ff10b..861d72d21f4 100644 --- a/mithril-signer/src/dependency_injection/builder.rs +++ b/mithril-signer/src/dependency_injection/builder.rs @@ -398,6 +398,7 @@ impl<'a> DependenciesBuilder<'a> { self.root_logger(), )); let epoch_service = Arc::new(RwLock::new(MithrilEpochService::new( + era_checker.clone(), stake_store.clone(), protocol_initializer_store.clone(), self.root_logger(), diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index 190c80b912f..0e2244ec23f 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -546,6 +546,7 @@ mod tests { None, )); let epoch_service = Arc::new(RwLock::new(MithrilEpochService::new( + era_checker.clone(), stake_store.clone(), protocol_initializer_store.clone(), logger.clone(), diff --git a/mithril-signer/src/services/epoch_service.rs b/mithril-signer/src/services/epoch_service.rs index 7c3b14d2753..ae69bacb02d 100644 --- a/mithril-signer/src/services/epoch_service.rs +++ b/mithril-signer/src/services/epoch_service.rs @@ -14,8 +14,10 @@ use mithril_common::crypto_helper::ProtocolInitializer; use mithril_common::entities::{ CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, PartyId, ProtocolParameters, SignedEntityConfig, SignedEntityTypeDiscriminants, Signer, SignerWithStake, + SupportedEra, }; use mithril_common::logging::LoggerExtensions; +use mithril_era::EraChecker; use mithril_persistence::store::StakeStorer; /// Errors dedicated to the EpochService. @@ -41,6 +43,9 @@ pub trait EpochService: Sync + Send { next_signers: Vec, ) -> StdResult<()>; + /// Get the current Mithril era. + fn mithril_era(&self) -> StdResult; + /// Get the current epoch for which the data stored in this service are computed. fn epoch_of_current_data(&self) -> StdResult; @@ -82,6 +87,7 @@ pub trait EpochService: Sync + Send { } pub(crate) struct EpochData { + pub mithril_era: SupportedEra, pub epoch: Epoch, pub registration_protocol_parameters: ProtocolParameters, pub protocol_initializer: Option, @@ -94,6 +100,7 @@ pub(crate) struct EpochData { /// Implementation of the [epoch service][EpochService]. pub struct MithrilEpochService { + era_checker: Arc, stake_storer: Arc, protocol_initializer_store: Arc, epoch_data: Option, @@ -103,11 +110,13 @@ pub struct MithrilEpochService { impl MithrilEpochService { /// Create a new service instance pub fn new( + era_checker: Arc, stake_storer: Arc, protocol_initializer_store: Arc, logger: Logger, ) -> Self { Self { + era_checker, stake_storer, protocol_initializer_store, epoch_data: None, @@ -220,7 +229,10 @@ impl EpochService for MithrilEpochService { let cardano_blocks_transactions_signing_config = signed_entity_types_config.cardano_blocks_transactions.clone(); + let mithril_era = self.era_checker.current_era(); + self.epoch_data = Some(EpochData { + mithril_era, epoch: aggregator_signer_registration_epoch, registration_protocol_parameters, protocol_initializer, @@ -234,6 +246,10 @@ impl EpochService for MithrilEpochService { Ok(()) } + fn mithril_era(&self) -> StdResult { + Ok(self.unwrap_data()?.mithril_era) + } + fn epoch_of_current_data(&self) -> StdResult { Ok(self.unwrap_data()?.epoch) } @@ -358,13 +374,17 @@ impl MithrilEpochService { use crate::database::repository::StakePoolStore; use crate::database::test_helper::main_db_connection; use crate::test::TestLogger; + use mithril_common::entities::Epoch; + use mithril_common::test::double::Dummy; let sqlite_connection = Arc::new(main_db_connection().unwrap()); let stake_store = Arc::new(StakePoolStore::new(sqlite_connection.clone(), None)); let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(sqlite_connection, None)); + let era_checker = Arc::new(EraChecker::new(SupportedEra::dummy(), Epoch::default())); Self::new( + era_checker, stake_store, protocol_initializer_store, TestLogger::stdout(), @@ -374,9 +394,10 @@ impl MithrilEpochService { /// `TEST ONLY` - Set all data to either default values, empty values, or fake values /// if no default/empty can be set. pub fn set_data_to_default_or_fake(mut self, epoch: Epoch) -> Self { - use mithril_common::test::double::fake_data; + use mithril_common::test::double::{Dummy, fake_data}; let epoch_data = EpochData { + mithril_era: SupportedEra::dummy(), epoch, registration_protocol_parameters: fake_data::protocol_parameters(), protocol_initializer: None, @@ -418,6 +439,8 @@ pub(crate) mod mock_epoch_service { next_signers: Vec, ) -> StdResult<()>; + fn mithril_era(&self) -> StdResult; + fn epoch_of_current_data(&self) -> StdResult; fn registration_protocol_parameters(&self) -> StdResult<&'static ProtocolParameters>; @@ -480,6 +503,10 @@ mod tests { use super::*; + fn build_era_checker() -> Arc { + Arc::new(EraChecker::new(SupportedEra::dummy(), Epoch::default())) + } + #[test] fn test_is_signer_included_in_current_stake_distribution_returns_error_when_epoch_settings_is_not_set() { @@ -496,6 +523,7 @@ mod tests { let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(connection, None)); let service = MithrilEpochService::new( + build_era_checker(), stake_store, protocol_initializer_store, TestLogger::stdout(), @@ -532,6 +560,7 @@ mod tests { let next_signers = signers[2..5].to_vec(); let mut service = MithrilEpochService::new( + build_era_checker(), stake_store, protocol_initializer_store, TestLogger::stdout(), @@ -680,6 +709,7 @@ mod tests { // Build service and register epoch settings let service = MithrilEpochService::new( + build_era_checker(), stake_store, protocol_initializer_store, TestLogger::stdout(), @@ -722,6 +752,7 @@ mod tests { // Build service and register epoch settings let mut service = MithrilEpochService::new( + build_era_checker(), stake_store, protocol_initializer_store, TestLogger::stdout(), @@ -836,6 +867,7 @@ mod tests { // Build service and register epoch settings let mut service = MithrilEpochService::new( + build_era_checker(), stake_store, protocol_initializer_store, TestLogger::stdout(), @@ -890,6 +922,7 @@ mod tests { .unwrap(); let mut service = MithrilEpochService::new( + build_era_checker(), stake_store, protocol_initializer_store, TestLogger::stdout(), @@ -923,6 +956,7 @@ mod tests { let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(connection, None)); let epoch_service = Arc::new(RwLock::new(MithrilEpochService::new( + build_era_checker(), stake_store, protocol_initializer_store, TestLogger::stdout(), diff --git a/mithril-signer/src/services/signable_builder/signable_seed_builder.rs b/mithril-signer/src/services/signable_builder/signable_seed_builder.rs index b1a111f628e..6c509006420 100644 --- a/mithril-signer/src/services/signable_builder/signable_seed_builder.rs +++ b/mithril-signer/src/services/signable_builder/signable_seed_builder.rs @@ -15,6 +15,8 @@ use mithril_common::{ protocol::SignerBuilder, signable_builder::SignableSeedBuilder, }; +#[cfg(feature = "future_snark")] +use mithril_common::{crypto_helper::ProtocolKey, entities::SupportedEra}; use crate::{services::EpochService, store::ProtocolInitializerStorer}; @@ -58,6 +60,36 @@ impl SignerSignableSeedBuilder { Ok(encoded_avk) } + + #[cfg(feature = "future_snark")] + fn compute_encode_snark_avk( + &self, + protocol_initializer: ProtocolInitializer, + signers_with_stake: &[SignerWithStake], + ) -> StdResult { + let signer_builder = SignerBuilder::new( + signers_with_stake, + &protocol_initializer.get_protocol_parameters().into(), + ) + .with_context( + || "SignerSignableSeedBuilder can not compute SNARK aggregate verification key", + )?; + + let aggregate_verification_key = signer_builder.compute_aggregate_verification_key(); + let snark_avk = aggregate_verification_key + .to_snark_aggregate_verification_key() + .ok_or_else(|| { + anyhow::anyhow!( + "SNARK aggregate verification key is unavailable during Lagrange era" + ) + })?; + let snark_avk_encoded = + ProtocolKey::new(snark_avk.to_owned()).to_bytes_hex().with_context( + || "SignerSignableSeedBuilder can not serialize SNARK aggregate verification key", + )?; + + Ok(snark_avk_encoded) + } } #[async_trait] @@ -82,6 +114,42 @@ impl SignableSeedBuilder for SignerSignableSeedBuilder { Ok(next_aggregate_verification_key) } + async fn compute_next_aggregate_verification_key_for_snark( + &self, + ) -> StdResult> { + #[cfg(feature = "future_snark")] + { + let epoch_service = self.epoch_service.read().await; + + if epoch_service.mithril_era()? == SupportedEra::Pythagoras { + return Ok(None); + } + + let epoch = (*epoch_service).epoch_of_current_data()?; + let next_signer_retrieval_epoch = epoch.offset_to_next_signer_retrieval_epoch(); + let next_protocol_initializer = self + .protocol_initializer_store + .get_protocol_initializer(next_signer_retrieval_epoch) + .await? + .with_context(|| { + format!( + "can not get protocol_initializer at epoch {next_signer_retrieval_epoch}" + ) + })?; + let next_signers_with_stake = epoch_service.next_signers_with_stake().await?; + let next_snark_aggregate_verification_key = Some( + self.compute_encode_snark_avk(next_protocol_initializer, &next_signers_with_stake)?, + ); + + Ok(next_snark_aggregate_verification_key) + } + + #[cfg(not(feature = "future_snark"))] + { + Ok(None) + } + } + async fn compute_next_protocol_parameters(&self) -> StdResult { let epoch_service = self.epoch_service.read().await; let epoch = (*epoch_service).epoch_of_current_data()?; @@ -226,4 +294,72 @@ mod tests { assert_eq!(current_epoch, expected_current_epoch); } + + #[cfg(feature = "future_snark")] + mod snark_aggregate_verification_key { + use mithril_common::entities::SupportedEra; + + use super::*; + + #[tokio::test] + async fn returns_snark_avk_during_lagrange_era() { + let epoch = Epoch(5); + let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build(); + let protocol_initializer = + next_fixture.signers_fixture()[0].protocol_initializer.clone(); + let next_signers_with_stake = next_fixture.signers_with_stake(); + let mut mock_container = MockDependencyInjector::new(); + mock_container.mock_epoch_service = + MockEpochServiceImpl::new_with_config(|mock_epoch_service| { + mock_epoch_service + .expect_mithril_era() + .return_once(move || Ok(SupportedEra::Lagrange)) + .once(); + mock_epoch_service + .expect_epoch_of_current_data() + .return_once(move || Ok(epoch)) + .once(); + mock_epoch_service + .expect_next_signers_with_stake() + .return_once(move || Ok(next_signers_with_stake)) + .once(); + }); + mock_container + .mock_protocol_initializer_store + .expect_get_protocol_initializer() + .return_once(move |_| Ok(Some(protocol_initializer))) + .once(); + let signable_seed_builder = mock_container.build_signable_builder_service(); + + let result = signable_seed_builder + .compute_next_aggregate_verification_key_for_snark() + .await + .unwrap(); + + let expected_snark_avk = next_fixture + .compute_and_encode_snark_aggregate_verification_key() + .expect("SNARK AVK should be available"); + assert_eq!(result, Some(expected_snark_avk)); + } + + #[tokio::test] + async fn returns_none_during_pythagoras_era() { + let mut mock_container = MockDependencyInjector::new(); + mock_container.mock_epoch_service = + MockEpochServiceImpl::new_with_config(|mock_epoch_service| { + mock_epoch_service + .expect_mithril_era() + .return_once(move || Ok(SupportedEra::Pythagoras)) + .once(); + }); + let signable_seed_builder = mock_container.build_signable_builder_service(); + + let result = signable_seed_builder + .compute_next_aggregate_verification_key_for_snark() + .await + .unwrap(); + + assert_eq!(result, None); + } + } } diff --git a/mithril-signer/src/services/single_signer.rs b/mithril-signer/src/services/single_signer.rs index 59ce1e0c025..d8f97b28b54 100644 --- a/mithril-signer/src/services/single_signer.rs +++ b/mithril-signer/src/services/single_signer.rs @@ -202,13 +202,23 @@ mod tests { }; let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(connection, None)); - let epoch_service = - MithrilEpochService::new(stake_store, protocol_initializer_store, logger.clone()) - .set_data_to_default_or_fake(Epoch(10)) - .alter_data(|data| { - data.protocol_initializer = Some(current_signer.protocol_initializer.clone()); - data.current_signers = fixture.signers(); - }); + let era_checker = { + use mithril_common::entities::SupportedEra; + use mithril_common::test::double::Dummy; + use mithril_era::EraChecker; + Arc::new(EraChecker::new(SupportedEra::dummy(), Epoch::default())) + }; + let epoch_service = MithrilEpochService::new( + era_checker, + stake_store, + protocol_initializer_store, + logger.clone(), + ) + .set_data_to_default_or_fake(Epoch(10)) + .alter_data(|data| { + data.protocol_initializer = Some(current_signer.protocol_initializer.clone()); + data.current_signers = fixture.signers(); + }); let single_signer = MithrilSingleSigner::new( current_signer.party_id(), diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index 2c1c2afa31c..f8d729f9239 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -264,6 +264,7 @@ impl StateMachineTester { logger.clone(), )); let epoch_service = Arc::new(RwLock::new(MithrilEpochService::new( + era_checker.clone(), stake_store.clone(), protocol_initializer_store.clone(), logger.clone(), From 1bc9e0e9e0264d0ea0de64b1cc287b1bb42db3e6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 6 Mar 2026 18:34:11 +0100 Subject: [PATCH 07/19] feat(client): add SNARK AVK verification when present in protocol message --- mithril-client-cli/Cargo.toml | 1 + .../src/commands/cardano_db/download/v1.rs | 2 ++ .../src/commands/cardano_db/shared_steps.rs | 2 ++ mithril-client/Cargo.toml | 2 ++ mithril-client/src/message.rs | 29 +++++++++++++++++-- 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/mithril-client-cli/Cargo.toml b/mithril-client-cli/Cargo.toml index 0ef3e418b22..c3f5855631a 100644 --- a/mithril-client-cli/Cargo.toml +++ b/mithril-client-cli/Cargo.toml @@ -23,6 +23,7 @@ path = "src/main.rs" [features] bundle_tls = ["mithril-client/native-tls-vendored"] +future_snark = ["mithril-client/future_snark"] [dependencies] anyhow = { workspace = true } diff --git a/mithril-client-cli/src/commands/cardano_db/download/v1.rs b/mithril-client-cli/src/commands/cardano_db/download/v1.rs index a46bcf889c1..f2ffaad0564 100644 --- a/mithril-client-cli/src/commands/cardano_db/download/v1.rs +++ b/mithril-client-cli/src/commands/cardano_db/download/v1.rs @@ -267,6 +267,8 @@ mod tests { protocol_message: protocol_message.clone(), signed_message: "signed_message".to_string(), aggregate_verification_key: String::new(), + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: None, multi_signature: String::new(), genesis_signature: String::new(), } diff --git a/mithril-client-cli/src/commands/cardano_db/shared_steps.rs b/mithril-client-cli/src/commands/cardano_db/shared_steps.rs index 56c796acfb9..8049211b2e7 100644 --- a/mithril-client-cli/src/commands/cardano_db/shared_steps.rs +++ b/mithril-client-cli/src/commands/cardano_db/shared_steps.rs @@ -281,6 +281,8 @@ mod tests { protocol_message: protocol_message.clone(), signed_message: "signed_message".to_string(), aggregate_verification_key: String::new(), + #[cfg(feature = "future_snark")] + aggregate_verification_key_snark: None, multi_signature: String::new(), genesis_signature: String::new(), } diff --git a/mithril-client/Cargo.toml b/mithril-client/Cargo.toml index 74d51fae32b..cf4fbe1fec7 100644 --- a/mithril-client/Cargo.toml +++ b/mithril-client/Cargo.toml @@ -45,6 +45,8 @@ rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"] # Support compressed traffic with `reqwest` enable-http-compression = ["reqwest/gzip", "reqwest/zstd", "reqwest/deflate", "reqwest/brotli"] +future_snark = [] + # Enables usage of `rug` numerical backend in `mithril-stm` (dependency of `mithril-common`). rug-backend = ["mithril-common/rug-backend"] # Enables usage of `num-integer` numerical backend in `mithril-stm` (dependency of `mithril-common`) diff --git a/mithril-client/src/message.rs b/mithril-client/src/message.rs index d71c541672b..021b762ddad 100644 --- a/mithril-client/src/message.rs +++ b/mithril-client/src/message.rs @@ -134,9 +134,10 @@ impl MessageBuilder { || "Could not compute message: aggregate verification key computation failed", )?; + let aggregate_verification_key = signer_builder.compute_aggregate_verification_key(); + let avk = ProtocolKey::new( - signer_builder - .compute_aggregate_verification_key() + aggregate_verification_key .to_concatenation_aggregate_verification_key() .to_owned(), ) @@ -146,6 +147,30 @@ impl MessageBuilder { let mut message = certificate.protocol_message.clone(); message.set_message_part(ProtocolMessagePartKey::NextAggregateVerificationKey, avk); + #[cfg(feature = "future_snark")] + if certificate + .protocol_message + .get_message_part(&ProtocolMessagePartKey::NextSnarkAggregateVerificationKey) + .is_some() + { + let snark_avk = aggregate_verification_key + .to_snark_aggregate_verification_key() + .ok_or_else(|| { + anyhow::anyhow!( + "Could not compute message: SNARK aggregate verification key is unavailable" + ) + })?; + let snark_avk_encoded = ProtocolKey::new(snark_avk.to_owned()) + .to_bytes_hex() + .with_context(|| { + "Could not compute message: SNARK aggregate verification key encoding failed" + })?; + message.set_message_part( + ProtocolMessagePartKey::NextSnarkAggregateVerificationKey, + snark_avk_encoded, + ); + } + Ok(message) } From 62441616840794d9650f95e320d40aa5780fa9ab Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 10 Mar 2026 19:32:41 +0100 Subject: [PATCH 08/19] feat(signer): strip SNARK keys from initializer and signers in Pythagoras era --- .../cardano/key_certification.rs | 12 ++ mithril-common/src/entities/signer.rs | 97 +++++++++++++++ mithril-signer/src/services/single_signer.rs | 115 +++++++++++++++--- .../src/protocol/participant/initializer.rs | 57 +++++---- 4 files changed, 244 insertions(+), 37 deletions(-) diff --git a/mithril-common/src/crypto_helper/cardano/key_certification.rs b/mithril-common/src/crypto_helper/cardano/key_certification.rs index 21343df7d11..78c0f513fb4 100644 --- a/mithril-common/src/crypto_helper/cardano/key_certification.rs +++ b/mithril-common/src/crypto_helper/cardano/key_certification.rs @@ -203,6 +203,18 @@ impl StmInitializerWrapper { self.kes_signature_for_snark.map(|k| k.into()) } + /// Remove the SNARK-related keys from the underlying initializer and the + /// corresponding KES signature. + /// + /// This is used during eras that do not yet support SNARK proofs to ensure the + /// initializer's registration entry matches the closed key registration built from + /// signers without SNARK verification keys. + #[cfg(feature = "future_snark")] + pub fn strip_snark_keys(&mut self) { + self.stm_initializer.strip_snark_keys(); + self.kes_signature_for_snark = None; + } + /// Extract the protocol parameters of the initializer pub fn get_protocol_parameters(&self) -> ProtocolParameters { self.stm_initializer.parameters diff --git a/mithril-common/src/entities/signer.rs b/mithril-common/src/entities/signer.rs index 2a9982c0b61..91a2fbd9612 100644 --- a/mithril-common/src/entities/signer.rs +++ b/mithril-common/src/entities/signer.rs @@ -259,6 +259,24 @@ impl SignerWithStake { } } + /// Remove SNARK-related fields for backward compatibility with older eras. + /// + /// This clears the SNARK verification key and its KES signature, which is needed + /// during eras that do not support SNARK proofs (e.g. Pythagoras) to ensure + /// consistency between the signer's initializer and the key registration entries. + #[cfg(feature = "future_snark")] + pub fn without_snark_fields(mut self) -> Self { + self.verification_key_for_snark = None; + self.verification_key_signature_for_snark = None; + self + } + + /// Remove SNARK-related fields from a list of signers with stake for backward compatibility. + #[cfg(feature = "future_snark")] + pub fn strip_snark_fields(signers: Vec) -> Vec { + signers.into_iter().map(Self::without_snark_fields).collect() + } + /// Computes the hash of SignerWithStake pub fn compute_hash(&self) -> String { let mut hasher = Sha256::new(); @@ -466,4 +484,83 @@ mod tests { ); } } + + #[cfg(feature = "future_snark")] + mod strip_snark_fields { + use super::*; + + #[test] + fn snark_fields_are_cleared_by_without_snark_fields() { + let signers = MithrilFixtureBuilder::default() + .with_signers(1) + .build() + .signers_with_stake(); + let signer = signers[0].clone(); + assert!(signer.verification_key_for_snark.is_some()); + assert!(signer.verification_key_signature_for_snark.is_some()); + + let stripped = signer.without_snark_fields(); + + assert!(stripped.verification_key_for_snark.is_none()); + assert!(stripped.verification_key_signature_for_snark.is_none()); + } + + #[test] + fn without_snark_fields_preserves_non_snark_data() { + let signers = MithrilFixtureBuilder::default() + .with_signers(1) + .build() + .signers_with_stake(); + let signer = signers[0].clone(); + + let stripped = signer.clone().without_snark_fields(); + + assert_eq!(signer.party_id, stripped.party_id); + assert_eq!( + signer.verification_key_for_concatenation, + stripped.verification_key_for_concatenation + ); + assert_eq!(signer.stake, stripped.stake); + } + + #[test] + fn without_snark_fields_preserves_none_values() { + let signers = MithrilFixtureBuilder::default() + .with_signers(1) + .build() + .signers_with_stake(); + let mut signer = signers[0].clone(); + signer.verification_key_for_snark = None; + signer.verification_key_signature_for_snark = None; + + let stripped = signer.without_snark_fields(); + + assert!(stripped.verification_key_for_snark.is_none()); + assert!(stripped.verification_key_signature_for_snark.is_none()); + } + + #[test] + fn strip_snark_fields_clears_all_entries() { + let signers = MithrilFixtureBuilder::default() + .with_signers(3) + .build() + .signers_with_stake(); + assert!(signers.iter().all(|s| s.verification_key_for_snark.is_some())); + + let stripped = SignerWithStake::strip_snark_fields(signers); + + assert!(stripped.iter().all(|s| s.verification_key_for_snark.is_none())); + assert!( + stripped + .iter() + .all(|s| s.verification_key_signature_for_snark.is_none()) + ); + } + + #[test] + fn strip_snark_fields_handles_empty_list() { + let stripped = SignerWithStake::strip_snark_fields(vec![]); + assert!(stripped.is_empty()); + } + } } diff --git a/mithril-signer/src/services/single_signer.rs b/mithril-signer/src/services/single_signer.rs index d8f97b28b54..5bc0927ce51 100644 --- a/mithril-signer/src/services/single_signer.rs +++ b/mithril-signer/src/services/single_signer.rs @@ -10,6 +10,8 @@ use mithril_common::crypto_helper::{KesPeriod, KesSigner, ProtocolInitializer}; use mithril_common::entities::{ PartyId, ProtocolMessage, ProtocolParameters, SingleSignature, Stake, }; +#[cfg(feature = "future_snark")] +use mithril_common::entities::{SignerWithStake, SupportedEra}; use mithril_common::logging::LoggerExtensions; use mithril_common::protocol::{SignerBuilder, SingleSigner as ProtocolSingleSigner}; use mithril_common::{StdError, StdResult}; @@ -97,15 +99,32 @@ impl MithrilSingleSigner { ) })?; + #[cfg(not(feature = "future_snark"))] + let protocol_initializer = protocol_initializer.clone(); + #[cfg(feature = "future_snark")] + let mut protocol_initializer = protocol_initializer.clone(); + + let current_signers_with_stake = epoch_service.current_signers_with_stake().await?; + + #[cfg(feature = "future_snark")] + let current_signers_with_stake = { + if epoch_service.mithril_era()? == SupportedEra::Pythagoras { + protocol_initializer.strip_snark_keys(); + SignerWithStake::strip_snark_fields(current_signers_with_stake) + } else { + current_signers_with_stake + } + }; + let builder = SignerBuilder::new( - &epoch_service.current_signers_with_stake().await?, + ¤t_signers_with_stake, &protocol_initializer.get_protocol_parameters().into(), ) .with_context(|| "Mithril Single Signer can not build signer") .map_err(SingleSignerError::ProtocolSignerCreationFailure)?; let single_signer = builder - .restore_signer_from_initializer(self.party_id.clone(), protocol_initializer.clone()) + .restore_signer_from_initializer(self.party_id.clone(), protocol_initializer) .with_context(|| { format!( "Mithril Single Signer can not restore signer with party_id: '{}'", @@ -180,13 +199,18 @@ mod tests { use super::*; - #[tokio::test] - async fn compute_single_signature_success() { - let snapshot_digest = "digest".to_string(); + use mithril_common::entities::SupportedEra; + use mithril_era::EraChecker; + + async fn build_single_signer_for_era( + era: SupportedEra, + signers: Vec, + ) -> ( + MithrilSingleSigner, + mithril_common::test::builder::MithrilFixture, + ) { let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); let current_signer = &fixture.signers_fixture()[0]; - let clerk = ProtocolClerk::new_clerk_from_signer(¤t_signer.protocol_signer); - let avk = clerk.compute_aggregate_verification_key(); let logger = TestLogger::stdout(); let connection = Arc::new(main_db_connection().unwrap()); let stake_store = { @@ -202,12 +226,7 @@ mod tests { }; let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(connection, None)); - let era_checker = { - use mithril_common::entities::SupportedEra; - use mithril_common::test::double::Dummy; - use mithril_era::EraChecker; - Arc::new(EraChecker::new(SupportedEra::dummy(), Epoch::default())) - }; + let era_checker = Arc::new(EraChecker::new(era, Epoch::default())); let epoch_service = MithrilEpochService::new( era_checker, stake_store, @@ -216,8 +235,9 @@ mod tests { ) .set_data_to_default_or_fake(Epoch(10)) .alter_data(|data| { + data.mithril_era = era; data.protocol_initializer = Some(current_signer.protocol_initializer.clone()); - data.current_signers = fixture.signers(); + data.current_signers = signers; }); let single_signer = MithrilSingleSigner::new( @@ -226,8 +246,20 @@ mod tests { logger, ); + (single_signer, fixture) + } + + async fn sign_and_verify( + single_signer: &MithrilSingleSigner, + fixture: &mithril_common::test::builder::MithrilFixture, + ) { + let current_signer = &fixture.signers_fixture()[0]; + let clerk = ProtocolClerk::new_clerk_from_signer(¤t_signer.protocol_signer); + let avk = clerk.compute_aggregate_verification_key(); + let mut protocol_message = ProtocolMessage::new(); - protocol_message.set_message_part(ProtocolMessagePartKey::SnapshotDigest, snapshot_digest); + protocol_message + .set_message_part(ProtocolMessagePartKey::SnapshotDigest, "digest".to_string()); let sign_result = single_signer .compute_single_signature(&protocol_message) .await @@ -251,4 +283,57 @@ mod tests { "produced single signature should be valid" ); } + + #[tokio::test] + async fn compute_single_signature_success() { + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let signers = fixture.signers(); + let (single_signer, fixture) = + build_single_signer_for_era(SupportedEra::Pythagoras, signers).await; + sign_and_verify(&single_signer, &fixture).await; + } + + #[cfg(feature = "future_snark")] + mod snark_key_stripping { + use super::*; + + #[tokio::test] + async fn signing_succeeds_in_lagrange_era_with_snark_keys() { + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let signers = fixture.signers(); + let (single_signer, fixture) = + build_single_signer_for_era(SupportedEra::Lagrange, signers).await; + + sign_and_verify(&single_signer, &fixture).await; + } + + #[tokio::test] + async fn signing_succeeds_in_pythagoras_era_with_snark_keys() { + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let signers = fixture.signers(); + let (single_signer, fixture) = + build_single_signer_for_era(SupportedEra::Pythagoras, signers).await; + + sign_and_verify(&single_signer, &fixture).await; + } + + #[tokio::test] + async fn signing_succeeds_in_pythagoras_era_when_signers_already_lack_snark_keys() { + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let signers_without_snark_keys: Vec<_> = fixture + .signers() + .into_iter() + .map(|mut signer| { + signer.verification_key_for_snark = None; + signer.verification_key_signature_for_snark = None; + signer + }) + .collect(); + let (single_signer, fixture) = + build_single_signer_for_era(SupportedEra::Pythagoras, signers_without_snark_keys) + .await; + + sign_and_verify(&single_signer, &fixture).await; + } + } } diff --git a/mithril-stm/src/protocol/participant/initializer.rs b/mithril-stm/src/protocol/participant/initializer.rs index 935dab1e43e..485d2d9c3ee 100644 --- a/mithril-stm/src/protocol/participant/initializer.rs +++ b/mithril-stm/src/protocol/participant/initializer.rs @@ -114,28 +114,29 @@ impl Initializer { #[cfg(feature = "future_snark")] let snark_proof_signer = { - let key_registration_commitment_for_snark = closed_key_registration - .to_merkle_tree::() - .to_merkle_tree_commitment(); - let lottery_target_value = ClosedRegistrationEntry::try_from(( - registration_entry, - closed_key_registration.total_stake, - self.parameters.phi_f, - ))? - .get_lottery_target_value(); - SnarkProofSigner::new( - self.parameters, - self.schnorr_signing_key + match (self.schnorr_signing_key, self.schnorr_verification_key) { + (Some(schnorr_signing_key), Some(schnorr_verification_key)) => { + let key_registration_commitment_for_snark = closed_key_registration + .to_merkle_tree::() + .to_merkle_tree_commitment(); + let lottery_target_value = ClosedRegistrationEntry::try_from(( + registration_entry, + closed_key_registration.total_stake, + self.parameters.phi_f, + ))? + .get_lottery_target_value() .ok_or(RegisterError::SnarkProofSignerCreation) - .with_context(|| "missing schnorr signing key")?, - self.schnorr_verification_key - .ok_or(RegisterError::SnarkProofSignerCreation) - .with_context(|| "missing schnorr verification key")?, - lottery_target_value - .ok_or(RegisterError::SnarkProofSignerCreation) - .with_context(|| "missing lottery target value")?, - key_registration_commitment_for_snark, - ) + .with_context(|| "missing lottery target value")?; + Some(SnarkProofSigner::new( + self.parameters, + schnorr_signing_key, + schnorr_verification_key, + lottery_target_value, + key_registration_commitment_for_snark, + )) + } + _ => None, + } }; // Create and return signer @@ -146,7 +147,7 @@ impl Initializer { self.parameters, registration_entry.get_stake(), #[cfg(feature = "future_snark")] - Some(snark_proof_signer), + snark_proof_signer, )) } @@ -163,6 +164,18 @@ impl Initializer { self.schnorr_verification_key } + /// Remove the SNARK-related keys (Schnorr signing and verification keys) from this + /// initializer. + /// + /// This is used during eras that do not yet support SNARK proofs to ensure the + /// initializer's registration entry matches the closed key registration built from + /// signers without SNARK verification keys. + #[cfg(feature = "future_snark")] + pub fn strip_snark_keys(&mut self) { + self.schnorr_signing_key = None; + self.schnorr_verification_key = None; + } + /// Convert to bytes /// # Layout /// * Stake (u64) From 2e4c153609e0b332c7ba8c42bb70b977763160b3 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Thu, 12 Mar 2026 18:04:03 +0100 Subject: [PATCH 09/19] fix(stm): backward compatibility for Concatenation proof When a previous client tries to verify a certificate with a Concatenation proof which has been created with a new aggregator compiled with 'future_snark', it can't parse it because it embeds a 'ClosedKeyRegistrationEntry' which has a rigid serde deserialization based on array representation (receives an array of length 4 when expecting 2). --- .../src/proof_system/concatenation/clerk.rs | 13 ++- .../src/proof_system/concatenation/proof.rs | 12 ++- .../closed_registration_entry.rs | 80 +++++++++++++++++++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/mithril-stm/src/proof_system/concatenation/clerk.rs b/mithril-stm/src/proof_system/concatenation/clerk.rs index 608102aeca8..e29e4be61dc 100644 --- a/mithril-stm/src/proof_system/concatenation/clerk.rs +++ b/mithril-stm/src/proof_system/concatenation/clerk.rs @@ -236,9 +236,16 @@ mod tests { let sig_reg_list = all_sigs .iter() - .map(|sig| SingleSignatureWithRegisteredParty { - sig: sig.clone(), - reg_party: clerk.closed_key_registration.get_registration_entry_for_index(&sig.signer_index).unwrap(), + .map(|sig| { + let reg_party = clerk.closed_key_registration.get_registration_entry_for_index(&sig.signer_index).unwrap(); + #[cfg(feature = "future_snark")] + // We need to remove the SNARK fields from the registration entry used in Concatenation proofs to avoid breaking change with previous client nor able to parse the aggregate signature. + // This happens because of the way the `ClosedRegistrationEntry` is serialized with an array representation isntead of map representation. + let reg_party = reg_party.without_snark_fields(); + SingleSignatureWithRegisteredParty { + sig: sig.clone(), + reg_party, + } }) .collect::>(); diff --git a/mithril-stm/src/proof_system/concatenation/proof.rs b/mithril-stm/src/proof_system/concatenation/proof.rs index 6851b3bb5e7..2abd9fc4904 100644 --- a/mithril-stm/src/proof_system/concatenation/proof.rs +++ b/mithril-stm/src/proof_system/concatenation/proof.rs @@ -43,9 +43,15 @@ impl ConcatenationProof { clerk .closed_key_registration .get_registration_entry_for_index(&sig.signer_index) - .map(|reg_party| SingleSignatureWithRegisteredParty { - sig: sig.clone(), - reg_party, + .map(|reg_party| { + #[cfg(feature = "future_snark")] + // We need to remove the SNARK fields from the registration entry used in Concatenation proofs to avoid breaking change with previous client nor able to parse the aggregate signature. + // This happens because of the way the `ClosedRegistrationEntry` is serialized with an array representation isntead of map representation. + let reg_party = reg_party.without_snark_fields(); + SingleSignatureWithRegisteredParty { + sig: sig.clone(), + reg_party, + } }) }) .collect::, _>>()?; diff --git a/mithril-stm/src/protocol/key_registration/closed_registration_entry.rs b/mithril-stm/src/protocol/key_registration/closed_registration_entry.rs index 5f8636a52cd..4b2974058de 100644 --- a/mithril-stm/src/protocol/key_registration/closed_registration_entry.rs +++ b/mithril-stm/src/protocol/key_registration/closed_registration_entry.rs @@ -55,6 +55,21 @@ impl ClosedRegistrationEntry { self.stake } + /// Returns a copy of this entry with SNARK-specific fields removed. + /// + /// This is used when embedding registration entries in concatenation proofs, + /// which do not need SNARK fields and must remain backward-compatible with + /// clients that do not support the `future_snark` feature. + #[cfg(feature = "future_snark")] + pub fn without_snark_fields(&self) -> Self { + ClosedRegistrationEntry { + verification_key_for_concatenation: self.verification_key_for_concatenation, + stake: self.stake, + verification_key_for_snark: None, + lottery_target_value: None, + } + } + #[cfg(feature = "future_snark")] /// Gets the verification key for snark. pub fn get_verification_key_for_snark(&self) -> Option { @@ -362,4 +377,69 @@ mod tests { assert_eq!(golden_serialized, serialized); } } + + #[cfg(feature = "future_snark")] + mod without_snark_fields { + use super::*; + + #[test] + fn preserves_concatenation_key_and_stake() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let entry = create_closed_registration_entry(&mut rng, 42); + + let stripped = entry.without_snark_fields(); + + assert_eq!( + entry.get_verification_key_for_concatenation(), + stripped.get_verification_key_for_concatenation() + ); + assert_eq!(entry.get_stake(), stripped.get_stake()); + } + + #[test] + fn clears_snark_fields() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let entry = create_closed_registration_entry(&mut rng, 42); + assert!(entry.get_verification_key_for_snark().is_some()); + assert!(entry.get_lottery_target_value().is_some()); + + let stripped = entry.without_snark_fields(); + + assert!(stripped.get_verification_key_for_snark().is_none()); + assert!(stripped.get_lottery_target_value().is_none()); + } + + #[test] + fn serializes_as_two_element_json_tuple() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let entry = create_closed_registration_entry(&mut rng, 42); + + let stripped = entry.without_snark_fields(); + let json: serde_json::Value = + serde_json::to_value(stripped).expect("JSON serialization should not fail"); + + let array = json.as_array().expect("should serialize as a JSON array"); + assert_eq!( + 2, + array.len(), + "stripped entry should serialize as a 2-element tuple" + ); + } + + #[test] + fn entry_with_snark_fields_serializes_as_four_element_json_tuple() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let entry = create_closed_registration_entry(&mut rng, 42); + + let json: serde_json::Value = + serde_json::to_value(entry).expect("JSON serialization should not fail"); + + let array = json.as_array().expect("should serialize as a JSON array"); + assert_eq!( + 4, + array.len(), + "full entry should serialize as a 4-element tuple" + ); + } + } } From a9bb7b887c20465fa5d5048c60518c9c313b5632 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 16:27:35 +0100 Subject: [PATCH 10/19] feat(common): support Mithril era in genesis certificate --- .../certificate_chain/certificate_genesis.rs | 21 ++++-- .../certificate_chain/certificate_verifier.rs | 68 ++++++++++++++----- .../test/builder/certificate_chain_builder.rs | 23 ++++++- .../src/test/builder/mithril_fixture.rs | 4 ++ 4 files changed, 88 insertions(+), 28 deletions(-) diff --git a/mithril-common/src/certificate_chain/certificate_genesis.rs b/mithril-common/src/certificate_chain/certificate_genesis.rs index 514b98706e5..e62be74780e 100644 --- a/mithril-common/src/certificate_chain/certificate_genesis.rs +++ b/mithril-common/src/certificate_chain/certificate_genesis.rs @@ -13,7 +13,7 @@ use crate::{ }, entities::{ Certificate, CertificateMetadata, CertificateSignature, Epoch, ProtocolMessage, - ProtocolMessagePartKey, ProtocolParameters, + ProtocolMessagePartKey, ProtocolParameters, SupportedEra, }, protocol::ToMessage, }; @@ -46,6 +46,7 @@ impl CertificateGenesisProducer { genesis_protocol_parameters: &ProtocolParameters, genesis_avk: &ProtocolAggregateVerificationKey, genesis_epoch: &Epoch, + mithril_era: SupportedEra, ) -> StdResult { let genesis_aggregate_verification_key_for_concatenation = ProtocolKey::new(genesis_avk.to_concatenation_aggregate_verification_key().to_owned()); @@ -57,8 +58,8 @@ impl CertificateGenesisProducer { genesis_concatenation_avk, ); - #[cfg(feature = "future_snark")] - { + if mithril_era != SupportedEra::Pythagoras { + #[cfg(feature = "future_snark")] match genesis_avk.to_snark_aggregate_verification_key() { Some(snark_avk) => { let genesis_snark_avk: ProtocolAggregateVerificationKeyForSnark = @@ -70,8 +71,7 @@ impl CertificateGenesisProducer { } None => { eprintln!( - "WARNING: SNARK aggregate verification key is unavailable, \ - genesis certificate will not include SNARK AVK" + "WARNING: SNARK aggregate verification key is unavailable, genesis certificate will not include SNARK AVK" ); } } @@ -107,6 +107,7 @@ impl CertificateGenesisProducer { epoch: Epoch, genesis_avk: ProtocolAggregateVerificationKey, genesis_signature: ProtocolGenesisSignature, + mithril_era: SupportedEra, ) -> StdResult { let protocol_version = PROTOCOL_VERSION.to_string(); let initiated_at = Utc::now(); @@ -121,8 +122,12 @@ impl CertificateGenesisProducer { signers, ); let previous_hash = "".to_string(); - let genesis_protocol_message = - Self::create_genesis_protocol_message(&protocol_parameters, &genesis_avk, &epoch)?; + let genesis_protocol_message = Self::create_genesis_protocol_message( + &protocol_parameters, + &genesis_avk, + &epoch, + mithril_era, + )?; Ok(Certificate::new( previous_hash, epoch, @@ -150,6 +155,7 @@ mod tests { &genesis_protocol_parameters, &genesis_avk, &genesis_epoch, + SupportedEra::Pythagoras, ) .unwrap(); @@ -185,6 +191,7 @@ mod tests { &genesis_protocol_parameters, &genesis_avk, &genesis_epoch, + SupportedEra::Lagrange, ) .unwrap(); diff --git a/mithril-common/src/certificate_chain/certificate_verifier.rs b/mithril-common/src/certificate_chain/certificate_verifier.rs index fc41f85bbbb..682de003221 100644 --- a/mithril-common/src/certificate_chain/certificate_verifier.rs +++ b/mithril-common/src/certificate_chain/certificate_verifier.rs @@ -1214,9 +1214,23 @@ mod tests { use super::*; use crate::crypto_helper::ProtocolMembershipDigest; + use crate::entities::SupportedEra; + use crate::test::builder::{CertificateChainBuilder, CertificateChainFixture}; use mithril_stm::AggregateSignature; + fn setup_certificate_chain_with_lagrange_era( + total_certificates: u64, + certificates_per_epoch: u64, + ) -> CertificateChainFixture { + CertificateChainBuilder::new() + .with_total_certificates(total_certificates) + .with_certificates_per_epoch(certificates_per_epoch) + .with_protocol_parameters(setup_protocol_parameters()) + .with_mithril_era(SupportedEra::Lagrange) + .build() + } + fn with_snark_proof_type(mut certificate: Certificate) -> Certificate { if let CertificateSignature::MultiSignature(entity_type, _) = certificate.signature.clone() @@ -1235,8 +1249,10 @@ mod tests { #[test] fn snark_avk_chaining_succeeds_with_different_epochs() { let (total_certificates, certificates_per_epoch) = (5, 1); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let mut certificate = with_snark_proof_type(fake_certificates[0].clone()); let previous_certificate = fake_certificates[1].clone(); @@ -1254,8 +1270,10 @@ mod tests { #[test] fn snark_avk_chaining_succeeds_with_same_epoch() { let (total_certificates, certificates_per_epoch) = (5, 2); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let certificate = with_snark_proof_type(fake_certificates[0].clone()); let previous_certificate = fake_certificates[1].clone(); @@ -1272,8 +1290,10 @@ mod tests { fn snark_avk_chaining_fails_with_same_epoch_when_current_has_snark_avk_but_previous_does_not() { let (total_certificates, certificates_per_epoch) = (5, 2); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let certificate = with_snark_proof_type(fake_certificates[0].clone()); let mut previous_certificate = fake_certificates[1].clone(); @@ -1293,8 +1313,10 @@ mod tests { fn snark_avk_chaining_fails_with_same_epoch_when_previous_has_snark_avk_but_current_does_not() { let (total_certificates, certificates_per_epoch) = (5, 2); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let mut certificate = with_snark_proof_type(fake_certificates[0].clone()); certificate.aggregate_verification_key_snark = None; @@ -1314,8 +1336,10 @@ mod tests { #[test] fn snark_avk_chaining_fails_with_same_epoch_when_both_lack_snark_avk() { let (total_certificates, certificates_per_epoch) = (5, 2); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let mut certificate = with_snark_proof_type(fake_certificates[0].clone()); certificate.aggregate_verification_key_snark = None; @@ -1336,8 +1360,10 @@ mod tests { #[test] fn snark_avk_chaining_fails_when_next_snark_avk_is_tampered() { let (total_certificates, certificates_per_epoch) = (5, 1); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let certificate = with_snark_proof_type(fake_certificates[0].clone()); let mut previous_certificate = fake_certificates[1].clone(); @@ -1359,8 +1385,10 @@ mod tests { #[test] fn snark_avk_chaining_fails_when_next_snark_avk_is_missing() { let (total_certificates, certificates_per_epoch) = (5, 1); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let certificate = with_snark_proof_type(fake_certificates[0].clone()); let mut previous_certificate = fake_certificates[1].clone(); @@ -1382,8 +1410,10 @@ mod tests { #[test] fn avk_chaining_dispatches_to_snark_when_current_is_future_and_previous_is_concatenation() { let (total_certificates, certificates_per_epoch) = (5, 1); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let mut certificate = with_snark_proof_type(fake_certificates[0].clone()); let previous_certificate = fake_certificates[1].clone(); @@ -1400,8 +1430,10 @@ mod tests { #[test] fn snark_avk_chaining_succeeds_when_previous_is_genesis_certificate() { let (total_certificates, certificates_per_epoch) = (5, 1); - let fake_certificates = - setup_certificate_chain(total_certificates, certificates_per_epoch); + let fake_certificates = setup_certificate_chain_with_lagrange_era( + total_certificates, + certificates_per_epoch, + ); let verifier = MockDependencyInjector::new().build_certificate_verifier(); let genesis_certificate = fake_certificates.genesis_certificate().clone(); let mut certificate = with_snark_proof_type(fake_certificates[3].clone()); diff --git a/mithril-common/src/test/builder/certificate_chain_builder.rs b/mithril-common/src/test/builder/certificate_chain_builder.rs index 7a879696767..8d544c46488 100644 --- a/mithril-common/src/test/builder/certificate_chain_builder.rs +++ b/mithril-common/src/test/builder/certificate_chain_builder.rs @@ -14,7 +14,7 @@ use crate::{ }, entities::{ CardanoDbBeacon, Certificate, CertificateMetadata, CertificateSignature, Epoch, - ProtocolMessage, ProtocolMessagePartKey, SignedEntityType, + ProtocolMessage, ProtocolMessagePartKey, SignedEntityType, SupportedEra, }, test::{ builder::{MithrilFixture, MithrilFixtureBuilder, SignerFixture}, @@ -252,6 +252,7 @@ pub struct CertificateChainBuilder<'a> { standard_certificate_processor: &'a StandardCertificateProcessorFunc, certificate_chaining_method: CertificateChainingMethod, aggregate_signature_type: AggregateSignatureType, + mithril_era: SupportedEra, } impl<'a> CertificateChainBuilder<'a> { @@ -271,6 +272,7 @@ impl<'a> CertificateChainBuilder<'a> { standard_certificate_processor: &|certificate, _| certificate, certificate_chaining_method: Default::default(), aggregate_signature_type: Default::default(), + mithril_era: *SupportedEra::eras().first().unwrap(), } } @@ -345,6 +347,13 @@ impl<'a> CertificateChainBuilder<'a> { self } + /// Set the Mithril era to use for genesis certificate creation. + pub fn with_mithril_era(mut self, mithril_era: SupportedEra) -> Self { + self.mithril_era = mithril_era; + + self + } + /// Build the certificate chain. pub fn build(self) -> CertificateChainFixture { let (genesis_signer, genesis_verifier) = CertificateChainBuilder::setup_genesis(); @@ -365,7 +374,7 @@ impl<'a> CertificateChainBuilder<'a> { ); match index_certificate { 0 => genesis_certificate_processor( - self.build_genesis_certificate(&context, &genesis_signer), + self.build_genesis_certificate(&context, &genesis_signer, self.mithril_era), &context, &genesis_signer, ), @@ -498,6 +507,7 @@ impl<'a> CertificateChainBuilder<'a> { &self, context: &CertificateChainBuilderContext, genesis_signer: &ProtocolGenesisSigner, + mithril_era: SupportedEra, ) -> Certificate { let epoch = context.epoch; let certificate = self.build_base_certificate(context); @@ -509,6 +519,7 @@ impl<'a> CertificateChainBuilder<'a> { next_protocol_parameters, &next_avk, &epoch, + mithril_era, ) .unwrap(); let genesis_signature = genesis_producer @@ -521,6 +532,7 @@ impl<'a> CertificateChainBuilder<'a> { certificate.epoch, next_avk, genesis_signature, + mithril_era, ) .unwrap() } @@ -864,9 +876,14 @@ mod test { let expected_signed_message = expected_protocol_message.compute_hash(); let (protocol_genesis_signer, _) = CertificateChainBuilder::setup_genesis(); + let mithril_era = if cfg!(feature = "future_snark") { + SupportedEra::Lagrange + } else { + SupportedEra::Pythagoras + }; let genesis_certificate = CertificateChainBuilder::default() .with_protocol_parameters(expected_protocol_parameters) - .build_genesis_certificate(&context, &protocol_genesis_signer); + .build_genesis_certificate(&context, &protocol_genesis_signer, mithril_era); assert!(genesis_certificate.is_genesis()); assert_eq!( diff --git a/mithril-common/src/test/builder/mithril_fixture.rs b/mithril-common/src/test/builder/mithril_fixture.rs index 93e8baf5a44..7381fb21a6b 100644 --- a/mithril-common/src/test/builder/mithril_fixture.rs +++ b/mithril-common/src/test/builder/mithril_fixture.rs @@ -23,6 +23,7 @@ use crate::{ entities::{ Certificate, Epoch, HexEncodedAggregateVerificationKey, PartyId, ProtocolParameters, Signer, SignerWithStake, SingleSignature, Stake, StakeDistribution, StakeDistributionParty, + SupportedEra, }, protocol::{SignerBuilder, ToMessage}, test::crypto_helper::ProtocolInitializerTestExtension, @@ -232,10 +233,12 @@ impl MithrilFixture { let genesis_avk = self.compute_aggregate_verification_key(); let genesis_signer = ProtocolGenesisSigner::create_deterministic_signer(); let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer))); + let mithril_era = SupportedEra::Pythagoras; let genesis_protocol_message = CertificateGenesisProducer::create_genesis_protocol_message( &self.protocol_parameters, &genesis_avk, &epoch, + mithril_era, ) .unwrap(); let genesis_signature = genesis_producer @@ -248,6 +251,7 @@ impl MithrilFixture { epoch, genesis_avk, genesis_signature, + mithril_era, ) .unwrap() } From 8f3369c1d8d181dc3033bc933770dc581c416a3d Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 16:29:00 +0100 Subject: [PATCH 11/19] feat(aggregator): support Mithirl era in genesis commands --- .../src/commands/genesis_command.rs | 85 ++++++++++++++++--- .../src/dependency_injection/builder/mod.rs | 3 + .../containers/genesis.rs | 7 +- mithril-aggregator/src/tools/genesis.rs | 11 ++- 4 files changed, 93 insertions(+), 13 deletions(-) diff --git a/mithril-aggregator/src/commands/genesis_command.rs b/mithril-aggregator/src/commands/genesis_command.rs index 7ffd0697792..1ca85e56e2f 100644 --- a/mithril-aggregator/src/commands/genesis_command.rs +++ b/mithril-aggregator/src/commands/genesis_command.rs @@ -12,7 +12,7 @@ use mithril_common::{ crypto_helper::{ ProtocolGenesisSecretKey, ProtocolGenesisSigner, ProtocolGenesisVerificationKey, }, - entities::{HexEncodedGenesisSecretKey, HexEncodedGenesisVerificationKey}, + entities::{HexEncodedGenesisSecretKey, HexEncodedGenesisVerificationKey, SupportedEra}, }; use mithril_doc::{Documenter, StructDoc}; @@ -21,6 +21,27 @@ use crate::{ extract_all, tools::GenesisTools, }; +/// Resolve the Mithril era for the genesis command. +/// +/// If the era is explicitly provided, it is returned as-is. +/// If not provided and only one era exists, that era is used automatically. +/// If not provided and multiple eras exist, an error is returned. +fn resolve_mithril_era(mithril_era: Option) -> StdResult { + match mithril_era { + Some(era) => Ok(era), + None => { + let eras = SupportedEra::eras(); + if eras.len() == 1 { + Ok(eras[0]) + } else { + Err(anyhow::anyhow!( + "Multiple Mithril eras are supported ({eras:?}), please specify which era to use with --mithril-era" + )) + } + } + } +} + #[derive(Debug, Clone, Deserialize, Documenter)] pub struct GenesisCommandConfiguration { /// Cardano CLI tool path @@ -163,6 +184,12 @@ pub struct ExportGenesisSubCommand { /// Target path #[clap(long)] target_path: PathBuf, + + /// Mithril era to use for the genesis certificate + /// + /// Optional when only one era exists, required when multiple eras are supported. + #[clap(long)] + mithril_era: Option, } impl ExportGenesisSubCommand { @@ -181,11 +208,15 @@ impl ExportGenesisSubCommand { "Genesis export payload to sign to {}", self.target_path.display() ); + let mithril_era = resolve_mithril_era(self.mithril_era)?; let mut dependencies_builder = DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone())); - let dependencies = dependencies_builder.create_genesis_container().await.with_context( - || "Dependencies Builder can not create genesis command dependencies container", - )?; + let dependencies = dependencies_builder + .create_genesis_container(mithril_era) + .await + .with_context( + || "Dependencies Builder can not create genesis command dependencies container", + )?; let genesis_tools = GenesisTools::from_dependencies(dependencies) .await @@ -228,11 +259,15 @@ impl ImportGenesisSubCommand { "Genesis import signed payload from {}", self.signed_payload_path.to_string_lossy() ); + let mithril_era = resolve_mithril_era(None)?; let mut dependencies_builder = DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone())); - let dependencies = dependencies_builder.create_genesis_container().await.with_context( - || "Dependencies Builder can not create genesis command dependencies container", - )?; + let dependencies = dependencies_builder + .create_genesis_container(mithril_era) + .await + .with_context( + || "Dependencies Builder can not create genesis command dependencies container", + )?; let genesis_tools = GenesisTools::from_dependencies(dependencies) .await @@ -296,6 +331,12 @@ pub struct BootstrapGenesisSubCommand { /// Genesis Secret Key (test only) #[clap(long, env = "GENESIS_SECRET_KEY")] genesis_secret_key: HexEncodedGenesisSecretKey, + + /// Mithril era to use for the genesis certificate + /// + /// Optional when only one era exists, required when multiple eras are supported. + #[clap(long)] + mithril_era: Option, } impl BootstrapGenesisSubCommand { @@ -311,11 +352,15 @@ impl BootstrapGenesisSubCommand { .with_context(|| "configuration deserialize error")?; debug!(root_logger, "BOOTSTRAP GENESIS command"; "config" => format!("{config:?}")); println!("Genesis bootstrap for test only!"); + let mithril_era = resolve_mithril_era(self.mithril_era)?; let mut dependencies_builder = DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone())); - let dependencies = dependencies_builder.create_genesis_container().await.with_context( - || "Dependencies Builder can not create genesis command dependencies container", - )?; + let dependencies = dependencies_builder + .create_genesis_container(mithril_era) + .await + .with_context( + || "Dependencies Builder can not create genesis command dependencies container", + )?; let genesis_tools = GenesisTools::from_dependencies(dependencies) .await @@ -372,6 +417,24 @@ mod tests { use super::*; + #[test] + fn resolve_mithril_era_returns_provided_era() { + let era = resolve_mithril_era(Some(SupportedEra::Lagrange)).unwrap(); + assert_eq!(SupportedEra::Lagrange, era); + } + + #[test] + fn resolve_mithril_era_returns_error_when_multiple_eras_and_none_provided() { + let eras = SupportedEra::eras(); + if eras.len() > 1 { + let result = resolve_mithril_era(None); + assert!( + result.is_err(), + "Should error when multiple eras exist and none is provided" + ); + } + } + #[tokio::test] async fn create_container_does_not_panic() { let config = GenesisCommandConfiguration { @@ -386,7 +449,7 @@ mod tests { DependenciesBuilder::new(TestLogger::stdout(), Arc::new(config)); dependencies_builder - .create_genesis_container() + .create_genesis_container(SupportedEra::Pythagoras) .await .expect("Expected container creation to succeed without panicking"); } diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index 6e7dddb15d0..aca1db85390 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -29,6 +29,7 @@ use mithril_common::{ api_version::APIVersionProvider, certificate_chain::CertificateVerifier, crypto_helper::ProtocolGenesisVerifier, + entities::SupportedEra, signable_builder::{SignableBuilderService, SignableSeedBuilder}, }; use mithril_era::{EraChecker, EraReader, EraReaderAdapter}; @@ -480,6 +481,7 @@ impl DependenciesBuilder { /// Create dependencies for genesis commands pub async fn create_genesis_container( &mut self, + mithril_era: SupportedEra, ) -> Result { let network = self.configuration.get_network().with_context( || "Dependencies Builder can not get Cardano network while building genesis container", @@ -492,6 +494,7 @@ impl DependenciesBuilder { certificate_verifier: self.get_certificate_verifier().await?, protocol_parameters_retriever: self.get_protocol_parameters_retriever().await?, verification_key_store: self.get_verification_key_store().await?, + mithril_era, }; Ok(dependencies) diff --git a/mithril-aggregator/src/dependency_injection/containers/genesis.rs b/mithril-aggregator/src/dependency_injection/containers/genesis.rs index 38b51d2a147..f10cd0573d4 100644 --- a/mithril-aggregator/src/dependency_injection/containers/genesis.rs +++ b/mithril-aggregator/src/dependency_injection/containers/genesis.rs @@ -1,7 +1,9 @@ use std::sync::Arc; use mithril_cardano_node_chain::chain_observer::ChainObserver; -use mithril_common::{CardanoNetwork, certificate_chain::CertificateVerifier}; +use mithril_common::{ + CardanoNetwork, certificate_chain::CertificateVerifier, entities::SupportedEra, +}; use crate::database::repository::CertificateRepository; use crate::{ProtocolParametersRetriever, VerificationKeyStorer}; @@ -25,4 +27,7 @@ pub struct GenesisCommandDependenciesContainer { /// Certificate store. pub certificate_repository: Arc, + + /// Mithril era to use for the genesis certificate. + pub mithril_era: SupportedEra, } diff --git a/mithril-aggregator/src/tools/genesis.rs b/mithril-aggregator/src/tools/genesis.rs index 1076ca089fc..e7ff4c62b17 100644 --- a/mithril-aggregator/src/tools/genesis.rs +++ b/mithril-aggregator/src/tools/genesis.rs @@ -13,7 +13,7 @@ use mithril_common::{ ProtocolAggregateVerificationKey, ProtocolGenesisSecretKey, ProtocolGenesisSignature, ProtocolGenesisSigner, ProtocolGenesisVerificationKey, }, - entities::{Epoch, ProtocolParameters}, + entities::{Epoch, ProtocolParameters, SupportedEra}, protocol::SignerBuilder, }; @@ -29,6 +29,7 @@ pub struct GenesisTools { genesis_protocol_parameters: ProtocolParameters, certificate_verifier: Arc, certificate_repository: Arc, + mithril_era: SupportedEra, } impl GenesisTools { @@ -39,6 +40,7 @@ impl GenesisTools { genesis_protocol_parameters: ProtocolParameters, certificate_verifier: Arc, certificate_repository: Arc, + mithril_era: SupportedEra, ) -> Self { Self { network, @@ -47,6 +49,7 @@ impl GenesisTools { genesis_protocol_parameters, certificate_verifier, certificate_repository, + mithril_era, } } @@ -87,6 +90,7 @@ impl GenesisTools { genesis_protocol_parameters, certificate_verifier, certificate_repository, + dependencies.mithril_era, )) } @@ -97,6 +101,7 @@ impl GenesisTools { &self.genesis_protocol_parameters, &self.genesis_avk, &self.epoch, + self.mithril_era, )?; target_file.write_all(protocol_message.compute_hash().as_bytes())?; Ok(()) @@ -128,6 +133,7 @@ impl GenesisTools { &self.genesis_protocol_parameters, &self.genesis_avk, &self.epoch, + self.mithril_era, )?; let genesis_signature = genesis_producer.sign_genesis_protocol_message(genesis_protocol_message)?; @@ -169,6 +175,7 @@ impl GenesisTools { self.epoch, self.genesis_avk.clone(), genesis_signature, + self.mithril_era, )?; self.certificate_verifier .verify_genesis_certificate(&genesis_certificate, genesis_verification_key) @@ -208,6 +215,7 @@ mod tests { ProtocolGenesisSecretKey, ProtocolGenesisSigner, ProtocolGenesisVerificationKey, ProtocolGenesisVerifier, }, + entities::SupportedEra, test::{TempDir, builder::MithrilFixtureBuilder, double::fake_data}, }; use std::{fs::read_to_string, path::PathBuf}; @@ -250,6 +258,7 @@ mod tests { fake_data::protocol_parameters(), certificate_verifier.clone(), certificate_store.clone(), + SupportedEra::Pythagoras, ); ( From 9fd1bf398be3796458ad41b733f6fec9a750cb37 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 16:29:27 +0100 Subject: [PATCH 12/19] feat(e2e): support Mithril era in genesis commands --- .../backward-compatibility.md | 1 + .../mithril-end-to-end/src/assertions/exec.rs | 20 ++++++++++++++++++- .../src/mithril/aggregator.rs | 12 ++++++++--- .../src/stress_test/aggregator_helpers.rs | 2 +- .../src/utils/version_req.rs | 18 +++++++++++++++++ 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/backward-compatibility.md b/mithril-test-lab/mithril-end-to-end/backward-compatibility.md index e7c8d980acb..cef981a7de3 100644 --- a/mithril-test-lab/mithril-end-to-end/backward-compatibility.md +++ b/mithril-test-lab/mithril-end-to-end/backward-compatibility.md @@ -33,6 +33,7 @@ format is: `- **since 'X.Y.Z' (distribution version) [to 'X.Y.Z' (distribution v ### Mithril aggregator +- **after `0.8.14`**: addition of `--mithril-era` flag to `genesis bootstrap` command - **since `0.7.94` (next to 2543.1)**: only the leader aggregator must be restarted when updating protocol parameters ### Mithril signer diff --git a/mithril-test-lab/mithril-end-to-end/src/assertions/exec.rs b/mithril-test-lab/mithril-end-to-end/src/assertions/exec.rs index 3b78bee7ab2..8939ad6ce04 100644 --- a/mithril-test-lab/mithril-end-to-end/src/assertions/exec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/assertions/exec.rs @@ -1,16 +1,34 @@ use std::path::PathBuf; use crate::{Aggregator, Devnet}; +use anyhow::Context; use mithril_common::StdResult; use mithril_common::entities::{Epoch, ProtocolParameters}; +use mithril_common::messages::AggregatorStatusMessage; use slog_scope::info; +/// Retrieve the current Mithril era from a running aggregator by querying its `/status` route. +pub async fn retrieve_current_era(aggregator: &Aggregator) -> StdResult { + let url = format!("{}/status", aggregator.endpoint()); + let response = reqwest::get(&url) + .await + .with_context(|| format!("Failed to query aggregator status at `{url}`"))?; + let status_message: AggregatorStatusMessage = response + .json() + .await + .with_context(|| "Failed to parse aggregator status response")?; + + Ok(status_message.mithril_era.to_string()) +} + pub async fn bootstrap_genesis_certificate(aggregator: &Aggregator) -> StdResult<()> { info!("Bootstrap genesis certificate"; "aggregator" => &aggregator.name()); + info!("> retrieving current era from aggregator"; "aggregator" => &aggregator.name()); + let mithril_era = retrieve_current_era(aggregator).await?; info!("> stopping aggregator"; "aggregator" => &aggregator.name()); aggregator.stop().await?; info!("> bootstrapping genesis using signers registered two epochs ago..."; "aggregator" => &aggregator.name()); - aggregator.bootstrap_genesis().await?; + aggregator.bootstrap_genesis(&mithril_era).await?; info!("> done, restarting aggregator"; "aggregator" => &aggregator.name()); aggregator.serve().await?; diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs index 35dca77d438..fbef7dc7832 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs @@ -268,14 +268,20 @@ impl Aggregator { Ok(()) } - pub async fn bootstrap_genesis(&self) -> StdResult<()> { - // Clone the command so we can alter it without affecting the original + pub async fn bootstrap_genesis(&self, mithril_era: &str) -> StdResult<()> { let mut command = self.command.write().await; let command_name = &format!("mithril-aggregator-genesis-bootstrap-{}", self.name_suffix,); command.set_log_name(command_name); + let mut args = vec!["genesis".to_string(), "bootstrap".to_string()]; + if self.version.is_above("0.8.14") { + args.extend(["--mithril-era".to_string(), mithril_era.to_string()]); + } else { + info!("Aggregator version is below 0.8.14, skipping unsupported `--mithril-era` flag"); + } + let exit_status = command - .start(&["genesis".to_string(), "bootstrap".to_string()])? + .start(&args)? .wait() .await .with_context(|| "`mithril-aggregator genesis bootstrap` crashed")?; diff --git a/mithril-test-lab/mithril-end-to-end/src/stress_test/aggregator_helpers.rs b/mithril-test-lab/mithril-end-to-end/src/stress_test/aggregator_helpers.rs index aaffc4dda6e..3d2a61ee01e 100644 --- a/mithril-test-lab/mithril-end-to-end/src/stress_test/aggregator_helpers.rs +++ b/mithril-test-lab/mithril-end-to-end/src/stress_test/aggregator_helpers.rs @@ -108,7 +108,7 @@ pub async fn bootstrap_aggregator( info!(">> Compute genesis certificate"); let genesis_aggregator = Aggregator::copy_configuration(&aggregator); genesis_aggregator - .bootstrap_genesis() + .bootstrap_genesis(&args.mithril_era) .await .expect("Genesis aggregator should be able to bootstrap genesis"); } diff --git a/mithril-test-lab/mithril-end-to-end/src/utils/version_req.rs b/mithril-test-lab/mithril-end-to-end/src/utils/version_req.rs index b45efc0fbee..7b72e7524c9 100644 --- a/mithril-test-lab/mithril-end-to-end/src/utils/version_req.rs +++ b/mithril-test-lab/mithril-end-to-end/src/utils/version_req.rs @@ -54,6 +54,14 @@ impl NodeVersion { version_req.matches(&self.semver_version) } + /// Checks if the node version is strictly above the given version. + /// + /// Panics if `version` is not a valid semver version + pub fn is_above(&self, version: &'static str) -> bool { + let version_req = semver::VersionReq::parse(&format!(">{version}")).unwrap(); + version_req.matches(&self.semver_version) + } + /// Checks if the node version is equal or above the given version. /// /// Panics if `version` is not a valid semver version @@ -122,6 +130,16 @@ mod tests { assert!(!version.is_below("1.2.2")); } + #[test] + fn test_version_strictly_above() { + let version = NodeVersion::new(semver::Version::new(5, 7, 1)); + + assert!(version.is_above("4.6.0")); + assert!(version.is_above("5.7.0")); + assert!(!version.is_above("5.7.1")); + assert!(!version.is_above("5.7.2")); + } + #[test] fn test_version_equal_or_above() { let version = NodeVersion::new(semver::Version::new(5, 7, 1)); From 1d758484c5547c809db7e9655ebb640a4084ba27 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 16:30:28 +0100 Subject: [PATCH 13/19] docs(website): update genesis commands of aggregator --- .../develop/nodes/mithril-aggregator.md | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/docs/website/root/manual/develop/nodes/mithril-aggregator.md b/docs/website/root/manual/develop/nodes/mithril-aggregator.md index 93b83e90047..46baba6e1ad 100644 --- a/docs/website/root/manual/develop/nodes/mithril-aggregator.md +++ b/docs/website/root/manual/develop/nodes/mithril-aggregator.md @@ -550,27 +550,31 @@ Here is a list of the available parameters for the serve command: `genesis bootstrap` command: -| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory | -| -------------------------- | ------------------- | :------------------: | -------------------------- | ------------------------------------------------------------------ | ------------- | ---------------------------------- | :----------------: | -| `genesis_secret_key` | - | - | `GENESIS_SECRET_KEY` | Genesis secret key, :warning: for test only | - | - | :heavy_check_mark: | -| `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store aggregator databases | - | `./mithril-aggregator/stores` | :heavy_check_mark: | -| `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket opened by the Cardano node | - | `/ipc/node.socket` | :heavy_check_mark: | -| `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | - | -| `chain_observer_type` | - | - | `CHAIN_OBSERVER_TYPE` | Chain observer type that can be `cardano-cli`, `pallas` or `fake`. | `pallas` | - | :heavy_check_mark: | -| `network` | - | - | `NETWORK` | Cardano network | - | `mainnet` or `preprod` or `devnet` | :heavy_check_mark: | -| `network_magic` | - | - | `NETWORK_MAGIC` | Cardano network magic number (for `testnet` and `devnet`) | - | `1097911063` or `42` | - | +| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory | +| -------------------------- | ---------------------- | :------------------: | -------------------------- | -------------------------------------------------------------------------------------- | ------------- | ---------------------------------- | :----------------: | +| `genesis_secret_key` | `--genesis-secret-key` | - | `GENESIS_SECRET_KEY` | Genesis Secret Key (test only) | - | - | :heavy_check_mark: | +| `mithril_era` | `--mithril-era` | - | - | Mithril era to use for the genesis certificate | - | - | - | +| `help` | `--help` | `-h` | - | Print help (see more with '--help') | - | - | - | +| `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | - | +| `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket opened by the Cardano node | - | `/ipc/node.socket` | :heavy_check_mark: | +| `network_magic` | - | - | `NETWORK_MAGIC` | Cardano Network Magic number

useful for TestNet & DevNet | - | `1097911063` or `42` | - | +| `network` | - | - | `NETWORK` | Cardano network | - | `mainnet` or `preprod` or `devnet` | :heavy_check_mark: | +| `chain_observer_type` | - | - | `CHAIN_OBSERVER_TYPE` | Cardano chain observer type | - | - | :heavy_check_mark: | +| `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store aggregator data (Certificates, Snapshots, Protocol Parameters, ...) | - | `./mithril-aggregator/stores` | :heavy_check_mark: | `genesis export` command: -| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory | -| -------------------------- | ------------------- | :------------------: | -------------------------- | ------------------------------------------------------------------ | ------------- | ---------------------------------- | :----------------: | -| `target_path` | `--target-path` | - | - | Path of the file to export the payload to. | - | - | :heavy_check_mark: | -| `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store aggregator databases | - | `./mithril-aggregator/stores` | :heavy_check_mark: | -| `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket opened by the Cardano node | - | `/ipc/node.socket` | :heavy_check_mark: | -| `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | - | -| `chain_observer_type` | - | - | `CHAIN_OBSERVER_TYPE` | Chain observer type that can be `cardano-cli`, `pallas` or `fake`. | `pallas` | - | :heavy_check_mark: | -| `network` | - | - | `NETWORK` | Cardano network | - | `mainnet` or `preprod` or `devnet` | :heavy_check_mark: | -| `network_magic` | - | - | `NETWORK_MAGIC` | Cardano network magic number (for `testnet` and `devnet`) | - | `1097911063` or `42` | - | +| Parameter | Command line (long) | Command line (short) | Environment variable | Description | Default value | Example | Mandatory | +| -------------------------- | ------------------- | :------------------: | -------------------------- | -------------------------------------------------------------------------------------- | ------------- | ---------------------------------- | :----------------: | +| `target_path` | `--target-path` | - | - | Target path | - | - | :heavy_check_mark: | +| `mithril_era` | `--mithril-era` | - | - | Mithril era to use for the genesis certificate | - | - | - | +| `help` | `--help` | `-h` | - | Print help (see more with '--help') | - | - | - | +| `cardano_cli_path` | - | - | `CARDANO_CLI_PATH` | Cardano CLI tool path | - | `cardano-cli` | - | +| `cardano_node_socket_path` | - | - | `CARDANO_NODE_SOCKET_PATH` | Path of the socket opened by the Cardano node | - | `/ipc/node.socket` | :heavy_check_mark: | +| `network_magic` | - | - | `NETWORK_MAGIC` | Cardano Network Magic number

useful for TestNet & DevNet | - | `1097911063` or `42` | - | +| `network` | - | - | `NETWORK` | Cardano network | - | `mainnet` or `preprod` or `devnet` | :heavy_check_mark: | +| `chain_observer_type` | - | - | `CHAIN_OBSERVER_TYPE` | Cardano chain observer type | - | - | :heavy_check_mark: | +| `data_stores_directory` | - | - | `DATA_STORES_DIRECTORY` | Directory to store aggregator data (Certificates, Snapshots, Protocol Parameters, ...) | - | `./mithril-aggregator/stores` | :heavy_check_mark: | `genesis import` command: From 3c93f7b694763cbd0df32edc0caeafa9f28d5da9 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 18:07:35 +0100 Subject: [PATCH 14/19] chore: apply review comments --- .../src/database/record/certificate.rs | 4 - .../repository/certificate_repository.rs | 102 +++++------------- mithril-aggregator/src/tools/genesis.rs | 1 - mithril-client/Cargo.toml | 5 + .../src/crypto_helper/types/wrappers.rs | 6 +- .../src/proof_system/concatenation/clerk.rs | 4 +- .../src/proof_system/concatenation/proof.rs | 4 +- .../backward-compatibility.md | 2 +- .../src/mithril/aggregator.rs | 4 +- .../src/utils/version_req.rs | 18 ---- 10 files changed, 42 insertions(+), 108 deletions(-) diff --git a/mithril-aggregator/src/database/record/certificate.rs b/mithril-aggregator/src/database/record/certificate.rs index f0a473a7e1b..df03f74ed50 100644 --- a/mithril-aggregator/src/database/record/certificate.rs +++ b/mithril-aggregator/src/database/record/certificate.rs @@ -208,8 +208,6 @@ impl TryFrom for Certificate { .aggregate_verification_key_snark .map(|hex| hex.as_str().try_into()) .transpose()?; - #[cfg(not(feature = "future_snark"))] - let _ = other.aggregate_verification_key_snark; let certificate = Certificate { hash: other.certificate_id, @@ -243,8 +241,6 @@ impl From for CertificateMessage { } else { (value.signature, String::new()) }; - #[cfg(not(feature = "future_snark"))] - let _ = value.aggregate_verification_key_snark; CertificateMessage { hash: value.certificate_id, diff --git a/mithril-aggregator/src/database/repository/certificate_repository.rs b/mithril-aggregator/src/database/repository/certificate_repository.rs index b323a88a3ab..56b71cb4c80 100644 --- a/mithril-aggregator/src/database/repository/certificate_repository.rs +++ b/mithril-aggregator/src/database/repository/certificate_repository.rs @@ -181,9 +181,10 @@ mod tests { use super::*; - fn insert_golden_certificate(connection: &ConnectionThreadSafe) { + fn insert_golden_certificate(connection: &ConnectionThreadSafe, snark_avk: &str) { connection - .execute(r#" + .execute(format!( + r#" -- genesis certificate insert into certificate values( @@ -197,21 +198,21 @@ mod tests { 0, 241, '0.1.0', - '{"k":2422,"m":20973,"phi_f":0.2}', - '{"message_parts":{ + '{{"k":2422,"m":20973,"phi_f":0.2}}', + '{{"message_parts":{{ "next_aggregate_verification_key":"7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b37372c3230382c3138392c3138372c37362c3136322c36382c3233382c3134342c31372c3131342c3137352c36302c3136352c3230322c3134362c3139342c31332c37332c3233392c3233372c3232322c3136392c3230362c352c3130392c3132332c35322c3235342c39382c3133312c37395d2c226e725f6c6561766573223a332c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a32383439323639303636317d" - }}', - '[{ + }}}}', + '[{{ "party_id":"pool1vapqexnsx6hvc588yyysxpjecf3k43hcr5mvhmstutuvy085xpa", "verification_key":"7b22766b223a5b3133382c33322c3133382c3135322c3134362c3235352c3130382c3139302c37302c34322c3132362c3137322c31392c3135312c3133392c3133392c3235352c33352c3134312c38322c3138372c33372c3133332c3235322c3139322c302c32362c32342c3134342c372c3235332c3136362c3135312c3139332c392c3230392c3131392c3230302c3134312c34312c38302c342c3231372c3132322c3132302c3235332c3230382c3131312c362c37382c3234362c3134362c3131382c352c3235312c31392c3234332c3138342c3233382c3139352c39392c3235312c3135312c342c39342c3133382c3234362c33362c33372c34382c3133362c3130302c3233352c3134312c3232382c392c39362c3131332c35392c3137352c3130322c3232392c39352c39332c3134332c3137312c3130302c32302c3133362c36372c33302c3133312c3135332c32362c35372c3132385d2c22706f70223a5b3137342c3233302c33382c3138312c3131332c38332c372c34332c3130312c38392c3133372c3133302c37302c3135382c3235342c31342c31362c36372c38332c362c3234322c39312c3136372c34352c3232392c3139382c3130312c37302c3232382c36312c3138302c3132302c3130332c3232302c3231312c3134362c3136322c37302c33382c3230352c3139312c3235322c3138342c3235322c39362c3134382c3130322c3133362c3136362c34322c3137382c3133352c3130302c33312c38392c3233342c3135392c3131382c33382c3133392c31362c3134342c3132382c3134382c3132382c3139312c31382c34382c38392c3136352c35342c3134362c36332c3136302c3138362c3139362c31392c3137312c3136302c31342c39322c35382c3232312c3138352c3132392c382c3133322c35352c3231382c3235302c39352c32312c3235302c3135312c36352c3231395d7d", "verification_key_signature":"7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a5b35342c35372c32332c3234302c3234342c3130352c3139322c3138312c3130362c3232312c3132302c3139382c3136392c3134372c3233362c34382c32342c35382c3233352c31332c36302c31352c3231382c33312c34352c3135322c3133302c3230382c36392c38312c34372c3135302c3234352c3234332c32352c39342c3134382c3136322c39322c3136392c3131352c37382c31352c38382c3139382c38342c3233322c3138342c3135372c3139352c35342c3136352c33352c382c3232342c3130312c3138392c38372c32392c3131342c3133322c33382c3132322c31305d2c226c68735f706b223a5b3139322c3135342c3230322c3233342c36352c3234332c3132392c3230302c3131382c3137352c3131342c3233352c3232322c3235342c3134322c3232332c3137372c3233342c31352c31382c34312c31362c38382c38352c37322c3130372c33322c3134382c33352c35312c3132352c34355d2c227268735f706b223a5b3137342c39352c3132342c31382c36322c3135312c3137302c3136382c3232332c36362c3132322c36312c3234322c3130372c3132352c3137372c3137302c3132332c35382c3231362c3137362c392c3234302c3131382c3131302c35362c3232372c3230302c3131322c3130352c32392c3230385d7d2c226c68735f706b223a5b36392c3138322c39392c382c34302c39332c3130382c3233312c382c312c3235322c3131302c3132322c37332c3133302c3230372c3231332c3137312c3130352c3232322c31352c3134322c3230362c3137392c33382c3132302c39322c362c32302c3133352c3130382c3138335d2c227268735f706b223a5b33342c36372c3134302c3132392c3231352c36392c3136302c3135362c3230302c31302c3232362c35382c3132322c36342c33382c3135362c3230362c3230362c302c3137382c3132302c3139332c362c3135332c3131322c3130392c3135372c3131322c3132322c3133372c3233372c38355d7d2c226c68735f706b223a5b37332c3131342c3136352c3137312c34322c3131372c3139322c3139342c3137342c32302c38312c392c3230392c31392c3134352c3233302c3233302c3130392c34382c3135302c31332c3232392c3139322c35342c3138362c3137372c32382c3133362c31352c3230342c3231342c3132305d2c227268735f706b223a5b3139302c32322c3131312c38362c38322c3138362c3231372c3134312c302c3136382c3130382c3230362c3130392c332c3138342c3230342c382c3138362c3136362c32312c39372c34342c3135352c332c3136352c3139392c3132372c39312c3233382c38362c3139302c35305d7d2c226c68735f706b223a5b3135392c37352c3131382c3132372c3139382c34342c3137392c34322c3231382c3131382c3235332c3139392c32342c37312c3133302c362c3136332c3131342c3133392c31332c3130392c31372c3132372c35312c39342c3133312c3132382c3230332c3131382c3231312c3137392c36365d2c227268735f706b223a5b3139312c3136342c33362c3131312c37362c3132372c3231382c3230352c3234322c3134322c3230312c3233322c3235322c3233322c35372c39362c3131372c3232362c37332c34322c3231372c3235342c3130382c3233342c3234372c3137362c3234372c3133302c32342c36332c31392c38355d7d2c226c68735f706b223a5b35342c33392c3235342c33322c3131392c39332c3138322c3132372c3136352c3134362c3230352c33392c36352c3139362c3134362c36392c36392c34332c3139382c3130322c3139342c35372c31332c3230302c3232332c39382c38322c3134312c3133362c35382c3235322c3130325d2c227268735f706b223a5b3137372c34322c33372c3133322c3133352c3130322c3135342c392c3233362c31392c3235302c3235312c39382c36352c3133302c3232352c3136382c3232362c3136352c34392c35302c35322c3134312c3136392c35312c3230342c3234362c3130302c3233372c3234362c39322c32345d7d2c226c68735f706b223a5b33302c38302c3232322c3233372c3139302c342c3130352c3230362c37302c31372c3234382c3134322c362c31332c3137352c3136332c38342c3231352c3132322c3235352c3232302c3131382c34382c33312c34352c33332c3233372c3234352c3235302c3234302c3132392c3131355d2c227268735f706b223a5b3132332c31302c31352c36332c3138312c3231382c31302c36362c3138382c3138312c3130302c3138302c3130302c3139352c3137382c38372c3233362c32382c3138322c35362c3232362c35382c3234302c3131322c392c3133322c39332c33302c33372c3136332c3134322c39315d7d", "operational_certificate":"5b5b5b3131322c39352c34322c39372c382c3235322c31382c3231342c31392c3231382c3231372c3234322c3233302c3138372c3234302c3133392c31342c3135382c3137392c3234392c3231312c36332c3132332c342c32362c3132362c3132312c3234372c302c35372c31362c3136315d2c312c37312c5b3132392c3234382c3133342c3132342c3230372c3130332c3233312c37302c3130372c32382c3134322c3134312c38362c3234392c3230352c31312c33392c3232382c3130382c3132322c3233312c3138322c3132372c3130312c3234352c33332c3135322c3233342c35342c36372c3138312c39362c3137372c3234362c32382c322c3235322c3130382c35392c3231352c3232372c3230392c3131382c3130352c3135342c37312c36332c3134352c3132372c3137352c3133382c3131352c39362c3233352c3131382c31322c3234302c3232352c3130392c3130382c3231322c3232392c35372c31305d5d2c5b33302c3138312c32302c37382c33392c3232332c352c3133372c3134312c3138392c372c3132372c34352c3232372c3230362c3135372c39352c3131352c36312c3132382c3135392c3135362c34332c3132372c302c34302c3134332c3138332c3233302c32352c39312c3137305d5d", "kes_period":22, "stake":1009497432569 - }]', + }}]', '2023-06-23T08:37:49.066Z', '2023-06-23T08:37:49.066Z', - null + {snark_avk} ); -- multi-signature certificate @@ -225,92 +226,46 @@ mod tests { 142, 'preview', 2, - '{"epoch":142,"immutable_file_number":2838}', + '{{"epoch":142,"immutable_file_number":2838}}', '0.1.0', - '{"k":2422,"m":20973,"phi_f":0.2}', - '{"message_parts":{ + '{{"k":2422,"m":20973,"phi_f":0.2}}', + '{{"message_parts":{{ "snapshot_digest":"cfed71151e42f8208b841531dc95477f10db25083db5eb9759e745155e83ca7c", "next_aggregate_verification_key":"7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b3132322c3131322c3131302c37332c3131352c3130302c33352c3131322c37312c3130372c3139392c3139322c3131352c37382c32312c38322c3131362c3136312c35312c34332c3233342c3134332c3139382c3138352c33342c3233302c3131332c3234352c3136392c3137332c3136322c37315d2c226e725f6c6561766573223a322c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a323031383939353036313631357d" - }}', - '[{ + }}}}', + '[{{ "party_id":"pool1vapqexnsx6hvc588yyysxpjecf3k43hcr5mvhmstutuvy085xpa", "verification_key":"7b22766b223a5b3133382c33322c3133382c3135322c3134362c3235352c3130382c3139302c37302c34322c3132362c3137322c31392c3135312c3133392c3133392c3235352c33352c3134312c38322c3138372c33372c3133332c3235322c3139322c302c32362c32342c3134342c372c3235332c3136362c3135312c3139332c392c3230392c3131392c3230302c3134312c34312c38302c342c3231372c3132322c3132302c3235332c3230382c3131312c362c37382c3234362c3134362c3131382c352c3235312c31392c3234332c3138342c3233382c3139352c39392c3235312c3135312c342c39342c3133382c3234362c33362c33372c34382c3133362c3130302c3233352c3134312c3232382c392c39362c3131332c35392c3137352c3130322c3232392c39352c39332c3134332c3137312c3130302c32302c3133362c36372c33302c3133312c3135332c32362c35372c3132385d2c22706f70223a5b3137342c3233302c33382c3138312c3131332c38332c372c34332c3130312c38392c3133372c3133302c37302c3135382c3235342c31342c31362c36372c38332c362c3234322c39312c3136372c34352c3232392c3139382c3130312c37302c3232382c36312c3138302c3132302c3130332c3232302c3231312c3134362c3136322c37302c33382c3230352c3139312c3235322c3138342c3235322c39362c3134382c3130322c3133362c3136362c34322c3137382c3133352c3130302c33312c38392c3233342c3135392c3131382c33382c3133392c31362c3134342c3132382c3134382c3132382c3139312c31382c34382c38392c3136352c35342c3134362c36332c3136302c3138362c3139362c31392c3137312c3136302c31342c39322c35382c3232312c3138352c3132392c382c3133322c35352c3231382c3235302c39352c32312c3235302c3135312c36352c3231395d7d", "verification_key_signature":"7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a5b35342c35372c32332c3234302c3234342c3130352c3139322c3138312c3130362c3232312c3132302c3139382c3136392c3134372c3233362c34382c32342c35382c3233352c31332c36302c31352c3231382c33312c34352c3135322c3133302c3230382c36392c38312c34372c3135302c3234352c3234332c32352c39342c3134382c3136322c39322c3136392c3131352c37382c31352c38382c3139382c38342c3233322c3138342c3135372c3139352c35342c3136352c33352c382c3232342c3130312c3138392c38372c32392c3131342c3133322c33382c3132322c31305d2c226c68735f706b223a5b3139322c3135342c3230322c3233342c36352c3234332c3132392c3230302c3131382c3137352c3131342c3233352c3232322c3235342c3134322c3232332c3137372c3233342c31352c31382c34312c31362c38382c38352c37322c3130372c33322c3134382c33352c35312c3132352c34355d2c227268735f706b223a5b3137342c39352c3132342c31382c36322c3135312c3137302c3136382c3232332c36362c3132322c36312c3234322c3130372c3132352c3137372c3137302c3132332c35382c3231362c3137362c392c3234302c3131382c3131302c35362c3232372c3230302c3131322c3130352c32392c3230385d7d2c226c68735f706b223a5b36392c3138322c39392c382c34302c39332c3130382c3233312c382c312c3235322c3131302c3132322c37332c3133302c3230372c3231332c3137312c3130352c3232322c31352c3134322c3230362c3137392c33382c3132302c39322c362c32302c3133352c3130382c3138335d2c227268735f706b223a5b33342c36372c3134302c3132392c3231352c36392c3136302c3135362c3230302c31302c3232362c35382c3132322c36342c33382c3135362c3230362c3230362c302c3137382c3132302c3139332c362c3135332c3131322c3130392c3135372c3131322c3132322c3133372c3233372c38355d7d2c226c68735f706b223a5b37332c3131342c3136352c3137312c34322c3131372c3139322c3139342c3137342c32302c38312c392c3230392c31392c3134352c3233302c3233302c3130392c34382c3135302c31332c3232392c3139322c35342c3138362c3137372c32382c3133362c31352c3230342c3231342c3132305d2c227268735f706b223a5b3139302c32322c3131312c38362c38322c3138362c3231372c3134312c302c3136382c3130382c3230362c3130392c332c3138342c3230342c382c3138362c3136362c32312c39372c34342c3135352c332c3136352c3139392c3132372c39312c3233382c38362c3139302c35305d7d2c226c68735f706b223a5b3135392c37352c3131382c3132372c3139382c34342c3137392c34322c3231382c3131382c3235332c3139392c32342c37312c3133302c362c3136332c3131342c3133392c31332c3130392c31372c3132372c35312c39342c3133312c3132382c3230332c3131382c3231312c3137392c36365d2c227268735f706b223a5b3139312c3136342c33362c3131312c37362c3132372c3231382c3230352c3234322c3134322c3230312c3233322c3235322c3233322c35372c39362c3131372c3232362c37332c34322c3231372c3235342c3130382c3233342c3234372c3137362c3234372c3133302c32342c36332c31392c38355d7d2c226c68735f706b223a5b35342c33392c3235342c33322c3131392c39332c3138322c3132372c3136352c3134362c3230352c33392c36352c3139362c3134362c36392c36392c34332c3139382c3130322c3139342c35372c31332c3230302c3232332c39382c38322c3134312c3133362c35382c3235322c3130325d2c227268735f706b223a5b3137372c34322c33372c3133322c3133352c3130322c3135342c392c3233362c31392c3235302c3235312c39382c36352c3133302c3232352c3136382c3232362c3136352c34392c35302c35322c3134312c3136392c35312c3230342c3234362c3130302c3233372c3234362c39322c32345d7d2c226c68735f706b223a5b33302c38302c3232322c3233372c3139302c342c3130352c3230362c37302c31372c3234382c3134322c362c31332c3137352c3136332c38342c3231352c3132322c3235352c3232302c3131382c34382c33312c34352c33332c3233372c3234352c3235302c3234302c3132392c3131355d2c227268735f706b223a5b3132332c31302c31352c36332c3138312c3231382c31302c36362c3138382c3138312c3130302c3138302c3130302c3139352c3137382c38372c3233362c32382c3138322c35362c3232362c35382c3234302c3131322c392c3133322c39332c33302c33372c3136332c3134322c39315d7d", "operational_certificate":"5b5b5b3131322c39352c34322c39372c382c3235322c31382c3231342c31392c3231382c3231372c3234322c3233302c3138372c3234302c3133392c31342c3135382c3137392c3234392c3231312c36332c3132332c342c32362c3132362c3132312c3234372c302c35372c31362c3136315d2c312c37312c5b3132392c3234382c3133342c3132342c3230372c3130332c3233312c37302c3130372c32382c3134322c3134312c38362c3234392c3230352c31312c33392c3232382c3130382c3132322c3233312c3138322c3132372c3130312c3234352c33332c3135322c3233342c35342c36372c3138312c39362c3137372c3234362c32382c322c3235322c3130382c35392c3231352c3232372c3230392c3131382c3130352c3135342c37312c36332c3134352c3132372c3137352c3133382c3131352c39362c3233352c3131382c31322c3234302c3232352c3130392c3130382c3231322c3232392c35372c31305d5d2c5b33302c3138312c32302c37382c33392c3232332c352c3133372c3134312c3138392c372c3132372c34352c3232372c3230362c3135372c39352c3131352c36312c3132382c3135392c3135362c34332c3132372c302c34302c3134332c3138332c3233302c32352c39312c3137305d5d", "kes_period":22, "stake":1009497432569 - }]', + }}]', '2023-03-16T01:51:00.880Z', '2023-03-16T02:07:22.145Z', - null + {snark_avk} ); - "#, - ) + "# + )) .unwrap(); } + fn insert_golden_certificate_without_snark_aggregate_verification_key( + connection: &ConnectionThreadSafe, + ) { + insert_golden_certificate(connection, "null") + } + fn insert_golden_certificate_with_snark_aggregate_verification_key( connection: &ConnectionThreadSafe, - snark_avk: &str, ) { - connection - .execute(format!( - r#" - -- genesis certificate with SNARK AVK - insert into certificate - values( - 'bfb4efbd48d58f7677ddb7d5fe5b5b9e998e8ca549cbf7583873bdccfc70f194', - null, - '08420665c56dcf6981b7d8b64b5a584e148edbf7638f466cb36b278ce962439c', - 'b7944ddc7d728812f8e68abc93b668a84876e9867b97648bc937b20debdff15a8415470ee709599d1a12a50ac5a57a3a4955cf19307d04955fcad6931c3b9505', - '7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b37372c3230382c3138392c3138372c37362c3136322c36382c3233382c3134342c31372c3131342c3137352c36302c3136352c3230322c3134362c3139342c31332c37332c3233392c3233372c3232322c3136392c3230362c352c3130392c3132332c35322c3235342c39382c3133312c37395d2c226e725f6c6561766573223a332c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a32383439323639303636317d', - 241, - 'preview', - 0, - 241, - '0.1.0', - '{{"k":2422,"m":20973,"phi_f":0.2}}', - '{{"message_parts":{{ - "next_aggregate_verification_key":"7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b37372c3230382c3138392c3138372c37362c3136322c36382c3233382c3134342c31372c3131342c3137352c36302c3136352c3230322c3134362c3139342c31332c37332c3233392c3233372c3232322c3136392c3230362c352c3130392c3132332c35322c3235342c39382c3133312c37395d2c226e725f6c6561766573223a332c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a32383439323639303636317d" - }}}}', - '[{{"party_id":"pool1vapqexnsx6hvc588yyysxpjecf3k43hcr5mvhmstutuvy085xpa","verification_key":"7b22766b223a5b3133382c33322c3133382c3135322c3134362c3235352c3130382c3139302c37302c34322c3132362c3137322c31392c3135312c3133392c3133392c3235352c33352c3134312c38322c3138372c33372c3133332c3235322c3139322c302c32362c32342c3134342c372c3235332c3136362c3135312c3139332c392c3230392c3131392c3230302c3134312c34312c38302c342c3231372c3132322c3132302c3235332c3230382c3131312c362c37382c3234362c3134362c3131382c352c3235312c31392c3234332c3138342c3233382c3139352c39392c3235312c3135312c342c39342c3133382c3234362c33362c33372c34382c3133362c3130302c3233352c3134312c3232382c392c39362c3131332c35392c3137352c3130322c3232392c39352c39332c3134332c3137312c3130302c32302c3133362c36372c33302c3133312c3135332c32362c35372c3132385d2c22706f70223a5b3137342c3233302c33382c3138312c3131332c38332c372c34332c3130312c38392c3133372c3133302c37302c3135382c3235342c31342c31362c36372c38332c362c3234322c39312c3136372c34352c3232392c3139382c3130312c37302c3232382c36312c3138302c3132302c3130332c3232302c3231312c3134362c3136322c37302c33382c3230352c3139312c3235322c3138342c3235322c39362c3134382c3130322c3133362c3136362c34322c3137382c3133352c3130302c33312c38392c3233342c3135392c3131382c33382c3133392c31362c3134342c3132382c3134382c3132382c3139312c31382c34382c38392c3136352c35342c3134362c36332c3136302c3138362c3139362c31392c3137312c3136302c31342c39322c35382c3232312c3138352c3132392c382c3133322c35352c3231382c3235302c39352c32312c3235302c3135312c36352c3231395d7d","verification_key_signature":"7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a5b35342c35372c32332c3234302c3234342c3130352c3139322c3138312c3130362c3232312c3132302c3139382c3136392c3134372c3233362c34382c32342c35382c3233352c31332c36302c31352c3231382c33312c34352c3135322c3133302c3230382c36392c38312c34372c3135302c3234352c3234332c32352c39342c3134382c3136322c39322c3136392c3131352c37382c31352c38382c3139382c38342c3233322c3138342c3135372c3139352c35342c3136352c33352c382c3232342c3130312c3138392c38372c32392c3131342c3133322c33382c3132322c31305d2c226c68735f706b223a5b3139322c3135342c3230322c3233342c36352c3234332c3132392c3230302c3131382c3137352c3131342c3233352c3232322c3235342c3134322c3232332c3137372c3233342c31352c31382c34312c31362c38382c38352c37322c3130372c33322c3134382c33352c35312c3132352c34355d2c227268735f706b223a5b3137342c39352c3132342c31382c36322c3135312c3137302c3136382c3232332c36362c3132322c36312c3234322c3130372c3132352c3137372c3137302c3132332c35382c3231362c3137362c392c3234302c3131382c3131302c35362c3232372c3230302c3131322c3130352c32392c3230385d7d2c226c68735f706b223a5b36392c3138322c39392c382c34302c39332c3130382c3233312c382c312c3235322c3131302c3132322c37332c3133302c3230372c3231332c3137312c3130352c3232322c31352c3134322c3230362c3137392c33382c3132302c39322c362c32302c3133352c3130382c3138335d2c227268735f706b223a5b33342c36372c3134302c3132392c3231352c36392c3136302c3135362c3230302c31302c3232362c35382c3132322c36342c33382c3135362c3230362c3230362c302c3137382c3132302c3139332c362c3135332c3131322c3130392c3135372c3131322c3132322c3133372c3233372c38355d7d2c226c68735f706b223a5b37332c3131342c3136352c3137312c34322c3131372c3139322c3139342c3137342c32302c38312c392c3230392c31392c3134352c3233302c3233302c3130392c34382c3135302c31332c3232392c3139322c35342c3138362c3137372c32382c3133362c31352c3230342c3231342c3132305d2c227268735f706b223a5b3139302c32322c3131312c38362c38322c3138362c3231372c3134312c302c3136382c3130382c3230362c3130392c33342c33342c37382c3134342c3230322c3135362c3138372c3134302c3136302c32302c3233342c3138382c3234382c3131322c3131312c3136372c3234352c3138342c3137355d7d2c226c68735f706b223a5b3234382c33382c3134372c3234382c3138382c3136372c31332c3136362c3234352c31312c3234322c35332c37382c3138332c32352c31322c3136312c3235322c3130372c3231302c3234312c39352c3232302c3232362c3133392c3133302c39322c39392c3132382c3138342c33352c3233395d2c227268735f706b223a5b3134352c3136382c3130362c3137332c32342c3230302c39362c3231352c3131302c36382c3132382c3130322c3134372c3138342c31382c3230392c3138342c38392c3137322c3132372c35352c3232372c3133382c34312c38372c34312c3138382c35352c3131352c3133332c3134372c38315d7d2c226c68735f706b223a5b3137342c3130352c3131342c3132352c3131342c34392c33332c3135332c3234302c3131332c3136312c35372c34322c3233362c3137352c31382c37382c39382c33332c3230322c33382c3132312c3135372c3234332c3233382c39392c3135312c36362c35362c3134392c3135322c32345d2c227268735f706b223a5b3235322c3136382c3131372c3137302c3235302c3134332c3137302c35342c3130352c33312c3231392c3234392c3130342c33362c3133352c34382c3231362c372c3233332c3133372c3132342c3137302c3134372c3131332c35392c3233372c3234312c3132352c3231302c3233342c3234312c3130335d7d","operational_certificate":"5b5b5b3131322c39352c34322c39372c382c3235322c31382c3231342c31392c3231382c3231372c3234322c3233302c3138372c3234302c3133392c31342c3135382c3137392c3234392c3231312c36332c3132332c342c32362c3132362c3132312c3234372c302c35372c31362c3136315d2c312c37312c5b3132392c3234382c3133342c3132342c3230372c3130332c3233312c37302c3130372c32382c3134322c3134312c38362c3234392c3230352c31312c33392c3232382c3130382c3132322c3233312c3138322c3132372c3130312c3234352c33332c3135322c3233342c35342c36372c3138312c39362c3137372c3234362c32382c322c3235322c3130382c35392c3231352c3232372c3230392c3131382c3130352c3135342c37312c36332c3134352c3132372c3137352c3133382c3131352c39362c3233352c3131382c31322c3234302c3232352c3130392c3130382c3231322c3232392c35372c31305d5d2c5b33302c3138312c32302c37382c33392c3232332c352c3133372c3134312c3138392c372c3132372c34352c3232372c3230362c3135372c39352c3131352c36312c3132382c3135392c3135362c34332c3132372c302c34302c3134332c3138332c3233302c32352c39312c3137305d5d","kes_period":22,"stake":1009497432569}}]', - '2023-06-23T08:37:49.066Z', - '2023-06-23T08:37:49.066Z', - '{snark_avk}' - ); - - -- multi-signature certificate with SNARK AVK - insert into certificate - values( - '9a86b602d1eda6d3a48967e63f5b35885368795669d9293014e1c289ee0defa7', - '3997f18bbbe706a77fbf464101a3e6c6476a9d1dd2e10f2ed614f028713b8f11', - '33975e636d019513d93e9182e6a5e38092909620cd4b650e06a03e2c4cf2e65a', - '7b227369676e617475726573223a5b5b7b227369676d61223a5b3138342c3133342c38392c3137382c3234312c3232362c34372c34372c34312c36382c3136392c36352c38362c3136302c39322c362c3130382c33382c39322c3134332c3131372c3231382c33382c39342c3131332c3232372c3133332c3231302c3131332c3134312c31382c3139322c3133332c3230312c3231382c3233392c33342c3231322c39302c382c34302c3132302c3233342c3136382c3135332c3137372c3133322c34335d2c22696e6465786573223a5b312c382c31322c31342c31372c32332c32382c33332c33392c38382c39332c39382c3131342c3131352c3131372c3132372c3133322c3133342c3133362c3133392c3134302c3134312c3135302c3135372c3136332c3136342c3137322c3137372c3137382c3138312c3139302c3139312c3139322c3230302c3230312c3230332c3230342c3231352c3231362c3231392c3233322c3233342c3233372c3235302c3235312c3235352c3235362c3236322c3236352c3236362c3237372c3238302c3238342c3238392c3239372c3330302c3331312c3332302c3332312c3332382c3333332c3333342c3333372c3334322c3334332c3334342c3335342c3335372c3336302c3336392c3337352c3337362c3338362c3339342c3339372c3339382c3339392c3430312c3430322c3430352c3431302c3431352c3431372c3432302c3432372c3433302c3433362c3434312c3435302c3435392c3436352c3436362c3437322c3437342c3438322c3438352c3438382c3438392c3439312c3530342c3531302c3531342c3531362c3531372c3532312c3532322c3532342c3532382c3533302c3534342c3534392c3535302c3535312c3535322c3535372c3536322c3536382c3537342c3537392c3538322c3538352c3538382c3538392c3539342c3630392c3631382c3632312c3632342c3632392c3633312c3633352c3633392c3634302c3634312c3634322c3634362c3634372c3635302c3635372c3636342c3637332c3637352c3637362c3638312c3638342c3638372c3730312c3730322c3731352c3731382c3732352c3732392c3733302c3733362c3733382c3734322c3736312c3736372c3737312c3737322c3737342c3737382c3738392c3739312c3830362c3831332c3832332c3832372c3833342c3833382c3833392c3834352c3834382c3835322c3835352c3835362c3836352c3836372c3837302c3837312c3837322c3837342c3838332c3838352c3839302c3839372c3839392c3930312c3930332c3930352c3931362c3931382c3932322c3933342c3933362c3933382c3934342c3934362c3934392c3935352c3935382c3936382c3937302c3937342c3937362c3938382c3938392c3939322c3939352c3939382c3939395d2c227369676e65725f696e646578223a307d2c5b3138322c38362c3134352c3135362c31342c3130382c3135362c35392c3137372c31342c3134322c3133382c33382c3231332c3138322c3234342c3134302c3133362c3232322c3234312c3137372c3233302c3231332c3233302c3131342c3232352c39302c3133372c3230342c302c3234342c3131312c32362c3131372c3131312c32342c38392c3133332c3136372c3233342c3131332c37372c31312c34322c32322c3232322c3130312c3131302c3234352c3136352c35342c36302c33302c3131332c3132302c3133372c3137372c3138342c32312c3233312c3135302c3232332c36302c3134302c39302c36332c35372c3132362c3231332c3232322c352c3137322c3231362c3137352c39382c3231332c3133392c3137342c3231322c3234332c35302c34332c3234382c3233332c3138382c33392c3231352c382c3233342c35392c31362c36382c3133312c3233352c3233312c3231302c37305d5d5d2c2262617463685f70726f6f66223a7b2276616c756573223a5b5d2c22696e6469636573223a5b305d2c22686173686572223a6e756c6c7d7d', - '7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b3134302c31332c3135352c3134312c3136332c372c38362c3232372c34372c31392c3138302c3132372c3139362c3130382c3137312c3135382c3134302c37372c3137352c3135392c3133362c3139332c3130382c34322c3134322c3234342c38352c3131362c3235322c3135362c3233352c35305d2c226e725f6c6561766573223a312c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a313030393439373433323536397d', - 142, - 'preview', - 2, - '{{"epoch":142,"immutable_file_number":2838}}', - '0.1.0', - '{{"k":2422,"m":20973,"phi_f":0.2}}', - '{{"message_parts":{{ - "snapshot_digest":"cfed71151e42f8208b841531dc95477f10db25083db5eb9759e745155e83ca7c", - "next_aggregate_verification_key":"7b226d745f636f6d6d69746d656e74223a7b22726f6f74223a5b3132322c3131322c3131302c37332c3131352c3130302c33352c3131322c37312c3130372c3139392c3139322c3131352c37382c32312c38322c3131362c3136312c35312c34332c3233342c3134332c3139382c3138352c33342c3233302c3131332c3234352c3136392c3137332c3136322c37315d2c226e725f6c6561766573223a322c22686173686572223a6e756c6c7d2c22746f74616c5f7374616b65223a323031383939353036313631357d" - }}}}', - '[{{"party_id":"pool1vapqexnsx6hvc588yyysxpjecf3k43hcr5mvhmstutuvy085xpa","verification_key":"7b22766b223a5b3133382c33322c3133382c3135322c3134362c3235352c3130382c3139302c37302c34322c3132362c3137322c31392c3135312c3133392c3133392c3235352c33352c3134312c38322c3138372c33372c3133332c3235322c3139322c302c32362c32342c3134342c372c3235332c3136362c3135312c3139332c392c3230392c3131392c3230302c3134312c34312c38302c342c3231372c3132322c3132302c3235332c3230382c3131312c362c37382c3234362c3134362c3131382c352c3235312c31392c3234332c3138342c3233382c3139352c39392c3235312c3135312c342c39342c3133382c3234362c33362c33372c34382c3133362c3130302c3233352c3134312c3232382c392c39362c3131332c35392c3137352c3130322c3232392c39352c39332c3134332c3137312c3130302c32302c3133362c36372c33302c3133312c3135332c32362c35372c3132385d2c22706f70223a5b3137342c3233302c33382c3138312c3131332c38332c372c34332c3130312c38392c3133372c3133302c37302c3135382c3235342c31342c31362c36372c38332c362c3234322c39312c3136372c34352c3232392c3139382c3130312c37302c3232382c36312c3138302c3132302c3130332c3232302c3231312c3134362c3136322c37302c33382c3230352c3139312c3235322c3138342c3235322c39362c3134382c3130322c3133362c3136362c34322c3137382c3133352c3130302c33312c38392c3233342c3135392c3131382c33382c3133392c31362c3134342c3132382c3134382c3132382c3139312c31382c34382c38392c3136352c35342c3134362c36332c3136302c3138362c3139362c31392c3137312c3136302c31342c39322c35382c3232312c3138352c3132392c382c3133322c35352c3231382c3235302c39352c32312c3235302c3135312c36352c3231395d7d","verification_key_signature":"7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a7b227369676d61223a5b35342c35372c32332c3234302c3234342c3130352c3139322c3138312c3130362c3232312c3132302c3139382c3136392c3134372c3233362c34382c32342c35382c3233352c31332c36302c31352c3231382c33312c34352c3135322c3133302c3230382c36392c38312c34372c3135302c3234352c3234332c32352c39342c3134382c3136322c39322c3136392c3131352c37382c31352c38382c3139382c38342c3233322c3138342c3135372c3139352c35342c3136352c33352c382c3232342c3130312c3138392c38372c32392c3131342c3133322c33382c3132322c31305d2c226c68735f706b223a5b3139322c3135342c3230322c3233342c36352c3234332c3132392c3230302c3131382c3137352c3131342c3233352c3232322c3235342c3134322c3232332c3137372c3233342c31352c31382c34312c31362c38382c38352c37322c3130372c33322c3134382c33352c35312c3132352c34355d2c227268735f706b223a5b3137342c39352c3132342c31382c36322c3135312c3137302c3136382c3232332c36362c3132322c36312c3234322c3130372c3132352c3137372c3137302c3132332c35382c3231362c3137362c392c3234302c3131382c3131302c35362c3232372c3230302c3131322c3130352c32392c3230385d7d2c226c68735f706b223a5b36392c3138322c39392c382c34302c39332c3130382c3233312c382c312c3235322c3131302c3132322c37332c3133302c3230372c3231332c3137312c3130352c3232322c31352c3134322c3230362c3137392c33382c3132302c39322c362c32302c3133352c3130382c3138335d2c227268735f706b223a5b33342c36372c3134302c3132392c3231352c36392c3136302c3135362c3230302c31302c3232362c35382c3132322c36342c33382c3135362c3230362c3230362c302c3137382c3132302c3139332c362c3135332c3131322c3130392c3135372c3131322c3132322c3133372c3233372c38355d7d2c226c68735f706b223a5b37332c3131342c3136352c3137312c34322c3131372c3139322c3139342c3137342c32302c38312c392c3230392c31392c3134352c3233302c3233302c3130392c34382c3135302c31332c3232392c3139322c35342c3138362c3137372c32382c3133362c31352c3230342c3231342c3132305d2c227268735f706b223a5b3139302c32322c3131312c38362c38322c3138362c3231372c3134312c302c3136382c3130382c3230362c3130392c33342c33342c37382c3134342c3230322c3135362c3138372c3134302c3136302c32302c3233342c3138382c3234382c3131322c3131312c3136372c3234352c3138342c3137355d7d2c226c68735f706b223a5b3234382c33382c3134372c3234382c3138382c3136372c31332c3136362c3234352c31312c3234322c35332c37382c3138332c32352c31322c3136312c3235322c3130372c3231302c3234312c39352c3232302c3232362c3133392c3133302c39322c39392c3132382c3138342c33352c3233395d2c227268735f706b223a5b3134352c3136382c3130362c3137332c32342c3230302c39362c3231352c3131302c36382c3132382c3130322c3134372c3138342c31382c3230392c3138342c38392c3137322c3132372c35352c3232372c3133382c34312c38372c34312c3138382c35352c3131352c3133332c3134372c38315d7d2c226c68735f706b223a5b3137342c3130352c3131342c3132352c3131342c34392c33332c3135332c3234302c3131332c3136312c35372c34322c3233362c3137352c31382c37382c39382c33332c3230322c33382c3132312c3135372c3234332c3233382c39392c3135312c36362c35362c3134392c3135322c32345d2c227268735f706b223a5b3235322c3136382c3131372c3137302c3235302c3134332c3137302c35342c3130352c33312c3231392c3234392c3130342c33362c3133352c34382c3231362c372c3233332c3133372c3132342c3137302c3134372c3131332c35392c3233372c3234312c3132352c3231302c3233342c3234312c3130335d7d","operational_certificate":"5b5b5b3131322c39352c34322c39372c382c3235322c31382c3231342c31392c3231382c3231372c3234322c3233302c3138372c3234302c3133392c31342c3135382c3137392c3234392c3231312c36332c3132332c342c32362c3132362c3132312c3234372c302c35372c31362c3136315d2c312c37312c5b3132392c3234382c3133342c3132342c3230372c3130332c3233312c37302c3130372c32382c3134322c3134312c38362c3234392c3230352c31312c33392c3232382c3130382c3132322c3233312c3138322c3132372c3130312c3234352c33332c3135322c3233342c35342c36372c3138312c39362c3137372c3234362c32382c322c3235322c3130382c35392c3231352c3232372c3230392c3131382c3130352c3135342c37312c36332c3134352c3132372c3137352c3133382c3131352c39362c3233352c3131382c31322c3234302c3232352c3130392c3130382c3231322c3232392c35372c31305d5d2c5b33302c3138312c32302c37382c33392c3232332c352c3133372c3134312c3138392c372c3132372c34352c3232372c3230362c3135372c39352c3131352c36312c3132382c3135392c3135362c34332c3132372c302c34302c3134332c3138332c3233302c32352c39312c3137305d5d","kes_period":22,"stake":1009497432569}}]', - '2023-03-16T01:51:00.880Z', - '2023-03-16T02:07:22.145Z', - '{snark_avk}' - ); - "# - )) - .unwrap(); + insert_golden_certificate(connection, "'abcdef0123456789'") } #[tokio::test] async fn test_golden_master() { let connection = main_db_connection().unwrap(); - insert_golden_certificate(&connection); + insert_golden_certificate_without_snark_aggregate_verification_key(&connection); let repository = CertificateRepository::new(Arc::new(connection)); let certificate_records = repository @@ -330,8 +285,7 @@ mod tests { #[tokio::test] async fn test_golden_master_with_snark_aggregate_verification_key() { let connection = main_db_connection().unwrap(); - let snark_avk = "abcdef0123456789"; - insert_golden_certificate_with_snark_aggregate_verification_key(&connection, snark_avk); + insert_golden_certificate_with_snark_aggregate_verification_key(&connection); let repository = CertificateRepository::new(Arc::new(connection)); let certificate_records = repository @@ -343,7 +297,7 @@ mod tests { for record in &certificate_records { assert_eq!( record.aggregate_verification_key_snark.as_deref(), - Some(snark_avk), + Some("abcdef0123456789"), ); } } diff --git a/mithril-aggregator/src/tools/genesis.rs b/mithril-aggregator/src/tools/genesis.rs index e7ff4c62b17..7b9b8c9592e 100644 --- a/mithril-aggregator/src/tools/genesis.rs +++ b/mithril-aggregator/src/tools/genesis.rs @@ -215,7 +215,6 @@ mod tests { ProtocolGenesisSecretKey, ProtocolGenesisSigner, ProtocolGenesisVerificationKey, ProtocolGenesisVerifier, }, - entities::SupportedEra, test::{TempDir, builder::MithrilFixtureBuilder, double::fake_data}, }; use std::{fs::read_to_string, path::PathBuf}; diff --git a/mithril-client/Cargo.toml b/mithril-client/Cargo.toml index cf4fbe1fec7..1e99407c5dd 100644 --- a/mithril-client/Cargo.toml +++ b/mithril-client/Cargo.toml @@ -45,6 +45,11 @@ rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"] # Support compressed traffic with `reqwest` enable-http-compression = ["reqwest/gzip", "reqwest/zstd", "reqwest/deflate", "reqwest/brotli"] +# Empty feature: intentionally does NOT propagate to `mithril-common/future_snark` because +# `cargo publish --dry-run` resolves `mithril-common` from crates.io where `future_snark` +# does not exist yet. In CI, `--all-features` activates both this and `mithril-common`'s +# `future_snark` at the workspace level, so the gated client code compiles correctly. +# TODO: propagate to `mithril-common/future_snark` once the `future_snark` feature is published on crates.io. future_snark = [] # Enables usage of `rug` numerical backend in `mithril-stm` (dependency of `mithril-common`). diff --git a/mithril-common/src/crypto_helper/types/wrappers.rs b/mithril-common/src/crypto_helper/types/wrappers.rs index 6f9db99aaef..bd95c486523 100644 --- a/mithril-common/src/crypto_helper/types/wrappers.rs +++ b/mithril-common/src/crypto_helper/types/wrappers.rs @@ -1,12 +1,10 @@ use kes_summed_ed25519::kes::Sum6KesSig; -#[cfg(feature = "future_snark")] -use mithril_stm::AggregateVerificationKeyForSnark; -#[cfg(feature = "future_snark")] -use mithril_stm::VerificationKeyForSnark; use mithril_stm::{ AggregateSignature, AggregateVerificationKey, AggregateVerificationKeyForConcatenation, SingleSignature, VerificationKeyProofOfPossessionForConcatenation, }; +#[cfg(feature = "future_snark")] +use mithril_stm::{AggregateVerificationKeyForSnark, VerificationKeyForSnark}; use crate::crypto_helper::{MKMapProof, MKProof, OpCert, ProtocolKey, ProtocolMembershipDigest}; use crate::entities::BlockRange; diff --git a/mithril-stm/src/proof_system/concatenation/clerk.rs b/mithril-stm/src/proof_system/concatenation/clerk.rs index e29e4be61dc..019c54b572e 100644 --- a/mithril-stm/src/proof_system/concatenation/clerk.rs +++ b/mithril-stm/src/proof_system/concatenation/clerk.rs @@ -239,8 +239,8 @@ mod tests { .map(|sig| { let reg_party = clerk.closed_key_registration.get_registration_entry_for_index(&sig.signer_index).unwrap(); #[cfg(feature = "future_snark")] - // We need to remove the SNARK fields from the registration entry used in Concatenation proofs to avoid breaking change with previous client nor able to parse the aggregate signature. - // This happens because of the way the `ClosedRegistrationEntry` is serialized with an array representation isntead of map representation. + // We need to remove the SNARK fields from the registration entry used in Concatenation proofs to avoid breaking change with previous client not able to parse the aggregate signature. + // This happens because of the way the `ClosedRegistrationEntry` is serialized with an array representation instead of map representation. let reg_party = reg_party.without_snark_fields(); SingleSignatureWithRegisteredParty { sig: sig.clone(), diff --git a/mithril-stm/src/proof_system/concatenation/proof.rs b/mithril-stm/src/proof_system/concatenation/proof.rs index 2abd9fc4904..829d17407a5 100644 --- a/mithril-stm/src/proof_system/concatenation/proof.rs +++ b/mithril-stm/src/proof_system/concatenation/proof.rs @@ -45,8 +45,8 @@ impl ConcatenationProof { .get_registration_entry_for_index(&sig.signer_index) .map(|reg_party| { #[cfg(feature = "future_snark")] - // We need to remove the SNARK fields from the registration entry used in Concatenation proofs to avoid breaking change with previous client nor able to parse the aggregate signature. - // This happens because of the way the `ClosedRegistrationEntry` is serialized with an array representation isntead of map representation. + // We need to remove the SNARK fields from the registration entry used in Concatenation proofs to avoid breaking change with previous client not able to parse the aggregate signature. + // This happens because of the way the `ClosedRegistrationEntry` is serialized with an array representation instead of map representation. let reg_party = reg_party.without_snark_fields(); SingleSignatureWithRegisteredParty { sig: sig.clone(), diff --git a/mithril-test-lab/mithril-end-to-end/backward-compatibility.md b/mithril-test-lab/mithril-end-to-end/backward-compatibility.md index cef981a7de3..53824520d0c 100644 --- a/mithril-test-lab/mithril-end-to-end/backward-compatibility.md +++ b/mithril-test-lab/mithril-end-to-end/backward-compatibility.md @@ -33,7 +33,7 @@ format is: `- **since 'X.Y.Z' (distribution version) [to 'X.Y.Z' (distribution v ### Mithril aggregator -- **after `0.8.14`**: addition of `--mithril-era` flag to `genesis bootstrap` command +- **since `0.8.34`**: addition of `--mithril-era` flag to `genesis bootstrap` command - **since `0.7.94` (next to 2543.1)**: only the leader aggregator must be restarted when updating protocol parameters ### Mithril signer diff --git a/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs b/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs index fbef7dc7832..020108142e0 100644 --- a/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs +++ b/mithril-test-lab/mithril-end-to-end/src/mithril/aggregator.rs @@ -274,10 +274,10 @@ impl Aggregator { command.set_log_name(command_name); let mut args = vec!["genesis".to_string(), "bootstrap".to_string()]; - if self.version.is_above("0.8.14") { + if self.version.is_above_or_equal("0.8.34") { args.extend(["--mithril-era".to_string(), mithril_era.to_string()]); } else { - info!("Aggregator version is below 0.8.14, skipping unsupported `--mithril-era` flag"); + info!("Aggregator version is below 0.8.34, skipping unsupported `--mithril-era` flag"); } let exit_status = command diff --git a/mithril-test-lab/mithril-end-to-end/src/utils/version_req.rs b/mithril-test-lab/mithril-end-to-end/src/utils/version_req.rs index 7b72e7524c9..b45efc0fbee 100644 --- a/mithril-test-lab/mithril-end-to-end/src/utils/version_req.rs +++ b/mithril-test-lab/mithril-end-to-end/src/utils/version_req.rs @@ -54,14 +54,6 @@ impl NodeVersion { version_req.matches(&self.semver_version) } - /// Checks if the node version is strictly above the given version. - /// - /// Panics if `version` is not a valid semver version - pub fn is_above(&self, version: &'static str) -> bool { - let version_req = semver::VersionReq::parse(&format!(">{version}")).unwrap(); - version_req.matches(&self.semver_version) - } - /// Checks if the node version is equal or above the given version. /// /// Panics if `version` is not a valid semver version @@ -130,16 +122,6 @@ mod tests { assert!(!version.is_below("1.2.2")); } - #[test] - fn test_version_strictly_above() { - let version = NodeVersion::new(semver::Version::new(5, 7, 1)); - - assert!(version.is_above("4.6.0")); - assert!(version.is_above("5.7.0")); - assert!(!version.is_above("5.7.1")); - assert!(!version.is_above("5.7.2")); - } - #[test] fn test_version_equal_or_above() { let version = NodeVersion::new(semver::Version::new(5, 7, 1)); From f4fb52849b1512a95b082e997ae9b1f2cd88b3e3 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 19:30:53 +0100 Subject: [PATCH 15/19] feat(aggregator): add logger in genesis tools --- .../src/dependency_injection/builder/mod.rs | 1 + .../containers/genesis.rs | 5 ++ mithril-aggregator/src/tools/genesis.rs | 25 +++++-- .../certificate_chain/certificate_genesis.rs | 69 ++++++++++++------- .../test/builder/certificate_chain_builder.rs | 34 ++++----- .../src/test/builder/mithril_fixture.rs | 34 ++++----- 6 files changed, 107 insertions(+), 61 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index aca1db85390..add0f022fc9 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -495,6 +495,7 @@ impl DependenciesBuilder { protocol_parameters_retriever: self.get_protocol_parameters_retriever().await?, verification_key_store: self.get_verification_key_store().await?, mithril_era, + logger: self.root_logger(), }; Ok(dependencies) diff --git a/mithril-aggregator/src/dependency_injection/containers/genesis.rs b/mithril-aggregator/src/dependency_injection/containers/genesis.rs index f10cd0573d4..6f8bcbdf65f 100644 --- a/mithril-aggregator/src/dependency_injection/containers/genesis.rs +++ b/mithril-aggregator/src/dependency_injection/containers/genesis.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use slog::Logger; + use mithril_cardano_node_chain::chain_observer::ChainObserver; use mithril_common::{ CardanoNetwork, certificate_chain::CertificateVerifier, entities::SupportedEra, @@ -30,4 +32,7 @@ pub struct GenesisCommandDependenciesContainer { /// Mithril era to use for the genesis certificate. pub mithril_era: SupportedEra, + + /// Logger. + pub logger: Logger, } diff --git a/mithril-aggregator/src/tools/genesis.rs b/mithril-aggregator/src/tools/genesis.rs index 7b9b8c9592e..778e987efaa 100644 --- a/mithril-aggregator/src/tools/genesis.rs +++ b/mithril-aggregator/src/tools/genesis.rs @@ -1,4 +1,3 @@ -use anyhow::Context; use std::{ fs::File, io::{Write, prelude::*}, @@ -6,6 +5,9 @@ use std::{ sync::Arc, }; +use anyhow::Context; +use slog::Logger; + use mithril_common::{ CardanoNetwork, StdResult, certificate_chain::{CertificateGenesisProducer, CertificateVerifier}, @@ -30,6 +32,7 @@ pub struct GenesisTools { certificate_verifier: Arc, certificate_repository: Arc, mithril_era: SupportedEra, + logger: Logger, } impl GenesisTools { @@ -41,6 +44,7 @@ impl GenesisTools { certificate_verifier: Arc, certificate_repository: Arc, mithril_era: SupportedEra, + logger: Logger, ) -> Self { Self { network, @@ -50,6 +54,7 @@ impl GenesisTools { certificate_verifier, certificate_repository, mithril_era, + logger, } } @@ -91,13 +96,16 @@ impl GenesisTools { certificate_verifier, certificate_repository, dependencies.mithril_era, + dependencies.logger, )) } /// Export AVK of the genesis stake distribution to a payload file pub fn export_payload_to_sign(&self, target_path: &Path) -> StdResult<()> { let mut target_file = File::create(target_path)?; - let protocol_message = CertificateGenesisProducer::create_genesis_protocol_message( + let genesis_producer = + CertificateGenesisProducer::new(None).with_logger(self.logger.clone()); + let protocol_message = genesis_producer.create_genesis_protocol_message( &self.genesis_protocol_parameters, &self.genesis_avk, &self.epoch, @@ -128,8 +136,9 @@ impl GenesisTools { genesis_signer: ProtocolGenesisSigner, ) -> StdResult<()> { let genesis_verification_key = &genesis_signer.verification_key(); - let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer))); - let genesis_protocol_message = CertificateGenesisProducer::create_genesis_protocol_message( + let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer))) + .with_logger(self.logger.clone()); + let genesis_protocol_message = genesis_producer.create_genesis_protocol_message( &self.genesis_protocol_parameters, &self.genesis_avk, &self.epoch, @@ -169,7 +178,9 @@ impl GenesisTools { genesis_signature: ProtocolGenesisSignature, genesis_verification_key: &ProtocolGenesisVerificationKey, ) -> StdResult<()> { - let genesis_certificate = CertificateGenesisProducer::create_genesis_certificate( + let genesis_producer = + CertificateGenesisProducer::new(None).with_logger(self.logger.clone()); + let genesis_certificate = genesis_producer.create_genesis_certificate( self.genesis_protocol_parameters.clone(), self.network, self.epoch, @@ -209,6 +220,8 @@ impl GenesisTools { #[cfg(test)] mod tests { + use std::{fs::read_to_string, path::PathBuf}; + use mithril_common::{ certificate_chain::MithrilCertificateVerifier, crypto_helper::{ @@ -217,7 +230,6 @@ mod tests { }, test::{TempDir, builder::MithrilFixtureBuilder, double::fake_data}, }; - use std::{fs::read_to_string, path::PathBuf}; use crate::database::test_helper::main_db_connection; use crate::test::TestLogger; @@ -258,6 +270,7 @@ mod tests { certificate_verifier.clone(), certificate_store.clone(), SupportedEra::Pythagoras, + TestLogger::stdout(), ); ( diff --git a/mithril-common/src/certificate_chain/certificate_genesis.rs b/mithril-common/src/certificate_chain/certificate_genesis.rs index e62be74780e..af63fc79959 100644 --- a/mithril-common/src/certificate_chain/certificate_genesis.rs +++ b/mithril-common/src/certificate_chain/certificate_genesis.rs @@ -3,8 +3,13 @@ use std::sync::Arc; use chrono::prelude::*; +#[cfg(feature = "future_snark")] +use slog::warn; +use slog::{Logger, o}; use thiserror::Error; +#[cfg(feature = "future_snark")] +use crate::crypto_helper::ProtocolAggregateVerificationKeyForSnark; use crate::{ StdResult, crypto_helper::{ @@ -18,9 +23,6 @@ use crate::{ protocol::ToMessage, }; -#[cfg(feature = "future_snark")] -use crate::crypto_helper::ProtocolAggregateVerificationKeyForSnark; - /// [CertificateGenesisProducer] related errors. #[derive(Error, Debug)] pub enum CertificateGenesisProducerError { @@ -33,16 +35,27 @@ pub enum CertificateGenesisProducerError { #[derive(Debug)] pub struct CertificateGenesisProducer { genesis_signer: Option>, + logger: Logger, } impl CertificateGenesisProducer { /// CertificateGenesisProducer factory pub fn new(genesis_signer: Option>) -> Self { - Self { genesis_signer } + Self { + genesis_signer, + logger: Logger::root(slog::Discard, o!()), + } + } + + /// Set the [Logger] to use. + pub fn with_logger(mut self, logger: Logger) -> Self { + self.logger = logger; + self } /// Create the Genesis protocol message pub fn create_genesis_protocol_message( + &self, genesis_protocol_parameters: &ProtocolParameters, genesis_avk: &ProtocolAggregateVerificationKey, genesis_epoch: &Epoch, @@ -70,8 +83,9 @@ impl CertificateGenesisProducer { ); } None => { - eprintln!( - "WARNING: SNARK aggregate verification key is unavailable, genesis certificate will not include SNARK AVK" + warn!( + self.logger, + "SNARK aggregate verification key is unavailable, genesis certificate will not include SNARK AVK" ); } } @@ -102,6 +116,7 @@ impl CertificateGenesisProducer { /// Create a Genesis Certificate pub fn create_genesis_certificate>( + &self, protocol_parameters: ProtocolParameters, network: T, epoch: Epoch, @@ -122,7 +137,7 @@ impl CertificateGenesisProducer { signers, ); let previous_hash = "".to_string(); - let genesis_protocol_message = Self::create_genesis_protocol_message( + let genesis_protocol_message = self.create_genesis_protocol_message( &protocol_parameters, &genesis_avk, &epoch, @@ -143,21 +158,26 @@ impl CertificateGenesisProducer { mod tests { use super::*; - use crate::{entities::ProtocolMessagePartKey, test::builder::MithrilFixtureBuilder}; + use crate::entities::ProtocolMessagePartKey; + use crate::test::TestLogger; + use crate::test::builder::MithrilFixtureBuilder; #[test] - fn test_create_genesis_protocol_message_has_expected_keys_and_values() { + fn genesis_protocol_message_has_expected_keys_and_values() { let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); let genesis_protocol_parameters = fixture.protocol_parameters(); let genesis_avk = fixture.compute_aggregate_verification_key(); let genesis_epoch = Epoch(123); - let protocol_message = CertificateGenesisProducer::create_genesis_protocol_message( - &genesis_protocol_parameters, - &genesis_avk, - &genesis_epoch, - SupportedEra::Pythagoras, - ) - .unwrap(); + let genesis_producer = + CertificateGenesisProducer::new(None).with_logger(TestLogger::stdout()); + let protocol_message = genesis_producer + .create_genesis_protocol_message( + &genesis_protocol_parameters, + &genesis_avk, + &genesis_epoch, + SupportedEra::Pythagoras, + ) + .unwrap(); let expected_genesis_avk_value = fixture.compute_and_encode_concatenation_aggregate_verification_key(); @@ -187,13 +207,16 @@ mod tests { let genesis_protocol_parameters = fixture.protocol_parameters(); let genesis_avk = fixture.compute_aggregate_verification_key(); let genesis_epoch = Epoch(123); - let protocol_message = CertificateGenesisProducer::create_genesis_protocol_message( - &genesis_protocol_parameters, - &genesis_avk, - &genesis_epoch, - SupportedEra::Lagrange, - ) - .unwrap(); + let genesis_producer = + CertificateGenesisProducer::new(None).with_logger(TestLogger::stdout()); + let protocol_message = genesis_producer + .create_genesis_protocol_message( + &genesis_protocol_parameters, + &genesis_avk, + &genesis_epoch, + SupportedEra::Lagrange, + ) + .unwrap(); let expected_snark_avk_value = fixture .compute_and_encode_snark_aggregate_verification_key() diff --git a/mithril-common/src/test/builder/certificate_chain_builder.rs b/mithril-common/src/test/builder/certificate_chain_builder.rs index 8d544c46488..47a9dcf4ae6 100644 --- a/mithril-common/src/test/builder/certificate_chain_builder.rs +++ b/mithril-common/src/test/builder/certificate_chain_builder.rs @@ -515,26 +515,28 @@ impl<'a> CertificateChainBuilder<'a> { let next_protocol_parameters = &context.next_fixture.protocol_parameters(); let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer.to_owned()))); - let genesis_protocol_message = CertificateGenesisProducer::create_genesis_protocol_message( - next_protocol_parameters, - &next_avk, - &epoch, - mithril_era, - ) - .unwrap(); + let genesis_protocol_message = genesis_producer + .create_genesis_protocol_message( + next_protocol_parameters, + &next_avk, + &epoch, + mithril_era, + ) + .unwrap(); let genesis_signature = genesis_producer .sign_genesis_protocol_message(genesis_protocol_message) .unwrap(); - CertificateGenesisProducer::create_genesis_certificate( - certificate.metadata.protocol_parameters, - certificate.metadata.network, - certificate.epoch, - next_avk, - genesis_signature, - mithril_era, - ) - .unwrap() + genesis_producer + .create_genesis_certificate( + certificate.metadata.protocol_parameters, + certificate.metadata.network, + certificate.epoch, + next_avk, + genesis_signature, + mithril_era, + ) + .unwrap() } fn build_standard_certificate(&self, context: &CertificateChainBuilderContext) -> Certificate { diff --git a/mithril-common/src/test/builder/mithril_fixture.rs b/mithril-common/src/test/builder/mithril_fixture.rs index 7381fb21a6b..fa3fc9f833e 100644 --- a/mithril-common/src/test/builder/mithril_fixture.rs +++ b/mithril-common/src/test/builder/mithril_fixture.rs @@ -234,26 +234,28 @@ impl MithrilFixture { let genesis_signer = ProtocolGenesisSigner::create_deterministic_signer(); let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer))); let mithril_era = SupportedEra::Pythagoras; - let genesis_protocol_message = CertificateGenesisProducer::create_genesis_protocol_message( - &self.protocol_parameters, - &genesis_avk, - &epoch, - mithril_era, - ) - .unwrap(); + let genesis_protocol_message = genesis_producer + .create_genesis_protocol_message( + &self.protocol_parameters, + &genesis_avk, + &epoch, + mithril_era, + ) + .unwrap(); let genesis_signature = genesis_producer .sign_genesis_protocol_message(genesis_protocol_message) .unwrap(); - CertificateGenesisProducer::create_genesis_certificate( - self.protocol_parameters.clone(), - network, - epoch, - genesis_avk, - genesis_signature, - mithril_era, - ) - .unwrap() + genesis_producer + .create_genesis_certificate( + self.protocol_parameters.clone(), + network, + epoch, + genesis_avk, + genesis_signature, + mithril_era, + ) + .unwrap() } /// Make all underlying signers sign the given message, filter the resulting list to remove From dd48413dd3837c3f27b9e7f1690ddb18d3427892 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 19:38:31 +0100 Subject: [PATCH 16/19] docs: update runbook for manual genesis --- docs/runbook/genesis-manually/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/runbook/genesis-manually/README.md b/docs/runbook/genesis-manually/README.md index 0b3f00f3a7e..b74949f288a 100644 --- a/docs/runbook/genesis-manually/README.md +++ b/docs/runbook/genesis-manually/README.md @@ -32,6 +32,7 @@ export NETWORK_MAGIC=**NETWORK_MAGIC** export DATA_STORES_DIRECTORY=**DATA_STORES_DIRECTORY** export CARDANO_NODE_SOCKET_PATH=**CARDANO_NODE_SOCKET_PATH** export CHAIN_OBSERVER_TYPE=**CHAIN_OBSERVER_TYPE** +export MITHRIL_ERA=**MITHRIL_ERA** ``` And create genesis dir: @@ -49,9 +50,11 @@ docker exec -it mithril-aggregator bash Once connected to the aggregator container, export the genesis payload to sign: ```bash -/app/bin/mithril-aggregator -vvv genesis export --target-path /mithril-aggregator/mithril/genesis/genesis-payload-to-sign.txt +/app/bin/mithril-aggregator -vvv genesis export --target-path /mithril-aggregator/mithril/genesis/genesis-payload-to-sign.txt [--mithril-era $MITHRIL_ERA] ``` +> The `--mithril-era` parameter is optional when only one era exists, and required when multiple eras are supported. It specifies which Mithril era to use for the genesis certificate (e.g. `pythagoras`, `lagrange`). + Then disconnect from the aggregator container: ```bash From c57a142e3e66d43bc39adfa500180b02ff811dd0 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Sun, 15 Mar 2026 19:09:23 +0100 Subject: [PATCH 17/19] chore: fix clippy warning --- mithril-aggregator/src/tools/genesis.rs | 87 +++++++++++++++---------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/mithril-aggregator/src/tools/genesis.rs b/mithril-aggregator/src/tools/genesis.rs index 778e987efaa..d2dc7a7066c 100644 --- a/mithril-aggregator/src/tools/genesis.rs +++ b/mithril-aggregator/src/tools/genesis.rs @@ -24,36 +24,44 @@ use crate::{ dependency_injection::GenesisCommandDependenciesContainer, }; +/// Configuration for the genesis tools. +pub struct GenesisToolsConfiguration { + /// Cardano network. + pub network: CardanoNetwork, + + /// Current epoch. + pub epoch: Epoch, + + /// Aggregate verification key for the genesis stake distribution. + pub genesis_avk: ProtocolAggregateVerificationKey, + + /// Protocol parameters for the genesis stake distribution. + pub genesis_protocol_parameters: ProtocolParameters, + + /// Mithril era to use for the genesis certificate. + pub mithril_era: SupportedEra, +} + +/// Genesis tools for creating and managing genesis certificates. pub struct GenesisTools { - network: CardanoNetwork, - epoch: Epoch, - genesis_avk: ProtocolAggregateVerificationKey, - genesis_protocol_parameters: ProtocolParameters, + configuration: GenesisToolsConfiguration, certificate_verifier: Arc, certificate_repository: Arc, - mithril_era: SupportedEra, logger: Logger, } impl GenesisTools { + /// GenesisTools factory pub fn new( - network: CardanoNetwork, - epoch: Epoch, - genesis_avk: ProtocolAggregateVerificationKey, - genesis_protocol_parameters: ProtocolParameters, + configuration: GenesisToolsConfiguration, certificate_verifier: Arc, certificate_repository: Arc, - mithril_era: SupportedEra, logger: Logger, ) -> Self { Self { - network, - epoch, - genesis_avk, - genesis_protocol_parameters, + configuration, certificate_verifier, certificate_repository, - mithril_era, logger, } } @@ -88,14 +96,18 @@ impl GenesisTools { .build_multi_signer(); let genesis_avk = protocol_multi_signer.compute_aggregate_verification_key(); - Ok(Self::new( - dependencies.network, + let configuration = GenesisToolsConfiguration { + network: dependencies.network, epoch, genesis_avk, genesis_protocol_parameters, + mithril_era: dependencies.mithril_era, + }; + + Ok(Self::new( + configuration, certificate_verifier, certificate_repository, - dependencies.mithril_era, dependencies.logger, )) } @@ -106,10 +118,10 @@ impl GenesisTools { let genesis_producer = CertificateGenesisProducer::new(None).with_logger(self.logger.clone()); let protocol_message = genesis_producer.create_genesis_protocol_message( - &self.genesis_protocol_parameters, - &self.genesis_avk, - &self.epoch, - self.mithril_era, + &self.configuration.genesis_protocol_parameters, + &self.configuration.genesis_avk, + &self.configuration.epoch, + self.configuration.mithril_era, )?; target_file.write_all(protocol_message.compute_hash().as_bytes())?; Ok(()) @@ -139,10 +151,10 @@ impl GenesisTools { let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer))) .with_logger(self.logger.clone()); let genesis_protocol_message = genesis_producer.create_genesis_protocol_message( - &self.genesis_protocol_parameters, - &self.genesis_avk, - &self.epoch, - self.mithril_era, + &self.configuration.genesis_protocol_parameters, + &self.configuration.genesis_avk, + &self.configuration.epoch, + self.configuration.mithril_era, )?; let genesis_signature = genesis_producer.sign_genesis_protocol_message(genesis_protocol_message)?; @@ -181,12 +193,12 @@ impl GenesisTools { let genesis_producer = CertificateGenesisProducer::new(None).with_logger(self.logger.clone()); let genesis_certificate = genesis_producer.create_genesis_certificate( - self.genesis_protocol_parameters.clone(), - self.network, - self.epoch, - self.genesis_avk.clone(), + self.configuration.genesis_protocol_parameters.clone(), + self.configuration.network, + self.configuration.epoch, + self.configuration.genesis_avk.clone(), genesis_signature, - self.mithril_era, + self.configuration.mithril_era, )?; self.certificate_verifier .verify_genesis_certificate(&genesis_certificate, genesis_verification_key) @@ -262,14 +274,17 @@ mod tests { )); let genesis_avk = create_fake_genesis_avk(); let genesis_verifier = Arc::new(genesis_signer.create_verifier()); - let genesis_tools = GenesisTools::new( - fake_data::network(), - Epoch(10), + let configuration = GenesisToolsConfiguration { + network: fake_data::network(), + epoch: Epoch(10), genesis_avk, - fake_data::protocol_parameters(), + genesis_protocol_parameters: fake_data::protocol_parameters(), + mithril_era: SupportedEra::Pythagoras, + }; + let genesis_tools = GenesisTools::new( + configuration, certificate_verifier.clone(), certificate_store.clone(), - SupportedEra::Pythagoras, TestLogger::stdout(), ); From 4853aa24141713aa17d3eea0e721c72025d5d483 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 19:40:28 +0100 Subject: [PATCH 18/19] docs: update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50607d2b670..7bab2096ecf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ As a minor extension, we have adopted a slightly different versioning convention - **UNSTABLE**: - Support for **DMQ consumer deduplication** to prevent processing the same message multiple times upon reconnection to the DMQ server. - Support for SNARK-friendly signer registration. + - Support for SNARK-friendly certificate chain. | Crate | Version | | ----- | ------- | From 4949549f724cc355e4102dad0587c86ac4a44714 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 13 Mar 2026 19:38:48 +0100 Subject: [PATCH 19/19] chore: upgrade crate versions * mithril-aggregator from `0.8.33` to `0.8.34` * mithril-client-cli from `0.12.42` to `0.12.43` * mithril-client from `0.13.4` to `0.13.5` * mithril-common from `0.6.55` to `0.6.56` * mithril-signer from `0.3.22` to `0.3.23` * mithril-stm from `0.9.28` to `0.9.29` * mithril-end-to-end from `0.4.121` to `0.4.122` --- Cargo.lock | 14 +++++++------- mithril-aggregator/Cargo.toml | 2 +- mithril-client-cli/Cargo.toml | 2 +- mithril-client/Cargo.toml | 2 +- mithril-common/Cargo.toml | 2 +- mithril-signer/Cargo.toml | 2 +- mithril-stm/Cargo.toml | 2 +- mithril-test-lab/mithril-end-to-end/Cargo.toml | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29e086f7e5b..caa0cdab044 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3932,7 +3932,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.8.33" +version = "0.8.34" dependencies = [ "anyhow", "async-trait", @@ -4137,7 +4137,7 @@ dependencies = [ [[package]] name = "mithril-client" -version = "0.13.4" +version = "0.13.5" dependencies = [ "anyhow", "async-trait", @@ -4176,7 +4176,7 @@ dependencies = [ [[package]] name = "mithril-client-cli" -version = "0.12.42" +version = "0.12.43" dependencies = [ "anyhow", "async-trait", @@ -4229,7 +4229,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.6.55" +version = "0.6.56" dependencies = [ "anyhow", "async-trait", @@ -4314,7 +4314,7 @@ dependencies = [ [[package]] name = "mithril-end-to-end" -version = "0.4.121" +version = "0.4.122" dependencies = [ "anyhow", "async-recursion", @@ -4464,7 +4464,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.3.22" +version = "0.3.23" dependencies = [ "anyhow", "async-trait", @@ -4507,7 +4507,7 @@ dependencies = [ [[package]] name = "mithril-stm" -version = "0.9.28" +version = "0.9.29" dependencies = [ "anyhow", "blake2 0.10.6", diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index 11784945c1a..7b989968816 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.8.33" +version = "0.8.34" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-client-cli/Cargo.toml b/mithril-client-cli/Cargo.toml index c3f5855631a..8a0d58a0fe1 100644 --- a/mithril-client-cli/Cargo.toml +++ b/mithril-client-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-client-cli" -version = "0.12.42" +version = "0.12.43" description = "A Mithril Client" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-client/Cargo.toml b/mithril-client/Cargo.toml index 1e99407c5dd..ff19f9b6563 100644 --- a/mithril-client/Cargo.toml +++ b/mithril-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-client" -version = "0.13.4" +version = "0.13.5" description = "Mithril client library" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index b80b2a70d5e..7bebc76bc08 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.6.55" +version = "0.6.56" description = "Common types, interfaces, and utilities for Mithril nodes." authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index 86b4be6205b..a4db7de3cda 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.3.22" +version = "0.3.23" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index bbf5ed15ed0..721ba5dce72 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-stm" -version = "0.9.28" +version = "0.9.29" edition = { workspace = true } authors = { workspace = true } homepage = { workspace = true } diff --git a/mithril-test-lab/mithril-end-to-end/Cargo.toml b/mithril-test-lab/mithril-end-to-end/Cargo.toml index b4d082a8d68..209c580dd1f 100644 --- a/mithril-test-lab/mithril-end-to-end/Cargo.toml +++ b/mithril-test-lab/mithril-end-to-end/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-end-to-end" -version = "0.4.121" +version = "0.4.122" authors = { workspace = true } edition = { workspace = true } documentation = { workspace = true }