Skip to content

Add behavior tests for the IVC wiring #3465

Description

@jpraynaud

Why

The clerk entry point and the prover-input orchestration are exercised only through slow end-to-end tests, and several error paths are unreachable without a real prover.

What

Add behavior tests with mockall doubles at the IVC wiring seams (clerk entry point and prover input preparation) to verify orchestration, error mapping and state threading, without re-testing what the proof system module already covers: proof generation and verification stay covered by the existing prover and circuit tests.

How

Paths are relative to mithril-stm/src/.

  • Introduce one prover trait per proof system, CertificateProver (proof_system/halo2_snark, implemented by SnarkProver) and IvcStepProver (proof_system/ivc_halo2_snark, implemented by IvcProver), with a single-argument IvcStepInput carrying all inputs of an IVC step
  • Inject the provers into Clerk through a SnarkProverFactory built lazily per aggregation branch, with a test-gated constructor taking the mock and no public API change
  • Add behavior tests for Clerk::aggregate_signatures_with_type (protocol/aggregate_signature/clerk.rs) with mocked factory and provers, covering input assembly, error mapping and rolling-state threading for the Snark and IvcSnark branches; move the two existing precondition tests onto IvcStepInput::try_new
  • Cover the certificate-rejection path of IvcProverInput::prepare (proof_system/ivc_halo2_snark/prover_input.rs) in the fast tier with empty proof bytes against the dummy IVC setup
  • Retire the prover_input.rs slow tests made redundant by the fast tier, promote the two that need no setup, and un-ignore the ones that stay so the nightly slow run executes them
  • Align the trait, type and function names introduced here with the recursive / non-recursive harmonization of Harmonize the recursive and non-recursive SNARK implementations in STM #3421 (prefix vs suffix, module layout), so this issue does not introduce a third convention
  • Assess the gain (expected: closes the wiring gaps at zero runtime cost) and report it in the issue

Proposed implementation

Seams: one trait per proof system

Each trait mirrors an existing module and has a single production implementor that already exists. Both are pinned to MithrilMembershipDigest because the clerk already pins it, and both are object safe.

Naming. CertificateProver, IvcStepProver, IvcStepInput, SnarkProverFactory, NonDeterministicSnarkProverFactory, prove_certificate, prove_step and IvcProver::try_new_non_deterministic are working names. They must follow whatever convention #3421 settles on for the recursive and non-recursive stacks, at implementation time if #3421 lands first, or as part of #3421 otherwise.

Non-recursive (proof_system/halo2_snark):

/// Non-recursive proving side: one certificate proof per aggregation.
#[cfg_attr(test, mockall::automock)]
pub(crate) trait CertificateProver {
    fn verification_key(&self) -> &NonRecursiveCircuitVerifyingKey;
    fn prove_certificate(
        &mut self,
        clerk: &SnarkClerk,
        signatures: &[SingleSignature],
        message: &[u8],
    ) -> StmResult<SnarkProof<MithrilMembershipDigest>>;
}

impl<R: RngCore + CryptoRng> CertificateProver for SnarkProver<R> {
    // delegates to aggregate_signatures::<MithrilMembershipDigest> and verification_key
}

Recursive (proof_system/ivc_halo2_snark):

/// All inputs of one IVC step.
pub(crate) struct IvcStepInput {
    pub(crate) certificate_proof: SnarkProof<MithrilMembershipDigest>,
    pub(crate) message: Vec<u8>,
    pub(crate) aggregate_verification_key: AggregateVerificationKeyForSnark<MithrilMembershipDigest>,
    pub(crate) global: Global,
    pub(crate) protocol_message_preimage: ProtocolMessagePreimage,
    pub(crate) genesis_bootstrap: IvcGenesisBootstrapInput,
    pub(crate) rolling_state: Option<IvcRollingState>,
}

impl IvcStepInput {
    /// Fails on a missing genesis verifying key, a prover data without IVC rolling state,
    /// a missing genesis Schnorr signature or a preimage that is not PREIMAGE_SIZE bytes.
    pub(crate) fn try_new(
        certificate_proof: SnarkProof<MithrilMembershipDigest>,
        message: &[u8],
        aggregate_verification_key: AggregateVerificationKeyForSnark<MithrilMembershipDigest>,
        ancillary_input: AncillaryProofInput,
        certificate_verifying_key: &NonRecursiveCircuitVerifyingKey,
        ivc_verifying_key: &RecursiveCircuitVerifyingKey,
    ) -> StmResult<Self>
}

