From 84c5efdaf9772c0a096bd4388b72d797e48ce36d Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Tue, 15 Sep 2026 16:16:24 +0900 Subject: [PATCH 01/14] refactor(stm): share one verifying key codec between both circuits --- .../src/circuits/halo2/key_serialization.rs | 32 ++-------- mithril-stm/src/circuits/halo2/keys.rs | 25 +------- .../circuits/halo2_ivc/key_serialization.rs | 10 +--- mithril-stm/src/circuits/key_serialization.rs | 58 +++++++++++++++++++ mithril-stm/src/circuits/mod.rs | 1 + 5 files changed, 68 insertions(+), 58 deletions(-) create mode 100644 mithril-stm/src/circuits/key_serialization.rs diff --git a/mithril-stm/src/circuits/halo2/key_serialization.rs b/mithril-stm/src/circuits/halo2/key_serialization.rs index aff1857f721..a4da211a870 100644 --- a/mithril-stm/src/circuits/halo2/key_serialization.rs +++ b/mithril-stm/src/circuits/halo2/key_serialization.rs @@ -1,37 +1,15 @@ -//! [`TryToBytes`] / [`TryFromBytes`] impls for the certificate circuit's keys — the self-describing -//! Midnight `MidnightVK` / `MidnightPK` wrappers. Production keys use [`SerdeFormat::RawBytes`]. +//! [`TryToBytes`] / [`TryFromBytes`] impls for the certificate circuit's proving key. Its verifying +//! key is a `MidnightVK`, whose shared encoding lives in [`crate::circuits::key_serialization`]. use anyhow::Context; -use midnight_proofs::utils::SerdeFormat; -use midnight_zk_stdlib::{MidnightPK, MidnightVK}; +use midnight_zk_stdlib::MidnightPK; use crate::StmResult; +use crate::circuits::key_serialization::KEY_SERDE_FORMAT; use crate::codec::{TryFromBytes, TryToBytes}; use super::circuit::CertificateCircuit; -/// Serde format used for the on-disk / in-cache production keys. -const KEY_SERDE_FORMAT: SerdeFormat = SerdeFormat::RawBytes; - -// Certificate circuit verifying key. `MidnightVK` is self-describing, so reading needs only the -// serde format (no circuit type), unlike the recursive raw PLONK keys. -impl TryToBytes for MidnightVK { - fn to_bytes_vec(&self) -> StmResult> { - let mut bytes = Vec::new(); - self.write(&mut bytes, KEY_SERDE_FORMAT) - .with_context(|| "Failed to serialize the certificate verifying key")?; - Ok(bytes) - } -} - -impl TryFromBytes for MidnightVK { - fn try_from_bytes(bytes: &[u8]) -> StmResult { - let mut reader = bytes; - MidnightVK::read(&mut reader, KEY_SERDE_FORMAT) - .with_context(|| "Failed to deserialize the certificate verifying key") - } -} - // Certificate circuit proving key. impl TryToBytes for MidnightPK { fn to_bytes_vec(&self) -> StmResult> { @@ -53,7 +31,7 @@ impl TryFromBytes for MidnightPK { #[cfg(test)] mod tests { use midnight_proofs::poly::kzg::params::ParamsKZG; - use midnight_zk_stdlib::{self as zk, MidnightCircuit}; + use midnight_zk_stdlib::{self as zk, MidnightCircuit, MidnightVK}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; diff --git a/mithril-stm/src/circuits/halo2/keys.rs b/mithril-stm/src/circuits/halo2/keys.rs index 47deded4313..8726402f0e5 100644 --- a/mithril-stm/src/circuits/halo2/keys.rs +++ b/mithril-stm/src/circuits/halo2/keys.rs @@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize}; use crate::StmResult; use crate::circuits::halo2_ivc::{KZGCommitmentScheme, NativeField, PairingEngine, VerifyingKey}; use crate::circuits::key_generator::KeyGenerator; +use crate::circuits::key_serialization::midnight_verifying_key_serde; use crate::codec::{TryFromBytes, TryToBytes}; use super::circuit::CertificateCircuit; @@ -51,30 +52,6 @@ impl AsRef>> } } -/// Serde for the wrapped Midnight verifying key: delegates to the key's [`TryToBytes`] / -/// [`TryFromBytes`] impl so the raw-bytes encoding is defined in one place. -mod midnight_verifying_key_serde { - use midnight_zk_stdlib::MidnightVK; - use serde::{Deserializer, Serializer}; - - use crate::codec::{TryFromBytes, TryToBytes}; - - pub(super) fn serialize( - verifying_key: &MidnightVK, - serializer: S, - ) -> Result { - let bytes = verifying_key.to_bytes_vec().map_err(serde::ser::Error::custom)?; - serializer.serialize_bytes(&bytes) - } - - pub(super) fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result { - let bytes: Vec = serde::Deserialize::deserialize(deserializer)?; - MidnightVK::try_from_bytes(&bytes).map_err(serde::de::Error::custom) - } -} - impl NonRecursiveCircuitProvingKey { /// Borrows the wrapped Midnight proving key, for proof generation. pub(crate) fn midnight_pk(&self) -> &MidnightPK { diff --git a/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs b/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs index 510193c4b19..1b75c4a0131 100644 --- a/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs +++ b/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs @@ -1,26 +1,22 @@ //! [`TryToBytes`] / [`TryFromBytes`] impls for the recursive circuit's raw PLONK keys. //! //! The raw PLONK `read` is generic over the circuit type and takes its `Params`, so these impls -//! pin [`IvcCircuitData`] and its `()` params. Production keys use [`SerdeFormat::RawBytes`]. +//! pin [`IvcCircuitData`] and its `()` params. //! //! Note: only the IVC circuit's keys are ever deserialized raw here (the certificate keys //! round-trip as the high-level `MidnightVK` / `MidnightPK`), so pinning `IvcCircuitData` is //! correct. -use anyhow::Context; -use midnight_proofs::utils::SerdeFormat; - use crate::StmResult; +use crate::circuits::key_serialization::KEY_SERDE_FORMAT; use crate::codec::{TryFromBytes, TryToBytes}; +use anyhow::Context; use super::{ KZGCommitmentScheme, NativeField, PairingEngine, ProvingKey, VerifyingKey, circuit::IvcCircuitData, }; -/// Serde format used for the on-disk / in-cache production keys. -const KEY_SERDE_FORMAT: SerdeFormat = SerdeFormat::RawBytes; - // Recursive (IVC) circuit verifying key. The raw PLONK `read` is generic over the circuit and // takes its `Params`, so it is pinned to `IvcCircuitData` with its `()` params below. impl TryToBytes for VerifyingKey> { diff --git a/mithril-stm/src/circuits/key_serialization.rs b/mithril-stm/src/circuits/key_serialization.rs new file mode 100644 index 00000000000..ebc79bd0f9a --- /dev/null +++ b/mithril-stm/src/circuits/key_serialization.rs @@ -0,0 +1,58 @@ +//! Byte and serde encodings for a Midnight verifying key, shared by every circuit that stores its +//! key in that format. +//! +//! `MidnightVK` is self-describing and belongs to the standard library, so Rust coherence allows +//! only one implementation of the crate's byte traits for it. It lives here rather than beside one +//! circuit's keys so that both circuits reach the same encoding. + +use anyhow::Context; +use midnight_proofs::utils::SerdeFormat; +use midnight_zk_stdlib::MidnightVK; + +use crate::StmResult; +use crate::codec::{TryFromBytes, TryToBytes}; + +/// Serde format used for the on-disk / in-cache production keys. +pub(crate) const KEY_SERDE_FORMAT: SerdeFormat = SerdeFormat::RawBytes; + +// `MidnightVK` is self-describing, so reading needs only the serde format, no circuit type. +impl TryToBytes for MidnightVK { + fn to_bytes_vec(&self) -> StmResult> { + let mut bytes = Vec::new(); + self.write(&mut bytes, KEY_SERDE_FORMAT) + .with_context(|| "Failed to serialize the Midnight verifying key")?; + Ok(bytes) + } +} + +impl TryFromBytes for MidnightVK { + fn try_from_bytes(bytes: &[u8]) -> StmResult { + let mut reader = bytes; + MidnightVK::read(&mut reader, KEY_SERDE_FORMAT) + .with_context(|| "Failed to deserialize the Midnight verifying key") + } +} + +/// Serde for a wrapped Midnight verifying key: delegates to the key's [`TryToBytes`] / +/// [`TryFromBytes`] impl so the raw-bytes encoding is defined in one place. +pub(crate) mod midnight_verifying_key_serde { + use midnight_zk_stdlib::MidnightVK; + use serde::{Deserializer, Serializer}; + + use crate::codec::{TryFromBytes, TryToBytes}; + + pub(crate) fn serialize( + verifying_key: &MidnightVK, + serializer: S, + ) -> Result { + let bytes = verifying_key.to_bytes_vec().map_err(serde::ser::Error::custom)?; + serializer.serialize_bytes(&bytes) + } + + pub(crate) fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result { + let bytes: Vec = serde::Deserialize::deserialize(deserializer)?; + MidnightVK::try_from_bytes(&bytes).map_err(serde::de::Error::custom) + } +} diff --git a/mithril-stm/src/circuits/mod.rs b/mithril-stm/src/circuits/mod.rs index bbf3726721e..b97ae39deaa 100644 --- a/mithril-stm/src/circuits/mod.rs +++ b/mithril-stm/src/circuits/mod.rs @@ -10,6 +10,7 @@ pub mod halo2; pub mod halo2_ivc; pub(crate) mod key_generator; pub(crate) mod key_provider; +pub(crate) mod key_serialization; pub mod trusted_setup; mod verification_key_digest; From 05b234295fa8f396f656e6cabbc09a8ff3d0d088 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Tue, 15 Sep 2026 16:22:52 +0900 Subject: [PATCH 02/14] refactor(stm): separate the IVC circuit from a single step's values --- mithril-stm/src/circuits/halo2_ivc/circuit.rs | 109 +++++++++++------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/mithril-stm/src/circuits/halo2_ivc/circuit.rs b/mithril-stm/src/circuits/halo2_ivc/circuit.rs index f6550d3dd6a..f7db4fd9829 100644 --- a/mithril-stm/src/circuits/halo2_ivc/circuit.rs +++ b/mithril-stm/src/circuits/halo2_ivc/circuit.rs @@ -18,15 +18,68 @@ use super::{ witness_assignments, }; +/// The IVC (Incrementally Verifiable Computation) circuit itself, holding the metadata that fixes +/// its constraint system: the verifier metadata of the certificate circuit it verifies in-circuit, +/// and its own. +/// +/// Mirrors `CertificateCircuit`, which likewise carries only what fixes its constraint system and +/// none of a single execution's values. +#[derive(Clone, Debug)] +pub struct IvcCircuit { + // Domain and ConstraintSystem associated with certificate circuit VerifyingKey + certificate_circuit_domain_and_constraint_system: + (EvaluationDomain, ConstraintSystem), + // Domain and ConstraintSystem associated with IVC circuit VerifyingKey + ivc_circuit_domain_and_constraint_system: + (EvaluationDomain, ConstraintSystem), +} + +impl IvcCircuit { + /// Takes both circuits' verifier metadata from their verifying keys. + fn from_verification_keys( + certificate_verification_key: &NonRecursiveCircuitVerifyingKey, + ivc_verification_key: &RecursiveCircuitVerifyingKey, + ) -> Self { + IvcCircuit { + certificate_circuit_domain_and_constraint_system: ( + certificate_verification_key.as_ref().get_domain().clone(), + certificate_verification_key.as_ref().cs().clone(), + ), + ivc_circuit_domain_and_constraint_system: ( + ivc_verification_key.as_ref().get_domain().clone(), + ivc_verification_key.as_ref().cs().clone(), + ), + } + } + + /// Derives its own verifier metadata from the circuit's configuration, for the key generation + /// that has no IVC verifying key to read it from yet. + fn for_key_generation(certificate_verification_key: &NonRecursiveCircuitVerifyingKey) -> Self { + let mut ivc_circuit_constraint_system = ConstraintSystem::default(); + configure_ivc_circuit(&mut ivc_circuit_constraint_system); + let ivc_circuit_domain = EvaluationDomain::new( + ivc_circuit_constraint_system.degree() as u32, + RECURSIVE_CIRCUIT_DEGREE, + ); + + IvcCircuit { + certificate_circuit_domain_and_constraint_system: ( + certificate_verification_key.as_ref().get_domain().clone(), + certificate_verification_key.as_ref().cs().clone(), + ), + ivc_circuit_domain_and_constraint_system: ( + ivc_circuit_domain, + ivc_circuit_constraint_system, + ), + } + } +} + /// Data required to run one step of the IVC (Incrementally Verifiable Computation) circuit. /// /// Holds the global root-of-trust, the current state, the next certificate witness, -/// the associated SNARK proofs, the latest accumulator, and the verification-key metadata -/// for both the certificate circuit and the IVC circuit itself. -/// -/// Named for its contents rather than the circuit: unlike `CertificateCircuit`, which carries the -/// parameters fixing its constraint system, this bundles a concrete step's values with the -/// verifier metadata required during synthesis. +/// the associated SNARK proofs and the latest accumulator, alongside the circuit those values are +/// run against. #[derive(Clone, Debug)] pub struct IvcCircuitData { // Persistent values throughout an ivc stream. This is the root of trust for an ivc stream. @@ -41,12 +94,8 @@ pub struct IvcCircuitData { ivc_proof: CircuitValue>, // Latest Accumulator accumulator: CircuitValue>, - // Domain and ConstraintSystem associated with certificate circuit VerifyingKey - certificate_circuit_domain_and_constraint_system: - (EvaluationDomain, ConstraintSystem), - // Domain and ConstraintSystem associated with IVC circuit VerifyingKey - ivc_circuit_domain_and_constraint_system: - (EvaluationDomain, ConstraintSystem), + // Circuit these values are run against + circuit: IvcCircuit, } impl IvcCircuitData { @@ -126,13 +175,9 @@ impl IvcCircuitData { certificate_proof: CircuitValue::known(certificate_proof.into_vec()), ivc_proof: CircuitValue::known(ivc_proof.into_vec()), accumulator: CircuitValue::known(accumulator), - certificate_circuit_domain_and_constraint_system: ( - certificate_verification_key.as_ref().get_domain().clone(), - certificate_verification_key.as_ref().cs().clone(), - ), - ivc_circuit_domain_and_constraint_system: ( - ivc_verification_key.as_ref().get_domain().clone(), - ivc_verification_key.as_ref().cs().clone(), + circuit: IvcCircuit::from_verification_keys( + certificate_verification_key, + ivc_verification_key, ), }) } @@ -142,12 +187,6 @@ impl IvcCircuitData { certificate_verification_key: &NonRecursiveCircuitVerifyingKey, ) -> StmResult { Self::validate_column_counts()?; - let mut ivc_circuit_constraint_system = ConstraintSystem::default(); - configure_ivc_circuit(&mut ivc_circuit_constraint_system); - let ivc_circuit_domain = EvaluationDomain::new( - ivc_circuit_constraint_system.degree() as u32, - RECURSIVE_CIRCUIT_DEGREE, - ); Ok(IvcCircuitData { global: CircuitValue::unknown(), @@ -156,14 +195,7 @@ impl IvcCircuitData { certificate_proof: CircuitValue::unknown(), ivc_proof: CircuitValue::unknown(), accumulator: CircuitValue::unknown(), - certificate_circuit_domain_and_constraint_system: ( - certificate_verification_key.as_ref().get_domain().clone(), - certificate_verification_key.as_ref().cs().clone(), - ), - ivc_circuit_domain_and_constraint_system: ( - ivc_circuit_domain, - ivc_circuit_constraint_system, - ), + circuit: IvcCircuit::for_key_generation(certificate_verification_key), }) } } @@ -181,12 +213,7 @@ impl Circuit for IvcCircuitData { certificate_proof: CircuitValue::unknown(), ivc_proof: CircuitValue::unknown(), accumulator: CircuitValue::unknown(), - certificate_circuit_domain_and_constraint_system: self - .certificate_circuit_domain_and_constraint_system - .clone(), - ivc_circuit_domain_and_constraint_system: self - .ivc_circuit_domain_and_constraint_system - .clone(), + circuit: self.circuit.clone(), } } @@ -206,8 +233,8 @@ impl Circuit for IvcCircuitData { &builder, &mut layouter, &self.global, - &self.certificate_circuit_domain_and_constraint_system, - &self.ivc_circuit_domain_and_constraint_system, + &self.circuit.certificate_circuit_domain_and_constraint_system, + &self.circuit.ivc_circuit_domain_and_constraint_system, )?; // Assign previous state let state = witness_assignments::assign_state(&builder, &mut layouter, &self.state)?; From 6578964c54e6d31cacc861ba646309e6d6aa8f76 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Wed, 16 Sep 2026 10:02:17 +0900 Subject: [PATCH 03/14] refactor(stm): move the IVC circuit onto the Relation trait The IVC circuit now implements Relation and declares its chips through ZkStdLibArch instead of a hand written configuration, so both circuits share one configuration mechanism. The logical statement is unchanged. The standard library registers gates in a different order, which changes the recursive circuit's identity, so the recursive keys and every asset derived from them are regenerated. --- .../src/circuits/halo2_ivc/bench/helpers.rs | 29 +- mithril-stm/src/circuits/halo2_ivc/circuit.rs | 392 +++++++++--------- mithril-stm/src/circuits/halo2_ivc/config.rs | 144 ------- .../circuits/halo2_ivc/constraint_builder.rs | 115 ++--- .../src/circuits/halo2_ivc/embedded_assets.rs | 16 +- mithril-stm/src/circuits/halo2_ivc/errors.rs | 12 - .../halo2_ivc/gadgets/schnorr_signature.rs | 13 +- .../circuits/halo2_ivc/key_serialization.rs | 40 +- mithril-stm/src/circuits/halo2_ivc/keys.rs | 14 +- .../circuits/halo2_ivc/midnight_backend.rs | 7 +- mithril-stm/src/circuits/halo2_ivc/mod.rs | 31 +- ...ircuit_verification_key_for_production.bin | Bin 6054 -> 6054 bytes .../tests/assets/genesis_step_output.bin | Bin 13869 -> 13869 bytes ...den_recursive_circuit_verification_key.bin | Bin 6054 -> 6054 bytes .../tests/assets/recursive_chain_state.bin | Bin 13827 -> 13827 bytes .../recursive_proof_accumulator_bytes.bin | Bin 10397 -> 10397 bytes .../tests/assets/recursive_step_output.bin | Bin 17469 -> 17469 bytes ...ecursive_step_output_accumulator_bytes.bin | Bin 5759 -> 5759 bytes .../tests/assets/same_epoch_step_output.bin | Bin 17469 -> 17469 bytes .../tests/assets/verification_context.bin | Bin 21142 -> 21142 bytes .../tests/common/failure_signature.rs | 16 +- .../common/generators/asset_generation.rs | 54 ++- .../tests/common/generators/proofs.rs | 18 +- .../tests/common/generators/setup.rs | 13 +- .../common/generators/verification_key.rs | 9 +- .../halo2_ivc/tests/common/helpers.rs | 48 ++- .../halo2_ivc/tests/encoding/negative.rs | 2 + .../halo2_ivc/tests/encoding/positive.rs | 17 +- .../halo2_ivc/tests/golden/positive.rs | 1 + .../halo2_ivc/tests/in_circuit/accumulator.rs | 9 +- .../tests/in_circuit/genesis_gating.rs | 16 +- .../tests/off_circuit/circuit_validation.rs | 4 +- .../tests/transitions/negative/genesis.rs | 1 + .../tests/transitions/negative/next_epoch.rs | 1 + .../tests/transitions/negative/same_epoch.rs | 1 + .../halo2_ivc/tests/transitions/positive.rs | 2 + .../tests/verification_key_computation.rs | 9 +- .../circuits/halo2_ivc/witness_assignments.rs | 16 +- mithril-stm/src/circuits/key_provider.rs | 81 +++- .../src/circuits/verification_key_digest.rs | 4 +- .../src/proof_system/halo2_ivc_snark/proof.rs | 43 +- .../aggregate_signature/ancillary_data.rs | 7 +- 42 files changed, 582 insertions(+), 603 deletions(-) delete mode 100644 mithril-stm/src/circuits/halo2_ivc/config.rs diff --git a/mithril-stm/src/circuits/halo2_ivc/bench/helpers.rs b/mithril-stm/src/circuits/halo2_ivc/bench/helpers.rs index d762c74f30a..90df7b4428a 100644 --- a/mithril-stm/src/circuits/halo2_ivc/bench/helpers.rs +++ b/mithril-stm/src/circuits/halo2_ivc/bench/helpers.rs @@ -37,7 +37,7 @@ use crate::{ halo2::{circuit::CertificateCircuit, types::CircuitBase}, halo2_ivc::{ PREIMAGE_SIZE, RECURSIVE_CIRCUIT_DEGREE, - circuit::IvcCircuitData, + circuit::{IvcCircuit, IvcCircuitData}, embedded_assets::{ FollowingCertificateInEpochAsset, NextEpochStepOutputAsset, load_embedded_following_certificate_in_epoch_asset, @@ -82,7 +82,8 @@ pub enum TransitionPath { /// Everything one path needs across the measured operations, built untimed and validated once. pub struct PreparedStep { path: TransitionPath, - // Prover inputs: `create_proof` runs over this prebuilt circuit data and public inputs. + // Prover inputs: `create_proof` runs over this prebuilt relation, circuit data and public inputs. + ivc_circuit: IvcCircuit, circuit_data: IvcCircuitData, public_inputs: Vec, // Verifier inputs: the committed step proof and the state/accumulator/message it is checked against. @@ -262,16 +263,18 @@ impl IvcBenchEnv { let prover_input = IvcProverInput::prepare_genesis(&rolling_state, &preimage, &self.global)?; - let circuit_data = IvcCircuitData::try_new( + let ivc_circuit = IvcCircuit::try_new( + &self.setup.certificate_verifying_key, + &self.setup.ivc_verifying_key, + )?; + let circuit_data = IvcCircuitData::new( self.global.clone(), rolling_state.state().clone(), prover_input.witness, CertificateProofBytes::empty(), rolling_state.ivc_proof().clone(), rolling_state.accumulator().clone(), - &self.setup.certificate_verifying_key, - &self.setup.ivc_verifying_key, - )?; + ); let public_inputs = self.public_inputs_for(&prover_input.next_state, &prover_input.next_accumulator); @@ -289,6 +292,7 @@ impl IvcBenchEnv { Ok(PreparedStep { path: TransitionPath::Genesis, + ivc_circuit, circuit_data, public_inputs, proof_bytes: genesis_output.ivc_proof, @@ -336,16 +340,18 @@ impl IvcBenchEnv { &self.setup.prover_input_verification_context(), )?; - let circuit_data = IvcCircuitData::try_new( + let ivc_circuit = IvcCircuit::try_new( + &self.setup.certificate_verifying_key, + &self.setup.ivc_verifying_key, + )?; + let circuit_data = IvcCircuitData::new( self.global.clone(), rolling_state.state().clone(), prover_input.witness, asset.certificate_proof, rolling_state.ivc_proof().clone(), rolling_state.accumulator().clone(), - &self.setup.certificate_verifying_key, - &self.setup.ivc_verifying_key, - )?; + ); let public_inputs = self.public_inputs_for(&prover_input.next_state, &prover_input.next_accumulator); @@ -366,6 +372,7 @@ impl IvcBenchEnv { Ok(PreparedStep { path, + ivc_circuit, circuit_data, public_inputs, proof_bytes: asset.ivc_proof, @@ -460,6 +467,7 @@ impl IvcBenchEnv { IvcProof::>::prove_with_transcript( &self.setup.srs, &self.setup.ivc_proving_key, + &prepared.ivc_circuit, &prepared.circuit_data, &prepared.public_inputs, &mut OsRng, @@ -471,6 +479,7 @@ impl IvcBenchEnv { IvcProof::::prove_with_transcript( &self.setup.srs, &self.setup.ivc_proving_key, + &prepared.ivc_circuit, &prepared.circuit_data, &prepared.public_inputs, &mut OsRng, diff --git a/mithril-stm/src/circuits/halo2_ivc/circuit.rs b/mithril-stm/src/circuits/halo2_ivc/circuit.rs index f7db4fd9829..63661ec9fab 100644 --- a/mithril-stm/src/circuits/halo2_ivc/circuit.rs +++ b/mithril-stm/src/circuits/halo2_ivc/circuit.rs @@ -4,101 +4,57 @@ use crate::circuits::halo2_ivc::keys::RecursiveCircuitVerifyingKey; use anyhow::anyhow; use super::{ - Accumulator, BinaryInstructions, Circuit, CircuitValue, ComposableChip, ConstraintSystem, - EmulatedCurve, Error, EvaluationDomain, IvcNativeGadget, Layouter, NB_ARITH_COLS, - NB_ARITH_FIXED_COLS, NB_EDWARDS_COLS, NB_POSEIDON_ADVICE_COLS, NB_POSEIDON_FIXED_COLS, - NB_SHA256_ADVICE_COLS, NB_SHA256_FIXED_COLS, NativeField, PublicInputInstructions, - RECURSIVE_CIRCUIT_DEGREE, RecursiveEmulation, SimpleFloorPlanner, - config::{IvcConfig, configure_ivc_circuit, ivc_column_pool_sizes}, + Accumulator, BinaryInstructions, CircuitValue, ConstraintSystem, Error, EvaluationDomain, + Layouter, NativeField, PublicInputInstructions, RECURSIVE_CIRCUIT_DEGREE, RecursiveEmulation, + Relation, ZkStdLib, ZkStdLibArch, constraint_builder::IvcConstraintBuilder, errors::IvcCircuitError, - nb_foreign_ecc_chip_columns, state::{Global, State, Witness}, types::{CertificateProofBytes, IvcProofBytes}, witness_assignments, }; +use crate::codec::{TryFromBytes, TryToBytes}; -/// The IVC (Incrementally Verifiable Computation) circuit itself, holding the metadata that fixes -/// its constraint system: the verifier metadata of the certificate circuit it verifies in-circuit, -/// and its own. +/// Chips the recursive circuit enables. +/// +/// Single source: the relation declares these to the standard library, and key generation +/// configures its own verifier metadata from the same value, so the two cannot drift. +pub(crate) fn recursive_circuit_architecture() -> ZkStdLibArch { + ZkStdLibArch { + jubjub: true, + poseidon: true, + sha2_256: true, + sha2_512: false, + keccak_256: false, + sha3_256: false, + secp256k1: false, + bls12_381: true, + base64: false, + nr_pow2range_cols: 4, + automaton: false, + blake2b: false, + curve25519: false, + p256: false, + } +} + +/// The IVC (Incrementally Verifiable Computation) circuit, holding what fixes its constraint +/// system: the certificate circuit whose proofs it verifies in-circuit, and its own verifier +/// metadata. /// /// Mirrors `CertificateCircuit`, which likewise carries only what fixes its constraint system and /// none of a single execution's values. #[derive(Clone, Debug)] pub struct IvcCircuit { - // Domain and ConstraintSystem associated with certificate circuit VerifyingKey - certificate_circuit_domain_and_constraint_system: - (EvaluationDomain, ConstraintSystem), + // Certificate circuit verified in-circuit: its domain and constraint system are the verifier + // metadata, and it is what a serialized relation carries. + certificate_verification_key: NonRecursiveCircuitVerifyingKey, // Domain and ConstraintSystem associated with IVC circuit VerifyingKey ivc_circuit_domain_and_constraint_system: (EvaluationDomain, ConstraintSystem), } impl IvcCircuit { - /// Takes both circuits' verifier metadata from their verifying keys. - fn from_verification_keys( - certificate_verification_key: &NonRecursiveCircuitVerifyingKey, - ivc_verification_key: &RecursiveCircuitVerifyingKey, - ) -> Self { - IvcCircuit { - certificate_circuit_domain_and_constraint_system: ( - certificate_verification_key.as_ref().get_domain().clone(), - certificate_verification_key.as_ref().cs().clone(), - ), - ivc_circuit_domain_and_constraint_system: ( - ivc_verification_key.as_ref().get_domain().clone(), - ivc_verification_key.as_ref().cs().clone(), - ), - } - } - - /// Derives its own verifier metadata from the circuit's configuration, for the key generation - /// that has no IVC verifying key to read it from yet. - fn for_key_generation(certificate_verification_key: &NonRecursiveCircuitVerifyingKey) -> Self { - let mut ivc_circuit_constraint_system = ConstraintSystem::default(); - configure_ivc_circuit(&mut ivc_circuit_constraint_system); - let ivc_circuit_domain = EvaluationDomain::new( - ivc_circuit_constraint_system.degree() as u32, - RECURSIVE_CIRCUIT_DEGREE, - ); - - IvcCircuit { - certificate_circuit_domain_and_constraint_system: ( - certificate_verification_key.as_ref().get_domain().clone(), - certificate_verification_key.as_ref().cs().clone(), - ), - ivc_circuit_domain_and_constraint_system: ( - ivc_circuit_domain, - ivc_circuit_constraint_system, - ), - } - } -} - -/// Data required to run one step of the IVC (Incrementally Verifiable Computation) circuit. -/// -/// Holds the global root-of-trust, the current state, the next certificate witness, -/// the associated SNARK proofs and the latest accumulator, alongside the circuit those values are -/// run against. -#[derive(Clone, Debug)] -pub struct IvcCircuitData { - // Persistent values throughout an ivc stream. This is the root of trust for an ivc stream. - global: CircuitValue, - // State values from the last aggregated certificate - state: CircuitValue, - // Witness (mainly the next certificate to be aggregated) for deriving the next state - witness: CircuitValue, - // Snark proof of the next certificate - certificate_proof: CircuitValue>, - // Latest IVC proof - ivc_proof: CircuitValue>, - // Latest Accumulator - accumulator: CircuitValue>, - // Circuit these values are run against - circuit: IvcCircuit, -} - -impl IvcCircuitData { /// Validates that the IVC verification key degree matches the IVC circuit degree constant RECURSIVE_CIRCUIT_DEGREE. pub(crate) fn validate_ivc_verification_key_degree( ivc_verification_key: &RecursiveCircuitVerifyingKey, @@ -113,144 +69,105 @@ impl IvcCircuitData { Ok(()) } - /// Validates that the column pool allocated by `configure_ivc_circuit` is large enough - /// for every chip. Must be called before `Circuit::configure` is reached (e.g. in - /// `try_new` and `unknown`) so that the `.expect` calls inside `configure_ivc_circuit` - /// are guaranteed not to trigger. - fn validate_column_counts() -> StmResult<()> { - let (nb_advice_cols, nb_fixed_cols) = ivc_column_pool_sizes(); - - for needed in [ - NB_ARITH_COLS, - NB_EDWARDS_COLS, - NB_POSEIDON_ADVICE_COLS, - NB_SHA256_ADVICE_COLS, - nb_foreign_ecc_chip_columns::( - ), - ] { - if needed > nb_advice_cols { - return Err(anyhow!(IvcCircuitError::InsufficientAdviceColumns { - needed, - available: nb_advice_cols, - })); - } - } - - for needed in [NB_ARITH_FIXED_COLS, NB_POSEIDON_FIXED_COLS, NB_SHA256_FIXED_COLS] { - if needed > nb_fixed_cols { - return Err(anyhow!(IvcCircuitError::InsufficientFixedColumns { - needed, - available: nb_fixed_cols, - })); - } - } - - Ok(()) - } - - /// Creates a new `IvcCircuitData` with the given witness and proof data. - /// - /// Validates that `ivc_verification_key` has degree `RECURSIVE_CIRCUIT_DEGREE` and that the column pool allocated by - /// `configure_ivc_circuit` is sufficient for all chips. Returns an error containing - /// [`IvcCircuitError::IvcVerificationKeyDegreeMismatch`] or - /// [`IvcCircuitError::InsufficientAdviceColumns`] / - /// [`IvcCircuitError::InsufficientFixedColumns`] if either check fails. - #[allow(clippy::too_many_arguments)] + /// Builds the circuit from both circuits' verifying keys. pub(crate) fn try_new( - global: Global, - state: State, - witness: Witness, - certificate_proof: CertificateProofBytes, - ivc_proof: IvcProofBytes, - accumulator: Accumulator, certificate_verification_key: &NonRecursiveCircuitVerifyingKey, ivc_verification_key: &RecursiveCircuitVerifyingKey, ) -> StmResult { Self::validate_ivc_verification_key_degree(ivc_verification_key)?; - Self::validate_column_counts()?; - Ok(IvcCircuitData { - global: CircuitValue::known(global), - state: CircuitValue::known(state), - witness: CircuitValue::known(witness), - certificate_proof: CircuitValue::known(certificate_proof.into_vec()), - ivc_proof: CircuitValue::known(ivc_proof.into_vec()), - accumulator: CircuitValue::known(accumulator), - circuit: IvcCircuit::from_verification_keys( - certificate_verification_key, - ivc_verification_key, + + Ok(IvcCircuit { + certificate_verification_key: certificate_verification_key.clone(), + ivc_circuit_domain_and_constraint_system: ( + ivc_verification_key.as_ref().get_domain().clone(), + ivc_verification_key.as_ref().cs().clone(), ), }) } - /// Creates a default IVC circuit for generating the proving and verifying keys. - pub fn unknown( + /// Derives its own verifier metadata from the circuit's configuration, for the key generation + /// that has no IVC verifying key to read it from yet. + pub(crate) fn for_key_generation( certificate_verification_key: &NonRecursiveCircuitVerifyingKey, - ) -> StmResult { - Self::validate_column_counts()?; - - Ok(IvcCircuitData { - global: CircuitValue::unknown(), - state: CircuitValue::unknown(), - witness: CircuitValue::unknown(), - certificate_proof: CircuitValue::unknown(), - ivc_proof: CircuitValue::unknown(), - accumulator: CircuitValue::unknown(), - circuit: IvcCircuit::for_key_generation(certificate_verification_key), - }) - } -} - -impl Circuit for IvcCircuitData { - type Config = IvcConfig; - type FloorPlanner = SimpleFloorPlanner; - type Params = (); + ) -> Self { + let mut ivc_circuit_constraint_system = ConstraintSystem::default(); + ZkStdLib::configure( + &mut ivc_circuit_constraint_system, + ( + recursive_circuit_architecture(), + (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, + ), + ); + let ivc_circuit_domain = EvaluationDomain::new( + ivc_circuit_constraint_system.degree() as u32, + RECURSIVE_CIRCUIT_DEGREE, + ); - fn without_witnesses(&self) -> Self { - IvcCircuitData { - global: CircuitValue::unknown(), - state: CircuitValue::unknown(), - witness: CircuitValue::unknown(), - certificate_proof: CircuitValue::unknown(), - ivc_proof: CircuitValue::unknown(), - accumulator: CircuitValue::unknown(), - circuit: self.circuit.clone(), + IvcCircuit { + certificate_verification_key: certificate_verification_key.clone(), + ivc_circuit_domain_and_constraint_system: ( + ivc_circuit_domain, + ivc_circuit_constraint_system, + ), } } +} - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - configure_ivc_circuit(meta) +impl Relation for IvcCircuit { + type Error = Error; + type Instance = Vec; + type Witness = IvcCircuitData; + + fn format_instance(instance: &Self::Instance) -> Result, Error> { + Ok(instance.clone()) } - fn synthesize( + /// The statement is constrained by the assignment helpers below as each part is derived, so the + /// instance argument is not assigned a second time here. + fn circuit( &self, - config: Self::Config, - mut layouter: impl Layouter, + std_lib: &ZkStdLib, + layouter: &mut impl Layouter, + _instance: CircuitValue, + witness: CircuitValue, ) -> Result<(), Error> { - let builder = IvcConstraintBuilder::new(&config); + let builder = IvcConstraintBuilder::new(std_lib); + + let global_value = witness.clone().map(|data| data.global); + let state_value = witness.clone().map(|data| data.state); + let witness_value = witness.clone().map(|data| data.witness); + let certificate_proof_value = witness.clone().map(|data| data.certificate_proof); + let ivc_proof_value = witness.clone().map(|data| data.ivc_proof); + let accumulator_value = witness.map(|data| data.accumulator); + + let (ivc_circuit_domain, ivc_circuit_constraint_system) = + &self.ivc_circuit_domain_and_constraint_system; // Assign global and constraint it as public input let global = witness_assignments::assign_global_as_public_input( &builder, - &mut layouter, - &self.global, - &self.circuit.certificate_circuit_domain_and_constraint_system, - &self.circuit.ivc_circuit_domain_and_constraint_system, + layouter, + &global_value, + self.certificate_verification_key.as_ref().get_domain(), + self.certificate_verification_key.as_ref().cs(), + ivc_circuit_domain, + ivc_circuit_constraint_system, )?; // Assign previous state - let state = witness_assignments::assign_state(&builder, &mut layouter, &self.state)?; + let state = witness_assignments::assign_state(&builder, layouter, &state_value)?; // Assign witness for the new certificate to be aggregated - let witness = witness_assignments::assign_witness(&builder, &mut layouter, &self.witness)?; + let witness = witness_assignments::assign_witness(&builder, layouter, &witness_value)?; // If state.step_counter = 0, we are aggregating the genesis certificate - let is_genesis = builder.is_genesis(&mut layouter, &state)?; - let is_not_genesis = builder.native_gadget.not(&mut layouter, &is_genesis)?; + let is_genesis = builder.is_genesis(layouter, &state)?; + let is_not_genesis = builder.native_gadget.not(layouter, &is_genesis)?; // Verify genesis certificate - builder.assert_genesis(&mut layouter, &is_not_genesis, &global, &witness)?; + builder.assert_genesis(layouter, &is_not_genesis, &global, &witness)?; // Verify certificate chain link between the last aggregated certificate and the new certificate to obtain the next state let next_state = builder.transition( - &mut layouter, + layouter, &is_genesis, &is_not_genesis, &global, @@ -258,26 +175,88 @@ impl Circuit for IvcCircuitData { &witness, )?; // Constrain the next state as public input - witness_assignments::constrain_state_as_public_input(&builder, &mut layouter, &next_state)?; + witness_assignments::constrain_state_as_public_input(&builder, layouter, &next_state)?; // Verify (prepare) certificate_proof and previous ivc_proof and update accumulator let next_acc = builder.verify_prepare( - &mut layouter, + layouter, &global, &is_not_genesis, &state, &witness, - &self.certificate_proof, - &self.ivc_proof, - &self.accumulator, + &certificate_proof_value, + &ivc_proof_value, + &accumulator_value, )?; // Constrain the next accumulator as public input - builder - .verifier_gadget - .constrain_as_public_input(&mut layouter, &next_acc)?; + builder.verifier_gadget.constrain_as_public_input(layouter, &next_acc) + } - builder.core_decomp_chip.load(&mut layouter)?; - builder.sha2_256_chip.load(&mut layouter) + fn used_chips(&self) -> ZkStdLibArch { + recursive_circuit_architecture() + } + + fn write_relation(&self, writer: &mut W) -> std::io::Result<()> { + let bytes = self + .certificate_verification_key + .to_bytes_vec() + .map_err(|error| std::io::Error::other(error.to_string()))?; + writer.write_all(&(bytes.len() as u32).to_le_bytes())?; + writer.write_all(&bytes) + } + + fn read_relation(reader: &mut R) -> std::io::Result { + let mut length_bytes = [0u8; 4]; + reader.read_exact(&mut length_bytes)?; + let mut bytes = vec![0u8; u32::from_le_bytes(length_bytes) as usize]; + reader.read_exact(&mut bytes)?; + + let certificate_verification_key = NonRecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .map_err(|error| std::io::Error::other(error.to_string()))?; + + Ok(Self::for_key_generation(&certificate_verification_key)) + } +} + +/// Values of one step of the IVC (Incrementally Verifiable Computation) circuit: the witness of the +/// relation above. +/// +/// Holds the global root-of-trust, the current state, the next certificate witness, the associated +/// SNARK proofs and the latest accumulator. +#[derive(Clone, Debug)] +pub struct IvcCircuitData { + // Persistent values throughout an ivc stream. This is the root of trust for an ivc stream. + global: Global, + // State values from the last aggregated certificate + state: State, + // Witness (mainly the next certificate to be aggregated) for deriving the next state + witness: Witness, + // Snark proof of the next certificate + certificate_proof: Vec, + // Latest IVC proof + ivc_proof: Vec, + // Latest Accumulator + accumulator: Accumulator, +} + +impl IvcCircuitData { + /// Collects the values of a single IVC step. + pub(crate) fn new( + global: Global, + state: State, + witness: Witness, + certificate_proof: CertificateProofBytes, + ivc_proof: IvcProofBytes, + accumulator: Accumulator, + ) -> Self { + IvcCircuitData { + global, + state, + witness, + certificate_proof: certificate_proof.into_vec(), + ivc_proof: ivc_proof.into_vec(), + accumulator, + } } } @@ -293,12 +272,27 @@ mod tests { codec::TryFromBytes, }; + use midnight_zk_stdlib::MidnightCircuit; + use super::*; + fn production_certificate_verification_key() -> NonRecursiveCircuitVerifyingKey { + NonRecursiveCircuitVerifyingKey::try_from_bytes( + NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, + ) + .unwrap() + } + #[test] fn ivc_circuit_constraint_count() { let mut cs = ConstraintSystem::::default(); - configure_ivc_circuit(&mut cs); + ZkStdLib::configure( + &mut cs, + ( + recursive_circuit_architecture(), + (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, + ), + ); let poly_constraints: usize = cs.gates().iter().map(|g| g.polynomials().len()).sum(); assert_eq!( @@ -318,16 +312,14 @@ mod tests { #[test] fn recursive_circuit_constraint_degree_stays_constant() { - let certificate_verification_key = NonRecursiveCircuitVerifyingKey::try_from_bytes( - NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, - ) - .unwrap(); - let ivc_data = IvcCircuitData::unknown(&certificate_verification_key).unwrap(); + let certificate_verification_key = production_certificate_verification_key(); + let ivc_circuit = IvcCircuit::for_key_generation(&certificate_verification_key); + let circuit = MidnightCircuit::from_relation(&ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)); const SIZE_BLS12_KZG_COMMITMENT: usize = 48; const SIZE_SCALAR_FIELD_ELEMENT: usize = 32; let circuit_model = - circuit_model::<_, SIZE_BLS12_KZG_COMMITMENT, SIZE_SCALAR_FIELD_ELEMENT>(&ivc_data); + circuit_model::<_, SIZE_BLS12_KZG_COMMITMENT, SIZE_SCALAR_FIELD_ELEMENT>(&circuit); assert_eq!(circuit_model.k, RECURSIVE_CIRCUIT_DEGREE); } diff --git a/mithril-stm/src/circuits/halo2_ivc/config.rs b/mithril-stm/src/circuits/halo2_ivc/config.rs deleted file mode 100644 index 1a53ef7f5a3..00000000000 --- a/mithril-stm/src/circuits/halo2_ivc/config.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; - -use super::{ - CircuitCurve, ComposableChip, ConstraintSystem, EccChip, EccConfig, EmulatedCurve, - EmulatedCurveBaseField, FieldChip, ForeignWeierstrassEccChip, ForeignWeierstrassEccConfig, - IvcNativeGadget, NB_ARITH_COLS, NB_ARITH_FIXED_COLS, NB_EDWARDS_COLS, NB_POSEIDON_ADVICE_COLS, - NB_POSEIDON_FIXED_COLS, NativeChip, NativeConfig, NativeField, P2RDecompositionChip, - P2RDecompositionConfig, PoseidonChip, PoseidonConfig, Pow2RangeChip, - nb_foreign_ecc_chip_columns, -}; -use midnight_circuits::hash::sha256::{ - NB_SHA256_ADVICE_COLS, NB_SHA256_FIXED_COLS, Sha256Chip, Sha256Config, -}; - -#[derive(Debug, Clone)] -pub struct IvcConfig { - pub(crate) native_config: NativeConfig, - pub(crate) core_decomp_config: P2RDecompositionConfig, - pub(crate) jubjub_config: EccConfig, - pub(crate) bls12_381_config: ForeignWeierstrassEccConfig, - pub(crate) poseidon_config: PoseidonConfig, - pub(crate) sha256_config: Sha256Config, -} - -/// Returns `(nb_advice_cols, nb_fixed_cols)` — the column pool sizes allocated by -/// `configure_ivc_circuit`. Single source of truth shared with `validate_column_counts`. -pub(crate) fn ivc_column_pool_sizes() -> (usize, usize) { - // unwrap_or(0) never panics: max() returns None only on an empty iterator, - // but both arrays are non-empty so the fallback is unreachable. - let nb_advice_cols = [ - NB_ARITH_COLS, - NB_EDWARDS_COLS, - NB_POSEIDON_ADVICE_COLS, - NB_SHA256_ADVICE_COLS, - nb_foreign_ecc_chip_columns::(), - ] - .into_iter() - .max() - .unwrap_or(0); - - let nb_fixed_cols = [NB_ARITH_FIXED_COLS, NB_POSEIDON_FIXED_COLS, NB_SHA256_FIXED_COLS] - .into_iter() - .max() - .unwrap_or(0); - - (nb_advice_cols, nb_fixed_cols) -} - -pub fn configure_ivc_circuit(meta: &mut ConstraintSystem) -> IvcConfig { - let (nb_advice_cols, nb_fixed_cols) = ivc_column_pool_sizes(); - - let advice_columns: Vec<_> = (0..nb_advice_cols).map(|_| meta.advice_column()).collect(); - let fixed_columns: Vec<_> = (0..nb_fixed_cols).map(|_| meta.fixed_column()).collect(); - // Committed-instance column: reserved for committed public inputs; this circuit leaves it empty - // and places its entire public statement in the plaintext instance column below. - let committed_instance_column = meta.instance_column(); - // Instance column: carries this circuit's public statement (global, state, accumulator). - let instance_column = meta.instance_column(); - - let native_config = NativeChip::configure( - meta, - &( - advice_columns[..NB_ARITH_COLS] - .try_into() - .expect("column counts pre-validated by validate_column_counts"), - fixed_columns[..NB_ARITH_FIXED_COLS] - .try_into() - .expect("column counts pre-validated by validate_column_counts"), - [committed_instance_column, instance_column], - ), - ); - let core_decomp_config = { - let pow2_config = Pow2RangeChip::configure(meta, &advice_columns[1..NB_ARITH_COLS]); - P2RDecompositionChip::configure(meta, &(native_config.clone(), pow2_config)) - }; - - let jubjub_config = EccChip::::configure( - meta, - &advice_columns[..NB_EDWARDS_COLS] - .try_into() - .expect("column counts pre-validated by validate_column_counts"), - ); - - let nb_parallel_range_check = NB_ARITH_COLS - 1; - let max_bit_length = RECURSIVE_CIRCUIT_DEGREE - 1; - - let base_config = FieldChip::< - NativeField, - EmulatedCurveBaseField, - EmulatedCurve, - IvcNativeGadget, - >::configure( - meta, - &advice_columns, - nb_parallel_range_check, - max_bit_length, - ); - let bls12_381_config = ForeignWeierstrassEccChip::< - NativeField, - EmulatedCurve, - EmulatedCurve, - IvcNativeGadget, - IvcNativeGadget, - >::configure( - meta, - &base_config, - &advice_columns, - nb_parallel_range_check, - max_bit_length, - ); - - let poseidon_config = PoseidonChip::configure( - meta, - &( - advice_columns[..NB_POSEIDON_ADVICE_COLS] - .try_into() - .expect("column counts pre-validated by validate_column_counts"), - fixed_columns[..NB_POSEIDON_FIXED_COLS] - .try_into() - .expect("column counts pre-validated by validate_column_counts"), - ), - ); - - let sha256_config = Sha256Chip::configure( - meta, - &( - advice_columns[..NB_SHA256_ADVICE_COLS] - .try_into() - .expect("column counts pre-validated by validate_column_counts"), - fixed_columns[..NB_SHA256_FIXED_COLS] - .try_into() - .expect("column counts pre-validated by validate_column_counts"), - ), - ); - - IvcConfig { - native_config, - core_decomp_config, - jubjub_config, - bls12_381_config, - poseidon_config, - sha256_config, - } -} diff --git a/mithril-stm/src/circuits/halo2_ivc/constraint_builder.rs b/mithril-stm/src/circuits/halo2_ivc/constraint_builder.rs index 87be8019a6a..1e6a1cce04c 100644 --- a/mithril-stm/src/circuits/halo2_ivc/constraint_builder.rs +++ b/mithril-stm/src/circuits/halo2_ivc/constraint_builder.rs @@ -1,23 +1,21 @@ //! In-circuit constraint builder for one recursive IVC step. //! -//! `IvcConstraintBuilder` owns the chips the recursive circuit needs and exposes the constraint -//! logic driven by `IvcCircuitData::synthesize`: genesis-signature gating, the state transition, and -//! accumulator/proof verification. Reusable sub-gadgets live in the `gadgets` module. +//! `IvcConstraintBuilder` borrows the chips the recursive circuit needs from the standard library +//! and exposes the constraint logic driven by `IvcCircuit::circuit`: genesis-signature gating, the +//! state transition, and accumulator/proof verification. Reusable sub-gadgets live in the `gadgets` +//! module. use ff::Field; use group::Group; -use midnight_circuits::hash::sha256::Sha256Chip; use super::{ Accumulator, ArithInstructions, AssertionInstructions, AssignedAccumulator, AssignedBit, AssignedForeignPoint, AssignedNative, AssignmentInstructions, BinaryInstructions, CircuitCurve, - CircuitValue, ComposableChip, ControlFlowInstructions, EccChip, EccInstructions, EmulatedCurve, - EqualityInstructions, Error, ForeignWeierstrassEccChip, HashInstructions, IvcNativeGadget, - Layouter, NativeChip, NativeField, NativeGadget, P2RDecompositionChip, + CircuitValue, ControlFlowInstructions, EccChip, EccInstructions, EmulatedCurve, + EqualityInstructions, Error, ForeignWeierstrassEccChip, IvcNativeGadget, Layouter, NativeField, PREIMAGE_CURRENT_EPOCH_BYTES, PREIMAGE_NEXT_MERKLE_TREE_COMMITMENT_BYTES, - PREIMAGE_NEXT_PROTOCOL_PARAMETERS_BYTES, PoseidonChip, PublicInputInstructions, - RECURSIVE_CIRCUIT_DEGREE, RecursiveEmulation, VerifierGadget, ZeroInstructions, - config::IvcConfig, + PREIMAGE_NEXT_PROTOCOL_PARAMETERS_BYTES, PublicInputInstructions, RecursiveEmulation, + VerifierGadget, ZeroInstructions, ZkStdLib, gadgets::{GenesisSchnorrSignatureInputs, combine_bytes, verify_genesis_signature}, state::{AssignedGlobal, AssignedState, AssignedWitness}, }; @@ -30,56 +28,38 @@ type DecodedProtocolMessageFields = ( AssignedNative, ); -/// Owns the chips for the recursive IVC circuit and builds its in-circuit constraints. +/// Borrows the standard library's chips and builds the recursive IVC circuit's constraints. /// -/// Constructed once per synthesis from an [`IvcConfig`]; its methods emit the constraints for a -/// single IVC step. -#[derive(Debug, Clone)] -pub struct IvcConstraintBuilder { - pub(crate) core_decomp_chip: P2RDecompositionChip, - pub(crate) native_gadget: IvcNativeGadget, - pub(crate) jubjub_chip: EccChip, - pub(crate) poseidon_chip: PoseidonChip, - pub(crate) sha2_256_chip: Sha256Chip, - pub(crate) bls12_381_chip: ForeignWeierstrassEccChip< +/// Constructed once per synthesis from the [`ZkStdLib`] the relation is handed; its methods emit +/// the constraints for a single IVC step. +#[derive(Clone)] +pub struct IvcConstraintBuilder<'a> { + pub(crate) std_lib: &'a ZkStdLib, + pub(crate) native_gadget: &'a IvcNativeGadget, + pub(crate) jubjub_chip: &'a EccChip, + pub(crate) bls12_381_chip: &'a ForeignWeierstrassEccChip< NativeField, EmulatedCurve, EmulatedCurve, IvcNativeGadget, IvcNativeGadget, >, - pub(crate) verifier_gadget: VerifierGadget, + pub(crate) verifier_gadget: &'a VerifierGadget, } -impl IvcConstraintBuilder { - /// Builds the circuit's chips from the circuit configuration. - pub fn new(config: &IvcConfig) -> Self { - let native_chip = as ComposableChip>::new( - &config.native_config, - &(), - ); - let core_decomp_chip = P2RDecompositionChip::new( - &config.core_decomp_config, - &(RECURSIVE_CIRCUIT_DEGREE as usize - 1), - ); - let native_gadget = NativeGadget::new(core_decomp_chip.clone(), native_chip.clone()); - let jubjub_chip = EccChip::::new(&config.jubjub_config, &native_gadget); - let bls12_381_chip: ForeignWeierstrassEccChip<_, EmulatedCurve, EmulatedCurve, _, _> = { - ForeignWeierstrassEccChip::new(&config.bls12_381_config, &native_gadget, &native_gadget) - }; - let poseidon_chip = PoseidonChip::new(&config.poseidon_config, &native_chip); - let sha2_256_chip = Sha256Chip::new(&config.sha256_config, &native_gadget); - let verifier_gadget: VerifierGadget = - VerifierGadget::new(&bls12_381_chip, &native_gadget, &poseidon_chip); - +impl<'a> IvcConstraintBuilder<'a> { + /// Borrows the chips the recursive circuit needs from the standard library. + /// + /// The native gadget is reached through the BLS chip rather than built here: it is the standard + /// library's own instance, and a separately constructed one would not share its public input + /// counter. + pub fn new(std_lib: &'a ZkStdLib) -> Self { IvcConstraintBuilder { - core_decomp_chip, - native_gadget, - jubjub_chip, - poseidon_chip, - sha2_256_chip, - bls12_381_chip, - verifier_gadget, + std_lib, + native_gadget: std_lib.bls12_381().scalar_field_chip(), + jubjub_chip: std_lib.jubjub(), + bls12_381_chip: std_lib.bls12_381(), + verifier_gadget: std_lib.verifier(), } } @@ -100,9 +80,7 @@ impl IvcConstraintBuilder { witness: &AssignedWitness, ) -> Result, Error> { verify_genesis_signature( - &self.jubjub_chip, - &self.native_gadget, - &self.poseidon_chip, + self.std_lib, layouter, GenesisSchnorrSignatureInputs { verification_key: &global.genesis_verification_key, @@ -260,9 +238,9 @@ impl IvcConstraintBuilder { witness: &AssignedWitness, bases: &[NativeField], ) -> Result<(), Error> { - let hash = self.sha2_256_chip.hash(layouter, &witness.message_preimage)?; + let hash = self.std_lib.sha2_256(layouter, &witness.message_preimage)?; // Compare message and hash - let hash_native = combine_bytes(&self.native_gadget, layouter, &hash, bases)?; + let hash_native = combine_bytes(self.native_gadget, layouter, &hash, bases)?; self.native_gadget.assert_equal(layouter, message, &hash_native) } @@ -285,19 +263,19 @@ impl IvcConstraintBuilder { // Get the field elements by linearly combining the bytes let next_merkle_tree_commitment = combine_bytes( - &self.native_gadget, + self.native_gadget, layouter, next_merkle_tree_commitment_bytes, bases, )?; let next_protocol_parameters = combine_bytes( - &self.native_gadget, + self.native_gadget, layouter, next_protocol_parameters_bytes, bases, )?; let current_epoch = - combine_bytes(&self.native_gadget, layouter, current_epoch_bytes, bases)?; + combine_bytes(self.native_gadget, layouter, current_epoch_bytes, bases)?; Ok(( next_merkle_tree_commitment, next_protocol_parameters, @@ -478,20 +456,20 @@ impl IvcConstraintBuilder { // invalid) accumulator by a default accumulator that satisfies the invariant. AssignedAccumulator::scale_by_bit( layouter, - &self.native_gadget, + self.native_gadget, is_not_genesis, &mut certificate_proof_accumulator, )?; certificate_proof_accumulator.collapse( layouter, - &self.bls12_381_chip, - &self.native_gadget, + self.bls12_381_chip, + self.native_gadget, )?; let acc = AssignedAccumulator::assign( layouter, - &self.bls12_381_chip, - &self.native_gadget, + self.bls12_381_chip, + self.native_gadget, 1, 1, &[], @@ -522,21 +500,18 @@ impl IvcConstraintBuilder { // invalid) accumulator by a default accumulator that satisfies the invariant. AssignedAccumulator::scale_by_bit( layouter, - &self.native_gadget, + self.native_gadget, is_not_genesis, &mut ivc_proof_accumulator, )?; - ivc_proof_accumulator.collapse(layouter, &self.bls12_381_chip, &self.native_gadget)?; + ivc_proof_accumulator.collapse(layouter, self.bls12_381_chip, self.native_gadget)?; // Accumulate the certificate and IVC proof accumulators. - let mut next_acc = AssignedAccumulator::::accumulate( + let mut next_acc = self.verifier_gadget.accumulate( layouter, - &self.verifier_gadget, - &self.native_gadget, - &self.poseidon_chip, &[acc, certificate_proof_accumulator, ivc_proof_accumulator], )?; - next_acc.collapse(layouter, &self.bls12_381_chip, &self.native_gadget)?; + next_acc.collapse(layouter, self.bls12_381_chip, self.native_gadget)?; Ok(next_acc) } diff --git a/mithril-stm/src/circuits/halo2_ivc/embedded_assets.rs b/mithril-stm/src/circuits/halo2_ivc/embedded_assets.rs index b2d781d9bcc..77cd4474991 100644 --- a/mithril-stm/src/circuits/halo2_ivc/embedded_assets.rs +++ b/mithril-stm/src/circuits/halo2_ivc/embedded_assets.rs @@ -19,15 +19,15 @@ use midnight_proofs::{ poly::kzg::params::ParamsVerifierKZG, utils::{SerdeFormat, helpers::ProcessedSerdeObject}, }; -use midnight_zk_stdlib::MidnightVK; +use midnight_zk_stdlib::{MidnightCircuit, MidnightVK}; use crate::StmResult; use crate::circuits::halo2::keys::NonRecursiveCircuitVerifyingKey; use crate::circuits::halo2_ivc::keys::RecursiveCircuitVerifyingKey; use crate::circuits::halo2_ivc::{ Accumulator, EmulatedCurve, KZGCommitmentScheme, NativeField, PREIMAGE_SIZE, PairingEngine, - RecursiveEmulation, VerifyingKey, - circuit::IvcCircuitData, + RECURSIVE_CIRCUIT_DEGREE, RecursiveEmulation, VerifyingKey, + circuit::{IvcCircuit, recursive_circuit_architecture}, io::ReadWithFormat, state::State, types::{ @@ -352,10 +352,16 @@ fn load_verification_context_asset_from_reader( .map(|_| read_field_element(reader)) .collect::, _>>()?; let recursive_verifying_key = - VerifyingKey::>::read::<_, IvcCircuitData>( + VerifyingKey::>::read::< + _, + MidnightCircuit, + >( reader, SerdeFormat::RawBytesUnchecked, - (), + ( + recursive_circuit_architecture(), + (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, + ), )?; let combined_fixed_bases = read_named_fixed_bases(reader)?; diff --git a/mithril-stm/src/circuits/halo2_ivc/errors.rs b/mithril-stm/src/circuits/halo2_ivc/errors.rs index ef112826985..c0a0131fd53 100644 --- a/mithril-stm/src/circuits/halo2_ivc/errors.rs +++ b/mithril-stm/src/circuits/halo2_ivc/errors.rs @@ -22,18 +22,6 @@ pub enum IvcCircuitError { #[error("Byte-to-field conversion received {bytes} bytes but only {bases} base weights")] ByteCountExceedsBaseCount { bytes: usize, bases: usize }, - /// Not enough advice columns were allocated to satisfy chip requirements. - #[error( - "Too few advice columns allocated for the IVC circuit: need {needed}, only {available} allocated" - )] - InsufficientAdviceColumns { needed: usize, available: usize }, - - /// Not enough fixed columns were allocated to satisfy chip requirements. - #[error( - "Too few fixed columns allocated for the IVC circuit: need {needed}, only {available} allocated" - )] - InsufficientFixedColumns { needed: usize, available: usize }, - /// Off-circuit step transition: the incoming certificate's epoch does not advance the /// chain correctly. The `kind` field carries an `EpochTransitionErrorKind` with the /// specific violation. diff --git a/mithril-stm/src/circuits/halo2_ivc/gadgets/schnorr_signature.rs b/mithril-stm/src/circuits/halo2_ivc/gadgets/schnorr_signature.rs index b7f5f9b212a..d430ca7de95 100644 --- a/mithril-stm/src/circuits/halo2_ivc/gadgets/schnorr_signature.rs +++ b/mithril-stm/src/circuits/halo2_ivc/gadgets/schnorr_signature.rs @@ -4,9 +4,8 @@ use group::Group; use crate::circuits::halo2_ivc::{ AssignedBit, AssignedNative, AssignedNativePoint, AssignedScalarOfNativeCurve, - AssignmentInstructions, CircuitCurve, CircuitCurveTrait, ConversionInstructions, EccChip, - EccInstructions, EqualityInstructions, Error, HashInstructions, IvcNativeGadget, Layouter, - NativeField, PoseidonChip, + AssignmentInstructions, CircuitCurve, CircuitCurveTrait, ConversionInstructions, + EccInstructions, EqualityInstructions, Error, Layouter, NativeField, ZkStdLib, }; use crate::signature_scheme::DOMAIN_SEPARATION_TAG_STANDARD_SIGNATURE; @@ -29,12 +28,12 @@ pub(crate) struct GenesisSchnorrSignatureInputs<'a> { /// equality against the committed challenge scalar. Returns an `AssignedBit` that /// is `true` when the signature is valid. pub(crate) fn verify_genesis_signature( - jubjub_chip: &EccChip, - native_gadget: &IvcNativeGadget, - poseidon_chip: &PoseidonChip, + std_lib: &ZkStdLib, layouter: &mut impl Layouter, inputs: GenesisSchnorrSignatureInputs<'_>, ) -> Result, Error> { + let jubjub_chip = std_lib.jubjub(); + let native_gadget = std_lib.bls12_381().scalar_field_chip(); let response = inputs.response.clone(); let challenge_as_scalar: AssignedScalarOfNativeCurve<_> = jubjub_chip.convert(layouter, inputs.challenge)?; @@ -57,7 +56,7 @@ pub(crate) fn verify_genesis_signature( let cap_r_x = jubjub_chip.x_coordinate(&cap_r); let cap_r_y = jubjub_chip.y_coordinate(&cap_r); - let recomputed_challenge = poseidon_chip.hash( + let recomputed_challenge = std_lib.poseidon( layouter, &[ dst_signature.clone(), diff --git a/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs b/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs index 1b75c4a0131..179928cfe0f 100644 --- a/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs +++ b/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs @@ -1,24 +1,28 @@ //! [`TryToBytes`] / [`TryFromBytes`] impls for the recursive circuit's raw PLONK keys. //! -//! The raw PLONK `read` is generic over the circuit type and takes its `Params`, so these impls -//! pin [`IvcCircuitData`] and its `()` params. +//! The raw PLONK `read` is generic over the circuit type and takes its `Params`, so these impls pin +//! the standard library's wrapper around [`IvcCircuit`] and its architecture and range parameters, +//! rebuilding the constraint system key generation used. //! -//! Note: only the IVC circuit's keys are ever deserialized raw here (the certificate keys -//! round-trip as the high-level `MidnightVK` / `MidnightPK`), so pinning `IvcCircuitData` is -//! correct. +//! Note: only the IVC circuit's keys are ever deserialized raw here; the certificate keys round-trip +//! as the high-level `MidnightVK` / `MidnightPK`. use crate::StmResult; use crate::circuits::key_serialization::KEY_SERDE_FORMAT; use crate::codec::{TryFromBytes, TryToBytes}; use anyhow::Context; +use midnight_zk_stdlib::MidnightCircuit; + use super::{ - KZGCommitmentScheme, NativeField, PairingEngine, ProvingKey, VerifyingKey, - circuit::IvcCircuitData, + KZGCommitmentScheme, NativeField, PairingEngine, ProvingKey, RECURSIVE_CIRCUIT_DEGREE, + VerifyingKey, + circuit::{IvcCircuit, recursive_circuit_architecture}, }; // Recursive (IVC) circuit verifying key. The raw PLONK `read` is generic over the circuit and -// takes its `Params`, so it is pinned to `IvcCircuitData` with its `()` params below. +// takes its `Params`, so it is pinned below to the standard library's wrapper around `IvcCircuit`, +// with the architecture and range parameters that rebuild the same constraint system. impl TryToBytes for VerifyingKey> { fn to_bytes_vec(&self) -> StmResult> { let mut bytes = Vec::new(); @@ -31,10 +35,16 @@ impl TryToBytes for VerifyingKey impl TryFromBytes for VerifyingKey> { fn try_from_bytes(bytes: &[u8]) -> StmResult { let mut reader = bytes; - VerifyingKey::>::read::<_, IvcCircuitData>( + VerifyingKey::>::read::< + _, + MidnightCircuit, + >( &mut reader, KEY_SERDE_FORMAT, - (), + ( + recursive_circuit_architecture(), + (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, + ), ) .with_context(|| "Failed to deserialize the recursive PLONK verifying key") } @@ -53,10 +63,16 @@ impl TryToBytes for ProvingKey> impl TryFromBytes for ProvingKey> { fn try_from_bytes(bytes: &[u8]) -> StmResult { let mut reader = bytes; - ProvingKey::>::read::<_, IvcCircuitData>( + ProvingKey::>::read::< + _, + MidnightCircuit, + >( &mut reader, KEY_SERDE_FORMAT, - (), + ( + recursive_circuit_architecture(), + (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, + ), ) .with_context(|| "Failed to deserialize the recursive PLONK proving key") } diff --git a/mithril-stm/src/circuits/halo2_ivc/keys.rs b/mithril-stm/src/circuits/halo2_ivc/keys.rs index 5bfbf943306..3a694092886 100644 --- a/mithril-stm/src/circuits/halo2_ivc/keys.rs +++ b/mithril-stm/src/circuits/halo2_ivc/keys.rs @@ -6,6 +6,7 @@ use midnight_curves::Bls12; use midnight_proofs::plonk::{keygen_pk, keygen_vk_with_k}; use midnight_proofs::poly::commitment::Params; use midnight_proofs::poly::kzg::params::ParamsKZG; +use midnight_zk_stdlib::MidnightCircuit; use serde::{Deserialize, Serialize}; use crate::StmResult; @@ -17,7 +18,7 @@ use crate::codec::{TryFromBytes, TryToBytes}; use super::{ KZGCommitmentScheme, NativeField, PairingEngine, ProvingKey, RECURSIVE_CIRCUIT_DEGREE, - VerifyingKey, circuit::IvcCircuitData, + VerifyingKey, circuit::IvcCircuit, }; /// Verifying key of the recursive (IVC) circuit. @@ -133,7 +134,7 @@ mod recursive_verifying_key_serde { } } -impl KeyGenerator for IvcCircuitData { +impl KeyGenerator for IvcCircuit { type VerifyingKey = RecursiveCircuitVerifyingKey; type ProvingKey = RecursiveCircuitProvingKey; @@ -149,14 +150,15 @@ impl KeyGenerator for IvcCircuitData { srs.max_k() >= RECURSIVE_CIRCUIT_DEGREE, "the SRS must be at least the recursive circuit degree" ); + let circuit = MidnightCircuit::from_relation(self, Some(RECURSIVE_CIRCUIT_DEGREE)); let verifying_key = if srs.max_k() == RECURSIVE_CIRCUIT_DEGREE { - keygen_vk_with_k(srs, self, RECURSIVE_CIRCUIT_DEGREE)? + keygen_vk_with_k(srs, &circuit, RECURSIVE_CIRCUIT_DEGREE)? } else { let mut recursive_srs = srs.clone(); recursive_srs.downsize(RECURSIVE_CIRCUIT_DEGREE); - keygen_vk_with_k(&recursive_srs, self, RECURSIVE_CIRCUIT_DEGREE)? + keygen_vk_with_k(&recursive_srs, &circuit, RECURSIVE_CIRCUIT_DEGREE)? }; - let proving_key = keygen_pk(verifying_key.clone(), self)?; + let proving_key = keygen_pk(verifying_key.clone(), &circuit)?; Ok(( RecursiveCircuitVerifyingKey(verifying_key), RecursiveCircuitProvingKey(proving_key), @@ -200,7 +202,7 @@ impl KeyGenerator for RecursiveCircuitKeyGenerator { srs: &ParamsKZG, ) -> StmResult<(Self::VerifyingKey, Self::ProvingKey)> { let certificate_verifying_key = self.non_recursive_key_provider.verification_key(srs)?; - IvcCircuitData::unknown(&certificate_verifying_key)?.generate_key_pair(srs) + IvcCircuit::for_key_generation(&certificate_verifying_key).generate_key_pair(srs) } } diff --git a/mithril-stm/src/circuits/halo2_ivc/midnight_backend.rs b/mithril-stm/src/circuits/halo2_ivc/midnight_backend.rs index a5a770a9e96..b9491822116 100644 --- a/mithril-stm/src/circuits/halo2_ivc/midnight_backend.rs +++ b/mithril-stm/src/circuits/halo2_ivc/midnight_backend.rs @@ -4,7 +4,6 @@ //! `BlstrsEmulation` — its self-emulation of BLS12-381 proof verification. Isolating them here //! keeps the dependency on the Midnight backend in one place. -use midnight_circuits::ecc::curves::CircuitCurve as CircuitCurveTrait; use midnight_circuits::verifier::{BlstrsEmulation, SelfEmulation}; /// Midnight self-emulation backend the recursive circuit is built on: BLS12-381 verification @@ -16,12 +15,8 @@ pub(crate) type RecursiveEmulation = BlstrsEmulation; pub(crate) type NativeField = ::F; /// Curve verified in-circuit during recursive proof verification: BLS12-381 G1, on which the -/// recursive proof's commitments live. Its coordinates are emulated in [`EmulatedCurveBaseField`]. +/// recursive proof's commitments live. Its coordinates are emulated over [`NativeField`]. pub(crate) type EmulatedCurve = ::C; /// Pairing engine backing the recursive circuit's KZG commitments (BLS12-381). pub(crate) type PairingEngine = ::Engine; - -/// Coordinate field of [`EmulatedCurve`] — the foreign field emulated in-circuit for BLS12-381 G1 -/// arithmetic. Larger than and distinct from [`NativeField`]; not the certificate circuit's `CircuitBase`. -pub(crate) type EmulatedCurveBaseField = ::Base; diff --git a/mithril-stm/src/circuits/halo2_ivc/mod.rs b/mithril-stm/src/circuits/halo2_ivc/mod.rs index 53a561689b4..f0caa5a2b55 100644 --- a/mithril-stm/src/circuits/halo2_ivc/mod.rs +++ b/mithril-stm/src/circuits/halo2_ivc/mod.rs @@ -14,39 +14,24 @@ pub(crate) use crate::circuits::CircuitCurve; pub(crate) use midnight_circuits::{ ecc::{ curves::CircuitCurve as CircuitCurveTrait, - foreign::weierstrass_chip::{ - ForeignWeierstrassEccChip, ForeignWeierstrassEccConfig, nb_foreign_ecc_chip_columns, - }, - native::{EccChip, EccConfig, NB_EDWARDS_COLS}, + foreign::weierstrass_chip::ForeignWeierstrassEccChip, native::EccChip, }, - field::{ - NativeChip, NativeConfig, NativeGadget, - decomposition::{ - chip::{P2RDecompositionChip, P2RDecompositionConfig}, - pow2range::Pow2RangeChip, - }, - foreign::FieldChip, - native::{NB_ARITH_COLS, NB_ARITH_FIXED_COLS}, - }, - hash::poseidon::{ - NB_POSEIDON_ADVICE_COLS, NB_POSEIDON_FIXED_COLS, PoseidonChip, PoseidonConfig, - }, - hash::sha256::{NB_SHA256_ADVICE_COLS, NB_SHA256_FIXED_COLS}, + field::{NativeChip, NativeGadget, decomposition::chip::P2RDecompositionChip}, instructions::{ ArithInstructions, AssertionInstructions, AssignmentInstructions, BinaryInstructions, ControlFlowInstructions, ConversionInstructions, EccInstructions, EqualityInstructions, - HashInstructions, PublicInputInstructions, ZeroInstructions, + PublicInputInstructions, ZeroInstructions, }, types::{ AssignedBit, AssignedByte, AssignedForeignPoint, AssignedNative, AssignedNativePoint, - AssignedScalarOfNativeCurve, ComposableChip, Instantiable, + AssignedScalarOfNativeCurve, Instantiable, }, verifier::{self, Accumulator, AssignedAccumulator, AssignedVk, Msm, VerifierGadget}, }; pub(crate) use midnight_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{Circuit, ConstraintSystem, Error, ProvingKey, VerifyingKey}, + circuit::{Layouter, Value}, + plonk::{ConstraintSystem, Error, ProvingKey, VerifyingKey}, poly::{EvaluationDomain, kzg::KZGCommitmentScheme}, }; @@ -55,7 +40,6 @@ pub(crate) mod accumulator; pub mod bench; pub(crate) mod certificate_proof; pub(crate) mod circuit; -pub(crate) mod config; pub(crate) mod constraint_builder; #[cfg(any(test, feature = "benchmark-internals"))] #[cfg_attr(not(test), allow(dead_code))] @@ -80,8 +64,9 @@ pub(crate) use types::{CircuitValue, ProtocolMessagePreimage}; mod midnight_backend; -use midnight_backend::{EmulatedCurve, EmulatedCurveBaseField, RecursiveEmulation}; +use midnight_backend::{EmulatedCurve, RecursiveEmulation}; pub(crate) use midnight_backend::{NativeField, PairingEngine}; +pub(crate) use midnight_zk_stdlib::{Relation, ZkStdLib, ZkStdLibArch}; type IvcNativeGadget = NativeGadget, NativeChip>; diff --git a/mithril-stm/src/circuits/halo2_ivc/recursive_circuit_verification_key_for_production.bin b/mithril-stm/src/circuits/halo2_ivc/recursive_circuit_verification_key_for_production.bin index 8e1cff5ad0675ec0ae646b41d88c2a74d9888ee8..89b19f59e410655c5ba1a8b2a00ce042db797b82 100644 GIT binary patch delta 1407 zcmV-_1%UddFQzY$W?&O}&_QuKc({%?sp`c^9>k7=R}v|qunmoHOPn?#9Wt2Z+yNQ> z-%gSP66ZhMwNMZd$C?(HY$NSYE0D?!A*=<)ZQqln`Rjtqn94?S7UVL{;?zmDB2}&{7fvqFilS=)-)ebM zA$X!aP|A{D?+@Vfd;~ysbyySWSt+@zgWOuJ%JKbDG4z5}QL#`NS>Gglu zNnZSg%XZY_mllK}gVV$v4~rGtsw73$>oQ3l1 zg(PJKyWqXrTupgyiQ598$mFFGLS$>FJKPt(cLU52JCmjYV1EhMk|zFk>Vp5JJ)h}& z^va#Gg6rQh=9zCy)fx^leX1x$jr8p%!BF};A+XJbW6}ty?rqPeZphL53UQACK>7lq zqH8h=TpR#;Qfei?8-InMDR7gi9qUU{c$T$PP}dP(ZW!TVu)8E7Me3;k(6x=@_i6$< ztnH>bOFhitRzYw;;#Vi8Cswi0f&oWfX~0Gh4QQG~m0@rxfKc{EMRY%ortc!bIY+Iq z>3rEYEItVo7LB_Hz?+H7?WRtN>e$V*#seS$vql9V0h8tgV6$chpaQdI41ffarVGHc z1__`7e*!JnY;Gqbq08$b8q2YPu@o7f5coTT_(9B4f%y?e>Z!Fc07!alECb;TMyc~m zc?IVE4fto&{Qe+zn$RXXfjPvWlcWrbNW5V&e`F>q#MFPPVUIZzAVejU67U3&n8rfhhxF1s4s zHVMcwUo!z`J!r`i54GMDv-8-G8F+bti<& z3QAi+R!10msb-){e)MKYj}UoyNDLX^=nh2i53f56$A9XmLfnm37rCp#>k}NJ>A52= z)~|DI(7$$S6%L;5n0(=wH45567g`5kV>1cv-X-`7;M0J=So-Q_ zLp0gF`qB&jZk#EM9ef9BJ!@UTZra3Vhv2i^X``d#`G7CSCVkN45~~K()ziTRE5AfF NLn!e~_$?*gA`~eAqHF*F delta 1407 zcmV-_1%UddFQzY$W?&V7TQfoT1-HfMBQGQd`rdFEDj|zRef?e|0LsJuVGcTB)MM)1 zm^SKd`C@aM5q}sV<`EnG4d2>7d|HaZ+!@)6GV8YvagwbvQRxB$M|;Hsde*>dV%?sw zt7uwE+8U9@V1EOeTc$AmR6%v^!p`0&bBrS!xH*L}lOJ7~-jCgM=ktChCxp)w5jbn^ zb@3h8R&5Eu$F8cTcsS8gv6_QrRhC$m!}KB6wHYI{uTQ%9`}>d5FxrR&57>Svlamh=Aim;TH?6|L&z?1nrNeQqa-|B=AWPesO7druJih{UsvK@o$$0(Kn z#CaH4KzR@UtJ}k1h)@2JH>YgdOuTv*j^)iF^qCI}1WYa??EHc&JXC-J29p>SXFz+n;Xm>F9UC*(=c10=)L)?7SD|#6=Ptb1fi@E4gZHno6y)bo#;oOK0=C%Ql&4 zi1c|u9e*u{VzIT{-elmk?oJDTJg&&ivA~bx+k<#RSJw0dhySfh1RH$GFys=RK%Sr1 zOmpkPW->c^I5~HiseC#<3j}#t41M7sgC=r~0e*ICq6u1N6RbkkZjRfJM)1W$-3WGi zIxCnwd2U{AE{;_>7RkIlX#hC1)ITi@ zWewxRlkM9pe0O$sKY%5+nVdA3`CLPMgSryuKPnT-2+|qzT11!d%3l!c<2H~QuTo<)0?A#seS$lLiK0v*rXK0kdWXpaQc-4xj>)h7G{8 z1_^)!e-tn}YeWmg<%{K+#Y>~%8k{B=2DWeZm*a=6W`-j|rdsMcCr`;?6D@f}2v)&HNYdsj8eNSJ0j|2o^ zR?kb13HW|k!KK{?-{+tZbkLLeY|8R;(0UEwiz zz)xzd;j5E)E2M*r|OJ&b|`&F_upmusXif#D&|B{f3zfr4FkjPO&v^tggr*GVN?HF&2Q03-jd=SP4W9X#)z0V4rmmg?)3IRyIKZJH)t!qsTa9N*CeTV0>JL^Dld zkgH)lnmH||Z_l}EZZGTZ0vFyBeoL2HwkDa-w zs9d79=!Ea5ae7X7QnMx#Ap~FFPXK7V(r6vYO>}tFO(k8!ijo*s|B?B6N%?Zyy}FC# z@nV!w8AWy(YPDaJqCN&YJ^C^aL|sQHV=vT=#;MSqkk@Wckh63sTNx8i@H6;peEnbl N{_SVHzsTD;$lUymukQc= diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/assets/genesis_step_output.bin b/mithril-stm/src/circuits/halo2_ivc/tests/assets/genesis_step_output.bin index 6b221c963d203c43f25d8c2b6d8b898b8684dddd..14c5a19b1e359e3694d3b5c05af03da7250e19f0 100644 GIT binary patch delta 7611 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 delta 7611 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 diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/assets/golden_recursive_circuit_verification_key.bin b/mithril-stm/src/circuits/halo2_ivc/tests/assets/golden_recursive_circuit_verification_key.bin index 3ee23f0a98543a787aa283ede2d5a4d70e351a99..c57615eb5e7032a05acff4aef160f85996c0268b 100644 GIT binary patch delta 1408 zcmV-`1%LXcFQzY$W?%)v)GsadUU^d$AlbjjdGST;F2me&2X}(tqXKH^329oy*9MCn zs@AWP_kR}tc1#XXybH#J8eb=MQq}c`--Is8SCYh9L}#;mc1C`B#M(Kl0Q+a!$K`^= zh#$V9H9L{UV1EE*L^At@b9Af2J{~8fos*_3@(D;aOU=uI*aw<5Jh3bs!d?<93=fzvSXb9nb)VLCdd++p94=NV8DUB*Kbf1tsc zpr95B5r$(FU9AdXn?DOjO8ZcWs~1G4aGz}e2o!olcxZUhbjc*-YMy{3j%)#m?e#ZnJrtn?%Riu2Z4nL1jXZ! zOXtI20drS1s;8Ac3#Q;@L6Uj%UEb!8kZX=gyPvYrDgdAL@J80+_&=TtU=xowhuqZ$ z)PrdzL4SBgyVu9CCE45b>KC|nl|wg~m-B5%`7(M!7V1--z?Rbx4NliAMI(_MXTFln z;*dKcCumx_tvcceadJXyh@fCedZXjk4AA@{u(C%6e0yAx4O(aPPL(^O1DM?r&v#>) z+5tk=LwRLrCI6#rr(9ObIYyg|1OWK6e}eaXQUmK7eUqjFV1F4>H0DwgCh#cF-2fi4 zSyB#3%k5y1;!z(Bj|g&FUxhh^iF~Ps+R7|*)cBd_KV<@z4!up}!Na+u$_UxRPHN)w zr=iC9!4P3{UYtppFVxKEL^lN0i0S<%W|z(<6w?OWOC}hA>+vhOM%Q?kZ^a5T`21Yi zk!&H$!h#-F4?#PnCWLU%y6Qs)UXgV$M7_=k4D;nc9CUMXRw~fngvO%F6mfl4w4fcL zwz&ucTO-fl)xG&POHCosJ?p+cLXPCK#seS$vql9V0h8tgV6$chpaQdI41ffarVGHc z1__`7e-j2*uRHY|PZ;8>4sdAIXtP>6Dlnh#Rt`tGfnL&vtEKft-K57q;_RNZskN)^ zn+K@y6TEXEsDQXXd9%0C-wCzX97pG*D()fkC-jGi@`!3)UCcR|@H7fA7x5=z7zc>{ z1PNaVX#oQx^oIw!uW4YjOhQjc`;oL)cvA~(f3xBsTYyrK!m;h|g$OIAK%5hxdzk*} zvo!pK%+DEjqUFKfsfN!)^^Gw^mL=a}JnP05g5sagu?Qx9|8M?J$R7%E*W8pNI#arK z4*R0ou9p%VAw_RG$40%$)6ktt(5qPRSv7irn0=x$kR*V-t9uJZOFk@vpaf%)B*113 ze{Ytpj$tqh-wwsUY?rOGcUXJLB}+uT##Rn8v=-_(QgW*oYCzqo-vA044>h!{I=t0= zvv7gc&^J4yJ9ncP=GY_v86Jh(Cghug%AeCy+YFKh)qaNAC4&&8l*FU;O)+iuV1gra zCc7HGW{Qmd4IGs9CpXDss{R@Z6uQ)xe=pwr7PDo19PPx_ieJhF*k95|dIF4GPdG~v zqR8m`vLMPLNGo|YdrQrZcm>^&Dnf**0trDC1!bZ54|$ON7E1~$h-AndTC4T_`BZ57bjbt92lHe41>19A*XBc$>nOgnO30v!E-74S3G|2Y(*Pr!b#^4 zT`6)O8MoTmi|V^(IkP4cAOv6r*6xgGC%T0Gq*1afA}}khpQo-tEEM6D>4oe#kEJ}x zH2mItSvtUu%@(D;7S=%z?{#M+s1ZK64O}j}(T}Yq9S@&~sF7wz!0F5HENx^KjMp@` OTaCFxnVRu6h1ioN`Knj| delta 1408 zcmV-`1%LXcFQzY$W?%*l3VUMbeP3nSVRfF!`ykdBti!T)%jzD>zPfOcV*;Kes3pfl7fC~zc+_d|!+7-U7WqIyd z6_sNQ>AR7}V1EOFe4fo3M0=Rs&9qc`ns0K{8ICeDLismJZ8A|RiJf0ZMJ;MQ)O)XN}dYL7>k)2hpZYJW?PG`xVNAem4krN}PF`3>X+ zg*b{uq=~La=U@~H{Kv1Pi~6GLb(S45a+Wu7RICVT6Bgy^W_Zxi%D74C4Htj|M9zeW zyC4@W)$%?}c*>E;$+eR}jV>%$$r#YSBpT1nm$df{>C3`7&BjkUYN7rYB%|hr=at?9 zxNX_ZC4c^flJIAI67tn))ULwU<4YKmLSHx385w;4M<5xSPr3gJ89YqlwNi%Aunn;1 zU~IO;d+Co5i#QFL&o)s=GU8+i_DFXt5*X zM1W!CQ@A;fDM7st@|)h7G{8 z1_^)!e-cH*&`M(21^FB6Dvj3IBQL<>8PTG8A}ELUI3ZBQ-GAI*s*v~7W@&uz{Xs-A z9TFjw`v9%oD;Gb$NUqAWC>4c5NFA3GdDV52$w!O$WW1l@bWKTiuMZo-MoO$2yA&0z zj6IwxAmGIN2Yu$7oC4g^we=+fBAaVL*}oshdxo1S8*ijC!ZJ-TM&+@M8$SX+`aGNEO<#jM{VX`4Zl%7w*ivy~v0!p*K1=_QolX+{zct)Y=duA z-@=Tmw6VAOu#3V!{Q@lp)kS-Ap%Zg4Kx5RyyZL1da{5FnVRKevr#;Nm=U2bCsk9TS zdQU(LPrgzf9}GfoZ{vtpvWn1D5iIi{e>c4S3$cVTl2vSOVS%`2&K}nXhy7DE+iv!M zwz3PXK`^aN_(ENExr0|-Zhx)pJw|4K7liZ$VeSrssUEO>bB0D8Bhz0h_}x9adQ?@x zKy`-sq-_qyBG9w5xq#&=l&kDo6LR3T!ieH?4M!k=f7eyDT3S4J9%j{ab;Mv1M6VNB z(gB#mhumDhq{{^2Z3=2GjtKX+hN^l$`S-6iMW0iXy<%WaX86(qyU0aa?h9=BOqWrR z=_!JeUp)a7T65rS4YMW_AOv6yyv2GR|BdmNNk}@I&+yXj>56gR8$E5EDCJ()1!kAZ zLaXYv$S|}Id2q4$N)b%3PtadSnLR| O+OYz)VP|0$a4Fx3+p2W{ diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/assets/recursive_chain_state.bin b/mithril-stm/src/circuits/halo2_ivc/tests/assets/recursive_chain_state.bin index 0850bcec11ef3f6010582c5f6251a2863a3e6622..b337020e422d590f04f6dfc24e836e3445970e74 100644 GIT binary patch delta 11506 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 delta 11508 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 diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/assets/recursive_step_output.bin b/mithril-stm/src/circuits/halo2_ivc/tests/assets/recursive_step_output.bin index 1fd65ae13ed22b0f7d4e4058a4d8048884f2c75a..82c6ef71a251d9ada3d98ce26ee039996b11f693 100644 GIT binary patch literal 17469 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 anqVVm2L}@yCp{B8TO;%T(GmY!;Qt2f{FvPU literal 17469 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 diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/failure_signature.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/failure_signature.rs index 7c1befbd4b3..2dae7161128 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/failure_signature.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/failure_signature.rs @@ -5,6 +5,11 @@ //! Advice-side members of a broken class are not enumerated: their columns and regions are named by //! the gadget layer and carry no stability guarantee. +use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; +use crate::circuits::halo2_ivc::circuit::IvcCircuit; +use midnight_proofs::circuit::Value; +use midnight_zk_stdlib::MidnightCircuit; + use std::collections::{BTreeMap, BTreeSet}; use midnight_proofs::{ @@ -136,15 +141,18 @@ pub(crate) fn assert_circuit_rejects_public_input_rows>( /// Recursive-circuit wrapper over [`assert_circuit_rejects_public_input_rows`]. pub(crate) fn assert_recursive_mock_prover_rejects_public_input_rows( + ivc_circuit: &IvcCircuit, ivc_circuit_data: IvcCircuitData, public_inputs: Vec, expected_rows: &BTreeMap, ) { - assert_circuit_rejects_public_input_rows( - &ivc_circuit_data, - vec![vec![], public_inputs], - expected_rows, + let circuit = MidnightCircuit::new( + ivc_circuit, + Value::known(public_inputs.clone()), + Value::known(ivc_circuit_data), + Some(RECURSIVE_CIRCUIT_DEGREE), ); + assert_circuit_rejects_public_input_rows(&circuit, vec![vec![], public_inputs], expected_rows); } #[cfg(test)] diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/asset_generation.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/asset_generation.rs index 702dd826bdb..000e98cdb6b 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/asset_generation.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/asset_generation.rs @@ -30,7 +30,7 @@ use crate::circuits::halo2_ivc::tests::common::asset_readers::{ use crate::circuits::halo2_ivc::{ Accumulator, AssignedAccumulator, EmulatedCurve, PREIMAGE_SIZE, RecursiveEmulation, accumulator::trivial_accumulator, - circuit::IvcCircuitData, + circuit::{IvcCircuit, IvcCircuitData}, keys::RecursiveCircuitProvingKey, state::{Global, State}, types::{CertificateProofBytes, IvcProofBytes}, @@ -129,17 +129,19 @@ fn build_recursive_chain_snapshot( INITIAL_CHAIN_LENGTH + 1 ); let recursive_step_start = Instant::now(); - let ivc_circuit_data = IvcCircuitData::try_new( + let ivc_circuit = IvcCircuit::try_new( + &context.certificate_verifying_key, + &context.recursive_verifying_key, + ) + .expect("valid IvcCircuit construction"); + let ivc_circuit_data = IvcCircuitData::new( global.clone(), current_state.clone(), artifacts.recursive_witnesses[i].clone(), artifacts.certificate_proofs[i].clone(), recursive_proof.clone(), current_accumulator.clone(), - &context.certificate_verifying_key, - &context.recursive_verifying_key, - ) - .expect("valid IvcCircuitData construction"); + ); let public_inputs = [ global.as_public_input(), @@ -151,6 +153,7 @@ fn build_recursive_chain_snapshot( let proof = prove_poseidon_ivc( &context.recursive_commitment_parameters, recursive_proving_key, + &ivc_circuit, &ivc_circuit_data, &public_inputs, &mut recursive_random_generator, @@ -312,17 +315,19 @@ fn build_recursive_step_output_proof( next_step_inputs: &NextRecursiveStepInputs, ) -> Vec { let mut recursive_step_output_random_generator = OsRng; - let ivc_circuit_data = IvcCircuitData::try_new( + let ivc_circuit = IvcCircuit::try_new( + &context.certificate_verifying_key, + &context.recursive_verifying_key, + ) + .expect("valid IvcCircuit construction"); + let ivc_circuit_data = IvcCircuitData::new( global.clone(), recursive_chain_state.state.clone(), next_step_inputs.recursive_witness.clone(), next_step_inputs.certificate_proof.clone(), recursive_chain_state.ivc_proof.clone(), recursive_chain_state.accumulator.clone(), - &context.certificate_verifying_key, - &context.recursive_verifying_key, - ) - .expect("valid IvcCircuitData construction"); + ); let public_inputs = [ global.as_public_input(), next_step_inputs.next_state.as_public_input(), @@ -335,6 +340,7 @@ fn build_recursive_step_output_proof( let final_proof = prove_blake2b_ivc( &context.recursive_commitment_parameters, recursive_proving_key, + &ivc_circuit, &ivc_circuit_data, &public_inputs, &mut recursive_step_output_random_generator, @@ -541,17 +547,19 @@ pub(crate) fn generate_genesis_step_output_asset(setup: &AssetGenerationSetup, p let current_accumulator = trivial_accumulator(&combined_fixed_base_names); let next_accumulator = current_accumulator.clone(); - let ivc_circuit_data = IvcCircuitData::try_new( + let ivc_circuit = IvcCircuit::try_new( + &context.certificate_verifying_key, + &context.recursive_verifying_key, + ) + .expect("valid IvcCircuit construction"); + let ivc_circuit_data = IvcCircuitData::new( global.clone(), State::genesis(), genesis_witness, CertificateProofBytes::empty(), IvcProofBytes::empty(), current_accumulator, - &context.certificate_verifying_key, - &context.recursive_verifying_key, - ) - .expect("valid IvcCircuitData construction"); + ); let public_inputs = [ global.as_public_input(), @@ -566,6 +574,7 @@ pub(crate) fn generate_genesis_step_output_asset(setup: &AssetGenerationSetup, p let proof = prove_blake2b_ivc( &context.recursive_commitment_parameters, &recursive_proving_key, + &ivc_circuit, &ivc_circuit_data, &public_inputs, &mut rng, @@ -692,17 +701,19 @@ pub(crate) fn generate_same_epoch_step_output_asset( "same-epoch next accumulator check failed" ); - let ivc_circuit_data = IvcCircuitData::try_new( + let ivc_circuit = IvcCircuit::try_new( + &context.certificate_verifying_key, + &context.recursive_verifying_key, + ) + .expect("valid IvcCircuit construction"); + let ivc_circuit_data = IvcCircuitData::new( global.clone(), chain_state.state.clone(), ivc_witness.clone(), certificate_proof.clone(), chain_state.ivc_proof.clone(), chain_state.accumulator.clone(), - &context.certificate_verifying_key, - &context.recursive_verifying_key, - ) - .expect("valid IvcCircuitData construction"); + ); let public_inputs = [ global.as_public_input(), @@ -716,6 +727,7 @@ pub(crate) fn generate_same_epoch_step_output_asset( let proof = prove_blake2b_ivc( &context.recursive_commitment_parameters, &recursive_proving_key, + &ivc_circuit, &ivc_circuit_data, &public_inputs, &mut rng, diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs index 78bea6941c7..36bc2f2cad2 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs @@ -1,3 +1,7 @@ +use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; +use crate::circuits::halo2_ivc::circuit::IvcCircuit; +use midnight_proofs::circuit::Value; +use midnight_zk_stdlib::MidnightCircuit; use std::hash::Hash; use ff::FromUniformBytes; @@ -23,6 +27,7 @@ use crate::circuits::halo2_ivc::{ fn prove_ivc_with_transcript( commitment_parameters: &ParamsKZG, proving_key: &RecursiveCircuitProvingKey, + ivc_circuit: &IvcCircuit, ivc_circuit_data: &IvcCircuitData, public_inputs: &[NativeField], random_generator: &mut (impl RngCore + CryptoRng), @@ -38,11 +43,16 @@ where NativeField, KZGCommitmentScheme, CircuitTranscript, - IvcCircuitData, + MidnightCircuit, >( commitment_parameters, proving_key.proving_key(), - std::slice::from_ref(ivc_circuit_data), + std::slice::from_ref(&MidnightCircuit::new( + ivc_circuit, + Value::known(public_inputs.to_vec()), + Value::known(ivc_circuit_data.clone()), + Some(RECURSIVE_CIRCUIT_DEGREE), + )), 1, &[&[&[], public_inputs]], &mut transcript, @@ -82,6 +92,7 @@ where pub(crate) fn prove_poseidon_ivc( commitment_parameters: &ParamsKZG, proving_key: &RecursiveCircuitProvingKey, + ivc_circuit: &IvcCircuit, ivc_circuit_data: &IvcCircuitData, public_inputs: &[NativeField], random_generator: &mut (impl RngCore + CryptoRng), @@ -89,6 +100,7 @@ pub(crate) fn prove_poseidon_ivc( prove_ivc_with_transcript::>( commitment_parameters, proving_key, + ivc_circuit, ivc_circuit_data, public_inputs, random_generator, @@ -115,6 +127,7 @@ pub(crate) fn verify_prepare_poseidon_ivc( pub(crate) fn prove_blake2b_ivc( commitment_parameters: &ParamsKZG, proving_key: &RecursiveCircuitProvingKey, + ivc_circuit: &IvcCircuit, ivc_circuit_data: &IvcCircuitData, public_inputs: &[NativeField], random_generator: &mut (impl RngCore + CryptoRng), @@ -122,6 +135,7 @@ pub(crate) fn prove_blake2b_ivc( prove_ivc_with_transcript::( commitment_parameters, proving_key, + ivc_circuit, ivc_circuit_data, public_inputs, random_generator, diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs index 160a1789601..53fbefded9e 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs @@ -1,3 +1,4 @@ +use midnight_zk_stdlib::MidnightCircuit; use std::{ collections::BTreeMap, io::Write, @@ -25,7 +26,7 @@ use crate::circuits::halo2_ivc::keys::{RecursiveCircuitProvingKey, RecursiveCirc use crate::circuits::halo2_ivc::types::MessageHash; use crate::circuits::halo2_ivc::{ CERTIFICATE_FIXED_BASES_PREFIX, EmulatedCurve, IVC_FIXED_BASES_PREFIX, NativeField, - PairingEngine, RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, circuit::IvcCircuitData, + PairingEngine, RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, circuit::IvcCircuit, state::Global, }; use crate::circuits::test_utils::file_mutex::FileMutex; @@ -216,12 +217,11 @@ fn derive_recursive_verifying_key( recursive_commitment_parameters: &ParamsKZG, certificate_verifying_key: &NonRecursiveCircuitVerifyingKey, ) -> RecursiveCircuitVerifyingKey { - let default_ivc_circuit = - IvcCircuitData::unknown(certificate_verifying_key).expect("valid IvcCircuitData unknown"); + let default_ivc_circuit = IvcCircuit::for_key_generation(certificate_verifying_key); RecursiveCircuitVerifyingKey::new( keygen_vk_with_k( recursive_commitment_parameters, - &default_ivc_circuit, + &MidnightCircuit::from_relation(&default_ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)), RECURSIVE_CIRCUIT_DEGREE, ) .expect("recursive verifying key generation should not fail"), @@ -421,12 +421,11 @@ fn build_shared_recursive_context_with( pub(crate) fn build_recursive_proving_key( context: &SharedRecursiveContext, ) -> RecursiveCircuitProvingKey { - let default_ivc_circuit = IvcCircuitData::unknown(&context.certificate_verifying_key) - .expect("valid IvcCircuitData unknown"); + let default_ivc_circuit = IvcCircuit::for_key_generation(&context.certificate_verifying_key); RecursiveCircuitProvingKey::new( keygen_pk( context.recursive_verifying_key.verifying_key().clone(), - &default_ivc_circuit, + &MidnightCircuit::from_relation(&default_ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)), ) .expect("recursive proving key generation should not fail"), ) diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/verification_key.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/verification_key.rs index 12ea0e780bb..b84c43ffd0d 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/verification_key.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/verification_key.rs @@ -12,9 +12,7 @@ use crate::{ circuits::{ halo2::circuit::CertificateCircuit, halo2::keys::NonRecursiveCircuitVerifyingKey, - halo2_ivc::{ - NativeField, PairingEngine, RECURSIVE_CIRCUIT_DEGREE, circuit::IvcCircuitData, - }, + halo2_ivc::{NativeField, PairingEngine, RECURSIVE_CIRCUIT_DEGREE, circuit::IvcCircuit}, }, }; @@ -41,12 +39,11 @@ pub(crate) fn golden_recursive_circuit_verification_key_bytes() -> Vec { midnight_zk_stdlib::setup_vk(&srs_for_non_recursive_circuit, &circuit), ); - let default_ivc_circuit = - IvcCircuitData::unknown(&circuit_verification_key).expect("valid IvcCircuitData unknown"); + let default_ivc_circuit = IvcCircuit::for_key_generation(&circuit_verification_key); let recursive_verifying_key: VerifyingKey> = keygen_vk_with_k( &srs_for_recursive_circuit, - &default_ivc_circuit, + &MidnightCircuit::from_relation(&default_ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)), RECURSIVE_CIRCUIT_DEGREE, ) .expect("recursive verifying key generation should not fail"); diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/helpers.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/helpers.rs index 855e1ff016e..f58351e31ea 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/helpers.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/helpers.rs @@ -1,3 +1,6 @@ +use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; +use midnight_proofs::circuit::Value; +use midnight_zk_stdlib::MidnightCircuit; use std::collections::BTreeMap; use midnight_circuits::types::Instantiable; @@ -11,7 +14,7 @@ use crate::circuits::halo2_ivc::{ Accumulator, AssignedAccumulator, EmulatedCurve, NativeField, PREIMAGE_SIZE, PairingEngine, RecursiveEmulation, accumulator::trivial_accumulator, - circuit::IvcCircuitData, + circuit::{IvcCircuit, IvcCircuitData}, state::{Global, State, Witness}, types::{CertificateProofBytes, IvcProofBytes, MerkleTreeCommitment, ProtocolMessagePreimage}, }; @@ -50,6 +53,17 @@ pub(crate) struct MockProverSetup { pub(crate) trivial_accumulator: Accumulator, } +impl MockProverSetup { + /// The relation these stimuli are run against, built from the loaded verifying keys. + pub(crate) fn ivc_circuit(&self) -> IvcCircuit { + IvcCircuit::try_new( + &self.certificate_verifying_key, + &self.recursive_verifying_key, + ) + .expect("valid IvcCircuit construction") + } +} + /// Builds the lightweight MockProver setup by loading VKs from the committed asset. /// /// Unlike `build_recursive_mock_prover_setup`, this skips SRS generation entirely @@ -95,6 +109,17 @@ pub(crate) struct RecursiveMockProverSetup { pub(crate) universal_verifier_params: ParamsVerifierKZG, } +impl RecursiveMockProverSetup { + /// The relation these stimuli are run against, built from the loaded verifying keys. + pub(crate) fn ivc_circuit(&self) -> IvcCircuit { + IvcCircuit::try_new( + &self.certificate_verifying_key, + &self.recursive_verifying_key, + ) + .expect("valid IvcCircuit construction") + } +} + /// Builds the shared recursive circuit context needed by MockProver-based golden tests. /// /// This mirrors the verifier-side setup used by the asset generators, but keeps @@ -131,11 +156,18 @@ pub(crate) fn build_recursive_mock_prover_setup( /// Runs `MockProver` and asserts all constraints hold, printing `label` on failure so /// the failing case is identifiable when multiple scenarios share one `#[test]` function. pub(crate) fn assert_recursive_mock_prover_accepts_with_label( + ivc_circuit: &IvcCircuit, ivc_circuit_data: IvcCircuitData, public_inputs: Vec, label: &str, ) { - let prover = MockProver::run(&ivc_circuit_data, vec![vec![], public_inputs]) + let circuit = MidnightCircuit::new( + ivc_circuit, + Value::known(public_inputs.clone()), + Value::known(ivc_circuit_data), + Some(RECURSIVE_CIRCUIT_DEGREE), + ); + let prover = MockProver::run(&circuit, vec![vec![], public_inputs]) .expect("recursive MockProver setup should succeed"); prover.verify().unwrap_or_else(|errors| { panic!( @@ -304,17 +336,14 @@ fn build_asset_backed_step_fixture( AssignedAccumulator::as_public_input(&stored.next_accumulator), ] .concat(); - let ivc_circuit_data = IvcCircuitData::try_new( + let ivc_circuit_data = IvcCircuitData::new( mock_prover_setup.global.clone(), state, witness, stored.certificate_proof, ivc_proof, accumulator, - &mock_prover_setup.certificate_verifying_key, - &mock_prover_setup.recursive_verifying_key, - ) - .expect("valid IvcCircuitData construction"); + ); AssetBackedStepFixture { ivc_circuit_data, @@ -374,17 +403,14 @@ pub(crate) fn build_genesis_mock_prover_circuit( 0, "the trivial-accumulator stimulus is satisfiable only at genesis" ); - IvcCircuitData::try_new( + IvcCircuitData::new( setup.global.clone(), prev_state, witness, CertificateProofBytes::empty(), IvcProofBytes::empty(), setup.trivial_accumulator.clone(), - &setup.certificate_verifying_key, - &setup.recursive_verifying_key, ) - .expect("valid IvcCircuitData construction") } /// Builds the public-input vector for a genesis MockProver stimulus. diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/encoding/negative.rs b/mithril-stm/src/circuits/halo2_ivc/tests/encoding/negative.rs index 4ba16224069..62163eb663c 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/encoding/negative.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/encoding/negative.rs @@ -236,6 +236,7 @@ mod slow { ); assert_recursive_mock_prover_rejects_public_input_rows( + &mock_prover_setup.ivc_circuit(), ivc_circuit_data, public_inputs, &BTreeMap::from([ @@ -299,6 +300,7 @@ mod slow { ); assert_recursive_mock_prover_rejects_public_input_rows( + &mock_prover_setup.ivc_circuit(), ivc_circuit_data, public_inputs, &BTreeMap::from([(StateField::Message.row(), StateField::Message.name())]), diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/encoding/positive.rs b/mithril-stm/src/circuits/halo2_ivc/tests/encoding/positive.rs index 50a4e92a7d5..9bec8499822 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/encoding/positive.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/encoding/positive.rs @@ -1,6 +1,11 @@ //! Positive encoding tests: preimage layout, state public input format, and //! serialization round-trips. +use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; +use crate::circuits::halo2_ivc::circuit::IvcCircuit; +use crate::circuits::halo2_ivc::circuit::recursive_circuit_architecture; +use midnight_zk_stdlib::MidnightCircuit; + use ff::Field; use midnight_proofs::utils::SerdeFormat; use proptest::prelude::*; @@ -10,7 +15,6 @@ use crate::circuits::halo2_ivc::{ Accumulator, KZGCommitmentScheme, NativeField, PREIMAGE_CURRENT_EPOCH_BYTES, PREIMAGE_NEXT_MERKLE_TREE_COMMITMENT_BYTES, PREIMAGE_NEXT_PROTOCOL_PARAMETERS_BYTES, PREIMAGE_SIZE, PairingEngine, ProtocolMessagePreimage, RecursiveEmulation, VerifyingKey, - circuit::IvcCircuitData, io::{ReadWithFormat, WriteWithFormat}, protocol_message::{DynamicProtocolMessagePartKey, ProtocolMessage}, state::State, @@ -216,8 +220,15 @@ fn vk_serialization_round_trip() { let deserialized = VerifyingKey::>::read::< _, - IvcCircuitData, - >(&mut bytes.as_slice(), SerdeFormat::RawBytesUnchecked, ()) + MidnightCircuit, + >( + &mut bytes.as_slice(), + SerdeFormat::RawBytesUnchecked, + ( + recursive_circuit_architecture(), + (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, + ), + ) .expect("verifying key deserialization should succeed"); assert_eq!( diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/golden/positive.rs b/mithril-stm/src/circuits/halo2_ivc/tests/golden/positive.rs index f8b384ec58e..bcf4bd1fe1f 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/golden/positive.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/golden/positive.rs @@ -159,6 +159,7 @@ mod slow { let public_inputs = build_genesis_mock_prover_public_inputs(&mock_prover_setup, &next_state); assert_recursive_mock_prover_accepts_with_label( + &mock_prover_setup.ivc_circuit(), ivc_circuit_data, public_inputs, "genesis base case", diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/in_circuit/accumulator.rs b/mithril-stm/src/circuits/halo2_ivc/tests/in_circuit/accumulator.rs index a86a06b5ead..01f7a2c4875 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/in_circuit/accumulator.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/in_circuit/accumulator.rs @@ -139,17 +139,14 @@ mod slow { cert_accumulator, ); - let ivc_circuit_data = IvcCircuitData::try_new( + let ivc_circuit_data = IvcCircuitData::new( mock_prover_setup.global.clone(), recursive_chain_state.state.clone(), ivc_witness, certificate_proof, recursive_chain_state.ivc_proof.clone(), recursive_chain_state.accumulator.clone(), - &mock_prover_setup.certificate_verifying_key, - &mock_prover_setup.recursive_verifying_key, - ) - .expect("valid IvcCircuitData construction"); + ); let public_inputs = [ mock_prover_setup.global.as_public_input(), @@ -161,6 +158,7 @@ mod slow { // The signature below must not be satisfiable by a rejection that was already present. // Cloning keeps the certificate proved once. assert_recursive_mock_prover_accepts_with_label( + &mock_prover_setup.ivc_circuit(), ivc_circuit_data.clone(), public_inputs.clone(), "same-epoch step with a freshly proved certificate", @@ -176,6 +174,7 @@ mod slow { } assert_recursive_mock_prover_rejects_public_input_rows( + &mock_prover_setup.ivc_circuit(), ivc_circuit_data, tampered_public_inputs, &expected_rows, diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/in_circuit/genesis_gating.rs b/mithril-stm/src/circuits/halo2_ivc/tests/in_circuit/genesis_gating.rs index 2e858e11792..691176c8914 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/in_circuit/genesis_gating.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/in_circuit/genesis_gating.rs @@ -39,18 +39,16 @@ mod slow { AssignedAccumulator::as_public_input(&mock_prover_setup.trivial_accumulator), ] .concat(); - let ivc_circuit_data = IvcCircuitData::try_new( + let ivc_circuit_data = IvcCircuitData::new( mock_prover_setup.global.clone(), State::genesis(), build_genesis_base_case_witness(&setup), CertificateProofBytes::garbage(vec![0u8; 64]), IvcProofBytes::empty(), mock_prover_setup.trivial_accumulator.clone(), - &mock_prover_setup.certificate_verifying_key, - &mock_prover_setup.recursive_verifying_key, - ) - .expect("valid IvcCircuitData construction"); + ); assert_recursive_mock_prover_accepts_with_label( + &mock_prover_setup.ivc_circuit(), ivc_circuit_data, public_inputs, "garbage certificate proof bytes (64 × 0x00)", @@ -70,18 +68,16 @@ mod slow { AssignedAccumulator::as_public_input(&mock_prover_setup.trivial_accumulator), ] .concat(); - let ivc_circuit_data = IvcCircuitData::try_new( + let ivc_circuit_data = IvcCircuitData::new( mock_prover_setup.global.clone(), State::genesis(), build_genesis_base_case_witness(&setup), CertificateProofBytes::empty(), IvcProofBytes::new(vec![0u8; 64]), mock_prover_setup.trivial_accumulator.clone(), - &mock_prover_setup.certificate_verifying_key, - &mock_prover_setup.recursive_verifying_key, - ) - .expect("valid IvcCircuitData construction"); + ); assert_recursive_mock_prover_accepts_with_label( + &mock_prover_setup.ivc_circuit(), ivc_circuit_data, public_inputs, "garbage IVC proof bytes (64 × 0x00)", diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/off_circuit/circuit_validation.rs b/mithril-stm/src/circuits/halo2_ivc/tests/off_circuit/circuit_validation.rs index a6692be0bb0..889c0f7edcd 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/off_circuit/circuit_validation.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/off_circuit/circuit_validation.rs @@ -1,7 +1,7 @@ //! Tests that IvcCircuitData constructor validations return the expected typed errors. use crate::circuits::halo2_ivc::{ - RECURSIVE_CIRCUIT_DEGREE, circuit::IvcCircuitData, errors::IvcCircuitError, + RECURSIVE_CIRCUIT_DEGREE, circuit::IvcCircuit, errors::IvcCircuitError, keys::RecursiveCircuitVerifyingKey, tests::common::asset_readers::load_embedded_verification_context_asset, }; @@ -17,7 +17,7 @@ fn validate_ivc_verification_key_degree_rejects_wrong_degree_vk() { RecursiveCircuitVerifyingKey::new(ctx.certificate_verifying_key.midnight_vk().vk().clone()); let actual_degree = wrong_degree_vk.verifying_key().get_domain().k(); - let result = IvcCircuitData::validate_ivc_verification_key_degree(&wrong_degree_vk); + let result = IvcCircuit::validate_ivc_verification_key_degree(&wrong_degree_vk); let err = result .unwrap_err() diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/genesis.rs b/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/genesis.rs index a33d2695b82..e5efcfd3a37 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/genesis.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/genesis.rs @@ -124,6 +124,7 @@ mod slow { } assert_recursive_mock_prover_rejects_public_input_rows( + &mock_prover_setup.ivc_circuit(), ivc_circuit_data, public_inputs, &expected_rows, diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/next_epoch.rs b/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/next_epoch.rs index 964ecaa2514..18afd9622e3 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/next_epoch.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/next_epoch.rs @@ -105,6 +105,7 @@ mod slow { } assert_recursive_mock_prover_rejects_public_input_rows( + &mock_prover_setup.ivc_circuit(), fixture.ivc_circuit_data, fixture.public_inputs, &expected_rows, diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/same_epoch.rs b/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/same_epoch.rs index 52ba3823465..85feb886591 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/same_epoch.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/transitions/negative/same_epoch.rs @@ -105,6 +105,7 @@ mod slow { } assert_recursive_mock_prover_rejects_public_input_rows( + &mock_prover_setup.ivc_circuit(), fixture.ivc_circuit_data, fixture.public_inputs, &expected_rows, diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/transitions/positive.rs b/mithril-stm/src/circuits/halo2_ivc/tests/transitions/positive.rs index 667314b24f5..bd2129d6790 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/transitions/positive.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/transitions/positive.rs @@ -100,6 +100,7 @@ mod slow { let fixture = build_asset_backed_same_epoch_fixture(&mock_prover_setup); assert_recursive_mock_prover_accepts_with_label( + &mock_prover_setup.ivc_circuit(), fixture.ivc_circuit_data, fixture.public_inputs, "same-epoch step from committed assets", @@ -114,6 +115,7 @@ mod slow { let fixture = build_asset_backed_next_epoch_fixture(&mock_prover_setup); assert_recursive_mock_prover_accepts_with_label( + &mock_prover_setup.ivc_circuit(), fixture.ivc_circuit_data, fixture.public_inputs, "next-epoch step from committed assets", diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/verification_key_computation.rs b/mithril-stm/src/circuits/halo2_ivc/tests/verification_key_computation.rs index 057c7257c8d..c131dd8d6f6 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/verification_key_computation.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/verification_key_computation.rs @@ -4,7 +4,7 @@ use midnight_proofs::{ poly::kzg::KZGCommitmentScheme, utils::SerdeFormat, }; -use midnight_zk_stdlib::MidnightVK; +use midnight_zk_stdlib::{MidnightCircuit, MidnightVK}; use crate::{ StmResult, @@ -15,7 +15,7 @@ use crate::{ }, halo2_ivc::{ NativeField, PairingEngine, RECURSIVE_CIRCUIT_DEGREE, - RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, circuit::IvcCircuitData, + RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, circuit::IvcCircuit, }, trusted_setup::TrustedSetupProvider, }, @@ -37,12 +37,11 @@ fn compute_recursive_circuit_verification_key() -> StmResult> { .with_context(|| "Failed to deserialize the circuit verification key.")?, ); - let default_ivc_circuit = - IvcCircuitData::unknown(&certificate_verifying_key).expect("valid IvcCircuitData unknown"); + let default_ivc_circuit = IvcCircuit::for_key_generation(&certificate_verifying_key); let recursive_verification_key: VerifyingKey> = keygen_vk_with_k( &recursive_commitment_parameters, - &default_ivc_circuit, + &MidnightCircuit::from_relation(&default_ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)), shared_srs_degree, )?; diff --git a/mithril-stm/src/circuits/halo2_ivc/witness_assignments.rs b/mithril-stm/src/circuits/halo2_ivc/witness_assignments.rs index 2a07b5dac9a..1257257b9f6 100644 --- a/mithril-stm/src/circuits/halo2_ivc/witness_assignments.rs +++ b/mithril-stm/src/circuits/halo2_ivc/witness_assignments.rs @@ -20,14 +20,10 @@ pub(crate) fn assign_global_as_public_input( builder: &IvcConstraintBuilder, layouter: &mut impl Layouter, global: &CircuitValue, - certificate_circuit_domain_and_constraint_system: &( - EvaluationDomain, - ConstraintSystem, - ), - ivc_circuit_domain_and_constraint_system: &( - EvaluationDomain, - ConstraintSystem, - ), + certificate_circuit_domain: &EvaluationDomain, + certificate_circuit_constraint_system: &ConstraintSystem, + ivc_circuit_domain: &EvaluationDomain, + ivc_circuit_constraint_system: &ConstraintSystem, ) -> Result { let genesis_message: AssignedNative<_> = builder.native_gadget.assign_as_public_input( layouter, @@ -41,8 +37,6 @@ pub(crate) fn assign_global_as_public_input( .map(|gl| *gl.genesis_verification_key.as_jubjub_subgroup()), )?; - let (certificate_circuit_domain, certificate_circuit_constraint_system) = - &certificate_circuit_domain_and_constraint_system; let certificate_verification_key: AssignedVk = builder.verifier_gadget.assign_vk_as_public_input( layouter, @@ -55,8 +49,6 @@ pub(crate) fn assign_global_as_public_input( )?; // Assign for IVC proof verification - let (ivc_circuit_domain, ivc_circuit_constraint_system) = - &ivc_circuit_domain_and_constraint_system; let ivc_verification_key: AssignedVk = builder.verifier_gadget.assign_vk_as_public_input( layouter, diff --git a/mithril-stm/src/circuits/key_provider.rs b/mithril-stm/src/circuits/key_provider.rs index ddab365485b..85c41877174 100644 --- a/mithril-stm/src/circuits/key_provider.rs +++ b/mithril-stm/src/circuits/key_provider.rs @@ -71,6 +71,32 @@ impl CircuitCacheIdentity { /// it changes with the circuit and only with it, which no configuration outside production can /// check against. fn for_configuration(parameters: &Parameters, merkle_tree_depth: u32) -> StmResult { + Self::fingerprint(parameters, merkle_tree_depth, None) + } + + /// Identifies a configuration of the recursive circuit, additionally bound to that circuit's own + /// identity. + /// + /// The certificate production key alone does not stand for the recursive circuit: the recursive + /// circuit can change while the certificate circuit does not. A non-production entry is trusted + /// without comparison, so without this an entry cached for an earlier recursive circuit would be + /// selected and its fixed and permutation polynomials loaded against the new constraint system. + fn for_recursive_configuration( + parameters: &Parameters, + merkle_tree_depth: u32, + ) -> StmResult { + Self::fingerprint( + parameters, + merkle_tree_depth, + Some(RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION), + ) + } + + fn fingerprint( + parameters: &Parameters, + merkle_tree_depth: u32, + recursive_circuit_identity: Option<&[u8]>, + ) -> StmResult { if parameters == &STM_PARAMETERS_FOR_PRODUCTION && merkle_tree_depth == MERKLE_TREE_DEPTH_FOR_SNARK { @@ -88,6 +114,11 @@ impl CircuitCacheIdentity { hasher.update((input.len() as u64).to_le_bytes()); hasher.update(input); } + // Appended only for the recursive circuit, so certificate entries keep their identity. + if let Some(identity) = recursive_circuit_identity { + hasher.update((identity.len() as u64).to_le_bytes()); + hasher.update(identity); + } Ok(Self::Fingerprinted(hex::encode(hasher.finalize()))) } @@ -335,7 +366,8 @@ impl KeyProvider { parameters: &Parameters, merkle_tree_depth: u32, ) -> StmResult { - let identity = CircuitCacheIdentity::for_configuration(parameters, merkle_tree_depth)?; + let identity = + CircuitCacheIdentity::for_recursive_configuration(parameters, merkle_tree_depth)?; Ok(Self::new( std::env::temp_dir(), @@ -367,7 +399,7 @@ mod tests { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; - use super::{CacheState, KeyGenerator, KeyProvider}; + use super::{CacheState, CircuitCacheIdentity, KeyGenerator, KeyProvider}; use crate::StmResult; use crate::circuits::halo2::{ NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, STM_PARAMETERS_FOR_PRODUCTION, @@ -376,6 +408,51 @@ mod tests { use crate::codec::{TryFromBytes, TryToBytes}; use crate::{MERKLE_TREE_DEPTH_FOR_SNARK, Parameters}; + // The recursive and certificate circuits change independently, and a non-production entry is + // trusted without comparison, so the two must not share a cache directory. + #[test] + fn recursive_cache_identity_differs_from_the_certificate_one() { + let parameters = Parameters { + k: 3, + m: 10, + phi_f: 0.2, + }; + let merkle_tree_depth = 4; + + let certificate = CircuitCacheIdentity::for_configuration(¶meters, merkle_tree_depth) + .expect("certificate identity should build"); + let recursive = + CircuitCacheIdentity::for_recursive_configuration(¶meters, merkle_tree_depth) + .expect("recursive identity should build"); + + assert_ne!( + certificate.directory_name("recursive-keys"), + recursive.directory_name("recursive-keys"), + "the recursive cache must not reuse an entry keyed only by the certificate circuit" + ); + } + + // Binding the recursive circuit into its own fingerprint must leave certificate entries where + // they were, so a recursive circuit change does not force certificate key generation. + #[test] + fn certificate_cache_identity_is_unchanged_by_the_recursive_binding() { + let parameters = Parameters { + k: 3, + m: 10, + phi_f: 0.2, + }; + let merkle_tree_depth = 4; + + let certificate = CircuitCacheIdentity::for_configuration(¶meters, merkle_tree_depth) + .expect("certificate identity should build"); + + assert_eq!( + certificate.directory_name("non-recursive-keys"), + "non-recursive-keys-ac5086eedf8c9015ea3b5f1b39f123e04d6105b89e17f45e731b3d80a2f91c27", + "certificate cache identity must not move" + ); + } + /// Key backed by raw bytes, so the provider mechanics can be tested without real keygen. #[derive(Clone, Debug, PartialEq)] struct ByteKey(Vec); diff --git a/mithril-stm/src/circuits/verification_key_digest.rs b/mithril-stm/src/circuits/verification_key_digest.rs index 088989d436d..54d8d542e86 100644 --- a/mithril-stm/src/circuits/verification_key_digest.rs +++ b/mithril-stm/src/circuits/verification_key_digest.rs @@ -264,7 +264,7 @@ mod tests { "golden production certificate circuit verification key digest changed, either the digest computation, the embedded production key or the certificate circuit changed, which breaks published circuit verification key registries" ); assert_eq!( - "d4c87805251f4bb7e68dde64c18d0da544cc9c2385983074fada3e753dbf6423", + "8a6b414e9b007a6c4ff68a349ea2a7447cb1c94cb7d3de59307197284a8db252", CircuitVerificationKeyDigest::for_ivc_circuit().unwrap().to_string(), "golden IVC circuit verification key digest changed, either the digest computation, the embedded production key or the IVC circuit changed, which breaks published circuit verification key registries" ); @@ -288,7 +288,7 @@ mod tests { "golden certificate circuit verification key digest changed, either the digest computation or the circuit changed, which breaks published circuit verification key registries" ); assert_eq!( - "d3688e5681a2a35a218006bd07f2be2ff8991a35249ac5646f9d6ba042d4ac0c", + "763dfd2235083e2fb78a6d35b69a8daef9331900ba61b0fea5879fd1cce7a00f", recursive_key_digest.to_string(), "golden IVC circuit verification key digest changed, either the digest computation or the circuit changed, which breaks published circuit verification key registries" ); diff --git a/mithril-stm/src/proof_system/halo2_ivc_snark/proof.rs b/mithril-stm/src/proof_system/halo2_ivc_snark/proof.rs index 53d166f83c6..2f430a51621 100644 --- a/mithril-stm/src/proof_system/halo2_ivc_snark/proof.rs +++ b/mithril-stm/src/proof_system/halo2_ivc_snark/proof.rs @@ -11,6 +11,7 @@ use midnight_circuits::{ verifier::{Accumulator, AssignedAccumulator, BlstrsEmulation}, }; use midnight_curves::{Bls12, G1Projective}; +use midnight_proofs::circuit::Value; use midnight_proofs::{ plonk::{create_proof, prepare}, poly::{ @@ -23,9 +24,11 @@ use midnight_proofs::{ }, transcript::{Blake2b256, CircuitTranscript, Hashable, Sampleable, Transcript, TranscriptHash}, }; +use midnight_zk_stdlib::MidnightCircuit; use rand_core::{CryptoRng, OsRng, RngCore}; use serde::{Deserialize, Serialize}; +use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; use crate::{ AggregateVerificationKeyForSnark, AggregationError, AncillaryGenesisData, AncillaryProofInput, BaseFieldElement, MembershipDigest, SnarkProof, StmResult, @@ -34,7 +37,7 @@ use crate::{ halo2_ivc::{ PREIMAGE_SIZE, accumulator::check_accumulator_fixed_bases_present, - circuit::IvcCircuitData, + circuit::{IvcCircuit, IvcCircuitData}, keys::{RecursiveCircuitProvingKey, RecursiveCircuitVerifyingKey}, state::{Global, State}, types::{CertificateProofBytes, IvcProofBytes, MessageHash, ProtocolMessagePreimage}, @@ -281,20 +284,27 @@ where pub(crate) fn prove_with_transcript( srs: &ParamsKZG, proving_key: &RecursiveCircuitProvingKey, + ivc_circuit: &IvcCircuit, circuit_data: &IvcCircuitData, public_inputs: &[CircuitBase], rng: &mut (impl RngCore + CryptoRng), ) -> StmResult> { + let circuit = MidnightCircuit::new( + ivc_circuit, + Value::known(public_inputs.to_vec()), + Value::known(circuit_data.clone()), + Some(RECURSIVE_CIRCUIT_DEGREE), + ); let mut transcript = CircuitTranscript::::init(); create_proof::< CircuitBase, KZGCommitmentScheme, CircuitTranscript, - IvcCircuitData, + MidnightCircuit, >( srs, proving_key.proving_key(), - std::slice::from_ref(circuit_data), + std::slice::from_ref(&circuit), 1, &[&[&[], public_inputs]], &mut transcript, @@ -434,16 +444,18 @@ impl IvcProver { let certificate_proof_bytes = snark_proof.into_circuit_proof_bytes(); - let circuit_data = IvcCircuitData::try_new( + let ivc_circuit = IvcCircuit::try_new( + &self.ivc_setup.certificate_verifying_key, + &self.ivc_setup.ivc_verifying_key, + )?; + let circuit_data = IvcCircuitData::new( global.clone(), effective_rolling_state.state().clone(), prover_input.witness, certificate_proof_bytes, effective_rolling_state.ivc_proof().clone(), effective_rolling_state.accumulator().clone(), - &self.ivc_setup.certificate_verifying_key, - &self.ivc_setup.ivc_verifying_key, - )?; + ); // Public inputs for the new step: [global | next_state | next_accumulator]. let public_inputs: Vec = [ @@ -462,6 +474,7 @@ impl IvcProver { let poseidon_bytes = IvcProof::>::prove_with_transcript( &self.ivc_setup.srs, &self.ivc_setup.ivc_proving_key, + &ivc_circuit, &circuit_data, &public_inputs, &mut self.rng, @@ -479,6 +492,7 @@ impl IvcProver { let blake2b_bytes = IvcProof::::prove_with_transcript( &self.ivc_setup.srs, &self.ivc_setup.ivc_proving_key, + &ivc_circuit, &circuit_data, &public_inputs, &mut self.rng, @@ -514,16 +528,18 @@ impl IvcProver { global, )?; - let genesis_circuit_data = IvcCircuitData::try_new( + let genesis_ivc_circuit = IvcCircuit::try_new( + &self.ivc_setup.certificate_verifying_key, + &self.ivc_setup.ivc_verifying_key, + )?; + let genesis_circuit_data = IvcCircuitData::new( global.clone(), genesis_rolling_state.state().clone(), genesis_prover_input.witness, CertificateProofBytes::empty(), genesis_rolling_state.ivc_proof().clone(), genesis_rolling_state.accumulator().clone(), - &self.ivc_setup.certificate_verifying_key, - &self.ivc_setup.ivc_verifying_key, - )?; + ); let genesis_public_inputs: Vec = [ global.as_public_input(), @@ -535,6 +551,7 @@ impl IvcProver { let poseidon_bytes = IvcProof::>::prove_with_transcript( &self.ivc_setup.srs, &self.ivc_setup.ivc_proving_key, + &genesis_ivc_circuit, &genesis_circuit_data, &genesis_public_inputs, &mut self.rng, @@ -1763,8 +1780,8 @@ mod tests { use super::*; const GOLDEN_R: [u8; 32] = [ - 167, 66, 11, 195, 134, 213, 22, 97, 36, 22, 169, 16, 222, 26, 110, 27, 81, 13, 53, 172, - 191, 68, 90, 117, 248, 154, 30, 122, 198, 17, 214, 30, + 236, 82, 235, 208, 194, 213, 21, 52, 158, 242, 42, 124, 219, 198, 65, 232, 86, 191, 84, + 104, 0, 39, 228, 81, 172, 96, 198, 123, 29, 236, 243, 50, ]; #[test] diff --git a/mithril-stm/src/protocol/aggregate_signature/ancillary_data.rs b/mithril-stm/src/protocol/aggregate_signature/ancillary_data.rs index 5b8ce826f1c..b7cf8b21f4f 100644 --- a/mithril-stm/src/protocol/aggregate_signature/ancillary_data.rs +++ b/mithril-stm/src/protocol/aggregate_signature/ancillary_data.rs @@ -491,15 +491,16 @@ mod tests { /// hardcoded digest, so appending the `Snark` variant to `AncillaryVerifierData` cannot /// silently change the encoding of the existing variant — which would break committed IVC /// certificates. ciborium tags enum variants by name, so the encoding is independent of variant - /// order; the digest below is the pre-change value and must never change. + /// order. The digest pins the complete encoded bytes, embedded verifying keys included, so it + /// legitimately moves whenever a circuit changes and must be recomputed with those keys. #[cfg(feature = "future_snark")] #[test] fn ivc_ancillary_encoding_is_byte_stable() { use sha2::{Digest, Sha256}; const EXPECTED_IVC_ANCILLARY_DIGEST: [u8; 32] = [ - 98, 51, 110, 52, 61, 7, 186, 33, 25, 69, 151, 75, 36, 223, 4, 98, 6, 240, 88, 93, 35, - 185, 12, 157, 96, 39, 6, 65, 20, 46, 13, 146, + 166, 23, 43, 148, 143, 254, 123, 150, 165, 210, 35, 250, 36, 248, 222, 137, 180, 56, + 75, 253, 228, 42, 64, 179, 144, 17, 68, 254, 99, 93, 223, 242, ]; let context = load_embedded_verification_context_asset() .expect("verification context asset should load"); From 9a2bc1a27ed316e9e06e9dd4cfac6e4003b7868b Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Wed, 16 Sep 2026 14:15:39 +0900 Subject: [PATCH 04/14] refactor(stm): encode the recursive circuit keys in the Midnight format Both circuits now serialize their keys in the same format, so a single codec serves them. The inner verifying key is unchanged, so the circuit identity, the digests and the existing proofs are untouched; the keys only gain a 23 byte self-describing envelope. Because the two formats are now identical, the recursive decoders validate the declared architecture and every declared degree before the bytes reach the dependency's readers, so a key belonging to another circuit is rejected. --- .../src/circuits/halo2_ivc/embedded_assets.rs | 19 +- mithril-stm/src/circuits/halo2_ivc/errors.rs | 14 + .../circuits/halo2_ivc/key_serialization.rs | 99 ---- mithril-stm/src/circuits/halo2_ivc/keys.rs | 511 +++++++++++++++--- mithril-stm/src/circuits/halo2_ivc/mod.rs | 4 +- ...ircuit_verification_key_for_production.bin | Bin 6054 -> 6077 bytes ...den_recursive_circuit_verification_key.bin | Bin 6054 -> 6077 bytes .../tests/assets/verification_context.bin | Bin 21142 -> 21165 bytes .../halo2_ivc/tests/common/asset_readers.rs | 2 +- .../tests/common/generators/proofs.rs | 2 +- .../tests/common/generators/setup.rs | 30 +- .../common/generators/verification_key.rs | 18 +- .../tests/off_circuit/circuit_validation.rs | 2 +- .../tests/verification_key_computation.rs | 20 +- mithril-stm/src/circuits/trusted_setup.rs | 2 + .../src/proof_system/halo2_ivc_snark/proof.rs | 2 +- .../halo2_ivc_snark/verifier_setup.rs | 48 ++ .../aggregate_signature/ancillary_data.rs | 4 +- 18 files changed, 544 insertions(+), 233 deletions(-) delete mode 100644 mithril-stm/src/circuits/halo2_ivc/key_serialization.rs diff --git a/mithril-stm/src/circuits/halo2_ivc/embedded_assets.rs b/mithril-stm/src/circuits/halo2_ivc/embedded_assets.rs index 77cd4474991..ce8ff29626d 100644 --- a/mithril-stm/src/circuits/halo2_ivc/embedded_assets.rs +++ b/mithril-stm/src/circuits/halo2_ivc/embedded_assets.rs @@ -19,15 +19,13 @@ use midnight_proofs::{ poly::kzg::params::ParamsVerifierKZG, utils::{SerdeFormat, helpers::ProcessedSerdeObject}, }; -use midnight_zk_stdlib::{MidnightCircuit, MidnightVK}; +use midnight_zk_stdlib::MidnightVK; use crate::StmResult; use crate::circuits::halo2::keys::NonRecursiveCircuitVerifyingKey; use crate::circuits::halo2_ivc::keys::RecursiveCircuitVerifyingKey; use crate::circuits::halo2_ivc::{ - Accumulator, EmulatedCurve, KZGCommitmentScheme, NativeField, PREIMAGE_SIZE, PairingEngine, - RECURSIVE_CIRCUIT_DEGREE, RecursiveEmulation, VerifyingKey, - circuit::{IvcCircuit, recursive_circuit_architecture}, + Accumulator, EmulatedCurve, NativeField, PREIMAGE_SIZE, PairingEngine, RecursiveEmulation, io::ReadWithFormat, state::State, types::{ @@ -351,18 +349,7 @@ fn load_verification_context_asset_from_reader( let global_field_elements = (0..5) .map(|_| read_field_element(reader)) .collect::, _>>()?; - let recursive_verifying_key = - VerifyingKey::>::read::< - _, - MidnightCircuit, - >( - reader, - SerdeFormat::RawBytesUnchecked, - ( - recursive_circuit_architecture(), - (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, - ), - )?; + let recursive_verifying_key = MidnightVK::read(reader, SerdeFormat::RawBytesUnchecked)?; let combined_fixed_bases = read_named_fixed_bases(reader)?; // verifier_params is length-prefixed so the certificate verification key can follow it. diff --git a/mithril-stm/src/circuits/halo2_ivc/errors.rs b/mithril-stm/src/circuits/halo2_ivc/errors.rs index c0a0131fd53..1ebbdc62ca9 100644 --- a/mithril-stm/src/circuits/halo2_ivc/errors.rs +++ b/mithril-stm/src/circuits/halo2_ivc/errors.rs @@ -22,6 +22,20 @@ pub enum IvcCircuitError { #[error("Byte-to-field conversion received {bytes} bytes but only {bases} base weights")] ByteCountExceedsBaseCount { bytes: usize, bases: usize }, + /// A recursive verifying key was encoded for a different standard library architecture, so it + /// belongs to another circuit. + #[error("The recursive verifying key declares an architecture that is not the IVC circuit's")] + RecursiveVerificationKeyArchitectureMismatch, + + /// A recursive verifying key carried a different number of fixed commitments than the + /// configured constraint system has columns. + #[error("The recursive verifying key declares {actual} fixed commitments, expected {expected}")] + RecursiveVerificationKeyCommitmentCountMismatch { expected: usize, actual: usize }, + + /// A standalone recursive key encoding carried bytes beyond the key. + #[error("The recursive key encoding carries {trailing} trailing bytes")] + RecursiveKeyEncodingHasTrailingBytes { trailing: usize }, + /// Off-circuit step transition: the incoming certificate's epoch does not advance the /// chain correctly. The `kind` field carries an `EpochTransitionErrorKind` with the /// specific violation. diff --git a/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs b/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs deleted file mode 100644 index 179928cfe0f..00000000000 --- a/mithril-stm/src/circuits/halo2_ivc/key_serialization.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! [`TryToBytes`] / [`TryFromBytes`] impls for the recursive circuit's raw PLONK keys. -//! -//! The raw PLONK `read` is generic over the circuit type and takes its `Params`, so these impls pin -//! the standard library's wrapper around [`IvcCircuit`] and its architecture and range parameters, -//! rebuilding the constraint system key generation used. -//! -//! Note: only the IVC circuit's keys are ever deserialized raw here; the certificate keys round-trip -//! as the high-level `MidnightVK` / `MidnightPK`. - -use crate::StmResult; -use crate::circuits::key_serialization::KEY_SERDE_FORMAT; -use crate::codec::{TryFromBytes, TryToBytes}; -use anyhow::Context; - -use midnight_zk_stdlib::MidnightCircuit; - -use super::{ - KZGCommitmentScheme, NativeField, PairingEngine, ProvingKey, RECURSIVE_CIRCUIT_DEGREE, - VerifyingKey, - circuit::{IvcCircuit, recursive_circuit_architecture}, -}; - -// Recursive (IVC) circuit verifying key. The raw PLONK `read` is generic over the circuit and -// takes its `Params`, so it is pinned below to the standard library's wrapper around `IvcCircuit`, -// with the architecture and range parameters that rebuild the same constraint system. -impl TryToBytes for VerifyingKey> { - fn to_bytes_vec(&self) -> StmResult> { - let mut bytes = Vec::new(); - self.write(&mut bytes, KEY_SERDE_FORMAT) - .with_context(|| "Failed to serialize the recursive PLONK verifying key")?; - Ok(bytes) - } -} - -impl TryFromBytes for VerifyingKey> { - fn try_from_bytes(bytes: &[u8]) -> StmResult { - let mut reader = bytes; - VerifyingKey::>::read::< - _, - MidnightCircuit, - >( - &mut reader, - KEY_SERDE_FORMAT, - ( - recursive_circuit_architecture(), - (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, - ), - ) - .with_context(|| "Failed to deserialize the recursive PLONK verifying key") - } -} - -// Recursive (IVC) circuit proving key. -impl TryToBytes for ProvingKey> { - fn to_bytes_vec(&self) -> StmResult> { - let mut bytes = Vec::new(); - self.write(&mut bytes, KEY_SERDE_FORMAT) - .with_context(|| "Failed to serialize the recursive PLONK proving key")?; - Ok(bytes) - } -} - -impl TryFromBytes for ProvingKey> { - fn try_from_bytes(bytes: &[u8]) -> StmResult { - let mut reader = bytes; - ProvingKey::>::read::< - _, - MidnightCircuit, - >( - &mut reader, - KEY_SERDE_FORMAT, - ( - recursive_circuit_architecture(), - (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, - ), - ) - .with_context(|| "Failed to deserialize the recursive PLONK proving key") - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; - - #[test] - fn production_verifying_key_serializes_to_the_embedded_bytes() { - let verifying_key = - VerifyingKey::>::try_from_bytes( - RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, - ) - .expect("production recursive verifying key bytes should deserialize"); - assert_eq!( - verifying_key.to_bytes_vec().expect("serialize should succeed"), - RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, - "the embedded production recursive verifying key must be its own canonical serialization" - ); - } -} diff --git a/mithril-stm/src/circuits/halo2_ivc/keys.rs b/mithril-stm/src/circuits/halo2_ivc/keys.rs index 3a694092886..8c49b1920d4 100644 --- a/mithril-stm/src/circuits/halo2_ivc/keys.rs +++ b/mithril-stm/src/circuits/halo2_ivc/keys.rs @@ -1,12 +1,14 @@ // Per-circuit key newtypes for the recursive (IVC) circuit, and the circuit's implementation of -// [`KeyGenerator`]. The newtypes wrap the raw PLONK keys and delegate their byte -// (de)serialization to the impls in `key_serialization`; the raw keys stay internal to -// `circuits::halo2_ivc`, where Halo2's keygen APIs require them. +// [`KeyGenerator`]. The newtypes wrap Midnight's self-describing `MidnightVK` / `MidnightPK`, the +// same format the certificate circuit's keys use; the raw verifying key stays reachable for the +// accumulator and verifier code that needs it. +use std::io::Read; + +use anyhow::{Context, anyhow}; use midnight_curves::Bls12; -use midnight_proofs::plonk::{keygen_pk, keygen_vk_with_k}; use midnight_proofs::poly::commitment::Params; use midnight_proofs::poly::kzg::params::ParamsKZG; -use midnight_zk_stdlib::MidnightCircuit; +use midnight_zk_stdlib::{self as zk, MidnightPK, MidnightVK}; use serde::{Deserialize, Serialize}; use crate::StmResult; @@ -14,38 +16,43 @@ use crate::circuits::halo2::circuit::CertificateCircuit; use crate::circuits::halo2::keys::NonRecursiveCircuitVerifyingKey; use crate::circuits::key_generator::KeyGenerator; use crate::circuits::key_provider::KeyProvider; +use crate::circuits::key_serialization::KEY_SERDE_FORMAT; +use crate::circuits::trusted_setup::MIDNIGHT_SRS_DEGREE; use crate::codec::{TryFromBytes, TryToBytes}; use super::{ - KZGCommitmentScheme, NativeField, PairingEngine, ProvingKey, RECURSIVE_CIRCUIT_DEGREE, - VerifyingKey, circuit::IvcCircuit, + ConstraintSystem, KZGCommitmentScheme, NativeField, PairingEngine, RECURSIVE_CIRCUIT_DEGREE, + VerifyingKey, ZkStdLib, ZkStdLibArch, + circuit::{IvcCircuit, recursive_circuit_architecture}, + errors::IvcCircuitError, }; /// Verifying key of the recursive (IVC) circuit. #[derive(Clone, Debug, Serialize, Deserialize)] pub(crate) struct RecursiveCircuitVerifyingKey( - #[serde(with = "recursive_verifying_key_serde")] - VerifyingKey>, + #[serde(with = "recursive_verifying_key_serde")] MidnightVK, ); /// Proving key of the recursive (IVC) circuit. -pub(crate) struct RecursiveCircuitProvingKey( - ProvingKey>, -); +#[derive(Clone)] +pub(crate) struct RecursiveCircuitProvingKey(MidnightPK); impl RecursiveCircuitVerifyingKey { - /// Wraps a raw recursive verifying key. - pub(crate) fn new( - verifying_key: VerifyingKey>, - ) -> Self { - Self(verifying_key) + /// Wraps a Midnight verifying key. + pub(crate) fn new(midnight_vk: MidnightVK) -> Self { + Self(midnight_vk) + } + + /// Borrows the wrapped Midnight verifying key. + pub(crate) fn midnight_vk(&self) -> &MidnightVK { + &self.0 } - /// Borrows the wrapped raw verifying key, for the prover/verifier and fixed-base construction. + /// Borrows the raw verifying key, for the prover/verifier and fixed-base construction. pub(crate) fn verifying_key( &self, ) -> &VerifyingKey> { - &self.0 + self.0.vk() } #[cfg(test)] @@ -53,32 +60,159 @@ impl RecursiveCircuitVerifyingKey { pub(crate) fn circuit_degree(&self) -> u32 { self.as_ref().get_domain().k() } + + /// Rejects a declared degree that is not the recursive circuit's. + /// + /// Both readers derive a range bit length from their degree as `k - 1` on a byte, which + /// underflows for a zero degree, and a large degree reaches a domain constructor that asserts. + /// So every declared degree is checked before the bytes reach the dependency. + fn validate_declared_degree(degree: u8) -> StmResult<()> { + if u32::from(degree) != RECURSIVE_CIRCUIT_DEGREE { + return Err(anyhow!(IvcCircuitError::IvcVerificationKeyDegreeMismatch { + expected: RECURSIVE_CIRCUIT_DEGREE, + actual: u32::from(degree), + })); + } + Ok(()) + } + + /// Number of fixed commitments the approved architecture produces. + /// + /// The reader takes this count from the bytes and reads that many commitments, so a key can + /// declare fewer than the configured constraint system has columns; later verification indexes + /// commitments by those columns. + fn expected_fixed_commitment_count() -> usize { + let mut constraint_system = ConstraintSystem::::default(); + ZkStdLib::configure( + &mut constraint_system, + ( + recursive_circuit_architecture(), + (RECURSIVE_CIRCUIT_DEGREE - 1) as u8, + ), + ); + // Selectors become fixed columns when the key is read. + constraint_system.num_fixed_columns() + constraint_system.num_selectors() + } + + /// Checks every degree an encoded recursive proving key declares, before it is decoded. + /// + /// The reader takes four in turn without validating any: its own, the two the relation's + /// certificate key declares, and the one inside the wrapped raw recursive key. Each reaches a + /// `k - 1` subtraction on a byte or a domain constructor that asserts, so all four are checked + /// here. + fn validate_encoded_proving_key_header(bytes: &[u8]) -> StmResult<()> { + let mut reader = bytes; + + let mut proving_key_degree = [0u8; 1]; + reader + .read_exact(&mut proving_key_degree) + .with_context(|| "Failed to read the recursive proving key degree")?; + Self::validate_declared_degree(proving_key_degree[0])?; + + // The relation carries the certificate key it was generated against, length prefixed. + let mut certificate_key_length = [0u8; 4]; + reader + .read_exact(&mut certificate_key_length) + .with_context(|| "Failed to read the relation's certificate key length")?; + let certificate_key_length = u32::from_le_bytes(certificate_key_length) as usize; + let certificate_key = reader + .get(..certificate_key_length) + .ok_or_else(|| anyhow!("The relation's certificate key is truncated"))?; + validate_certificate_key_declared_degrees(certificate_key)?; + reader = &reader[certificate_key_length..]; + + // The wrapped raw recursive key opens with its own version and degree. + let mut raw_header = [0u8; 2]; + reader + .read_exact(&mut raw_header) + .with_context(|| "Failed to read the wrapped raw proving key header")?; + Self::validate_declared_degree(raw_header[1]) + } + + /// Checks the header of an encoded recursive verifying key before it is decoded. + /// + /// `MidnightVK` takes its architecture and degree from the bytes, so without this any Midnight + /// circuit's key would decode in the recursive position. Both degrees are checked: the envelope + /// declares one and the raw key it wraps declares another, and the reader takes them + /// independently without comparing them, so an envelope claiming the expected degree can carry + /// a raw key of a different one. + fn validate_encoded_header(bytes: &[u8]) -> StmResult<()> { + let mut reader = bytes; + let architecture = ZkStdLibArch::read_from_serialized_vk(&mut reader) + .with_context(|| "Failed to read the recursive verifying key architecture")?; + if architecture != recursive_circuit_architecture() { + return Err(anyhow!( + IvcCircuitError::RecursiveVerificationKeyArchitectureMismatch + )); + } + + let mut envelope_degree = [0u8; 1]; + reader + .read_exact(&mut envelope_degree) + .with_context(|| "Failed to read the recursive verifying key degree")?; + Self::validate_declared_degree(envelope_degree[0])?; + + let mut public_input_count = [0u8; 4]; + reader + .read_exact(&mut public_input_count) + .with_context(|| "Failed to read the recursive verifying key public input count")?; + + // The wrapped raw key opens with its own version and degree. + let mut raw_header = [0u8; 2]; + reader + .read_exact(&mut raw_header) + .with_context(|| "Failed to read the wrapped raw verifying key header")?; + Self::validate_declared_degree(raw_header[1]) + } } impl AsRef>> for RecursiveCircuitVerifyingKey { fn as_ref(&self) -> &VerifyingKey> { - &self.0 + self.0.vk() } } impl RecursiveCircuitProvingKey { - /// Wraps a raw recursive proving key. - pub(crate) fn new( - proving_key: ProvingKey>, - ) -> Self { - Self(proving_key) + /// Wraps a Midnight proving key. + pub(crate) fn new(midnight_pk: MidnightPK) -> Self { + Self(midnight_pk) } - /// Borrows the wrapped raw proving key, for proof generation. - pub(crate) fn proving_key( - &self, - ) -> &ProvingKey> { + /// Borrows the wrapped Midnight proving key, for proof generation. + pub(crate) fn midnight_pk(&self) -> &MidnightPK { &self.0 } } +/// Serde for the wrapped Midnight verifying key, routed through the newtype's guarded byte decoder +/// so the verifier-data envelopes cannot reach the dependency's readers unchecked. +mod recursive_verifying_key_serde { + use midnight_zk_stdlib::MidnightVK; + use serde::{Deserializer, Serializer}; + + use super::RecursiveCircuitVerifyingKey; + use crate::codec::{TryFromBytes, TryToBytes}; + + pub(super) fn serialize( + verifying_key: &MidnightVK, + serializer: S, + ) -> Result { + let bytes = verifying_key.to_bytes_vec().map_err(serde::ser::Error::custom)?; + serializer.serialize_bytes(&bytes) + } + + pub(super) fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result { + let bytes: Vec = serde::Deserialize::deserialize(deserializer)?; + RecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .map(|key| key.0) + .map_err(serde::de::Error::custom) + } +} + impl TryToBytes for RecursiveCircuitVerifyingKey { fn to_bytes_vec(&self) -> StmResult> { self.0.to_bytes_vec() @@ -87,51 +221,104 @@ impl TryToBytes for RecursiveCircuitVerifyingKey { impl TryFromBytes for RecursiveCircuitVerifyingKey { fn try_from_bytes(bytes: &[u8]) -> StmResult { - Ok(Self(VerifyingKey::< - NativeField, - KZGCommitmentScheme, - >::try_from_bytes(bytes)?)) + Self::validate_encoded_header(bytes)?; + + let mut reader = bytes; + let midnight_vk = MidnightVK::read(&mut reader, KEY_SERDE_FORMAT) + .with_context(|| "Failed to deserialize the recursive verifying key")?; + // A standalone encoding holds one key and nothing else; the streaming asset reader, where a + // fixed-base map follows the key, deliberately does not go through here. + if !reader.is_empty() { + return Err(anyhow!( + IvcCircuitError::RecursiveKeyEncodingHasTrailingBytes { + trailing: reader.len(), + } + )); + } + + let actual = midnight_vk.vk().fixed_commitments().len(); + let expected = Self::expected_fixed_commitment_count(); + if actual != expected { + return Err(anyhow!( + IvcCircuitError::RecursiveVerificationKeyCommitmentCountMismatch { + expected, + actual + } + )); + } + + Ok(Self(midnight_vk)) } } impl TryToBytes for RecursiveCircuitProvingKey { fn to_bytes_vec(&self) -> StmResult> { - self.0.to_bytes_vec() + let mut bytes = Vec::new(); + self.0 + .write(&mut bytes, KEY_SERDE_FORMAT) + .with_context(|| "Failed to serialize the recursive proving key")?; + Ok(bytes) } } impl TryFromBytes for RecursiveCircuitProvingKey { fn try_from_bytes(bytes: &[u8]) -> StmResult { - Ok(Self(ProvingKey::< - NativeField, - KZGCommitmentScheme, - >::try_from_bytes(bytes)?)) + RecursiveCircuitVerifyingKey::validate_encoded_proving_key_header(bytes)?; + + let mut reader = bytes; + Ok(Self( + MidnightPK::::read(&mut reader, KEY_SERDE_FORMAT) + .with_context(|| "Failed to deserialize the recursive proving key")?, + )) } } -/// Serde for the wrapped raw recursive verifying key: the raw-bytes encoding pinned to the IVC -/// circuit (via the key's byte codec), matching the embedded verifier-data wire format. -mod recursive_verifying_key_serde { - use serde::{Deserializer, Serializer}; +/// Rejects a certificate key whose declared degrees could not belong to any supported certificate +/// circuit. +/// +/// A certificate key is generated from the trusted setup, so it cannot exceed that setup's degree. +/// +/// Certificate degrees legitimately vary — production, the full fixture and the small golden context +/// all differ — so these are bounded rather than pinned. The key declares a degree twice, once in +/// its Midnight envelope and once in the raw key it wraps, and the reader takes them independently: +/// an envelope declaring a supported degree can wrap a raw key declaring any other, which reaches a +/// domain constructor that asserts. +fn validate_certificate_key_declared_degrees(certificate_key: &[u8]) -> StmResult<()> { + let mut reader = certificate_key; + ZkStdLibArch::read_from_serialized_vk(&mut reader) + .with_context(|| "Failed to read the certificate key architecture")?; - use super::{KZGCommitmentScheme, NativeField, PairingEngine, VerifyingKey}; - use crate::codec::{TryFromBytes, TryToBytes}; + let mut envelope_degree = [0u8; 1]; + reader + .read_exact(&mut envelope_degree) + .with_context(|| "Failed to read the certificate key envelope degree")?; - pub(super) fn serialize( - verifying_key: &VerifyingKey>, - serializer: S, - ) -> Result { - let bytes = verifying_key.to_bytes_vec().map_err(serde::ser::Error::custom)?; - serializer.serialize_bytes(&bytes) - } + let mut public_input_count = [0u8; 4]; + reader + .read_exact(&mut public_input_count) + .with_context(|| "Failed to read the certificate key public input count")?; - pub(super) fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result>, D::Error> { - let bytes: Vec = serde::Deserialize::deserialize(deserializer)?; - VerifyingKey::>::try_from_bytes(&bytes) - .map_err(serde::de::Error::custom) + // The wrapped raw key opens with its own version and degree. + let mut raw_header = [0u8; 2]; + reader + .read_exact(&mut raw_header) + .with_context(|| "Failed to read the wrapped raw certificate key header")?; + + for degree in [envelope_degree[0], raw_header[1]] { + if degree == 0 || degree > MIDNIGHT_SRS_DEGREE { + return Err(anyhow!(IvcCircuitError::IvcVerificationKeyDegreeMismatch { + expected: u32::from(MIDNIGHT_SRS_DEGREE), + actual: u32::from(degree), + })); + } + } + if envelope_degree[0] != raw_header[1] { + return Err(anyhow!(IvcCircuitError::IvcVerificationKeyDegreeMismatch { + expected: u32::from(envelope_degree[0]), + actual: u32::from(raw_header[1]), + })); } + Ok(()) } impl KeyGenerator for IvcCircuit { @@ -150,15 +337,15 @@ impl KeyGenerator for IvcCircuit { srs.max_k() >= RECURSIVE_CIRCUIT_DEGREE, "the SRS must be at least the recursive circuit degree" ); - let circuit = MidnightCircuit::from_relation(self, Some(RECURSIVE_CIRCUIT_DEGREE)); + // `setup_vk` takes the degree from the SRS, so it must be exactly the circuit's. let verifying_key = if srs.max_k() == RECURSIVE_CIRCUIT_DEGREE { - keygen_vk_with_k(srs, &circuit, RECURSIVE_CIRCUIT_DEGREE)? + zk::setup_vk(srs, self) } else { let mut recursive_srs = srs.clone(); recursive_srs.downsize(RECURSIVE_CIRCUIT_DEGREE); - keygen_vk_with_k(&recursive_srs, &circuit, RECURSIVE_CIRCUIT_DEGREE)? + zk::setup_vk(&recursive_srs, self) }; - let proving_key = keygen_pk(verifying_key.clone(), &circuit)?; + let proving_key = zk::setup_pk(self, &verifying_key); Ok(( RecursiveCircuitVerifyingKey(verifying_key), RecursiveCircuitProvingKey(proving_key), @@ -209,6 +396,7 @@ impl KeyGenerator for RecursiveCircuitKeyGenerator { #[cfg(test)] mod tests { use super::*; + use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; #[test] @@ -223,4 +411,205 @@ mod tests { "the recursive verifying key newtype must round-trip to the embedded production bytes" ); } + + // Both circuits now encode their keys the same way, so only the declared architecture separates + // them: without the header check the certificate's key would decode in the recursive position. + #[test] + fn a_certificate_verifying_key_is_rejected_in_the_recursive_position() { + let error = RecursiveCircuitVerifyingKey::try_from_bytes( + NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, + ) + .expect_err("a certificate key must not decode as a recursive one"); + + assert!( + matches!( + error.downcast_ref::(), + Some(IvcCircuitError::RecursiveVerificationKeyArchitectureMismatch) + ), + "expected an architecture mismatch, got: {error}" + ); + } + + // `MidnightVK::read` derives its range bit length as `k - 1` on a byte, so a zero degree would + // underflow inside the dependency before any check of ours could run. + #[test] + fn a_zero_degree_header_is_rejected_before_it_can_underflow() { + let mut bytes = RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + // The degree byte follows the encoded architecture. + let mut reader = RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); + let degree_index = bytes.len() - reader.len(); + bytes[degree_index] = 0; + + let error = RecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .expect_err("a zero degree must be rejected"); + + assert!( + matches!( + error.downcast_ref::(), + Some(IvcCircuitError::IvcVerificationKeyDegreeMismatch { actual: 0, .. }) + ), + "expected a degree mismatch, got: {error}" + ); + } + + // The envelope and the raw key it wraps each declare a degree, and the reader takes them + // independently, so an envelope claiming the right one can still carry a raw key of another. + #[test] + fn a_wrapped_raw_key_of_the_wrong_degree_is_rejected() { + let mut bytes = RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + // Past the architecture, the envelope degree and the public input count lies the raw + // key's own version byte, and its degree follows. + let mut reader = RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); + let raw_degree_index = bytes.len() - reader.len() + 1 + 4 + 1; + assert_eq!( + u32::from(bytes[raw_degree_index]), + RECURSIVE_CIRCUIT_DEGREE, + "the wrapped raw key should declare the recursive degree before it is mutated" + ); + bytes[raw_degree_index] = 20; + + let error = RecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .expect_err("a wrapped key of another degree must be rejected"); + + assert!( + matches!( + error.downcast_ref::(), + Some(IvcCircuitError::IvcVerificationKeyDegreeMismatch { actual: 20, .. }) + ), + "expected a degree mismatch, got: {error}" + ); + } + + // Deriving Deserialize would otherwise reach the dependency's reader without the guard, and + // that is the path the verifier data envelopes take. + #[test] + fn serde_rejects_a_certificate_verifying_key_in_the_recursive_position() { + let encoded = serde_json::to_vec(&serde_bytes::ByteBuf::from( + NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(), + )) + .expect("the certificate key bytes should encode"); + + let error = serde_json::from_slice::(&encoded) + .expect_err("a certificate key must not deserialize as a recursive one"); + + assert!( + error.to_string().contains("architecture"), + "expected an architecture mismatch, got: {error}" + ); + } + + // The proving key opens with its own degree, taken into the same `k - 1` arithmetic. + #[test] + fn a_proving_key_of_the_wrong_degree_is_rejected() { + let error = match RecursiveCircuitProvingKey::try_from_bytes(&[0u8; 8]) { + Ok(_) => panic!("a zero degree proving key must be rejected"), + Err(error) => error, + }; + + assert!( + matches!( + error.downcast_ref::(), + Some(IvcCircuitError::IvcVerificationKeyDegreeMismatch { actual: 0, .. }) + ), + "expected a degree mismatch, got: {error}" + ); + } + + // A standalone encoding holds one key and nothing else, so anything appended is a decoder + // disagreement rather than harmless padding. + #[test] + fn a_standalone_encoding_with_trailing_bytes_is_rejected() { + let mut bytes = RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + bytes.extend_from_slice(&[0u8; 4]); + + let error = RecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .expect_err("trailing bytes must be rejected"); + + assert!( + matches!( + error.downcast_ref::(), + Some(IvcCircuitError::RecursiveKeyEncodingHasTrailingBytes { trailing: 4 }) + ), + "expected trailing bytes, got: {error}" + ); + } + + // The reader takes the commitment count from the bytes and reads that many, so a key can + // declare fewer than the configured constraint system has columns. + #[test] + fn a_key_declaring_too_few_fixed_commitments_is_rejected() { + use midnight_proofs::utils::helpers::byte_length; + + let mut reader = RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); + // envelope degree, public input count, then the raw key's version and degree + let count_index = + RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.len() - reader.len() + 1 + 4 + 2; + + let mut bytes = RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + let declared = u32::from_le_bytes(bytes[count_index..count_index + 4].try_into().unwrap()); + assert_eq!( + declared as usize, + RecursiveCircuitVerifyingKey::expected_fixed_commitment_count(), + "the production key should declare the configured commitment count" + ); + + // Drop one commitment along with the count, so the encoding stays internally consistent and + // fully consumed: only the cardinality guard can reject it. + let commitment_length = byte_length::< + as midnight_proofs::poly::commitment::PolynomialCommitmentScheme>::Commitment, + >(KEY_SERDE_FORMAT); + let commitments_start = count_index + 4; + bytes.drain(commitments_start..commitments_start + commitment_length); + bytes[count_index..count_index + 4].copy_from_slice(&(declared - 1).to_le_bytes()); + + let error = RecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .expect_err("a short commitment count must be rejected"); + + assert!( + matches!( + error.downcast_ref::(), + Some(IvcCircuitError::RecursiveVerificationKeyCommitmentCountMismatch { + actual, + .. + }) if *actual as u32 == declared - 1 + ), + "expected a commitment count mismatch, got: {error}" + ); + } + + // The relation's certificate key declares a degree twice and the reader takes them + // independently, so a supported envelope can wrap a raw key of any other degree. + #[test] + fn a_proving_key_whose_certificate_inner_degree_disagrees_is_rejected() { + use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + + let mut certificate_key = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + let mut reader = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); + let raw_degree_index = + NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.len() - reader.len() + 1 + 4 + 1; + certificate_key[raw_degree_index] = 32; + + // A proving key encoding: its degree, then the length prefixed relation. + let mut bytes = vec![RECURSIVE_CIRCUIT_DEGREE as u8]; + bytes.extend_from_slice(&(certificate_key.len() as u32).to_le_bytes()); + bytes.extend_from_slice(&certificate_key); + bytes.extend_from_slice(&[0u8; 8]); + + let error = match RecursiveCircuitProvingKey::try_from_bytes(&bytes) { + Ok(_) => panic!("a disagreeing certificate degree must be rejected"), + Err(error) => error, + }; + + assert!( + matches!( + error.downcast_ref::(), + Some(IvcCircuitError::IvcVerificationKeyDegreeMismatch { actual: 32, .. }) + ), + "expected a degree mismatch, got: {error}" + ); + } } diff --git a/mithril-stm/src/circuits/halo2_ivc/mod.rs b/mithril-stm/src/circuits/halo2_ivc/mod.rs index f0caa5a2b55..94810e063b5 100644 --- a/mithril-stm/src/circuits/halo2_ivc/mod.rs +++ b/mithril-stm/src/circuits/halo2_ivc/mod.rs @@ -31,7 +31,7 @@ pub(crate) use midnight_circuits::{ pub(crate) use midnight_proofs::{ circuit::{Layouter, Value}, - plonk::{ConstraintSystem, Error, ProvingKey, VerifyingKey}, + plonk::{ConstraintSystem, Error, VerifyingKey}, poly::{EvaluationDomain, kzg::KZGCommitmentScheme}, }; @@ -48,8 +48,6 @@ pub(crate) mod errors; pub(crate) mod gadgets; pub(crate) mod io; #[cfg_attr(not(test), allow(dead_code))] -pub(crate) mod key_serialization; -#[cfg_attr(not(test), allow(dead_code))] pub(crate) mod keys; #[cfg(test)] pub(crate) mod protocol_message; diff --git a/mithril-stm/src/circuits/halo2_ivc/recursive_circuit_verification_key_for_production.bin b/mithril-stm/src/circuits/halo2_ivc/recursive_circuit_verification_key_for_production.bin index 89b19f59e410655c5ba1a8b2a00ce042db797b82..aa7513b71dccfeafaa66fbf1502bb7fc121c0507 100644 GIT binary patch delta 31 gcmZ3czgJ(JiGhKEk&zKdf*3%|B3uC^H!Kqe06we(+W-In delta 8 Pcmdn1zf6Cl_%d+-5Of1a diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/assets/golden_recursive_circuit_verification_key.bin b/mithril-stm/src/circuits/halo2_ivc/tests/assets/golden_recursive_circuit_verification_key.bin index c57615eb5e7032a05acff4aef160f85996c0268b..872b226061bb98c13879f8be51eda55e32b9d8e9 100644 GIT binary patch delta 31 gcmZ3czgJ(JiGhKEk&zKdf*3%|B3uC^H!Kqe06we(+W-In delta 8 Pcmdn1zf6Cl_%d+-5Of1a diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/assets/verification_context.bin b/mithril-stm/src/circuits/halo2_ivc/tests/assets/verification_context.bin index d08b7d3322e80dcabd8a4fa86fa523058eb1d27c..8d3c3f21db0387f49da4196bfec8d9771459ec9b 100644 GIT binary patch delta 36 lcmbQXlyU7+#t93=nHU%t7#SIXB!~gTEW#B)a^r$;fdGhi2Lu2B delta 12 UcmZ3xlyTZp#t92HZvPet04HPyZvX%Q diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/asset_readers.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/asset_readers.rs index 5a51d59de54..5afe123c6c1 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/asset_readers.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/asset_readers.rs @@ -136,7 +136,7 @@ pub(crate) fn store_verification_context_asset( } asset .recursive_verifying_key - .verifying_key() + .midnight_vk() .write(&mut writer, SerdeFormat::RawBytesUnchecked)?; write_named_fixed_bases(&mut writer, &asset.combined_fixed_bases)?; diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs index 36bc2f2cad2..cb2a662c69e 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs @@ -46,7 +46,7 @@ where MidnightCircuit, >( commitment_parameters, - proving_key.proving_key(), + proving_key.midnight_pk().pk(), std::slice::from_ref(&MidnightCircuit::new( ivc_circuit, Value::known(public_inputs.to_vec()), diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs index 53fbefded9e..144cb33c632 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs @@ -1,4 +1,4 @@ -use midnight_zk_stdlib::MidnightCircuit; +use midnight_zk_stdlib::{self as zk}; use std::{ collections::BTreeMap, io::Write, @@ -7,10 +7,7 @@ use std::{ use ff::Field; use midnight_curves::Bls12; -use midnight_proofs::{ - plonk::{keygen_pk, keygen_vk_with_k}, - poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}, -}; +use midnight_proofs::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use midnight_zk_stdlib as zk_lib; use rand_chacha::ChaCha20Rng; use rand_core::{CryptoRng, RngCore, SeedableRng}; @@ -218,14 +215,10 @@ fn derive_recursive_verifying_key( certificate_verifying_key: &NonRecursiveCircuitVerifyingKey, ) -> RecursiveCircuitVerifyingKey { let default_ivc_circuit = IvcCircuit::for_key_generation(certificate_verifying_key); - RecursiveCircuitVerifyingKey::new( - keygen_vk_with_k( - recursive_commitment_parameters, - &MidnightCircuit::from_relation(&default_ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)), - RECURSIVE_CIRCUIT_DEGREE, - ) - .expect("recursive verifying key generation should not fail"), - ) + RecursiveCircuitVerifyingKey::new(zk::setup_vk( + recursive_commitment_parameters, + &default_ivc_circuit, + )) } /// Content-keyed cache entry holding the recursive verifying key derived from these inputs. @@ -422,13 +415,10 @@ pub(crate) fn build_recursive_proving_key( context: &SharedRecursiveContext, ) -> RecursiveCircuitProvingKey { let default_ivc_circuit = IvcCircuit::for_key_generation(&context.certificate_verifying_key); - RecursiveCircuitProvingKey::new( - keygen_pk( - context.recursive_verifying_key.verifying_key().clone(), - &MidnightCircuit::from_relation(&default_ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)), - ) - .expect("recursive proving key generation should not fail"), - ) + RecursiveCircuitProvingKey::new(zk::setup_pk( + &default_ivc_circuit, + context.recursive_verifying_key.midnight_vk(), + )) } /// Returns the certificate, recursive, and combined fixed-base maps. diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/verification_key.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/verification_key.rs index b84c43ffd0d..53e17c6c2ec 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/verification_key.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/verification_key.rs @@ -1,18 +1,14 @@ use std::path::Path; -use midnight_proofs::{ - plonk::{VerifyingKey, keygen_vk_with_k}, - poly::kzg::KZGCommitmentScheme, - utils::SerdeFormat, -}; -use midnight_zk_stdlib::MidnightCircuit; +use midnight_proofs::utils::SerdeFormat; +use midnight_zk_stdlib::{self as zk, MidnightCircuit}; use crate::{ Parameters, circuits::{ halo2::circuit::CertificateCircuit, halo2::keys::NonRecursiveCircuitVerifyingKey, - halo2_ivc::{NativeField, PairingEngine, RECURSIVE_CIRCUIT_DEGREE, circuit::IvcCircuit}, + halo2_ivc::{RECURSIVE_CIRCUIT_DEGREE, circuit::IvcCircuit}, }, }; @@ -40,13 +36,7 @@ pub(crate) fn golden_recursive_circuit_verification_key_bytes() -> Vec { ); let default_ivc_circuit = IvcCircuit::for_key_generation(&circuit_verification_key); - let recursive_verifying_key: VerifyingKey> = - keygen_vk_with_k( - &srs_for_recursive_circuit, - &MidnightCircuit::from_relation(&default_ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)), - RECURSIVE_CIRCUIT_DEGREE, - ) - .expect("recursive verifying key generation should not fail"); + let recursive_verifying_key = zk::setup_vk(&srs_for_recursive_circuit, &default_ivc_circuit); let mut buf_cvk = vec![]; recursive_verifying_key diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/off_circuit/circuit_validation.rs b/mithril-stm/src/circuits/halo2_ivc/tests/off_circuit/circuit_validation.rs index 889c0f7edcd..a3c451f3f53 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/off_circuit/circuit_validation.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/off_circuit/circuit_validation.rs @@ -14,7 +14,7 @@ fn validate_ivc_verification_key_degree_rejects_wrong_degree_vk() { let ctx = load_embedded_verification_context_asset().expect("verification context asset should load"); let wrong_degree_vk = - RecursiveCircuitVerifyingKey::new(ctx.certificate_verifying_key.midnight_vk().vk().clone()); + RecursiveCircuitVerifyingKey::new(ctx.certificate_verifying_key.midnight_vk().clone()); let actual_degree = wrong_degree_vk.verifying_key().get_domain().k(); let result = IvcCircuit::validate_ivc_verification_key_degree(&wrong_degree_vk); diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/verification_key_computation.rs b/mithril-stm/src/circuits/halo2_ivc/tests/verification_key_computation.rs index c131dd8d6f6..fa6dae5038b 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/verification_key_computation.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/verification_key_computation.rs @@ -1,10 +1,6 @@ use anyhow::Context; -use midnight_proofs::{ - plonk::{VerifyingKey, keygen_vk_with_k}, - poly::kzg::KZGCommitmentScheme, - utils::SerdeFormat, -}; -use midnight_zk_stdlib::{MidnightCircuit, MidnightVK}; +use midnight_proofs::utils::SerdeFormat; +use midnight_zk_stdlib::{self as zk, MidnightVK}; use crate::{ StmResult, @@ -14,8 +10,8 @@ use crate::{ keys::NonRecursiveCircuitVerifyingKey, }, halo2_ivc::{ - NativeField, PairingEngine, RECURSIVE_CIRCUIT_DEGREE, - RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, circuit::IvcCircuit, + RECURSIVE_CIRCUIT_DEGREE, RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, + circuit::IvcCircuit, }, trusted_setup::TrustedSetupProvider, }, @@ -38,12 +34,8 @@ fn compute_recursive_circuit_verification_key() -> StmResult> { ); let default_ivc_circuit = IvcCircuit::for_key_generation(&certificate_verifying_key); - let recursive_verification_key: VerifyingKey> = - keygen_vk_with_k( - &recursive_commitment_parameters, - &MidnightCircuit::from_relation(&default_ivc_circuit, Some(RECURSIVE_CIRCUIT_DEGREE)), - shared_srs_degree, - )?; + let recursive_verification_key = + zk::setup_vk(&recursive_commitment_parameters, &default_ivc_circuit); let mut buffer_for_recursive_circuit_verification_key = vec![]; recursive_verification_key diff --git a/mithril-stm/src/circuits/trusted_setup.rs b/mithril-stm/src/circuits/trusted_setup.rs index 61b754d286f..caa8bfbd48a 100644 --- a/mithril-stm/src/circuits/trusted_setup.rs +++ b/mithril-stm/src/circuits/trusted_setup.rs @@ -22,6 +22,8 @@ use {rand_chacha::ChaCha20Rng, rand_core::SeedableRng, std::fs::create_dir_all}; /// the proper value available here: https://github.com/midnightntwrk/midnight-trusted-setup/blob/main/MIDNIGHT_SRS_CATALOG.md pub(crate) const MIDNIGHT_SRS_HASH_K22: &str = "e8ad5eed936d657a0fb59d2a55ba19f81a3083bb3554ef88f464f5377e9b2c2f"; +/// Degree of the SRS the two hashes above identify: the largest circuit it can support. +pub(crate) const MIDNIGHT_SRS_DEGREE: u8 = 22; /// Constant storing URL to download the SRS of degree 22 used to create proof in production const MIDNIGHT_SRS_URL_K22: &str = "https://srs.midnight.network/midnight-srs-2p22"; /// Constant holding the folder of the SRS file diff --git a/mithril-stm/src/proof_system/halo2_ivc_snark/proof.rs b/mithril-stm/src/proof_system/halo2_ivc_snark/proof.rs index 2f430a51621..9e0965a8f0e 100644 --- a/mithril-stm/src/proof_system/halo2_ivc_snark/proof.rs +++ b/mithril-stm/src/proof_system/halo2_ivc_snark/proof.rs @@ -303,7 +303,7 @@ where MidnightCircuit, >( srs, - proving_key.proving_key(), + proving_key.midnight_pk().pk(), std::slice::from_ref(&circuit), 1, &[&[&[], public_inputs]], diff --git a/mithril-stm/src/proof_system/halo2_ivc_snark/verifier_setup.rs b/mithril-stm/src/proof_system/halo2_ivc_snark/verifier_setup.rs index 0a002b79c9e..315d11774f6 100644 --- a/mithril-stm/src/proof_system/halo2_ivc_snark/verifier_setup.rs +++ b/mithril-stm/src/proof_system/halo2_ivc_snark/verifier_setup.rs @@ -331,4 +331,52 @@ mod tests { "embedded IVC verifier params must match the Midnight trusted SRS" ); } + // The recursive key's guard must hold at the public envelope, not only at the byte codec: this + // is the path a certificate carries, and the two circuits now share one key encoding. + #[test] + fn verifier_data_rejects_a_certificate_key_in_the_recursive_position() { + use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + + // Mirrors `IvcVerifierData`'s CBOR shape with both keys as opaque bytes, so the recursive + // slot can carry an encoding the typed constructor would never allow. + #[derive(serde::Serialize)] + struct VerifierDataWithOpaqueKeys { + genesis_message: MessageHash, + #[serde(with = "serde_bytes")] + certificate_circuit_verification_key: Vec, + #[serde(with = "serde_bytes")] + ivc_circuit_verification_key: Vec, + } + + let tampered = VerifierDataWithOpaqueKeys { + genesis_message: MessageHash::ZERO, + certificate_circuit_verification_key: + NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(), + // A certificate key where the recursive one belongs. + ivc_circuit_verification_key: NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION + .to_vec(), + }; + let bytes = crate::codec::to_cbor_bytes(&tampered).expect("the mirror should encode"); + + let error = IvcVerifierData::from_bytes(&bytes) + .expect_err("a certificate key must not decode in the recursive position"); + assert!( + error.to_string().contains("architecture") + || format!("{error:#}").contains("architecture"), + "expected an architecture mismatch, got: {error:#}" + ); + + // The same envelope with the real recursive key decodes, so the rejection is the guard and + // not the mirror's shape. + let valid = VerifierDataWithOpaqueKeys { + genesis_message: MessageHash::ZERO, + certificate_circuit_verification_key: + NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(), + ivc_circuit_verification_key: RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION + .to_vec(), + }; + let bytes = crate::codec::to_cbor_bytes(&valid).expect("the mirror should encode"); + IvcVerifierData::from_bytes(&bytes).expect("the real recursive key should decode"); + } } diff --git a/mithril-stm/src/protocol/aggregate_signature/ancillary_data.rs b/mithril-stm/src/protocol/aggregate_signature/ancillary_data.rs index b7cf8b21f4f..bcde56bb61e 100644 --- a/mithril-stm/src/protocol/aggregate_signature/ancillary_data.rs +++ b/mithril-stm/src/protocol/aggregate_signature/ancillary_data.rs @@ -499,8 +499,8 @@ mod tests { use sha2::{Digest, Sha256}; const EXPECTED_IVC_ANCILLARY_DIGEST: [u8; 32] = [ - 166, 23, 43, 148, 143, 254, 123, 150, 165, 210, 35, 250, 36, 248, 222, 137, 180, 56, - 75, 253, 228, 42, 64, 179, 144, 17, 68, 254, 99, 93, 223, 242, + 162, 102, 123, 88, 167, 76, 76, 67, 63, 218, 224, 79, 162, 202, 132, 227, 110, 187, 18, + 66, 152, 52, 140, 146, 184, 113, 211, 232, 230, 224, 207, 242, ]; let context = load_embedded_verification_context_asset() .expect("verification context asset should load"); From c733b29d37b293021e81d0c630492ecab54ccf2a Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Wed, 16 Sep 2026 16:54:37 +0900 Subject: [PATCH 05/14] docs(stm): record why the recursive range column count cannot be lowered --- mithril-stm/src/circuits/halo2_ivc/circuit.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mithril-stm/src/circuits/halo2_ivc/circuit.rs b/mithril-stm/src/circuits/halo2_ivc/circuit.rs index 63661ec9fab..4cf8e029e19 100644 --- a/mithril-stm/src/circuits/halo2_ivc/circuit.rs +++ b/mithril-stm/src/circuits/halo2_ivc/circuit.rs @@ -30,6 +30,7 @@ pub(crate) fn recursive_circuit_architecture() -> ZkStdLibArch { secp256k1: false, bls12_381: true, base64: false, + // With production certificate metadata, 1 to 3 range columns require k=20 instead of k=19. nr_pow2range_cols: 4, automaton: false, blake2b: false, From 9e2b3574b9770d1acc9361825ed331dcc397c563 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Wed, 16 Sep 2026 16:56:48 +0900 Subject: [PATCH 06/14] docs(stm): document the source constants that regeneration must update --- .../circuits/halo2_ivc/tests/assets/README.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/assets/README.md b/mithril-stm/src/circuits/halo2_ivc/tests/assets/README.md index 20e79930934..7ce8dec7dc8 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/assets/README.md +++ b/mithril-stm/src/circuits/halo2_ivc/tests/assets/README.md @@ -83,6 +83,40 @@ cargo test -p mithril-stm --features future_snark,rustls --release generate_recu These commands intentionally use `--release` because asset generation is a manual workflow dominated by real proof generation. +## Source Constants To Update + +Regenerating the binaries is only half the work. Four constants are pinned in source and are not +written by any generator. + +| Constant | File | Test that computes it | Computed value appears | +|---|---|---|---| +| production recursive key digest | `circuits/verification_key_digest.rs` | `golden_digests_of_production_circuit_keys` | right | +| verification-context recursive key digest | `circuits/verification_key_digest.rs` | `golden_digests_of_embedded_verification_context_keys` | right | +| `GOLDEN_R` combiner challenge | `proof_system/halo2_ivc_snark/proof.rs` | `golden_combiner_r_for_stored_recursive_step_output` | left | +| `EXPECTED_IVC_ANCILLARY_DIGEST` | `protocol/aggregate_signature/ancillary_data.rs` | `ivc_ancillary_encoding_is_byte_stable` | left | + +The assertions are not written the same way round, so check the last column before copying a value: +taking the wrong side copies the old expected value back, leaving the test failing. + +Each constant binds something different, which is what decides whether it moves: + +- The two key digests bind the raw verifying key's **transcript representation**. +- `GOLDEN_R` binds the **proof verification transcript and accumulator**. +- `EXPECTED_IVC_ANCILLARY_DIGEST` binds the **complete encoded CBOR**, embedded keys included. + +These four checks compute golden values; they do not establish validity. Confirm the regenerated keys +and proofs pass their integrity and verification checks before updating any constant — the +`GOLDEN_R` test in particular prepares the transcript without performing the final pairing check — +then rerun the golden checks. + +So the rule is not to predict which will move. Rerun all four checks after regenerating, and update +only those whose inputs you deliberately changed. Some cases are counter-intuitive: a constraint +system change can alter a transcript representation without altering the serialized commitments; a +key encoding change moves only the ancillary digest, since the other three never see the envelope; +and regenerating a randomized proof can move `GOLDEN_R` even when the circuit identity is untouched. + +If a value changes and you cannot say which input caused it, stop and find out before updating it. + ## When Regeneration Is Needed Regenerate the assets when one of these changes: From 7a53233e0d70bbcff1608e919b6ba5fa7216661b Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Thu, 17 Sep 2026 08:16:16 +0900 Subject: [PATCH 07/14] docs(stm): format the source constants table with prettier --- .../src/circuits/halo2_ivc/tests/assets/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/assets/README.md b/mithril-stm/src/circuits/halo2_ivc/tests/assets/README.md index 7ce8dec7dc8..9a2375fe0de 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/assets/README.md +++ b/mithril-stm/src/circuits/halo2_ivc/tests/assets/README.md @@ -88,12 +88,12 @@ manual workflow dominated by real proof generation. Regenerating the binaries is only half the work. Four constants are pinned in source and are not written by any generator. -| Constant | File | Test that computes it | Computed value appears | -|---|---|---|---| -| production recursive key digest | `circuits/verification_key_digest.rs` | `golden_digests_of_production_circuit_keys` | right | -| verification-context recursive key digest | `circuits/verification_key_digest.rs` | `golden_digests_of_embedded_verification_context_keys` | right | -| `GOLDEN_R` combiner challenge | `proof_system/halo2_ivc_snark/proof.rs` | `golden_combiner_r_for_stored_recursive_step_output` | left | -| `EXPECTED_IVC_ANCILLARY_DIGEST` | `protocol/aggregate_signature/ancillary_data.rs` | `ivc_ancillary_encoding_is_byte_stable` | left | +| Constant | File | Test that computes it | Computed value appears | +| ----------------------------------------- | ------------------------------------------------ | ------------------------------------------------------ | ---------------------- | +| production recursive key digest | `circuits/verification_key_digest.rs` | `golden_digests_of_production_circuit_keys` | right | +| verification-context recursive key digest | `circuits/verification_key_digest.rs` | `golden_digests_of_embedded_verification_context_keys` | right | +| `GOLDEN_R` combiner challenge | `proof_system/halo2_ivc_snark/proof.rs` | `golden_combiner_r_for_stored_recursive_step_output` | left | +| `EXPECTED_IVC_ANCILLARY_DIGEST` | `protocol/aggregate_signature/ancillary_data.rs` | `ivc_ancillary_encoding_is_byte_stable` | left | The assertions are not written the same way round, so check the last column before copying a value: taking the wrong side copies the old expected value back, leaving the test failing. From 8d7ef8d74e9aa055cda7ece84664b42aa605689e Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Fri, 18 Sep 2026 08:11:16 +0900 Subject: [PATCH 08/14] fix(stm): guard the certificate verifying key decoder --- mithril-stm/src/circuits/halo2/circuit.rs | 40 +-- mithril-stm/src/circuits/halo2/errors.rs | 13 + mithril-stm/src/circuits/halo2/keys.rs | 231 +++++++++++++++++- mithril-stm/src/circuits/halo2_ivc/keys.rs | 57 +---- mithril-stm/src/circuits/key_serialization.rs | 32 +-- 5 files changed, 273 insertions(+), 100 deletions(-) diff --git a/mithril-stm/src/circuits/halo2/circuit.rs b/mithril-stm/src/circuits/halo2/circuit.rs index 5d146443b19..167c2c5df29 100644 --- a/mithril-stm/src/circuits/halo2/circuit.rs +++ b/mithril-stm/src/circuits/halo2/circuit.rs @@ -23,6 +23,29 @@ use crate::signature_scheme::{ }; use crate::{LotteryIndex, Parameters, StmResult}; +/// Chips the certificate circuit enables. +/// +/// Single source: the relation declares these to the standard library, and the key decoder checks +/// an encoded key against the same value, so the two cannot drift. +pub(crate) fn certificate_circuit_architecture() -> ZkStdLibArch { + ZkStdLibArch { + jubjub: true, + poseidon: true, + sha2_256: false, + sha2_512: false, + keccak_256: false, + sha3_256: false, + secp256k1: false, + bls12_381: false, + base64: false, + nr_pow2range_cols: 2, + automaton: false, + blake2b: false, + curve25519: false, + p256: false, + } +} + /// Halo2 relation implementing the non-recursive STM verification circuit. /// /// Carries only the parameters that fix the constraint system; the instance and witness are @@ -296,22 +319,7 @@ impl Relation for CertificateCircuit { } fn used_chips(&self) -> ZkStdLibArch { - ZkStdLibArch { - jubjub: true, - poseidon: true, - sha2_256: false, - sha2_512: false, - keccak_256: false, - sha3_256: false, - secp256k1: false, - bls12_381: false, - base64: false, - nr_pow2range_cols: 2, - automaton: false, - blake2b: false, - curve25519: false, - p256: false, - } + certificate_circuit_architecture() } fn write_relation(&self, writer: &mut W) -> std::io::Result<()> { diff --git a/mithril-stm/src/circuits/halo2/errors.rs b/mithril-stm/src/circuits/halo2/errors.rs index 39881efff37..fe5b6644921 100644 --- a/mithril-stm/src/circuits/halo2/errors.rs +++ b/mithril-stm/src/circuits/halo2/errors.rs @@ -115,6 +115,19 @@ pub enum CertificateCircuitError { /// A proving/verification backend error surfaced at the relation boundary. #[error("Backend error: {0}")] Backend(String), + + /// An encoded verification key declares an architecture that is not the certificate circuit's. + #[error("The encoded key does not declare the certificate circuit architecture")] + VerificationKeyArchitectureMismatch, + + /// An encoded verification key declares a degree no certificate circuit could have been + /// generated with, or declares two degrees that disagree. + #[error("Certificate verification key degree mismatch: expected {expected}, got {actual}")] + VerificationKeyDegreeMismatch { expected: u32, actual: u32 }, + + /// A standalone encoded verification key is followed by bytes that are not part of it. + #[error("The encoded certificate verification key has {trailing} trailing byte(s)")] + VerificationKeyEncodingHasTrailingBytes { trailing: usize }, } impl From for CertificateCircuitError { diff --git a/mithril-stm/src/circuits/halo2/keys.rs b/mithril-stm/src/circuits/halo2/keys.rs index 8726402f0e5..20d49331594 100644 --- a/mithril-stm/src/circuits/halo2/keys.rs +++ b/mithril-stm/src/circuits/halo2/keys.rs @@ -2,24 +2,29 @@ // implementation of [`KeyGenerator`]. The newtypes wrap Midnight's self-describing // `MidnightVK` / `MidnightPK` and delegate their byte (de)serialization to the impls in // `key_serialization`. +use std::io::Read; + +use anyhow::{Context, anyhow}; use midnight_curves::Bls12; use midnight_proofs::poly::commitment::Params; use midnight_proofs::poly::kzg::params::ParamsKZG; -use midnight_zk_stdlib::{self as zk, MidnightCircuit, MidnightPK, MidnightVK}; +use midnight_zk_stdlib::{self as zk, MidnightCircuit, MidnightPK, MidnightVK, ZkStdLibArch}; use serde::{Deserialize, Serialize}; use crate::StmResult; +use crate::circuits::halo2::errors::CertificateCircuitError; use crate::circuits::halo2_ivc::{KZGCommitmentScheme, NativeField, PairingEngine, VerifyingKey}; use crate::circuits::key_generator::KeyGenerator; -use crate::circuits::key_serialization::midnight_verifying_key_serde; +use crate::circuits::key_serialization::KEY_SERDE_FORMAT; +use crate::circuits::trusted_setup::MIDNIGHT_SRS_DEGREE; use crate::codec::{TryFromBytes, TryToBytes}; -use super::circuit::CertificateCircuit; +use super::circuit::{CertificateCircuit, certificate_circuit_architecture}; /// Verifying key of the non-recursive certificate circuit. #[derive(Clone, Debug, Serialize, Deserialize)] pub(crate) struct NonRecursiveCircuitVerifyingKey( - #[serde(with = "midnight_verifying_key_serde")] MidnightVK, + #[serde(with = "certificate_verifying_key_serde")] MidnightVK, ); /// Proving key of the non-recursive certificate circuit. @@ -42,6 +47,64 @@ impl NonRecursiveCircuitVerifyingKey { pub(crate) fn circuit_degree(&self) -> u32 { self.midnight_vk().vk().get_domain().k() } + + /// Checks the header of an encoded certificate verifying key before it is decoded. + /// + /// `MidnightVK` takes its architecture and degree from the bytes, so without this any Midnight + /// circuit's key decodes in the certificate position — including the recursive circuit's, which + /// this crate now encodes the same way. + /// + /// Certificate degrees legitimately vary — production, the full fixture and the small golden + /// context all differ — so they are bounded by the trusted setup rather than pinned. The key + /// declares one twice, once in its Midnight envelope and once in the raw key it wraps, and the + /// reader takes them independently: each reaches a `k - 1` subtraction on a byte, which + /// underflows at zero, or a domain constructor that asserts. + pub(crate) fn validate_encoded_header(bytes: &[u8]) -> StmResult<()> { + let mut reader = bytes; + let architecture = ZkStdLibArch::read_from_serialized_vk(&mut reader) + .with_context(|| "Failed to read the certificate verifying key architecture")?; + if architecture != certificate_circuit_architecture() { + return Err(anyhow!( + CertificateCircuitError::VerificationKeyArchitectureMismatch + )); + } + + let mut envelope_degree = [0u8; 1]; + reader + .read_exact(&mut envelope_degree) + .with_context(|| "Failed to read the certificate verifying key degree")?; + + let mut public_input_count = [0u8; 4]; + reader + .read_exact(&mut public_input_count) + .with_context(|| "Failed to read the certificate verifying key public input count")?; + + // The wrapped raw key opens with its own version and degree. + let mut raw_header = [0u8; 2]; + reader + .read_exact(&mut raw_header) + .with_context(|| "Failed to read the wrapped raw certificate key header")?; + + for degree in [envelope_degree[0], raw_header[1]] { + if degree == 0 || degree > MIDNIGHT_SRS_DEGREE { + return Err(anyhow!( + CertificateCircuitError::VerificationKeyDegreeMismatch { + expected: u32::from(MIDNIGHT_SRS_DEGREE), + actual: u32::from(degree), + } + )); + } + } + if envelope_degree[0] != raw_header[1] { + return Err(anyhow!( + CertificateCircuitError::VerificationKeyDegreeMismatch { + expected: u32::from(envelope_degree[0]), + actual: u32::from(raw_header[1]), + } + )); + } + Ok(()) + } } impl AsRef>> @@ -59,6 +122,33 @@ impl NonRecursiveCircuitProvingKey { } } +/// Serde for the wrapped Midnight verifying key, routed through the newtype's guarded byte decoder +/// so the verifier-data envelopes cannot reach the dependency's readers unchecked. +mod certificate_verifying_key_serde { + use midnight_zk_stdlib::MidnightVK; + use serde::{Deserializer, Serializer}; + + use super::NonRecursiveCircuitVerifyingKey; + use crate::codec::{TryFromBytes, TryToBytes}; + + pub(super) fn serialize( + verifying_key: &MidnightVK, + serializer: S, + ) -> Result { + let bytes = verifying_key.to_bytes_vec().map_err(serde::ser::Error::custom)?; + serializer.serialize_bytes(&bytes) + } + + pub(super) fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result { + let bytes: Vec = serde::Deserialize::deserialize(deserializer)?; + NonRecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .map(|key| key.0) + .map_err(serde::de::Error::custom) + } +} + impl TryToBytes for NonRecursiveCircuitVerifyingKey { fn to_bytes_vec(&self) -> StmResult> { self.0.to_bytes_vec() @@ -67,7 +157,22 @@ impl TryToBytes for NonRecursiveCircuitVerifyingKey { impl TryFromBytes for NonRecursiveCircuitVerifyingKey { fn try_from_bytes(bytes: &[u8]) -> StmResult { - Ok(Self(MidnightVK::try_from_bytes(bytes)?)) + Self::validate_encoded_header(bytes)?; + + let mut reader = bytes; + let midnight_vk = MidnightVK::read(&mut reader, KEY_SERDE_FORMAT) + .with_context(|| "Failed to deserialize the certificate verifying key")?; + // A standalone encoding holds one key and nothing else; the streaming asset reader, where a + // fixed-base map follows the key, deliberately does not go through here. + if !reader.is_empty() { + return Err(anyhow!( + CertificateCircuitError::VerificationKeyEncodingHasTrailingBytes { + trailing: reader.len(), + } + )); + } + + Ok(Self(midnight_vk)) } } @@ -124,10 +229,14 @@ mod tests { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; + use midnight_zk_stdlib::ZkStdLibArch; + use super::{NonRecursiveCircuitProvingKey, NonRecursiveCircuitVerifyingKey}; use crate::Parameters; use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; use crate::circuits::halo2::circuit::CertificateCircuit; + use crate::circuits::halo2::errors::CertificateCircuitError; + use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; use crate::circuits::key_generator::KeyGenerator; use crate::codec::{TryFromBytes, TryToBytes}; @@ -139,7 +248,7 @@ mod tests { ) .expect("production verifying key bytes should deserialize"); - // Exercises the `#[serde(with = "midnight_verifying_key_serde")]` serialize + deserialize path. + // Exercises the `#[serde(with = "certificate_verifying_key_serde")]` serialize + deserialize path. let json = serde_json::to_vec(&verifying_key).expect("serde serialize should succeed"); let restored: NonRecursiveCircuitVerifyingKey = serde_json::from_slice(&json).expect("serde deserialize should succeed"); @@ -237,4 +346,114 @@ mod tests { "the already-sized SRS must be used directly, untouched" ); } + + // Both circuits encode their keys the same way, so only the declared architecture separates + // them: without the header check the recursive key would decode in the certificate position. + #[test] + fn a_recursive_verifying_key_is_rejected_in_the_certificate_position() { + let error = NonRecursiveCircuitVerifyingKey::try_from_bytes( + RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, + ) + .expect_err("a recursive key must not decode as a certificate one"); + + assert!( + matches!( + error.downcast_ref::(), + Some(CertificateCircuitError::VerificationKeyArchitectureMismatch) + ), + "expected an architecture mismatch, got: {error}" + ); + } + + // `MidnightVK::read` derives its range bit length as `k - 1` on a byte, so a zero degree would + // underflow inside the dependency before any check of ours could run. + #[test] + fn a_zero_degree_header_is_rejected_before_it_can_underflow() { + let mut bytes = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + // The degree byte follows the encoded architecture. + let mut reader = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); + let degree_index = bytes.len() - reader.len(); + bytes[degree_index] = 0; + + let error = NonRecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .expect_err("a zero degree must be rejected"); + + assert!( + matches!( + error.downcast_ref::(), + Some(CertificateCircuitError::VerificationKeyDegreeMismatch { actual: 0, .. }) + ), + "expected a degree mismatch, got: {error}" + ); + } + + // The envelope and the raw key it wraps each declare a degree, and the reader takes them + // independently, so an envelope declaring a supported degree can carry a raw key of another. + #[test] + fn a_wrapped_raw_key_of_another_degree_is_rejected() { + let mut bytes = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + // Past the architecture, the envelope degree and the public input count lies the raw key's + // own version byte, and its degree follows. + let mut reader = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); + let envelope_degree_index = bytes.len() - reader.len(); + let raw_degree_index = envelope_degree_index + 1 + 4 + 1; + // A degree the trusted setup could have produced, so only the disagreement is under test. + let other_degree = bytes[envelope_degree_index] - 1; + bytes[raw_degree_index] = other_degree; + + let error = NonRecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .expect_err("a wrapped key of another degree must be rejected"); + + assert!( + matches!( + error.downcast_ref::(), + Some(CertificateCircuitError::VerificationKeyDegreeMismatch { actual, .. }) + if *actual == u32::from(other_degree) + ), + "expected a degree mismatch, got: {error}" + ); + } + + // A key whose canonical re-serialization differs from the bytes it was decoded from would break + // every digest taken over an encoded key. + #[test] + fn an_encoding_with_trailing_bytes_is_rejected() { + let mut bytes = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + bytes.push(0); + + let error = NonRecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .expect_err("trailing bytes must be rejected"); + + assert!( + matches!( + error.downcast_ref::(), + Some( + CertificateCircuitError::VerificationKeyEncodingHasTrailingBytes { + trailing: 1 + } + ) + ), + "expected a trailing-byte rejection, got: {error}" + ); + } + + // Deriving Deserialize would otherwise reach the dependency's reader without the guard, and + // that is the path the verifier data envelopes take. + #[test] + fn serde_rejects_a_recursive_verifying_key_in_the_certificate_position() { + let encoded = serde_json::to_vec(&serde_bytes::ByteBuf::from( + RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(), + )) + .expect("the recursive key bytes should encode"); + + let error = serde_json::from_slice::(&encoded) + .expect_err("a recursive key must not deserialize as a certificate one"); + + assert!( + error.to_string().contains("architecture"), + "expected an architecture mismatch, got: {error}" + ); + } } diff --git a/mithril-stm/src/circuits/halo2_ivc/keys.rs b/mithril-stm/src/circuits/halo2_ivc/keys.rs index 8c49b1920d4..2ba9e4cc152 100644 --- a/mithril-stm/src/circuits/halo2_ivc/keys.rs +++ b/mithril-stm/src/circuits/halo2_ivc/keys.rs @@ -17,7 +17,6 @@ use crate::circuits::halo2::keys::NonRecursiveCircuitVerifyingKey; use crate::circuits::key_generator::KeyGenerator; use crate::circuits::key_provider::KeyProvider; use crate::circuits::key_serialization::KEY_SERDE_FORMAT; -use crate::circuits::trusted_setup::MIDNIGHT_SRS_DEGREE; use crate::codec::{TryFromBytes, TryToBytes}; use super::{ @@ -118,7 +117,7 @@ impl RecursiveCircuitVerifyingKey { let certificate_key = reader .get(..certificate_key_length) .ok_or_else(|| anyhow!("The relation's certificate key is truncated"))?; - validate_certificate_key_declared_degrees(certificate_key)?; + NonRecursiveCircuitVerifyingKey::validate_encoded_header(certificate_key)?; reader = &reader[certificate_key_length..]; // The wrapped raw recursive key opens with its own version and degree. @@ -273,54 +272,6 @@ impl TryFromBytes for RecursiveCircuitProvingKey { } } -/// Rejects a certificate key whose declared degrees could not belong to any supported certificate -/// circuit. -/// -/// A certificate key is generated from the trusted setup, so it cannot exceed that setup's degree. -/// -/// Certificate degrees legitimately vary — production, the full fixture and the small golden context -/// all differ — so these are bounded rather than pinned. The key declares a degree twice, once in -/// its Midnight envelope and once in the raw key it wraps, and the reader takes them independently: -/// an envelope declaring a supported degree can wrap a raw key declaring any other, which reaches a -/// domain constructor that asserts. -fn validate_certificate_key_declared_degrees(certificate_key: &[u8]) -> StmResult<()> { - let mut reader = certificate_key; - ZkStdLibArch::read_from_serialized_vk(&mut reader) - .with_context(|| "Failed to read the certificate key architecture")?; - - let mut envelope_degree = [0u8; 1]; - reader - .read_exact(&mut envelope_degree) - .with_context(|| "Failed to read the certificate key envelope degree")?; - - let mut public_input_count = [0u8; 4]; - reader - .read_exact(&mut public_input_count) - .with_context(|| "Failed to read the certificate key public input count")?; - - // The wrapped raw key opens with its own version and degree. - let mut raw_header = [0u8; 2]; - reader - .read_exact(&mut raw_header) - .with_context(|| "Failed to read the wrapped raw certificate key header")?; - - for degree in [envelope_degree[0], raw_header[1]] { - if degree == 0 || degree > MIDNIGHT_SRS_DEGREE { - return Err(anyhow!(IvcCircuitError::IvcVerificationKeyDegreeMismatch { - expected: u32::from(MIDNIGHT_SRS_DEGREE), - actual: u32::from(degree), - })); - } - } - if envelope_degree[0] != raw_header[1] { - return Err(anyhow!(IvcCircuitError::IvcVerificationKeyDegreeMismatch { - expected: u32::from(envelope_degree[0]), - actual: u32::from(raw_header[1]), - })); - } - Ok(()) -} - impl KeyGenerator for IvcCircuit { type VerifyingKey = RecursiveCircuitVerifyingKey; type ProvingKey = RecursiveCircuitProvingKey; @@ -397,6 +348,7 @@ impl KeyGenerator for RecursiveCircuitKeyGenerator { mod tests { use super::*; use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + use crate::circuits::halo2::errors::CertificateCircuitError; use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; #[test] @@ -604,10 +556,11 @@ mod tests { Err(error) => error, }; + // The certificate key is checked by the certificate newtype, so it reports its own error. assert!( matches!( - error.downcast_ref::(), - Some(IvcCircuitError::IvcVerificationKeyDegreeMismatch { actual: 32, .. }) + error.downcast_ref::(), + Some(CertificateCircuitError::VerificationKeyDegreeMismatch { actual: 32, .. }) ), "expected a degree mismatch, got: {error}" ); diff --git a/mithril-stm/src/circuits/key_serialization.rs b/mithril-stm/src/circuits/key_serialization.rs index ebc79bd0f9a..428653c9fd3 100644 --- a/mithril-stm/src/circuits/key_serialization.rs +++ b/mithril-stm/src/circuits/key_serialization.rs @@ -1,9 +1,13 @@ -//! Byte and serde encodings for a Midnight verifying key, shared by every circuit that stores its -//! key in that format. +//! Byte encoding of a Midnight verifying key, shared by every circuit that stores its key in that +//! format. //! //! `MidnightVK` is self-describing and belongs to the standard library, so Rust coherence allows //! only one implementation of the crate's byte traits for it. It lives here rather than beside one //! circuit's keys so that both circuits reach the same encoding. +//! +//! Being self-describing is also why this codec cannot say which circuit a key belongs to: it +//! decodes whatever the bytes declare. Each circuit's newtype guards its own decoder, and that is +//! the entry point callers use. use anyhow::Context; use midnight_proofs::utils::SerdeFormat; @@ -32,27 +36,3 @@ impl TryFromBytes for MidnightVK { .with_context(|| "Failed to deserialize the Midnight verifying key") } } - -/// Serde for a wrapped Midnight verifying key: delegates to the key's [`TryToBytes`] / -/// [`TryFromBytes`] impl so the raw-bytes encoding is defined in one place. -pub(crate) mod midnight_verifying_key_serde { - use midnight_zk_stdlib::MidnightVK; - use serde::{Deserializer, Serializer}; - - use crate::codec::{TryFromBytes, TryToBytes}; - - pub(crate) fn serialize( - verifying_key: &MidnightVK, - serializer: S, - ) -> Result { - let bytes = verifying_key.to_bytes_vec().map_err(serde::ser::Error::custom)?; - serializer.serialize_bytes(&bytes) - } - - pub(crate) fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result { - let bytes: Vec = serde::Deserialize::deserialize(deserializer)?; - MidnightVK::try_from_bytes(&bytes).map_err(serde::de::Error::custom) - } -} From f80cf88006b10a2d538cebcfa67d4b90b1dd9b8b Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Fri, 18 Sep 2026 08:22:18 +0900 Subject: [PATCH 09/14] style(stm): group imports and hoist the ones inside functions --- mithril-stm/src/circuits/halo2_ivc/circuit.rs | 5 +++-- mithril-stm/src/circuits/halo2_ivc/keys.rs | 6 ++---- .../halo2_ivc/tests/common/failure_signature.rs | 12 ++++++------ .../halo2_ivc/tests/common/generators/proofs.rs | 9 ++++----- .../halo2_ivc/tests/common/generators/setup.rs | 2 +- .../src/circuits/halo2_ivc/tests/common/helpers.rs | 7 +++---- .../circuits/halo2_ivc/tests/encoding/positive.rs | 10 ++++------ .../proof_system/halo2_ivc_snark/verifier_setup.rs | 5 ++--- 8 files changed, 25 insertions(+), 31 deletions(-) diff --git a/mithril-stm/src/circuits/halo2_ivc/circuit.rs b/mithril-stm/src/circuits/halo2_ivc/circuit.rs index 4cf8e029e19..eb433fffcad 100644 --- a/mithril-stm/src/circuits/halo2_ivc/circuit.rs +++ b/mithril-stm/src/circuits/halo2_ivc/circuit.rs @@ -1,7 +1,9 @@ +use anyhow::anyhow; + use crate::StmResult; use crate::circuits::halo2::keys::NonRecursiveCircuitVerifyingKey; use crate::circuits::halo2_ivc::keys::RecursiveCircuitVerifyingKey; -use anyhow::anyhow; +use crate::codec::{TryFromBytes, TryToBytes}; use super::{ Accumulator, BinaryInstructions, CircuitValue, ConstraintSystem, Error, EvaluationDomain, @@ -13,7 +15,6 @@ use super::{ types::{CertificateProofBytes, IvcProofBytes}, witness_assignments, }; -use crate::codec::{TryFromBytes, TryToBytes}; /// Chips the recursive circuit enables. /// diff --git a/mithril-stm/src/circuits/halo2_ivc/keys.rs b/mithril-stm/src/circuits/halo2_ivc/keys.rs index 2ba9e4cc152..28bff3459c4 100644 --- a/mithril-stm/src/circuits/halo2_ivc/keys.rs +++ b/mithril-stm/src/circuits/halo2_ivc/keys.rs @@ -346,6 +346,8 @@ impl KeyGenerator for RecursiveCircuitKeyGenerator { #[cfg(test)] mod tests { + use midnight_proofs::utils::helpers::byte_length; + use super::*; use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; use crate::circuits::halo2::errors::CertificateCircuitError; @@ -492,8 +494,6 @@ mod tests { // declare fewer than the configured constraint system has columns. #[test] fn a_key_declaring_too_few_fixed_commitments_is_rejected() { - use midnight_proofs::utils::helpers::byte_length; - let mut reader = RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); // envelope degree, public input count, then the raw key's version and degree @@ -536,8 +536,6 @@ mod tests { // independently, so a supported envelope can wrap a raw key of any other degree. #[test] fn a_proving_key_whose_certificate_inner_degree_disagrees_is_rejected() { - use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; - let mut certificate_key = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); let mut reader = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/failure_signature.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/failure_signature.rs index 2dae7161128..9362fbf2a35 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/failure_signature.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/failure_signature.rs @@ -5,19 +5,19 @@ //! Advice-side members of a broken class are not enumerated: their columns and regions are named by //! the gadget layer and carry no stability guarantee. -use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; -use crate::circuits::halo2_ivc::circuit::IvcCircuit; -use midnight_proofs::circuit::Value; -use midnight_zk_stdlib::MidnightCircuit; - use std::collections::{BTreeMap, BTreeSet}; +use midnight_proofs::circuit::Value; use midnight_proofs::{ dev::{FailureLocation, MockProver, VerifyFailure}, plonk::{Any, Circuit}, }; +use midnight_zk_stdlib::MidnightCircuit; -use crate::circuits::halo2_ivc::{NativeField, circuit::IvcCircuitData}; +use crate::circuits::halo2_ivc::{ + NativeField, RECURSIVE_CIRCUIT_DEGREE, + circuit::{IvcCircuit, IvcCircuitData}, +}; /// Index of the instance column carrying the circuit's public statement. /// diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs index cb2a662c69e..024a7b9430e 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/proofs.rs @@ -1,13 +1,10 @@ -use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; -use crate::circuits::halo2_ivc::circuit::IvcCircuit; -use midnight_proofs::circuit::Value; -use midnight_zk_stdlib::MidnightCircuit; use std::hash::Hash; use ff::FromUniformBytes; use group::Group; use midnight_circuits::hash::poseidon::PoseidonState; use midnight_curves::Bls12; +use midnight_proofs::circuit::Value; use midnight_proofs::{ plonk::{create_proof, prepare}, poly::{ @@ -16,11 +13,13 @@ use midnight_proofs::{ }, transcript::{Blake2b256, CircuitTranscript, Hashable, Sampleable, Transcript, TranscriptHash}, }; +use midnight_zk_stdlib::MidnightCircuit; use rand_core::{CryptoRng, RngCore}; use crate::circuits::halo2_ivc::keys::RecursiveCircuitProvingKey; use crate::circuits::halo2_ivc::{ - EmulatedCurve, NativeField, PairingEngine, VerifyingKey, circuit::IvcCircuitData, + EmulatedCurve, NativeField, PairingEngine, RECURSIVE_CIRCUIT_DEGREE, VerifyingKey, + circuit::{IvcCircuit, IvcCircuitData}, }; /// Generates a recursive proof using the chosen transcript hash. diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs index 144cb33c632..61d6f54d047 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/generators/setup.rs @@ -1,4 +1,3 @@ -use midnight_zk_stdlib::{self as zk}; use std::{ collections::BTreeMap, io::Write, @@ -9,6 +8,7 @@ use ff::Field; use midnight_curves::Bls12; use midnight_proofs::poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}; use midnight_zk_stdlib as zk_lib; +use midnight_zk_stdlib::{self as zk}; use rand_chacha::ChaCha20Rng; use rand_core::{CryptoRng, RngCore, SeedableRng}; use serde::{Deserialize, Serialize}; diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/common/helpers.rs b/mithril-stm/src/circuits/halo2_ivc/tests/common/helpers.rs index f58351e31ea..c93387a4e72 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/common/helpers.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/common/helpers.rs @@ -1,18 +1,17 @@ -use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; -use midnight_proofs::circuit::Value; -use midnight_zk_stdlib::MidnightCircuit; use std::collections::BTreeMap; use midnight_circuits::types::Instantiable; use midnight_curves::Bls12; +use midnight_proofs::circuit::Value; use midnight_proofs::{ dev::MockProver, poly::kzg::params::{ParamsKZG, ParamsVerifierKZG}, }; +use midnight_zk_stdlib::MidnightCircuit; use crate::circuits::halo2_ivc::{ Accumulator, AssignedAccumulator, EmulatedCurve, NativeField, PREIMAGE_SIZE, PairingEngine, - RecursiveEmulation, + RECURSIVE_CIRCUIT_DEGREE, RecursiveEmulation, accumulator::trivial_accumulator, circuit::{IvcCircuit, IvcCircuitData}, state::{Global, State, Witness}, diff --git a/mithril-stm/src/circuits/halo2_ivc/tests/encoding/positive.rs b/mithril-stm/src/circuits/halo2_ivc/tests/encoding/positive.rs index 9bec8499822..b84ba086f93 100644 --- a/mithril-stm/src/circuits/halo2_ivc/tests/encoding/positive.rs +++ b/mithril-stm/src/circuits/halo2_ivc/tests/encoding/positive.rs @@ -1,20 +1,18 @@ //! Positive encoding tests: preimage layout, state public input format, and //! serialization round-trips. -use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_DEGREE; -use crate::circuits::halo2_ivc::circuit::IvcCircuit; -use crate::circuits::halo2_ivc::circuit::recursive_circuit_architecture; -use midnight_zk_stdlib::MidnightCircuit; - use ff::Field; use midnight_proofs::utils::SerdeFormat; +use midnight_zk_stdlib::MidnightCircuit; use proptest::prelude::*; use sha2::{Digest as Sha2Digest, Sha256}; use crate::circuits::halo2_ivc::{ Accumulator, KZGCommitmentScheme, NativeField, PREIMAGE_CURRENT_EPOCH_BYTES, PREIMAGE_NEXT_MERKLE_TREE_COMMITMENT_BYTES, PREIMAGE_NEXT_PROTOCOL_PARAMETERS_BYTES, - PREIMAGE_SIZE, PairingEngine, ProtocolMessagePreimage, RecursiveEmulation, VerifyingKey, + PREIMAGE_SIZE, PairingEngine, ProtocolMessagePreimage, RECURSIVE_CIRCUIT_DEGREE, + RecursiveEmulation, VerifyingKey, + circuit::{IvcCircuit, recursive_circuit_architecture}, io::{ReadWithFormat, WriteWithFormat}, protocol_message::{DynamicProtocolMessagePartKey, ProtocolMessage}, state::State, diff --git a/mithril-stm/src/proof_system/halo2_ivc_snark/verifier_setup.rs b/mithril-stm/src/proof_system/halo2_ivc_snark/verifier_setup.rs index 315d11774f6..d18c49b5275 100644 --- a/mithril-stm/src/proof_system/halo2_ivc_snark/verifier_setup.rs +++ b/mithril-stm/src/proof_system/halo2_ivc_snark/verifier_setup.rs @@ -219,6 +219,8 @@ impl IvcVerifierData { #[cfg(test)] mod tests { use super::*; + use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; use crate::{ BaseFieldElement, circuits::{ @@ -335,9 +337,6 @@ mod tests { // is the path a certificate carries, and the two circuits now share one key encoding. #[test] fn verifier_data_rejects_a_certificate_key_in_the_recursive_position() { - use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; - use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; - // Mirrors `IvcVerifierData`'s CBOR shape with both keys as opaque bytes, so the recursive // slot can carry an encoding the typed constructor would never allow. #[derive(serde::Serialize)] From dd14e3bd131018d79c08d87fa0d6b61ec80e9d6f Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Fri, 18 Sep 2026 08:40:33 +0900 Subject: [PATCH 10/14] test(stm): decode the cached keys through their typed decoders --- .../halo2_ivc_snark/prover_setup.rs | 109 +++++++++++++++--- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/mithril-stm/src/proof_system/halo2_ivc_snark/prover_setup.rs b/mithril-stm/src/proof_system/halo2_ivc_snark/prover_setup.rs index e6b61a23e3a..4539c6a5eac 100644 --- a/mithril-stm/src/proof_system/halo2_ivc_snark/prover_setup.rs +++ b/mithril-stm/src/proof_system/halo2_ivc_snark/prover_setup.rs @@ -157,6 +157,24 @@ impl IvcProverSetup { Self::build_for_test_degree(parameters, merkle_tree_depth, RECURSIVE_CIRCUIT_DEGREE) } + /// The shared cache the test setup derives its keys in. + /// + /// Defined once so a test can locate the cache without restating the inputs its identity is + /// built from. + #[cfg(test)] + fn test_key_cache(parameters_bytes: &[u8], depth_bytes: &[u8], seed_bytes: &[u8]) -> FileMutex { + FileMutex::for_shared_cache( + "ivc-setup", + &[ + NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, + RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, + parameters_bytes, + depth_bytes, + seed_bytes, + ], + ) + } + /// Builds an [`IvcProverSetup`] from a deterministic unsafe SRS with degree determined by the input /// `unsafe_srs_degree`. /// Uses a cache for the unsafe SRS to avoid regenerating it when a SRS of the correct degree already exists @@ -178,16 +196,7 @@ impl IvcProverSetup { let trusted_setup_provider = TrustedSetupProvider::with_unsafe_srs(&srs_directory, unsafe_srs_degree); - let key_cache = FileMutex::for_shared_cache( - "ivc-setup", - &[ - NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, - RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, - ¶meters_bytes, - &depth_bytes, - &seed_bytes, - ], - ); + let key_cache = Self::test_key_cache(¶meters_bytes, &depth_bytes, &seed_bytes); let cache_directory = key_cache.directory().to_path_buf(); // Serialize cold-start keygen across the parallel slow-test processes. let _key_cache_lock = key_cache.lock()?; @@ -364,27 +373,30 @@ mod tests { } mod slow { + use std::path::Path; + use midnight_proofs::poly::commitment::Params; + use crate::circuits::halo2::keys::NonRecursiveCircuitProvingKey; use crate::circuits::halo2_ivc::tests::common::{ asset_readers::load_embedded_verification_context_asset, generators::setup::{QUORUM_SIZE, SIGNER_COUNT}, }; + use crate::codec::TryFromBytes; use super::*; // Runs the real `load` path against an oversized unsafe SRS; runs in the `slow` tier. #[test] fn load_succeeds_with_unsafe_srs() { - let setup = IvcProverSetup::build_for_test( - &Parameters { - k: 3, - m: 10, - phi_f: 0.2, - }, - 4, - ) - .expect("IvcProverSetup::build_for_test should succeed"); + let parameters = Parameters { + k: 3, + m: 10, + phi_f: 0.2, + }; + let merkle_tree_depth = 4; + let setup = IvcProverSetup::build_for_test(¶meters, merkle_tree_depth) + .expect("IvcProverSetup::build_for_test should succeed"); assert!( !setup.certificate_fixed_bases.is_empty(), @@ -409,6 +421,65 @@ mod tests { "combined map should preserve every IVC base" ); } + + // A cold run generates the keys, writes them and keeps the generated ones, so it never + // decodes what it wrote; no other slow test shares this configuration's cache. Without + // this, the recursive proving-key decoder would only ever see malformed bytes, being + // the one key type with no valid-key round-trip test of its own. The generated keys are + // dropped first so the recursive pair, a gigabyte on disk, is never held twice. + let key_cache = IvcProverSetup::test_key_cache( + ¶meters.to_bytes().expect("parameters should encode"), + &merkle_tree_depth.to_le_bytes(), + &UNSAFE_SRS_SEED.to_le_bytes(), + ); + let certificate_directory = key_cache.directory().join("certificate"); + let recursive_directory = key_cache.directory().join("recursive"); + drop(setup); + + NonRecursiveCircuitVerifyingKey::try_from_bytes(&read_cached_key( + &certificate_directory, + "verification-key", + )) + .expect("the cached certificate verifying key must decode"); + NonRecursiveCircuitProvingKey::try_from_bytes(&read_cached_key( + &certificate_directory, + "proving-key", + )) + .expect("the cached certificate proving key must decode"); + RecursiveCircuitVerifyingKey::try_from_bytes(&read_cached_key( + &recursive_directory, + "verification-key", + )) + .expect("the cached recursive verifying key must decode"); + RecursiveCircuitProvingKey::try_from_bytes(&read_cached_key( + &recursive_directory, + "proving-key", + )) + .expect("the cached recursive proving key must decode"); + } + + /// Reads the one cached key of the given name beneath `directory`, whose layout belongs to + /// the key provider rather than to this test. + fn read_cached_key(directory: &Path, name: &str) -> Vec { + let mut found = Vec::new(); + let mut pending = vec![directory.to_path_buf()]; + while let Some(current) = pending.pop() { + for entry in std::fs::read_dir(¤t).into_iter().flatten().flatten() { + let path = entry.path(); + if path.is_dir() { + pending.push(path); + } else if path.file_name().and_then(|file| file.to_str()) == Some(name) { + found.push(path); + } + } + } + assert_eq!( + found.len(), + 1, + "exactly one {name} should be cached under {}, found: {found:?}", + directory.display() + ); + std::fs::read(&found[0]).expect("a cached key should be readable") } // `IvcProverSetup::build_for_test` loads from an oversized unsafe SRS that shares the production From d6d8defd08d7829e2956ef85792fab60c93294b8 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Fri, 18 Sep 2026 08:50:18 +0900 Subject: [PATCH 11/14] fix(stm): reject trailing bytes after a recursive proving key --- mithril-stm/src/circuits/halo2_ivc/keys.rs | 17 +++++++++--- .../halo2_ivc_snark/prover_setup.rs | 26 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/mithril-stm/src/circuits/halo2_ivc/keys.rs b/mithril-stm/src/circuits/halo2_ivc/keys.rs index 28bff3459c4..6467b2186e9 100644 --- a/mithril-stm/src/circuits/halo2_ivc/keys.rs +++ b/mithril-stm/src/circuits/halo2_ivc/keys.rs @@ -265,10 +265,19 @@ impl TryFromBytes for RecursiveCircuitProvingKey { RecursiveCircuitVerifyingKey::validate_encoded_proving_key_header(bytes)?; let mut reader = bytes; - Ok(Self( - MidnightPK::::read(&mut reader, KEY_SERDE_FORMAT) - .with_context(|| "Failed to deserialize the recursive proving key")?, - )) + let midnight_pk = MidnightPK::::read(&mut reader, KEY_SERDE_FORMAT) + .with_context(|| "Failed to deserialize the recursive proving key")?; + // A cache entry holds one key and nothing else, the same rule the verifying key decoder + // above applies. + if !reader.is_empty() { + return Err(anyhow!( + IvcCircuitError::RecursiveKeyEncodingHasTrailingBytes { + trailing: reader.len(), + } + )); + } + + Ok(Self(midnight_pk)) } } diff --git a/mithril-stm/src/proof_system/halo2_ivc_snark/prover_setup.rs b/mithril-stm/src/proof_system/halo2_ivc_snark/prover_setup.rs index 4539c6a5eac..263bbe6cd98 100644 --- a/mithril-stm/src/proof_system/halo2_ivc_snark/prover_setup.rs +++ b/mithril-stm/src/proof_system/halo2_ivc_snark/prover_setup.rs @@ -378,6 +378,7 @@ mod tests { use midnight_proofs::poly::commitment::Params; use crate::circuits::halo2::keys::NonRecursiveCircuitProvingKey; + use crate::circuits::halo2_ivc::errors::IvcCircuitError; use crate::circuits::halo2_ivc::tests::common::{ asset_readers::load_embedded_verification_context_asset, generators::setup::{QUORUM_SIZE, SIGNER_COUNT}, @@ -451,11 +452,26 @@ mod tests { "verification-key", )) .expect("the cached recursive verifying key must decode"); - RecursiveCircuitProvingKey::try_from_bytes(&read_cached_key( - &recursive_directory, - "proving-key", - )) - .expect("the cached recursive proving key must decode"); + let mut recursive_proving_key_bytes = + read_cached_key(&recursive_directory, "proving-key"); + RecursiveCircuitProvingKey::try_from_bytes(&recursive_proving_key_bytes) + .expect("the cached recursive proving key must decode"); + + // Only a valid encoding can exercise the decoder's full-consumption rule, and this is + // the one place a valid recursive proving key exists. + recursive_proving_key_bytes.push(0); + let error = + match RecursiveCircuitProvingKey::try_from_bytes(&recursive_proving_key_bytes) { + Ok(_) => panic!("a recursive proving key with trailing bytes must be rejected"), + Err(error) => error, + }; + assert!( + matches!( + error.downcast_ref::(), + Some(IvcCircuitError::RecursiveKeyEncodingHasTrailingBytes { trailing: 1 }) + ), + "expected a trailing-byte rejection, got: {error}" + ); } /// Reads the one cached key of the given name beneath `directory`, whose layout belongs to From d3037e76ff3b1a200dd5ac142f4f04b24d4e5735 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Fri, 18 Sep 2026 09:56:09 +0900 Subject: [PATCH 12/14] fix(stm): check the certificate key commitment count and remove the unguarded decoder --- mithril-stm/src/circuits/halo2/errors.rs | 7 ++ .../src/circuits/halo2/key_serialization.rs | 10 ++- mithril-stm/src/circuits/halo2/keys.rs | 86 ++++++++++++++++++- mithril-stm/src/circuits/key_serialization.rs | 18 ++-- 4 files changed, 101 insertions(+), 20 deletions(-) diff --git a/mithril-stm/src/circuits/halo2/errors.rs b/mithril-stm/src/circuits/halo2/errors.rs index fe5b6644921..d09e390476f 100644 --- a/mithril-stm/src/circuits/halo2/errors.rs +++ b/mithril-stm/src/circuits/halo2/errors.rs @@ -128,6 +128,13 @@ pub enum CertificateCircuitError { /// A standalone encoded verification key is followed by bytes that are not part of it. #[error("The encoded certificate verification key has {trailing} trailing byte(s)")] VerificationKeyEncodingHasTrailingBytes { trailing: usize }, + + /// An encoded verification key declares fewer fixed commitments than its constraint system has + /// fixed columns, which the verifier indexes by column. + #[error( + "Certificate verification key fixed commitment count mismatch: expected {expected}, got {actual}" + )] + VerificationKeyCommitmentCountMismatch { expected: usize, actual: usize }, } impl From for CertificateCircuitError { diff --git a/mithril-stm/src/circuits/halo2/key_serialization.rs b/mithril-stm/src/circuits/halo2/key_serialization.rs index a4da211a870..37d6cde4f86 100644 --- a/mithril-stm/src/circuits/halo2/key_serialization.rs +++ b/mithril-stm/src/circuits/halo2/key_serialization.rs @@ -31,19 +31,21 @@ impl TryFromBytes for MidnightPK { #[cfg(test)] mod tests { use midnight_proofs::poly::kzg::params::ParamsKZG; - use midnight_zk_stdlib::{self as zk, MidnightCircuit, MidnightVK}; + use midnight_zk_stdlib::{self as zk, MidnightCircuit}; use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; use super::*; use crate::Parameters; use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + use crate::circuits::halo2::keys::NonRecursiveCircuitVerifyingKey; #[test] fn production_verifying_key_serializes_to_the_embedded_bytes() { - let verifying_key = - MidnightVK::try_from_bytes(NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION) - .expect("production verifying key bytes should deserialize"); + let verifying_key = NonRecursiveCircuitVerifyingKey::try_from_bytes( + NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, + ) + .expect("production verifying key bytes should deserialize"); assert_eq!( verifying_key.to_bytes_vec().expect("serialize should succeed"), NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, diff --git a/mithril-stm/src/circuits/halo2/keys.rs b/mithril-stm/src/circuits/halo2/keys.rs index 20d49331594..8326b41976a 100644 --- a/mithril-stm/src/circuits/halo2/keys.rs +++ b/mithril-stm/src/circuits/halo2/keys.rs @@ -8,12 +8,16 @@ use anyhow::{Context, anyhow}; use midnight_curves::Bls12; use midnight_proofs::poly::commitment::Params; use midnight_proofs::poly::kzg::params::ParamsKZG; -use midnight_zk_stdlib::{self as zk, MidnightCircuit, MidnightPK, MidnightVK, ZkStdLibArch}; +use midnight_zk_stdlib::{ + self as zk, MidnightCircuit, MidnightPK, MidnightVK, ZkStdLib, ZkStdLibArch, +}; use serde::{Deserialize, Serialize}; use crate::StmResult; use crate::circuits::halo2::errors::CertificateCircuitError; -use crate::circuits::halo2_ivc::{KZGCommitmentScheme, NativeField, PairingEngine, VerifyingKey}; +use crate::circuits::halo2_ivc::{ + ConstraintSystem, KZGCommitmentScheme, NativeField, PairingEngine, VerifyingKey, +}; use crate::circuits::key_generator::KeyGenerator; use crate::circuits::key_serialization::KEY_SERDE_FORMAT; use crate::circuits::trusted_setup::MIDNIGHT_SRS_DEGREE; @@ -48,6 +52,21 @@ impl NonRecursiveCircuitVerifyingKey { self.midnight_vk().vk().get_domain().k() } + /// Number of fixed commitments the approved architecture produces at `degree`. + /// + /// The reader takes this count from the bytes and reads that many commitments, so a key can + /// declare fewer than the configured constraint system has columns; later verification indexes + /// commitments by those columns. The degree comes from the key because certificate degrees vary. + fn expected_fixed_commitment_count(degree: u32) -> usize { + let mut constraint_system = ConstraintSystem::::default(); + ZkStdLib::configure( + &mut constraint_system, + (certificate_circuit_architecture(), (degree - 1) as u8), + ); + // Selectors become fixed columns when the key is read. + constraint_system.num_fixed_columns() + constraint_system.num_selectors() + } + /// Checks the header of an encoded certificate verifying key before it is decoded. /// /// `MidnightVK` takes its architecture and degree from the bytes, so without this any Midnight @@ -172,6 +191,17 @@ impl TryFromBytes for NonRecursiveCircuitVerifyingKey { )); } + let actual = midnight_vk.vk().fixed_commitments().len(); + let expected = Self::expected_fixed_commitment_count(midnight_vk.vk().get_domain().k()); + if actual != expected { + return Err(anyhow!( + CertificateCircuitError::VerificationKeyCommitmentCountMismatch { + expected, + actual + } + )); + } + Ok(Self(midnight_vk)) } } @@ -229,6 +259,8 @@ mod tests { use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; + use midnight_proofs::poly::commitment::PolynomialCommitmentScheme; + use midnight_proofs::utils::helpers::byte_length; use midnight_zk_stdlib::ZkStdLibArch; use super::{NonRecursiveCircuitProvingKey, NonRecursiveCircuitVerifyingKey}; @@ -236,8 +268,12 @@ mod tests { use crate::circuits::halo2::NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; use crate::circuits::halo2::circuit::CertificateCircuit; use crate::circuits::halo2::errors::CertificateCircuitError; - use crate::circuits::halo2_ivc::RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + use crate::circuits::halo2_ivc::{ + KZGCommitmentScheme, NativeField, PairingEngine, + RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION, + }; use crate::circuits::key_generator::KeyGenerator; + use crate::circuits::key_serialization::KEY_SERDE_FORMAT; use crate::codec::{TryFromBytes, TryToBytes}; #[test] @@ -456,4 +492,48 @@ mod tests { "expected an architecture mismatch, got: {error}" ); } + + // The reader takes the commitment count from the bytes and reads that many, so a key can declare + // fewer than its constraint system has fixed columns; verification then indexes by column. + #[test] + fn a_key_declaring_too_few_fixed_commitments_is_rejected() { + let mut reader = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION; + ZkStdLibArch::read_from_serialized_vk(&mut reader).expect("architecture should read"); + let base = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.len() - reader.len(); + // envelope degree, public input count, then the raw key's version and degree + let count_index = base + 1 + 4 + 2; + + let mut bytes = NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION.to_vec(); + let declared = u32::from_le_bytes(bytes[count_index..count_index + 4].try_into().unwrap()); + assert_eq!( + declared as usize, + NonRecursiveCircuitVerifyingKey::expected_fixed_commitment_count(u32::from( + bytes[base] + )), + "the production key should declare the configured commitment count" + ); + + // Drop one commitment along with the count, so the encoding stays internally consistent and + // fully consumed: only the cardinality guard can reject it. + let commitment_length = byte_length::< + as PolynomialCommitmentScheme>::Commitment, + >(KEY_SERDE_FORMAT); + let commitments_start = count_index + 4; + bytes.drain(commitments_start..commitments_start + commitment_length); + bytes[count_index..count_index + 4].copy_from_slice(&(declared - 1).to_le_bytes()); + + let error = NonRecursiveCircuitVerifyingKey::try_from_bytes(&bytes) + .expect_err("a short commitment count must be rejected"); + + assert!( + matches!( + error.downcast_ref::(), + Some(CertificateCircuitError::VerificationKeyCommitmentCountMismatch { + actual, + .. + }) if *actual as u32 == declared - 1 + ), + "expected a commitment count mismatch, got: {error}" + ); + } } diff --git a/mithril-stm/src/circuits/key_serialization.rs b/mithril-stm/src/circuits/key_serialization.rs index 428653c9fd3..5c67886192b 100644 --- a/mithril-stm/src/circuits/key_serialization.rs +++ b/mithril-stm/src/circuits/key_serialization.rs @@ -5,21 +5,21 @@ //! only one implementation of the crate's byte traits for it. It lives here rather than beside one //! circuit's keys so that both circuits reach the same encoding. //! -//! Being self-describing is also why this codec cannot say which circuit a key belongs to: it -//! decodes whatever the bytes declare. Each circuit's newtype guards its own decoder, and that is -//! the entry point callers use. +//! Only the encoding direction lives here. Being self-describing means a decoder cannot tell which +//! circuit a key belongs to — it decodes whatever the bytes declare — so decoding belongs to each +//! circuit's newtype, behind that circuit's guard, and there is deliberately no unguarded decoder to +//! reach for. use anyhow::Context; use midnight_proofs::utils::SerdeFormat; use midnight_zk_stdlib::MidnightVK; use crate::StmResult; -use crate::codec::{TryFromBytes, TryToBytes}; +use crate::codec::TryToBytes; /// Serde format used for the on-disk / in-cache production keys. pub(crate) const KEY_SERDE_FORMAT: SerdeFormat = SerdeFormat::RawBytes; -// `MidnightVK` is self-describing, so reading needs only the serde format, no circuit type. impl TryToBytes for MidnightVK { fn to_bytes_vec(&self) -> StmResult> { let mut bytes = Vec::new(); @@ -28,11 +28,3 @@ impl TryToBytes for MidnightVK { Ok(bytes) } } - -impl TryFromBytes for MidnightVK { - fn try_from_bytes(bytes: &[u8]) -> StmResult { - let mut reader = bytes; - MidnightVK::read(&mut reader, KEY_SERDE_FORMAT) - .with_context(|| "Failed to deserialize the Midnight verifying key") - } -} From b84ba47391802474e542461ee22b31a968be58a3 Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Mon, 21 Sep 2026 14:55:44 +0900 Subject: [PATCH 13/14] perf(stm): borrow the witness instead of cloning it once per field --- mithril-stm/src/circuits/halo2_ivc/circuit.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mithril-stm/src/circuits/halo2_ivc/circuit.rs b/mithril-stm/src/circuits/halo2_ivc/circuit.rs index eb433fffcad..9e174ebb8f3 100644 --- a/mithril-stm/src/circuits/halo2_ivc/circuit.rs +++ b/mithril-stm/src/circuits/halo2_ivc/circuit.rs @@ -135,12 +135,14 @@ impl Relation for IvcCircuit { ) -> Result<(), Error> { let builder = IvcConstraintBuilder::new(std_lib); - let global_value = witness.clone().map(|data| data.global); - let state_value = witness.clone().map(|data| data.state); - let witness_value = witness.clone().map(|data| data.witness); - let certificate_proof_value = witness.clone().map(|data| data.certificate_proof); - let ivc_proof_value = witness.clone().map(|data| data.ivc_proof); - let accumulator_value = witness.map(|data| data.accumulator); + // Borrowed, so each part is copied once rather than the whole witness copied once per part. + let data = witness.as_ref(); + let global_value = data.map(|data| data.global.clone()); + let state_value = data.map(|data| data.state.clone()); + let witness_value = data.map(|data| data.witness.clone()); + let certificate_proof_value = data.map(|data| data.certificate_proof.clone()); + let ivc_proof_value = data.map(|data| data.ivc_proof.clone()); + let accumulator_value = data.map(|data| data.accumulator.clone()); let (ivc_circuit_domain, ivc_circuit_constraint_system) = &self.ivc_circuit_domain_and_constraint_system; From 24eaa1dbe7fd5260987004c688ecd6cba56da8ac Mon Sep 17 00:00:00 2001 From: Hamza Jeljeli Date: Mon, 21 Sep 2026 14:57:51 +0900 Subject: [PATCH 14/14] chore(stm): update the changelog and the crate version --- Cargo.lock | 2 +- mithril-common/Cargo.toml | 2 +- mithril-stm/CHANGELOG.md | 18 ++++++++++++++++++ mithril-stm/Cargo.toml | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 904f183d223..9ae07e98a7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4834,7 +4834,7 @@ dependencies = [ [[package]] name = "mithril-stm" -version = "0.12.19" +version = "0.12.20" dependencies = [ "anyhow", "blake2 0.10.6", diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 5c66a49831f..c36a05a776c 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.19", default-features = false } +mithril-stm = { path = "../mithril-stm", version = "0.12.20", 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 927fd337477..f6c7f26a881 100644 --- a/mithril-stm/CHANGELOG.md +++ b/mithril-stm/CHANGELOG.md @@ -5,6 +5,24 @@ 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.20 (09-21-2026) + +### Changed + +- Moved the IVC circuit onto the Midnight standard library's `Relation` trait, replacing the hand-written configuration. The statement and the degree are unchanged, but the standard library registers gates in a different order, so the recursive verifying key moves: a breaking circuit key change requiring a re-genesis, with every derived asset regenerated. +- Encoded the recursive circuit keys in the certificate keys' format, through one shared codec. The inner key is byte-identical, so this adds a 23-byte envelope without altering circuit identity. +- Bound the recursive key into the recursive circuit's cache identity, non-production entries having been fingerprinted from the certificate key alone. +- Borrowed the witness during synthesis instead of copying it once per assigned part. + +### Fixed + +- Rejected a key of the wrong circuit, degree or shape before it reaches the dependency's readers, on both verifying keys and on the recursive proving key. One shared key format left each circuit's key decodable in the other's position, where a chosen architecture, degree or fixed commitment count reaches a panic. + +### Added + +- Documented the constants that asset regeneration must update, with the assertion orientation of each. +- Decoded the cached key pairs through their typed decoders in the prover setup test, the recursive proving key having had no coverage on a valid key. + ## 0.12.19 (09-15-2026) ### Added diff --git a/mithril-stm/Cargo.toml b/mithril-stm/Cargo.toml index c026bb7e1ca..0d5c6d70019 100644 --- a/mithril-stm/Cargo.toml +++ b/mithril-stm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-stm" -version = "0.12.19" +version = "0.12.20" edition = { workspace = true } authors = { workspace = true } homepage = { workspace = true }