diff --git a/.github/workflows/scripts/filter-slow-tests.sh b/.github/workflows/scripts/filter-slow-tests.sh index b987c96a365..9ce281c05c9 100755 --- a/.github/workflows/scripts/filter-slow-tests.sh +++ b/.github/workflows/scripts/filter-slow-tests.sh @@ -94,7 +94,6 @@ readonly -a SLOW_MITHRIL_STM_TESTS=( "mithril-stm/src/membership_commitment/merkle_tree#membership_commitment::merkle_tree::" "mithril-stm/src/proof_system/halo2_snark#proof_system::halo2_snark::" "mithril-stm/src/proof_system/ivc_halo2_snark/proof.rs#proof_system::ivc_halo2_snark::proof::" - "mithril-stm/src/proof_system/ivc_halo2_snark/prover_input.rs#proof_system::ivc_halo2_snark::prover_input::" "mithril-stm/src/proof_system/ivc_halo2_snark/prover_input.rs#proof_system::ivc_halo2_snark::proof::" "mithril-stm/src/proof_system/ivc_halo2_snark/prover_input_helpers.rs#proof_system::ivc_halo2_snark::prover_input_helpers::" "mithril-stm/src/proof_system/ivc_halo2_snark/prover_input_helpers.rs#proof_system::ivc_halo2_snark::proof::" diff --git a/Cargo.lock b/Cargo.lock index 3f8c08ed5b8..bad4d87307a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4834,7 +4834,7 @@ dependencies = [ [[package]] name = "mithril-stm" -version = "0.12.9" +version = "0.12.10" dependencies = [ "anyhow", "blake2 0.10.6", diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 529db967edb..aa42ed99055 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -51,7 +51,7 @@ fixed = "1.31.0" hex = { workspace = true } kes-summed-ed25519 = { version = "0.2.1", features = ["serde_enabled", "sk_clone_enabled"] } mithril-merkle-tree = { path = "../internal/mithril-merkle-tree", version = "0.1.4" } -mithril-stm = { path = "../mithril-stm", version = "0.12.9", default-features = false } +mithril-stm = { path = "../mithril-stm", version = "0.12.10", default-features = false } nom = "8.0.0" rand_chacha = { workspace = true } rand_core = { workspace = true } diff --git a/mithril-stm/CHANGELOG.md b/mithril-stm/CHANGELOG.md index 92881bed720..6e40883cd8d 100644 --- a/mithril-stm/CHANGELOG.md +++ b/mithril-stm/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.12.10 (08-28-2026) + +### Changed + +- Recovered the seven ignored IVC prover input preparation tests, which no CI tier ran because no job passes `--run-ignored`, and retired the two of them that no longer matched the API after the genesis step was simplified. +- Narrowed `IvcProverInput::prepare` and its helpers to a verification context holding only the verifying keys, their fixed bases and the KZG verifier parameters, so that preparing a prover input no longer requires the IVC proving key. +- Rebuilt the preparation tests on the committed assets and removed their inert `OnceLock` fixtures, so that the module needs no key generation, no SRS and no cache and its tests now run in the fast tier. + ## 0.12.9 (08-26-2026) ### Changed diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index 7bcbd177f23..559cf3b973f 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-stm" -version = "0.12.9" +version = "0.12.10" edition = { workspace = true } authors = { workspace = true } homepage = { workspace = true } diff --git a/mithril-stm/src/circuits/halo2_ivc/bench/helpers.rs b/mithril-stm/src/circuits/halo2_ivc/bench/helpers.rs index aaae3d4d80f..3d26bc0ca06 100644 --- a/mithril-stm/src/circuits/halo2_ivc/bench/helpers.rs +++ b/mithril-stm/src/circuits/halo2_ivc/bench/helpers.rs @@ -334,7 +334,7 @@ impl IvcBenchEnv { &self.global, &preimage, &rolling_state, - &self.setup, + &self.setup.prover_input_verification_context(), )?; let circuit_data = IvcCircuitData::try_new( @@ -386,16 +386,17 @@ impl IvcBenchEnv { aggregate_verification_key: &AggregateVerificationKeyForSnark, rolling_state: &IvcRollingState, ) -> StmResult { + let verification_context = self.setup.prover_input_verification_context(); let certificate_dual_msm = snark_proof.prepare_and_check( message, aggregate_verification_key, - &self.setup.certificate_verifying_key, - &self.setup.srs.verifier_params(), + verification_context.certificate_verifying_key(), + verification_context.verifier_params(), )?; let certificate_collapsed_accumulator = - self.setup.certificate_collapsed_accumulator(certificate_dual_msm)?; - let previous_ivc_proof_collapsed_accumulator = - self.setup.previous_ivc_proof_collapsed_accumulator( + verification_context.certificate_collapsed_accumulator(certificate_dual_msm)?; + let previous_ivc_proof_collapsed_accumulator = verification_context + .previous_ivc_proof_collapsed_accumulator( rolling_state.ivc_proof().as_bytes(), &rolling_state.previous_ivc_proof_public_inputs(&self.global), )?; diff --git a/mithril-stm/src/proof_system/ivc_halo2_snark/proof.rs b/mithril-stm/src/proof_system/ivc_halo2_snark/proof.rs index 6d8a0860a42..0e8862f0b10 100644 --- a/mithril-stm/src/proof_system/ivc_halo2_snark/proof.rs +++ b/mithril-stm/src/proof_system/ivc_halo2_snark/proof.rs @@ -364,7 +364,7 @@ impl IvcProver { global, protocol_message_preimage, effective_rolling_state, - &self.ivc_setup, + &self.ivc_setup.prover_input_verification_context(), )?; let certificate_proof_bytes = snark_proof.into_circuit_proof_bytes(); diff --git a/mithril-stm/src/proof_system/ivc_halo2_snark/prover_input.rs b/mithril-stm/src/proof_system/ivc_halo2_snark/prover_input.rs index 3435ed18969..46968bf3dc7 100644 --- a/mithril-stm/src/proof_system/ivc_halo2_snark/prover_input.rs +++ b/mithril-stm/src/proof_system/ivc_halo2_snark/prover_input.rs @@ -18,7 +18,7 @@ use crate::{ IvcTransitionType, build_next_accumulator, build_next_state, create_snark_message_for_next_state, verify_certificate_proof, }, - prover_setup::IvcSnarkProverSetup, + prover_setup::IvcProverInputVerificationContext, rolling_state::IvcRollingState, }, }; @@ -52,7 +52,7 @@ impl IvcProverInput { global: &Global, protocol_message_preimage: &ProtocolMessagePreimage, rolling_state: &IvcRollingState, - prover_setup: &IvcSnarkProverSetup, + verification_context: &IvcProverInputVerificationContext, ) -> StmResult { let transition_type = IvcTransitionType::try_compute_transition_type( rolling_state, @@ -70,7 +70,7 @@ impl IvcProverInput { certificate_proof, message, aggregate_verification_key_for_snark, - prover_setup, + verification_context, )?; let (certificate_message_hash, certificate_merkle_tree_commitment) = @@ -84,8 +84,12 @@ impl IvcProverInput { protocol_message_preimage, )?; - let next_accumulator = - build_next_accumulator(certificate_dual_msm, rolling_state, prover_setup, global)?; + let next_accumulator = build_next_accumulator( + certificate_dual_msm, + rolling_state, + verification_context, + global, + )?; let witness = Witness::new( rolling_state.genesis_signature(), @@ -141,570 +145,445 @@ impl IvcProverInput { } #[cfg(test)] -mod test { - use super::*; - - mod slow { - use std::sync::OnceLock; - - use midnight_proofs::utils::SerdeFormat; - use rand_chacha::ChaCha20Rng; - use rand_core::SeedableRng; - - use crate::{ - BaseFieldElement, MithrilMembershipDigest, Parameters, SchnorrSigningKey, - SchnorrVerificationKey, - circuits::halo2_ivc::{ - PREIMAGE_CURRENT_EPOCH_BYTES, PREIMAGE_NEXT_MERKLE_TREE_COMMITMENT_BYTES, - PREIMAGE_NEXT_PROTOCOL_PARAMETERS_BYTES, PREIMAGE_SIZE, - errors::{EpochTransitionErrorKind, IvcCircuitError}, - io::Write as IvcWrite, - tests::common::{ - asset_readers::{ - VerificationContextAsset, load_embedded_first_certificate_in_epoch_asset, - load_embedded_following_certificate_in_epoch_asset, - load_embedded_next_epoch_step_output_asset, - load_embedded_recursive_chain_state_asset, - load_embedded_verification_context_asset, - }, - generators::{ - build_asset_generation_setup_from_cache, - build_genesis_base_case_next_state, build_genesis_base_case_witness, - build_genesis_protocol_message_preimage, build_recursive_global, - setup::{ - AssetGenerationSetup, GENESIS_EPOCH, QUORUM_SIZE, SIGNER_COUNT, - TOTAL_STAKE, - }, - }, - }, - types::{ - CertificateCircuitVerificationKeyRepresentation, EpochNumber, - IvcCircuitVerificationKeyRepresentation, StepCounter, +mod tests { + use midnight_proofs::utils::SerdeFormat; + use rand_chacha::ChaCha20Rng; + use rand_core::SeedableRng; + + use crate::{ + BaseFieldElement, MithrilMembershipDigest, Parameters, SchnorrSigningKey, + SchnorrVerificationKey, + circuits::halo2_ivc::{ + PREIMAGE_CURRENT_EPOCH_BYTES, PREIMAGE_NEXT_MERKLE_TREE_COMMITMENT_BYTES, + PREIMAGE_NEXT_PROTOCOL_PARAMETERS_BYTES, PREIMAGE_SIZE, + errors::{EpochTransitionErrorKind, IvcCircuitError}, + io::Write as IvcWrite, + tests::common::{ + asset_readers::{ + load_embedded_following_certificate_in_epoch_asset, + load_embedded_genesis_benchmark_fixture, + load_embedded_next_epoch_step_output_asset, + load_embedded_recursive_chain_state_asset, + load_embedded_verification_context_asset, }, + generators::setup::{QUORUM_SIZE, SIGNER_COUNT, TOTAL_STAKE}, + }, + types::{ + CertificateCircuitVerificationKeyRepresentation, EpochNumber, + IvcCircuitVerificationKeyRepresentation, StepCounter, }, - signature_scheme::{SchnorrSignatureError, StandardSchnorrSignature}, + }, + proof_system::ivc_halo2_snark::prover_setup::IvcProverInputVerificationContext, + signature_scheme::SchnorrSignatureError, + }; + + use super::*; + + /// Builds the chain's global public inputs and the verification-side context `prepare` reads, + /// both from committed assets: no SRS, no key generation and no cache. + fn build_preparation_context() -> (Global, IvcProverInputVerificationContext) { + let asset = load_embedded_verification_context_asset() + .expect("verification context asset should load"); + let genesis_fixture = load_embedded_genesis_benchmark_fixture() + .expect("genesis benchmark fixture should load"); + + let global = Global::new( + genesis_fixture.genesis_message_hash(), + genesis_fixture.genesis_verification_key, + &asset.certificate_verifying_key, + &asset.recursive_verifying_key, + ); + let verification_context = IvcProverInputVerificationContext::from_verifying_keys( + asset.verifier_params, + &asset.certificate_verifying_key, + &asset.recursive_verifying_key, + ); + + (global, verification_context) + } + + fn wrap_snark_proof(certificate_proof_bytes: Vec) -> SnarkProof { + let parameters = Parameters { + k: QUORUM_SIZE as u64, + m: (QUORUM_SIZE * 10) as u64, + phi_f: 0.2, }; + let merkle_tree_depth = SIGNER_COUNT.next_power_of_two().trailing_zeros(); + SnarkProof::new(certificate_proof_bytes, parameters, merkle_tree_depth) + } - use super::*; - - fn shared_ivc_setup() -> &'static IvcSnarkProverSetup { - static CELL: OnceLock = OnceLock::new(); - CELL.get_or_init(|| { - let parameters = Parameters { - k: QUORUM_SIZE as u64, - m: (QUORUM_SIZE * 10) as u64, - phi_f: 0.2, - }; - let merkle_tree_depth = SIGNER_COUNT.next_power_of_two().trailing_zeros(); - IvcSnarkProverSetup::build_for_test(¶meters, merkle_tree_depth) - .expect("IvcSnarkProverSetup::load should succeed under the unsafe SRS") - }) - } - - fn shared_asset_setup() -> &'static AssetGenerationSetup { - static CELL: OnceLock = OnceLock::new(); - CELL.get_or_init(build_asset_generation_setup_from_cache) - } - - fn shared_verification_context() -> &'static VerificationContextAsset { - static CELL: OnceLock = OnceLock::new(); - CELL.get_or_init(|| { - load_embedded_verification_context_asset() - .expect("verification context asset should load") - }) - } - - fn build_global() -> Global { - let asset_setup = shared_asset_setup(); - let ctx = shared_verification_context(); - build_recursive_global( - asset_setup, - &ctx.certificate_verifying_key, - &ctx.recursive_verifying_key, - ) - } - - fn wrap_snark_proof( - certificate_proof_bytes: Vec, - ) -> SnarkProof { - let parameters = Parameters { - k: QUORUM_SIZE as u64, - m: (QUORUM_SIZE * 10) as u64, - phi_f: 0.2, - }; - let merkle_tree_depth = SIGNER_COUNT.next_power_of_two().trailing_zeros(); - SnarkProof::new(certificate_proof_bytes, parameters, merkle_tree_depth) - } - - fn wrap_avk( - aggregate_verification_key_merkle_root: &[u8; 32], - ) -> AggregateVerificationKeyForSnark { - let mut avk_bytes = [0u8; 40]; - avk_bytes[0..32].copy_from_slice(aggregate_verification_key_merkle_root); - avk_bytes[32..40].copy_from_slice(&TOTAL_STAKE.to_be_bytes()); - AggregateVerificationKeyForSnark::::from_bytes(&avk_bytes) - .expect("AVK should decode from asset bytes") - } - - fn wrap_protocol_message_preimage(preimage: &[u8]) -> ProtocolMessagePreimage { - use crate::circuits::halo2_ivc::PREIMAGE_SIZE; - let preimage_array: [u8; PREIMAGE_SIZE] = preimage - .try_into() - .expect("preimage should be exactly PREIMAGE_SIZE bytes"); - ProtocolMessagePreimage::new(preimage_array) - } - - fn accumulator_bytes(accumulator: &Accumulator) -> Vec { - let mut bytes = Vec::new(); - accumulator - .write(&mut bytes, SerdeFormat::RawBytesUnchecked) - .expect("accumulator serialization should succeed"); - bytes - } - - fn build_rolling_state( - state: State, - ivc_proof: crate::circuits::halo2_ivc::types::IvcProofBytes, - accumulator: midnight_circuits::verifier::Accumulator, - genesis_signature: StandardSchnorrSignature, - ) -> IvcRollingState { - IvcRollingState::new(state, ivc_proof, accumulator, genesis_signature) - } - - #[test] - fn prepare_genesis_rejects_invalid_signature() { - let mut chain_rng = ChaCha20Rng::from_seed([0u8; 32]); - let chain_signing_key = SchnorrSigningKey::generate(&mut chain_rng); - let chain_verification_key = - SchnorrVerificationKey::new_from_signing_key(chain_signing_key); - - let global = Global { - genesis_message: MessageHash::ZERO, - genesis_verification_key: chain_verification_key, - certificate_circuit_verification_key_representation: - CertificateCircuitVerificationKeyRepresentation::from_field( - BaseFieldElement::from(0u64).0, - ), - ivc_circuit_verification_key_representation: - IvcCircuitVerificationKeyRepresentation::from_field( - BaseFieldElement::from(0u64).0, - ), - }; - - let mut wrong_rng = ChaCha20Rng::from_seed([1u8; 32]); - let wrong_signing_key = SchnorrSigningKey::generate(&mut wrong_rng); - let invalid_signature = wrong_signing_key - .sign_standard( - &[BaseFieldElement::from(global.genesis_message.as_field())], - &mut wrong_rng, - ) - .expect("sign_standard should succeed for a synthetic message"); - let rolling_state = IvcRollingState::genesis(invalid_signature, &[]); - let protocol_message_preimage = ProtocolMessagePreimage::new([0u8; PREIMAGE_SIZE]); - - let err = IvcProverInput::prepare_genesis( - &rolling_state, - &protocol_message_preimage, - &global, - ) - .expect_err("prepare_genesis should reject a signature that does not verify"); - let schnorr_error = err - .downcast::() - .expect("error chain should carry SchnorrSignatureError"); - assert!(matches!( - schnorr_error, - SchnorrSignatureError::StandardSignatureInvalid(_) - )); - } - - #[test] - fn prepare_genesis_produces_expected_state_and_witness() { - let mut rng = ChaCha20Rng::from_seed([0u8; 32]); - let signing_key = SchnorrSigningKey::generate(&mut rng); - let verification_key = - SchnorrVerificationKey::new_from_signing_key(signing_key.clone()); - - let genesis_message = MessageHash::from_field( - BaseFieldElement::from_raw(&[0x42; 32]) - .expect("from_raw applies modulus reduction") - .0, - ); - let global = Global { - genesis_message, - genesis_verification_key: verification_key, - certificate_circuit_verification_key_representation: - CertificateCircuitVerificationKeyRepresentation::from_field( - BaseFieldElement::from(0u64).0, - ), - ivc_circuit_verification_key_representation: - IvcCircuitVerificationKeyRepresentation::from_field( - BaseFieldElement::from(0u64).0, - ), - }; - - let genesis_signature = signing_key - .sign_standard( - &[BaseFieldElement::from(global.genesis_message.as_field())], - &mut rng, - ) - .expect("sign_standard should succeed for the genesis message"); - let rolling_state = IvcRollingState::genesis(genesis_signature, &[]); - - let cert_epoch = EpochNumber::ZERO; - let mut preimage_bytes = [0u8; PREIMAGE_SIZE]; - preimage_bytes[PREIMAGE_CURRENT_EPOCH_BYTES] - .copy_from_slice(&cert_epoch.as_u64().to_le_bytes()); - preimage_bytes[PREIMAGE_NEXT_MERKLE_TREE_COMMITMENT_BYTES].copy_from_slice(&[0x11; 32]); - preimage_bytes[PREIMAGE_NEXT_PROTOCOL_PARAMETERS_BYTES].copy_from_slice(&[0x22; 32]); - let protocol_message_preimage = ProtocolMessagePreimage::new(preimage_bytes); - - let input = IvcProverInput::prepare_genesis( - &rolling_state, - &protocol_message_preimage, - &global, - ) - .expect("prepare_genesis should succeed for a valid genesis signature"); - - let expected_next_state = State::new( - StepCounter::new(1), - global.genesis_message, - MerkleTreeCommitment::ZERO, - protocol_message_preimage.next_merkle_tree_commitment(), - ProtocolParametersHash::ZERO, - protocol_message_preimage.next_protocol_parameters(), - protocol_message_preimage.current_epoch(), - ); - assert_eq!(input.next_state, expected_next_state); - - let expected_witness = Witness::new( - genesis_signature, - MessageHash::ZERO, - MerkleTreeCommitment::ZERO, - protocol_message_preimage.clone(), - ); - assert_eq!(input.witness, expected_witness); - } - - #[test] - #[ignore = "slow: runs real keygen via shared OnceLock; opt-in only"] - fn prepare_at_genesis_produces_advanced_state_and_witness() { - let setup = shared_ivc_setup(); - let asset_setup = shared_asset_setup(); - let first_step = load_embedded_first_certificate_in_epoch_asset() - .expect("first step cert asset should load"); - - let global = build_global(); - let combined_names: Vec = setup.combined_fixed_bases.keys().cloned().collect(); - let rolling_state = - IvcRollingState::genesis(asset_setup.genesis_signature, &combined_names); - - let snark_proof = wrap_snark_proof(first_step.certificate_proof.clone().into_vec()); - let avk = wrap_avk(&first_step.aggregate_verification_key_merkle_root); - let genesis_preimage_bytes = build_genesis_protocol_message_preimage(asset_setup); - let protocol_message_preimage = wrap_protocol_message_preimage(&genesis_preimage_bytes); - - let input = IvcProverInput::prepare( - &snark_proof, - &first_step.message, - &avk, - &global, - &protocol_message_preimage, - &rolling_state, - setup, - ) - .expect("prepare should succeed at genesis"); - - let expected_next_state = - build_genesis_base_case_next_state(asset_setup, GENESIS_EPOCH); - assert_eq!(input.next_state, expected_next_state); - - let expected_witness = build_genesis_base_case_witness(asset_setup); - assert_eq!(input.witness, expected_witness); - - assert_eq!( - accumulator_bytes(&input.next_accumulator), - accumulator_bytes(rolling_state.accumulator()), - ); - } - - #[test] - #[ignore = "slow: runs real keygen via shared OnceLock; opt-in only"] - fn prepare_at_same_epoch_advances_state_correctly() { - let setup = shared_ivc_setup(); - let chain_state = load_embedded_recursive_chain_state_asset() - .expect("recursive chain state asset should load"); - let step = load_embedded_following_certificate_in_epoch_asset() - .expect("same-epoch step output asset should load"); - - let global = build_global(); - let snark_proof = wrap_snark_proof(step.certificate_proof.clone().into_vec()); - let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); - let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); - let chain_genesis_signature = chain_state.genesis_signature; - let rolling_state = build_rolling_state( - chain_state.state, - chain_state.ivc_proof, - chain_state.accumulator, - chain_state.genesis_signature, - ); - - let input = IvcProverInput::prepare( - &snark_proof, - &step.message, - &avk, - &global, - &protocol_message_preimage, - &rolling_state, - setup, - ) - .expect("prepare should succeed at same-epoch step"); - - assert_eq!(input.next_state, step.next_state); - assert_eq!( - accumulator_bytes(&input.next_accumulator), - accumulator_bytes(&step.next_accumulator), - ); - assert_eq!(input.witness.genesis_signature, chain_genesis_signature); - assert_eq!(input.witness.message_preimage, protocol_message_preimage); - assert_eq!( - input.witness.certificate_merkle_tree_commitment, - step.next_state.merkle_tree_commitment, - ); - assert_eq!(input.witness.certificate_message, step.next_state.message); - } - - #[test] - #[ignore = "slow: runs real keygen via shared OnceLock; opt-in only"] - fn prepare_at_next_epoch_carries_lookahead_protocol_parameters() { - let setup = shared_ivc_setup(); - let chain_state = load_embedded_recursive_chain_state_asset() - .expect("recursive chain state asset should load"); - let step = load_embedded_next_epoch_step_output_asset() - .expect("recursive step output asset should load"); - - let global = build_global(); - let snark_proof = wrap_snark_proof(step.certificate_proof.clone().into_vec()); - let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); - let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); - let chain_genesis_signature = chain_state.genesis_signature; - let rolling_state = build_rolling_state( - chain_state.state, - chain_state.ivc_proof, - chain_state.accumulator, - chain_state.genesis_signature, - ); - - let input = IvcProverInput::prepare( - &snark_proof, - &step.message, - &avk, - &global, - &protocol_message_preimage, - &rolling_state, - setup, + fn wrap_avk( + aggregate_verification_key_merkle_root: &[u8; 32], + ) -> AggregateVerificationKeyForSnark { + let mut avk_bytes = [0u8; 40]; + avk_bytes[0..32].copy_from_slice(aggregate_verification_key_merkle_root); + avk_bytes[32..40].copy_from_slice(&TOTAL_STAKE.to_be_bytes()); + AggregateVerificationKeyForSnark::::from_bytes(&avk_bytes) + .expect("AVK should decode from asset bytes") + } + + fn wrap_protocol_message_preimage(preimage: &[u8]) -> ProtocolMessagePreimage { + let preimage_array: [u8; PREIMAGE_SIZE] = preimage + .try_into() + .expect("preimage should be exactly PREIMAGE_SIZE bytes"); + ProtocolMessagePreimage::new(preimage_array) + } + + fn accumulator_bytes(accumulator: &Accumulator) -> Vec { + let mut bytes = Vec::new(); + accumulator + .write(&mut bytes, SerdeFormat::RawBytesUnchecked) + .expect("accumulator serialization should succeed"); + bytes + } + + #[test] + fn prepare_genesis_rejects_invalid_signature() { + let mut chain_rng = ChaCha20Rng::from_seed([0u8; 32]); + let chain_signing_key = SchnorrSigningKey::generate(&mut chain_rng); + let chain_verification_key = + SchnorrVerificationKey::new_from_signing_key(chain_signing_key); + + let global = Global { + genesis_message: MessageHash::ZERO, + genesis_verification_key: chain_verification_key, + certificate_circuit_verification_key_representation: + CertificateCircuitVerificationKeyRepresentation::from_field( + BaseFieldElement::from(0u64).0, + ), + ivc_circuit_verification_key_representation: + IvcCircuitVerificationKeyRepresentation::from_field(BaseFieldElement::from(0u64).0), + }; + + let mut wrong_rng = ChaCha20Rng::from_seed([1u8; 32]); + let wrong_signing_key = SchnorrSigningKey::generate(&mut wrong_rng); + let invalid_signature = wrong_signing_key + .sign_standard( + &[BaseFieldElement::from(global.genesis_message.as_field())], + &mut wrong_rng, ) - .expect("prepare should succeed at next-epoch step"); - - assert_eq!(input.next_state, step.next_state); - assert_eq!( - accumulator_bytes(&input.next_accumulator), - accumulator_bytes(&step.next_accumulator), - ); - assert_eq!(input.witness.genesis_signature, chain_genesis_signature); - assert_eq!(input.witness.message_preimage, protocol_message_preimage); - assert_eq!( - input.witness.certificate_merkle_tree_commitment, - step.next_state.merkle_tree_commitment, - ); - assert_eq!(input.witness.certificate_message, step.next_state.message); - } - - #[test] - #[ignore = "slow: runs real keygen via shared OnceLock; opt-in only"] - fn prepare_rejects_invalid_snark_proof() { - let setup = shared_ivc_setup(); - let chain_state = load_embedded_recursive_chain_state_asset() - .expect("recursive chain state asset should load"); - let step = load_embedded_following_certificate_in_epoch_asset() - .expect("same-epoch step output asset should load"); - - let global = build_global(); - let mut corrupted = step.certificate_proof.clone().into_vec(); - corrupted[0] ^= 0xFF; - - let snark_proof = wrap_snark_proof(corrupted); - let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); - let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); - let rolling_state = build_rolling_state( - chain_state.state, - chain_state.ivc_proof, - chain_state.accumulator, - chain_state.genesis_signature, - ); - - let result = IvcProverInput::prepare( - &snark_proof, - &step.message, - &avk, - &global, - &protocol_message_preimage, - &rolling_state, - setup, - ); - - let err = result - .expect_err("prepare should reject a corrupted certificate proof") - .downcast::() - .expect("error should downcast to IvcCircuitError"); - assert!(matches!(err, IvcCircuitError::CertificateProofRejected(..))); - } - - #[test] - #[ignore = "slow: runs real keygen via shared OnceLock; opt-in only"] - fn prepare_rejects_invalid_genesis_signature() { - let setup = shared_ivc_setup(); - let asset_setup = shared_asset_setup(); - let first_step = load_embedded_first_certificate_in_epoch_asset() - .expect("first step cert asset should load"); - - let global = build_global(); - let mut sig_bytes = asset_setup.genesis_signature.to_bytes(); - sig_bytes[32] ^= 0x01; - let bad_signature = StandardSchnorrSignature::from_bytes(&sig_bytes) - .expect("mutated signature should still deserialize"); - - let combined_names: Vec = setup.combined_fixed_bases.keys().cloned().collect(); - let rolling_state = IvcRollingState::genesis(bad_signature, &combined_names); - - let snark_proof = wrap_snark_proof(first_step.certificate_proof.clone().into_vec()); - let avk = wrap_avk(&first_step.aggregate_verification_key_merkle_root); - let genesis_preimage_bytes = build_genesis_protocol_message_preimage(asset_setup); - let protocol_message_preimage = wrap_protocol_message_preimage(&genesis_preimage_bytes); - - let result = IvcProverInput::prepare( - &snark_proof, - &first_step.message, - &avk, - &global, - &protocol_message_preimage, - &rolling_state, - setup, - ); - - let err = result - .expect_err("prepare should reject an invalid genesis signature") - .downcast::() - .expect("error should downcast to SchnorrSignatureError"); - assert!( - matches!(err, SchnorrSignatureError::StandardSignatureInvalid(_)), - "expected StandardSignatureInvalid, got {err:?}" - ); - } - - #[test] - #[ignore = "slow: runs real keygen via shared OnceLock; opt-in only"] - fn prepare_rejects_invalid_epoch_transition() { - let setup = shared_ivc_setup(); - let chain_state = load_embedded_recursive_chain_state_asset() - .expect("recursive chain state asset should load"); - let step = load_embedded_following_certificate_in_epoch_asset() - .expect("same-epoch step output asset should load"); - - let modified_state = State::new( - chain_state.state.step_counter, - chain_state.state.message, - chain_state.state.merkle_tree_commitment, - chain_state.state.next_merkle_tree_commitment, - chain_state.state.protocol_parameters, - chain_state.state.next_protocol_parameters, - EpochNumber::new(u64::MAX - 100), - ); - let rolling_state = build_rolling_state( - modified_state, - chain_state.ivc_proof, - chain_state.accumulator, - chain_state.genesis_signature, - ); - - let global = build_global(); - let snark_proof = wrap_snark_proof(step.certificate_proof.clone().into_vec()); - let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); - let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); - - let result = IvcProverInput::prepare( - &snark_proof, - &step.message, - &avk, - &global, - &protocol_message_preimage, - &rolling_state, - setup, - ); - - let err = result - .expect_err("prepare should reject a bad epoch transition") - .downcast::() - .expect("error should downcast to IvcCircuitError"); - assert!( - matches!( - err, - IvcCircuitError::InvalidEpochTransition { - kind: EpochTransitionErrorKind::EpochGap { .. }, - .. - } + .expect("sign_standard should succeed for a synthetic message"); + let rolling_state = IvcRollingState::genesis(invalid_signature, &[]); + let protocol_message_preimage = ProtocolMessagePreimage::new([0u8; PREIMAGE_SIZE]); + + let err = + IvcProverInput::prepare_genesis(&rolling_state, &protocol_message_preimage, &global) + .expect_err("prepare_genesis should reject a signature that does not verify"); + let schnorr_error = err + .downcast::() + .expect("error chain should carry SchnorrSignatureError"); + assert!(matches!( + schnorr_error, + SchnorrSignatureError::StandardSignatureInvalid(_) + )); + } + + #[test] + fn prepare_genesis_produces_expected_state_and_witness() { + let mut rng = ChaCha20Rng::from_seed([0u8; 32]); + let signing_key = SchnorrSigningKey::generate(&mut rng); + let verification_key = SchnorrVerificationKey::new_from_signing_key(signing_key.clone()); + + let genesis_message = MessageHash::from_field( + BaseFieldElement::from_raw(&[0x42; 32]) + .expect("from_raw applies modulus reduction") + .0, + ); + let global = Global { + genesis_message, + genesis_verification_key: verification_key, + certificate_circuit_verification_key_representation: + CertificateCircuitVerificationKeyRepresentation::from_field( + BaseFieldElement::from(0u64).0, ), - "expected InvalidEpochTransition with OutOfRange kind, got {err:?}" - ); - } - - #[test] - #[ignore = "slow: runs real keygen via shared OnceLock; opt-in only"] - fn prepare_rejects_step_counter_overflow() { - let setup = shared_ivc_setup(); - let chain_state = load_embedded_recursive_chain_state_asset() - .expect("recursive chain state asset should load"); - let step = load_embedded_following_certificate_in_epoch_asset() - .expect("same-epoch step output asset should load"); - - let modified_state = State::new( - StepCounter::new(u64::MAX), - chain_state.state.message, - chain_state.state.merkle_tree_commitment, - chain_state.state.next_merkle_tree_commitment, - chain_state.state.protocol_parameters, - chain_state.state.next_protocol_parameters, - chain_state.state.current_epoch, - ); - let rolling_state = build_rolling_state( - modified_state, - chain_state.ivc_proof, - chain_state.accumulator, - chain_state.genesis_signature, - ); - - let global = build_global(); - let snark_proof = wrap_snark_proof(step.certificate_proof.clone().into_vec()); - let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); - let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); - - let result = IvcProverInput::prepare( - &snark_proof, - &step.message, - &avk, - &global, - &protocol_message_preimage, - &rolling_state, - setup, - ); - - let err = result - .expect_err("prepare should reject step counter overflow") - .downcast::() - .expect("error should downcast to IvcCircuitError"); - assert!( - matches!(err, IvcCircuitError::StepCounterOverflow { .. }), - "expected StepCounterOverflow, got {err:?}" - ); - } + ivc_circuit_verification_key_representation: + IvcCircuitVerificationKeyRepresentation::from_field(BaseFieldElement::from(0u64).0), + }; + + let genesis_signature = signing_key + .sign_standard( + &[BaseFieldElement::from(global.genesis_message.as_field())], + &mut rng, + ) + .expect("sign_standard should succeed for the genesis message"); + // Keep the complete fixed-base label set so replacing it with an empty trivial + // accumulator is observable. + let combined_fixed_base_names: Vec = load_embedded_verification_context_asset() + .expect("verification context asset should load") + .combined_fixed_bases + .keys() + .cloned() + .collect(); + let rolling_state = IvcRollingState::genesis(genesis_signature, &combined_fixed_base_names); + assert!( + !rolling_state.accumulator().rhs().fixed_base_scalars().is_empty(), + "the pass-through fixture must carry fixed-base labels" + ); + + let cert_epoch = EpochNumber::ZERO; + let mut preimage_bytes = [0u8; PREIMAGE_SIZE]; + preimage_bytes[PREIMAGE_CURRENT_EPOCH_BYTES] + .copy_from_slice(&cert_epoch.as_u64().to_le_bytes()); + preimage_bytes[PREIMAGE_NEXT_MERKLE_TREE_COMMITMENT_BYTES].copy_from_slice(&[0x11; 32]); + preimage_bytes[PREIMAGE_NEXT_PROTOCOL_PARAMETERS_BYTES].copy_from_slice(&[0x22; 32]); + let protocol_message_preimage = ProtocolMessagePreimage::new(preimage_bytes); + + let input = + IvcProverInput::prepare_genesis(&rolling_state, &protocol_message_preimage, &global) + .expect("prepare_genesis should succeed for a valid genesis signature"); + + let expected_next_state = State::new( + StepCounter::new(1), + global.genesis_message, + MerkleTreeCommitment::ZERO, + protocol_message_preimage.next_merkle_tree_commitment(), + ProtocolParametersHash::ZERO, + protocol_message_preimage.next_protocol_parameters(), + protocol_message_preimage.current_epoch(), + ); + assert_eq!(input.next_state, expected_next_state); + + let expected_witness = Witness::new( + genesis_signature, + MessageHash::ZERO, + MerkleTreeCommitment::ZERO, + protocol_message_preimage.clone(), + ); + assert_eq!(input.witness, expected_witness); + + assert_eq!(input.transition_type, IvcTransitionType::Genesis); + assert_eq!( + accumulator_bytes(&input.next_accumulator), + accumulator_bytes(rolling_state.accumulator()), + "the genesis step must pass the trivial accumulator through unchanged" + ); + } + + #[test] + fn prepare_at_same_epoch_advances_state_correctly() { + let chain_state = load_embedded_recursive_chain_state_asset() + .expect("recursive chain state asset should load"); + let step = load_embedded_following_certificate_in_epoch_asset() + .expect("same-epoch step output asset should load"); + + let (global, verification_context) = build_preparation_context(); + let snark_proof = wrap_snark_proof(step.certificate_proof.clone().into_vec()); + let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); + let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); + let chain_genesis_signature = chain_state.genesis_signature; + let rolling_state = IvcRollingState::new( + chain_state.state, + chain_state.ivc_proof, + chain_state.accumulator, + chain_state.genesis_signature, + ); + + let input = IvcProverInput::prepare( + &snark_proof, + &step.message, + &avk, + &global, + &protocol_message_preimage, + &rolling_state, + &verification_context, + ) + .expect("prepare should succeed at same-epoch step"); + + assert_eq!(input.next_state, step.next_state); + assert_eq!( + accumulator_bytes(&input.next_accumulator), + accumulator_bytes(&step.next_accumulator), + ); + assert_eq!(input.witness.genesis_signature, chain_genesis_signature); + assert_eq!(input.witness.message_preimage, protocol_message_preimage); + assert_eq!( + input.witness.certificate_merkle_tree_commitment, + step.next_state.merkle_tree_commitment, + ); + assert_eq!(input.witness.certificate_message, step.next_state.message); + } + + #[test] + fn prepare_at_next_epoch_carries_lookahead_protocol_parameters() { + let chain_state = load_embedded_recursive_chain_state_asset() + .expect("recursive chain state asset should load"); + let step = load_embedded_next_epoch_step_output_asset() + .expect("recursive step output asset should load"); + + let (global, verification_context) = build_preparation_context(); + let snark_proof = wrap_snark_proof(step.certificate_proof.clone().into_vec()); + let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); + let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); + let chain_genesis_signature = chain_state.genesis_signature; + let rolling_state = IvcRollingState::new( + chain_state.state, + chain_state.ivc_proof, + chain_state.accumulator, + chain_state.genesis_signature, + ); + + let input = IvcProverInput::prepare( + &snark_proof, + &step.message, + &avk, + &global, + &protocol_message_preimage, + &rolling_state, + &verification_context, + ) + .expect("prepare should succeed at next-epoch step"); + + assert_eq!(input.next_state, step.next_state); + assert_eq!( + accumulator_bytes(&input.next_accumulator), + accumulator_bytes(&step.next_accumulator), + ); + assert_eq!(input.witness.genesis_signature, chain_genesis_signature); + assert_eq!(input.witness.message_preimage, protocol_message_preimage); + assert_eq!( + input.witness.certificate_merkle_tree_commitment, + step.next_state.merkle_tree_commitment, + ); + assert_eq!(input.witness.certificate_message, step.next_state.message); + } + + #[test] + fn prepare_rejects_invalid_snark_proof() { + let chain_state = load_embedded_recursive_chain_state_asset() + .expect("recursive chain state asset should load"); + let step = load_embedded_following_certificate_in_epoch_asset() + .expect("same-epoch step output asset should load"); + + let (global, verification_context) = build_preparation_context(); + let mut corrupted = step.certificate_proof.clone().into_vec(); + corrupted[0] ^= 0xFF; + + let snark_proof = wrap_snark_proof(corrupted); + let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); + let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); + let rolling_state = IvcRollingState::new( + chain_state.state, + chain_state.ivc_proof, + chain_state.accumulator, + chain_state.genesis_signature, + ); + + let result = IvcProverInput::prepare( + &snark_proof, + &step.message, + &avk, + &global, + &protocol_message_preimage, + &rolling_state, + &verification_context, + ); + + let err = result + .expect_err("prepare should reject a corrupted certificate proof") + .downcast::() + .expect("error should downcast to IvcCircuitError"); + assert!(matches!(err, IvcCircuitError::CertificateProofRejected(..))); + } + + #[test] + fn prepare_rejects_invalid_epoch_transition() { + let chain_state = load_embedded_recursive_chain_state_asset() + .expect("recursive chain state asset should load"); + let step = load_embedded_following_certificate_in_epoch_asset() + .expect("same-epoch step output asset should load"); + + let modified_state = State::new( + chain_state.state.step_counter, + chain_state.state.message, + chain_state.state.merkle_tree_commitment, + chain_state.state.next_merkle_tree_commitment, + chain_state.state.protocol_parameters, + chain_state.state.next_protocol_parameters, + EpochNumber::new(u64::MAX - 100), + ); + let rolling_state = IvcRollingState::new( + modified_state, + chain_state.ivc_proof, + chain_state.accumulator, + chain_state.genesis_signature, + ); + + let (global, verification_context) = build_preparation_context(); + let snark_proof = wrap_snark_proof(step.certificate_proof.clone().into_vec()); + let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); + let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); + + let result = IvcProverInput::prepare( + &snark_proof, + &step.message, + &avk, + &global, + &protocol_message_preimage, + &rolling_state, + &verification_context, + ); + + let err = result + .expect_err("prepare should reject a bad epoch transition") + .downcast::() + .expect("error should downcast to IvcCircuitError"); + assert!( + matches!( + err, + IvcCircuitError::InvalidEpochTransition { + kind: EpochTransitionErrorKind::EpochGap { .. }, + .. + } + ), + "expected InvalidEpochTransition with EpochGap kind, got {err:?}" + ); + } + + #[test] + fn prepare_rejects_step_counter_overflow() { + let chain_state = load_embedded_recursive_chain_state_asset() + .expect("recursive chain state asset should load"); + let step = load_embedded_following_certificate_in_epoch_asset() + .expect("same-epoch step output asset should load"); + + let modified_state = State::new( + StepCounter::new(u64::MAX), + chain_state.state.message, + chain_state.state.merkle_tree_commitment, + chain_state.state.next_merkle_tree_commitment, + chain_state.state.protocol_parameters, + chain_state.state.next_protocol_parameters, + chain_state.state.current_epoch, + ); + let rolling_state = IvcRollingState::new( + modified_state, + chain_state.ivc_proof, + chain_state.accumulator, + chain_state.genesis_signature, + ); + + let (global, verification_context) = build_preparation_context(); + let snark_proof = wrap_snark_proof(step.certificate_proof.clone().into_vec()); + let avk = wrap_avk(&step.aggregate_verification_key_merkle_root); + let protocol_message_preimage = wrap_protocol_message_preimage(&step.message_preimage); + + let result = IvcProverInput::prepare( + &snark_proof, + &step.message, + &avk, + &global, + &protocol_message_preimage, + &rolling_state, + &verification_context, + ); + + let err = result + .expect_err("prepare should reject step counter overflow") + .downcast::() + .expect("error should downcast to IvcCircuitError"); + assert!( + matches!(err, IvcCircuitError::StepCounterOverflow { .. }), + "expected StepCounterOverflow, got {err:?}" + ); } } diff --git a/mithril-stm/src/proof_system/ivc_halo2_snark/prover_input_helpers.rs b/mithril-stm/src/proof_system/ivc_halo2_snark/prover_input_helpers.rs index ecda049aeec..6c6a5dbec65 100644 --- a/mithril-stm/src/proof_system/ivc_halo2_snark/prover_input_helpers.rs +++ b/mithril-stm/src/proof_system/ivc_halo2_snark/prover_input_helpers.rs @@ -11,7 +11,9 @@ use crate::{ }, proof_system::{ halo2_snark::build_snark_message, - ivc_halo2_snark::{prover_setup::IvcSnarkProverSetup, rolling_state::IvcRollingState}, + ivc_halo2_snark::{ + prover_setup::IvcProverInputVerificationContext, rolling_state::IvcRollingState, + }, }, }; @@ -69,20 +71,20 @@ impl IvcTransitionType { /// Runs the off-circuit verifier on the certificate proof and returns the prepared `DualMSM`. /// -/// The certificate verifying key is taken from `setup.certificate_verifying_key` — the single -/// source shared with the in-circuit IVC verifier gadget — so the off-circuit accumulator built by -/// `prepare_and_check` agrees with the one the gadget produces on the same proof. +/// The certificate verifying key comes from the verification context — the single source shared with the +/// in-circuit IVC verifier gadget — so the off-circuit accumulator built by `prepare_and_check` agrees with +/// the one the gadget produces on the same proof. pub(crate) fn verify_certificate_proof( certificate_proof: &SnarkProof, certificate_message_bytes: &[u8], aggregate_verification_key_for_snark: &AggregateVerificationKeyForSnark, - setup: &IvcSnarkProverSetup, + verification_context: &IvcProverInputVerificationContext, ) -> StmResult> { certificate_proof.prepare_and_check( certificate_message_bytes, aggregate_verification_key_for_snark, - &setup.certificate_verifying_key, - &setup.srs.verifier_params(), + verification_context.certificate_verifying_key(), + verification_context.verifier_params(), ) } @@ -140,15 +142,16 @@ pub(crate) fn build_next_state( pub(crate) fn build_next_accumulator( certificate_dual_msm: DualMSM, rolling_state: &IvcRollingState, - setup: &IvcSnarkProverSetup, + verification_context: &IvcProverInputVerificationContext, global: &Global, ) -> StmResult> { let certificate_collapsed_accumulator = - setup.certificate_collapsed_accumulator(certificate_dual_msm)?; - let previous_ivc_proof_collapsed_accumulator = setup.previous_ivc_proof_collapsed_accumulator( - rolling_state.ivc_proof().as_bytes(), - &rolling_state.previous_ivc_proof_public_inputs(global), - )?; + verification_context.certificate_collapsed_accumulator(certificate_dual_msm)?; + let previous_ivc_proof_collapsed_accumulator = verification_context + .previous_ivc_proof_collapsed_accumulator( + rolling_state.ivc_proof().as_bytes(), + &rolling_state.previous_ivc_proof_public_inputs(global), + )?; let mut next_accumulator = Accumulator::accumulate(&[ rolling_state.accumulator().clone(), certificate_collapsed_accumulator, diff --git a/mithril-stm/src/proof_system/ivc_halo2_snark/prover_setup.rs b/mithril-stm/src/proof_system/ivc_halo2_snark/prover_setup.rs index 4c702e5ad6c..888fe1b023c 100644 --- a/mithril-stm/src/proof_system/ivc_halo2_snark/prover_setup.rs +++ b/mithril-stm/src/proof_system/ivc_halo2_snark/prover_setup.rs @@ -1,10 +1,14 @@ -//! Load-once, deployment-constant artifacts shared by every step of an IVC proving session. +//! Load-once, deployment-constant artifacts shared by every step of an IVC proving session, and the +//! verification-side view of them that one step's input preparation reads. use std::collections::BTreeMap; use midnight_circuits::verifier::{Accumulator, BlstrsEmulation}; use midnight_curves::{Bls12, G1Projective}; -use midnight_proofs::poly::kzg::{msm::DualMSM, params::ParamsKZG}; +use midnight_proofs::poly::kzg::{ + msm::DualMSM, + params::{ParamsKZG, ParamsVerifierKZG}, +}; #[cfg(test)] use crate::{ @@ -111,50 +115,18 @@ impl IvcSnarkProverSetup { }) } - /// Wrap the certificate proof's prepared `DualMSM` into a collapsed accumulator on - /// the certificate circuit's fixed bases. - pub(crate) fn certificate_collapsed_accumulator( - &self, - dual_msm: DualMSM, - ) -> StmResult> { - check_dual_msm_matches_fixed_bases( - &dual_msm, - CERTIFICATE_FIXED_BASES_PREFIX, - &self.certificate_fixed_bases, - )?; - let mut accumulator: Accumulator = Accumulator::from_dual_msm( - dual_msm, - CERTIFICATE_FIXED_BASES_PREFIX, - &self.certificate_fixed_bases, - ); - accumulator.collapse(); - Ok(accumulator) - } - - /// Off-circuit verify of the previous step's IVC proof, returning the collapsed - /// accumulator the in-circuit IVC verifier gadget would have produced on the same - /// proof. Used at every non-genesis step. - pub(crate) fn previous_ivc_proof_collapsed_accumulator( - &self, - ivc_proof_bytes: &[u8], - public_inputs: &[CircuitBase], - ) -> StmResult> { - let verifier_params = self.srs.verifier_params(); - let dual_msm = verify_and_prepare_accumulator( - ivc_proof_bytes, - public_inputs, - self.ivc_verifying_key.as_ref(), - &verifier_params, - )?; - check_dual_msm_matches_fixed_bases( - &dual_msm, - IVC_FIXED_BASES_PREFIX, - &self.ivc_fixed_bases, - )?; - let mut accumulator: Accumulator = - Accumulator::from_dual_msm(dual_msm, IVC_FIXED_BASES_PREFIX, &self.ivc_fixed_bases); - accumulator.collapse(); - Ok(accumulator) + /// Returns the verification-side artifacts an IVC prover input preparation reads. + /// + /// The verifier parameters come from this setup's own SRS, so a test or benchmark running on the + /// deterministic unsafe SRS gets the parameters matching it rather than the production ones. + pub(crate) fn prover_input_verification_context(&self) -> IvcProverInputVerificationContext { + IvcProverInputVerificationContext { + verifier_params: self.srs.verifier_params(), + certificate_verifying_key: self.certificate_verifying_key.clone(), + ivc_verifying_key: self.ivc_verifying_key.clone(), + certificate_fixed_bases: self.certificate_fixed_bases.clone(), + ivc_fixed_bases: self.ivc_fixed_bases.clone(), + } } /// Builds an [`IvcSnarkProverSetup`] from a deterministic unsafe SRS with degree `RECURSIVE_CIRCUIT_DEGREE` @@ -218,20 +190,162 @@ impl IvcSnarkProverSetup { } } +/// Verification-side artifacts read while preparing an IVC prover input. +/// +/// `IvcProverInput::prepare` verifies the incoming certificate proof and folds accumulators. That needs the +/// two verifying keys, their fixed bases and the KZG verifier parameters — never the IVC proving key, which +/// is only used to create a proof. Keeping the narrower set in its own type means a caller that only prepares +/// an input does not have to hold a proving key. +/// +/// # Invariants +/// +/// Each fixed-base map must match its corresponding verifying key. `from_verifying_keys` derives the maps +/// directly, while `prover_input_verification_context` inherits the invariant established by +/// [`IvcSnarkProverSetup::load`]. +pub(crate) struct IvcProverInputVerificationContext { + /// KZG verifier parameters of the SRS the proofs were produced under. + verifier_params: ParamsVerifierKZG, + /// Verifying key of the certificate circuit. + certificate_verifying_key: NonRecursiveCircuitVerifyingKey, + /// Verifying key of the IVC circuit. + ivc_verifying_key: RecursiveCircuitVerifyingKey, + /// Fixed-base map used to normalize the certificate accumulator. + certificate_fixed_bases: BTreeMap, + /// Fixed-base map used to normalize the IVC proof accumulator. + ivc_fixed_bases: BTreeMap, +} + +impl IvcProverInputVerificationContext { + /// Builds the context from the two verifying keys, deriving both fixed-base maps from them. + /// + /// Lets a test assemble the context from the committed verification-context asset, with no SRS and no + /// key generation. + #[cfg(test)] + pub(crate) fn from_verifying_keys( + verifier_params: ParamsVerifierKZG, + certificate_verifying_key: &NonRecursiveCircuitVerifyingKey, + ivc_verifying_key: &RecursiveCircuitVerifyingKey, + ) -> Self { + let (certificate_fixed_bases, _) = fixed_bases_and_names_from_verifying_key( + CERTIFICATE_FIXED_BASES_PREFIX, + certificate_verifying_key.as_ref(), + ); + let (ivc_fixed_bases, _) = fixed_bases_and_names_from_verifying_key( + IVC_FIXED_BASES_PREFIX, + ivc_verifying_key.as_ref(), + ); + Self { + verifier_params, + certificate_verifying_key: certificate_verifying_key.clone(), + ivc_verifying_key: ivc_verifying_key.clone(), + certificate_fixed_bases, + ivc_fixed_bases, + } + } + + /// Returns the verifying key of the certificate circuit. + pub(crate) fn certificate_verifying_key(&self) -> &NonRecursiveCircuitVerifyingKey { + &self.certificate_verifying_key + } + + /// Returns the KZG verifier parameters. + pub(crate) fn verifier_params(&self) -> &ParamsVerifierKZG { + &self.verifier_params + } + + /// Wrap the certificate proof's prepared `DualMSM` into a collapsed accumulator on + /// the certificate circuit's fixed bases. + pub(crate) fn certificate_collapsed_accumulator( + &self, + dual_msm: DualMSM, + ) -> StmResult> { + check_dual_msm_matches_fixed_bases( + &dual_msm, + CERTIFICATE_FIXED_BASES_PREFIX, + &self.certificate_fixed_bases, + )?; + let mut accumulator: Accumulator = Accumulator::from_dual_msm( + dual_msm, + CERTIFICATE_FIXED_BASES_PREFIX, + &self.certificate_fixed_bases, + ); + accumulator.collapse(); + Ok(accumulator) + } + + /// Off-circuit verify of the previous step's IVC proof, returning the collapsed + /// accumulator the in-circuit IVC verifier gadget would have produced on the same + /// proof. Used at every non-genesis step. + pub(crate) fn previous_ivc_proof_collapsed_accumulator( + &self, + ivc_proof_bytes: &[u8], + public_inputs: &[CircuitBase], + ) -> StmResult> { + let dual_msm = verify_and_prepare_accumulator( + ivc_proof_bytes, + public_inputs, + self.ivc_verifying_key.as_ref(), + &self.verifier_params, + )?; + check_dual_msm_matches_fixed_bases( + &dual_msm, + IVC_FIXED_BASES_PREFIX, + &self.ivc_fixed_bases, + )?; + let mut accumulator: Accumulator = + Accumulator::from_dual_msm(dual_msm, IVC_FIXED_BASES_PREFIX, &self.ivc_fixed_bases); + accumulator.collapse(); + Ok(accumulator) + } +} + #[cfg(test)] mod tests { + use crate::{ + Parameters, + circuits::halo2_ivc::tests::common::asset_readers::load_embedded_verification_context_asset, + }; + use super::*; - use crate::Parameters; + + #[test] + fn prover_input_verification_context_derives_fixed_bases_from_both_verifying_keys() { + let asset = load_embedded_verification_context_asset() + .expect("verification context asset should load"); + let context = IvcProverInputVerificationContext::from_verifying_keys( + asset.verifier_params, + &asset.certificate_verifying_key, + &asset.recursive_verifying_key, + ); + + assert!( + !context.certificate_fixed_bases.is_empty(), + "certificate fixed bases should be populated" + ); + assert!( + !context.ivc_fixed_bases.is_empty(), + "IVC fixed bases should be populated" + ); + + // The two maps overlap on the shared generator base, so they merge as a union. + let mut derived_fixed_bases = context.certificate_fixed_bases.clone(); + derived_fixed_bases.extend(context.ivc_fixed_bases.clone()); + assert_eq!( + derived_fixed_bases, asset.combined_fixed_bases, + "the two derived maps together must reproduce the stored combined fixed bases" + ); + } mod slow { use midnight_proofs::poly::commitment::Params; - use super::*; use crate::circuits::halo2_ivc::tests::common::{ asset_readers::load_embedded_verification_context_asset, generators::setup::{QUORUM_SIZE, SIGNER_COUNT}, }; + use super::*; + // Runs the real `load` path against an oversized unsafe SRS; runs in the `slow` tier. #[test] fn load_succeeds_with_unsafe_srs() {