/// Recursive proving side: advances the IVC chain by one step.
#[cfg_attr(test, mockall::automock)]
pub(crate) trait IvcStepProver {
    fn ivc_verifying_key(&self) -> &RecursiveCircuitVerifyingKey;
    fn prove_step(
        &mut self,
        step_input: IvcStepInput,
    ) -> StmResult<(IvcProof<Blake2b256>, Option<IvcRollingState>)>;
}

impl<R: RngCore + CryptoRng> IvcStepProver for IvcProver<R> {
    // ivc_verifying_key read from ivc_setup, prove_step unpacks IvcStepInput into prove
}

IvcStepInput is the input-preparation half of today's ivc_prover_input_preparation_and_prove. The pure assembly lives in try_new, so the precondition errors are plain unit tests, and the seam takes one owned argument with no lifetime and no clone: message is copied once, the AVK is already returned owned by compute_aggregate_verification_key_for_snark, the certificate proof is moved as it is today, and the rolling state is moved out of the AncillaryProofInput that aggregate_signatures_with_type already receives by value. The move needs two consuming accessors:

impl AncillaryProofInput {
    pub fn into_prover_data(self) -> Option<AncillaryProverData>
}

impl AncillaryProverData {
    pub fn into_ivc_rolling_state(self) -> Option<IvcRollingState>
}

The struct cannot exist before the certificate proof, so the clerk keeps main's order: prove the certificate, then validate the ancillary input.

IvcStepProver exposes only the recursive key: the certificate key is the non-recursive prover's, there is no second certificate key. IvcProver keeps its internal ivc_setup.certificate_verifying_key for the in-circuit verifier gadget; the clerk never reads it.

Injection: SnarkProverFactory owned by the clerk

Provers are expensive (trusted setup download, key generation) and must only be built inside the branch that needs them, so the clerk owns a factory rather than the provers. The trait and the production factory live in a new proof_system/snark_prover_factory.rs since they depend on both proof-system modules, exported from proof_system/mod.rs under future_snark (MockSnarkProverFactory under test as well).

/// Builds the provers an aggregation needs, once the aggregate signature type is known.
#[cfg_attr(test, mockall::automock)]
pub(crate) trait SnarkProverFactory: Debug + Send + Sync {
    /// Builds the non-recursive prover for the certificate proof.
    fn certificate_prover(&self, parameters: &Parameters) -> StmResult<Box<dyn CertificateProver>>;

    /// Builds the recursive prover for one IVC step.
    fn ivc_step_prover(&self, parameters: &Parameters) -> StmResult<Box<dyn IvcStepProver>>;
}

/// Production factory: `SnarkProver<OsRng>` and `IvcProver<OsRng>` over the trusted setup.
#[derive(Debug, Default)]
pub(crate) struct NonDeterministicSnarkProverFactory;

impl SnarkProverFactory for NonDeterministicSnarkProverFactory {
    fn certificate_prover(&self, parameters: &Parameters) -> StmResult<Box<dyn CertificateProver>> {
        Ok(Box::new(SnarkProver::try_new_non_deterministic(
            parameters,
            MERKLE_TREE_DEPTH_FOR_SNARK,
        )?))
    }

    fn ivc_step_prover(&self, parameters: &Parameters) -> StmResult<Box<dyn IvcStepProver>> {
        Ok(Box::new(IvcProver::try_new_non_deterministic(parameters)?))
    }
}

The IVC side needs the symmetric constructor on IvcProver (proof_system/ivc_halo2_snark/proof.rs), which is step 3 of today's clerk branch moved out of clerk.rs:

impl IvcProver<OsRng> {
    /// Loads the IVC setup from the trusted setup and the recursive key provider
    /// derived from `parameters` and `MERKLE_TREE_DEPTH_FOR_SNARK`.
    pub(crate) fn try_new_non_deterministic(parameters: &Parameters) -> StmResult<Self> {
        let trusted_setup_provider = TrustedSetupProvider::default();
        let certificate_key_provider =
            KeyProvider::for_non_recursive_circuit(parameters, MERKLE_TREE_DEPTH_FOR_SNARK)?;
        let recursive_key_provider = KeyProvider::for_recursive_circuit(certificate_key_provider);
        let ivc_setup =
            IvcSnarkProverSetup::load(&trusted_setup_provider, &recursive_key_provider)?;

        Ok(Self {
            ivc_setup: Arc::new(ivc_setup),
            rng: OsRng,
        })
    }
}

The clerk owns the factory; the two public constructors keep their signatures and only gain one field initializer, and a test-gated constructor takes the mock explicitly:

#[cfg(feature = "future_snark")]
use crate::proof_system::{NonDeterministicSnarkProverFactory, SnarkProverFactory};
#[cfg(all(feature = "future_snark", test))]
use crate::proof_system::MockSnarkProverFactory;

#[derive(Debug, Clone)]
pub struct Clerk<D: MembershipDigest> {
    concatenation_proof_clerk: ConcatenationClerk,
    #[cfg(feature = "future_snark")]
    snark_proof_clerk: Option<SnarkClerk>,
    #[cfg(feature = "future_snark")]
    snark_prover_factory: Arc<dyn SnarkProverFactory>,
    phantom_data: PhantomData<D>,
}

impl<D: MembershipDigest> Clerk<D> {
    /// Create a Clerk from a signer.
    pub fn new_clerk_from_signer(signer: &Signer<D>) -> Self {
        Self {
            concatenation_proof_clerk: ConcatenationClerk::new_clerk_from_signer(signer),
            #[cfg(feature = "future_snark")]
            snark_proof_clerk: signer
                .closed_key_registration
                .has_snark_verification_keys()
                .then(|| SnarkClerk::new_clerk_from_signer(signer)),
            #[cfg(feature = "future_snark")]
            snark_prover_factory: Arc::new(NonDeterministicSnarkProverFactory),
            phantom_data: PhantomData,
        }
    }

    /// Create a Clerk from a closed key registration.
    pub fn new_clerk_from_closed_key_registration(
        parameters: &Parameters,
        closed_registration: &ClosedKeyRegistration,
    ) -> Self {
        Self {
            concatenation_proof_clerk: ConcatenationClerk::new_clerk_from_closed_key_registration(
                parameters,
                closed_registration,
            ),
            #[cfg(feature = "future_snark")]
            snark_proof_clerk: closed_registration.has_snark_verification_keys().then(|| {
                SnarkClerk::new_clerk_from_closed_key_registration(parameters, closed_registration)
            }),
            #[cfg(feature = "future_snark")]
            snark_prover_factory: Arc::new(NonDeterministicSnarkProverFactory),
            phantom_data: PhantomData,
        }
    }

    /// Create a Clerk from a signer whose SNARK provers come from a mocked factory.
    #[cfg(all(feature = "future_snark", test))]
    pub(crate) fn new_clerk_from_signer_with_mock_prover_factory(
        signer: &Signer<D>,
        snark_prover_factory: MockSnarkProverFactory,
    ) -> Self {
        Self {
            snark_prover_factory: Arc::new(snark_prover_factory),
            ..Self::new_clerk_from_signer(signer)
        }
    }
}

#[automock] generates Debug for mocked traits, so Clerk keeps #[derive(Debug, Clone)].

Clerk after the change

AggregateSignatureType::Snark => {
    let clerk = self
        .get_snark_clerk()
        .ok_or_else(|| anyhow!(AggregateSignatureError::MissingSnarkClerk))?;
    let mut prover = self.snark_prover_factory.certificate_prover(&clerk.parameters)?;
    Self::aggregate_signatures_for_snark(clerk, prover.as_mut(), sigs, msg)
}
AggregateSignatureType::IvcSnark => {
    let snark_clerk = self
        .get_snark_clerk()
        .ok_or_else(|| anyhow!(AggregateSignatureError::MissingSnarkClerk))?;
    let mut certificate_prover =
        self.snark_prover_factory.certificate_prover(&snark_clerk.parameters)?;
    let mut ivc_prover = self.snark_prover_factory.ivc_step_prover(&snark_clerk.parameters)?;
    Self::aggregate_signatures_for_ivc_snark(
        snark_clerk,
        certificate_prover.as_mut(),
        ivc_prover.as_mut(),
        sigs,
        msg,
        ancillary_input,
    )
}

fn aggregate_signatures_for_snark(
    snark_clerk: &SnarkClerk,
    prover: &mut dyn CertificateProver,
    sigs: &[SingleSignature],
    msg: &[u8],
) -> StmResult<(AggregateSignature<D>, AncillaryProofOutput)> {
    let certificate_verifying_key = prover.verification_key().clone();
    let snark_proof = prover.prove_certificate(snark_clerk, sigs, msg).with_context(..)?;
    Ok((
        AggregateSignature::Snark(Box::new(snark_proof)),
        AncillaryProofOutput::new(
            None,
            Some(AncillaryVerifierData::Snark(SnarkVerifierData::new(certificate_verifying_key))),
        ),
    ))
}

fn aggregate_signatures_for_ivc_snark(
    snark_clerk: &SnarkClerk,
    certificate_prover: &mut dyn CertificateProver,
    ivc_prover: &mut dyn IvcStepProver,
    sigs: &[SingleSignature],
    msg: &[u8],
    ancillary_input: AncillaryProofInput,
) -> StmResult<(AggregateSignature<D>, AncillaryProofOutput)> {
    let certificate_verifying_key = certificate_prover.verification_key().clone();
    let certificate_proof =
        certificate_prover.prove_certificate(snark_clerk, sigs, msg).with_context(..)?;
    let step_input = IvcStepInput::try_new(
        certificate_proof,
        msg,
        snark_clerk.compute_aggregate_verification_key_for_snark(),
        ancillary_input,
        &certificate_verifying_key,
        ivc_prover.ivc_verifying_key(),
    )?;
    let genesis_message = step_input.global.genesis_message;
    let (ivc_proof, next_rolling_state) = ivc_prover.prove_step(step_input)?;
    let verifier_data = IvcVerifierData::new(
        genesis_message,
        certificate_verifying_key,
        ivc_prover.ivc_verifying_key().clone(),
    );
    Ok((
        AggregateSignature::IvcSnark(Box::new(ivc_proof)),
        AncillaryProofOutput::new(
            next_rolling_state.map(AncillaryProverData::IvcSnark),
            Some(AncillaryVerifierData::IvcSnark(verifier_data)),
        ),
    ))
}
Before (main) After
ivc_prover_input_preparation_and_prove split into IvcStepInput::try_new (assembly) and IvcProver::prove_step (proving)
IvcSnarkProverSetup::load and key providers in clerk.rs moved to NonDeterministicSnarkProverFactory and IvcProver::try_new_non_deterministic
certificate key read from IvcSnarkProverSetup read from the prover that produced the certificate proof, as the Snark branch already does
IvcSnark error context labelled Snark labelled IvcSnark
build_fast_dummy_ivc_setup in clerk.rs tests relocated to prover_input.rs tests

The aggregate_signatures_for_* helpers are private and never called from tests: every clerk test goes through aggregate_signatures_with_type.

Test doubles and fixtures

Add mockall = { workspace = true } to mithril-stm/[dev-dependencies] (already pinned at workspace level, used by mithril-common). #[cfg_attr(test, automock)] generates MockSnarkProverFactory, MockCertificateProver and MockIvcStepProver.

Fixture Source
certificate verifying key NonRecursiveCircuitVerifyingKey::try_from_bytes(NON_RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION) (already used in circuits/halo2/keys.rs tests)
IVC verifying key RecursiveCircuitVerifyingKey::try_from_bytes(RECURSIVE_CIRCUIT_VERIFICATION_KEY_FOR_PRODUCTION)
certificate proof SnarkProof::new(vec![], params, MERKLE_TREE_DEPTH_FOR_SNARK)
IVC proof IvcProof::new(IvcProofBytes::empty(), State::genesis(), trivial_accumulator(&[]))
rolling state build_standard_rolling_state from prover_input_helpers::tests
dummy IVC setup build_fast_dummy_ivc_setup (tiny circuit, unsafe SRS, a couple of seconds of keygen), only for the certificate-rejection test in prover_input.rs

Shared clerk test helpers (clerk.rs test module):

fn certificate_verifying_key() -> NonRecursiveCircuitVerifyingKey
fn ivc_verifying_key() -> RecursiveCircuitVerifyingKey
fn dummy_certificate_proof(params: Parameters) -> SnarkProof<MithrilMembershipDigest>
fn dummy_ivc_proof() -> IvcProof<Blake2b256>
fn build_ancillary_input(prover_data: Option<AncillaryProverData>) -> AncillaryProofInput
    // PREIMAGE_SIZE genesis preimage, a Schnorr signature and key, PREIMAGE_SIZE message preimage

fn factory_with(
    certificate_prover: MockCertificateProver,
    ivc_prover: MockIvcStepProver,
) -> MockSnarkProverFactory {
    let mut factory = MockSnarkProverFactory::new();
    factory
        .expect_certificate_prover()
        .once()
        .return_once(move |_| Ok(Box::new(certificate_prover)));
    factory
        .expect_ivc_step_prover()
        .once()
        .return_once(move |_| Ok(Box::new(ivc_prover)));
    factory
}

Test matrix (fast tier, future_snark)

IvcStepInput::try_new (no double):

Test Pins
ivc_step_input_fails_when_prover_data_carries_no_ivc_rolling_state existing clerk precondition test, moved
ivc_step_input_fails_when_genesis_verification_key_is_absent existing clerk precondition test, moved
ivc_step_input_fails_when_genesis_signature_is_absent new precondition
ivc_step_input_fails_when_message_preimage_has_wrong_size new precondition
ivc_step_input_carries_rolling_state_from_ancillary_prover_data None without prover data, Some with AncillaryProverData::IvcSnark
ivc_step_input_builds_global_from_genesis_data_and_keys Global assembly

Clerk::aggregate_signatures_with_type (mock factory injected through new_clerk_from_signer_with_mock_prover_factory):

Test Pins
concatenation_aggregation_does_not_build_snark_provers both factory methods .never()
snark_aggregation_fails_when_snark_clerk_is_missing MissingSnarkClerk before any prover is built
snark_aggregation_builds_certificate_prover_with_clerk_parameters withf on parameters
snark_aggregation_propagates_prover_factory_error setup load failure
snark_aggregation_returns_verifier_data_from_prover_key SnarkVerifierData assembly
snark_aggregation_propagates_prover_error error context
ivc_aggregation_fails_when_snark_clerk_is_missing both factory methods .never()
ivc_aggregation_propagates_prover_factory_error setup load failure
ivc_aggregation_does_not_prove_step_on_invalid_ancillary_input expect_prove_step().never()
ivc_aggregation_passes_certificate_proof_and_message_to_step_prover withf on step_input: proof bytes, message, AVK, preimage bytes
ivc_aggregation_uses_certificate_prover_key_for_global_and_verifier_data mocks return distinct keys, Global and IvcVerifierData carry the certificate prover's one
ivc_aggregation_threads_next_rolling_state_into_prover_data Some -> AncillaryProverData::IvcSnark
ivc_aggregation_returns_no_prover_data_on_same_epoch_step None -> None
ivc_aggregation_propagates_certificate_prover_error expect_prove_step().never(), context reads IvcSnark
ivc_aggregation_propagates_ivc_prover_error error propagation

IvcProverInput::prepare (no double):

Test Pins
prepare_rejects_invalid_certificate_proof SnarkProof::new(vec![], ..) against build_fast_dummy_ivc_setup; empty proof bytes fail in prepare_and_check, so the rejection path is reached with no proof generated (replaces slow::prepare_rejects_invalid_snark_proof)

Run: cargo test -p mithril-stm --features future_snark -- --skip slow::

Examples

Rolling-state threading and input assembly, through the public entry point:

#[test]
fn ivc_aggregation_threads_next_rolling_state_into_prover_data() {
    let params = Parameters { k: 1, m: 10, phi_f: 0.9 };
    let signers = setup_equal_parties(params, 1);
    let current_rolling_state =
        build_standard_rolling_state(StepCounter::new(3), EpochNumber::new(2));
    let next_rolling_state =
        build_standard_rolling_state(StepCounter::new(4), EpochNumber::new(3));
    let expected_prover_data =
        AncillaryProverData::IvcSnark(next_rolling_state.clone()).to_bytes().unwrap();

    let mut certificate_prover = MockCertificateProver::new();
    certificate_prover
        .expect_verification_key()
        .return_const(certificate_verifying_key());
    certificate_prover
        .expect_prove_certificate()
        .once()
        .return_once(move |_, _, _| Ok(dummy_certificate_proof(params)));

    let mut ivc_prover = MockIvcStepProver::new();
    ivc_prover.expect_ivc_verifying_key().return_const(ivc_verifying_key());
    ivc_prover
        .expect_prove_step()
        .once()
        .withf(|step_input| {
            step_input.message == MESSAGE
                && step_input
                    .rolling_state
                    .as_ref()
                    .is_some_and(|rolling_state| rolling_state.state().step_counter == StepCounter::new(3))
        })
        .return_once(move |_| Ok((dummy_ivc_proof(), Some(next_rolling_state))));

    let clerk = Clerk::new_clerk_from_signer_with_mock_prover_factory(
        &signers[0],
        factory_with(certificate_prover, ivc_prover),
    );

    let (aggregate_signature, ancillary_output) = clerk
        .aggregate_signatures_with_type(
            &[],
            &MESSAGE,
            AggregateSignatureType::IvcSnark,
            build_ancillary_input(Some(AncillaryProverData::IvcSnark(current_rolling_state))),
        )
        .expect("aggregation with mocked provers should succeed");

    assert!(matches!(aggregate_signature, AggregateSignature::IvcSnark(_)));
    assert_eq!(
        ancillary_output.prover_data().unwrap().to_bytes().unwrap(),
        expected_prover_data
    );
}

Error mapping, with the recursive prover pinned to never run:

#[test]
fn ivc_aggregation_propagates_certificate_prover_error() {
    let params = Parameters { k: 1, m: 10, phi_f: 0.9 };
    let signers = setup_equal_parties(params, 1);

    let mut certificate_prover = MockCertificateProver::new();
    certificate_prover
        .expect_verification_key()
        .return_const(certificate_verifying_key());
    certificate_prover
        .expect_prove_certificate()
        .once()
        .return_once(|_, _, _| Err(anyhow!("certificate proving failed")));

    let mut ivc_prover = MockIvcStepProver::new();
    ivc_prover.expect_ivc_verifying_key().return_const(ivc_verifying_key());
    ivc_prover.expect_prove_step().never();

    let clerk = Clerk::new_clerk_from_signer_with_mock_prover_factory(
        &signers[0],
        factory_with(certificate_prover, ivc_prover),
    );

    let error = clerk
        .aggregate_signatures_with_type(
            &[],
            &MESSAGE,
            AggregateSignatureType::IvcSnark,
            build_ancillary_input(None),
        )
        .expect_err("a failing certificate prover must fail the aggregation");

    assert!(
        error
            .to_string()
            .contains("Signatures failed to aggregate for type IvcSnark"),
        "got: {error}"
    );
    assert_eq!(error.root_cause().to_string(), "certificate proving failed");
}

Slow tests

CI runs cargo nextest run without --run-ignored, so #[ignore] tests never run, not even in the nightly slow run. In prover_input.rs::slow:

Test Ignored Proposal
prepare_genesis_rejects_invalid_signature no move out of slow:: unchanged, it needs no setup
prepare_genesis_produces_expected_state_and_witness no same
prepare_at_genesis_produces_advanced_state_and_witness yes keep, drop #[ignore]
prepare_at_same_epoch_advances_state_correctly yes keep, drop #[ignore]
prepare_at_next_epoch_carries_lookahead_protocol_parameters yes keep, drop #[ignore]
prepare_rejects_invalid_snark_proof yes remove, replaced by fast prepare_rejects_invalid_certificate_proof
prepare_rejects_invalid_genesis_signature yes remove, stale: prepare never verifies the genesis signature, only prepare_genesis does
prepare_rejects_invalid_epoch_transition yes remove, redundant with fast rejects_out_of_range_cert_epoch in prover_input_helpers
prepare_rejects_step_counter_overflow yes remove, redundant with fast build_next_state::rejects_step_counter_overflow

Commits

Each commit builds warning-free with green tests: a trait commit also wires its clerk branch onto the trait, the factory comes once both traits exist, tests follow.

  1. refactor(stm): extract CertificateProver trait from SnarkProver
  2. refactor(stm): extract IvcStepProver trait and IvcStepInput from IvcProver
  3. refactor(stm): inject SNARK provers into the clerk through SnarkProverFactory
  4. test(stm): add behavior tests for IvcStepInput preparation
  5. test(stm): add behavior tests for SNARK clerk wiring
  6. test(stm): add behavior tests for IVC clerk wiring
  7. test(stm): cover certificate rejection in IVC prover input preparation
  8. test(stm): retire redundant slow prover input tests

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions