Skip to content

[std/recursion/plonk] Outer Prove fails with constraint mismatch (qL⋅xa + qR⋅xb... != 0) when inner circuit uses 2D slices #1745

@SanthoshCheemala

Description

@SanthoshCheemala

Description

Hey all! I'm trying to aggregate an array of BLS12-377 batch proofs into a single BW6-761 outer proof using std/recursion/plonk.

My inner proofs are mathematically sound and verify perfectly using the native plonk.Verify on ecc.BLS12_377. The outer BW6-761 aggregation circuit also compiles successfully using PlaceholderProof, PlaceholderWitness, and ValueOfVerifyingKey(innerVK).

But when I run the final plonk.Prove on the outer circuit, it consistently panics with a constraint violation inside AssertProof:
Aggregation failed: outer plonk prove constraint violation: constraint #1063395 is not satisfied: qL⋅xa + qR⋅xb + qO⋅xc + qM⋅(xaxb) + qC != 0 → 1776... + 1737... + 0 + 0 + 0 != 0

It looks like the inner VerifyingKey polynomials serialized via ValueOfVerifyingKey aren't lining up with the evaluations dynamically generated by AssertProof.

Expected Behavior

If the inner proof passes native verification, piping that exact same Proof/VK/Witness into the BW6-761 recursive aggregator shouldn't fail the internal constraint evaluations.

Actual Behavior

Instead of generating the outer proof, plonk.Prove crashes dynamically with the constraint mismatch mentioned above.

Possible Fix

My inner circuit uses dynamic bounded slices ([][]frontend.Variable) for processing batches. I suspect that ValueOfVerifyingKey or ValueOfWitness might be subtly stripping or misaligning some constraint indices when unrolling nested 2D slices compared to how standard flat 1D arrays are handled.

Steps to Reproduce

Here is the basic shape of the inner circuit causing the issue:

type BatchCircuit struct {
	W []frontend.Variable   `gnark:",secret"`
	B frontend.Variable     `gnark:",secret"`
	X [][]frontend.Variable `gnark:",public"` // The 2D slice I think is triggering it
	Y []frontend.Variable   `gnark:",public"`
        // ...
}
  1. Compile the inner BatchCircuit over BLS12-377 and run test/unsafekzg to get keys.
  2. Generate proofs natively and verify them with plonk.Verify (they pass).
  3. Set up the AggregationCircuit struct containing standard recursive_plonk slices.
  4. Compile the outer circuit over BW6-761 using PlaceholderProof, PlaceholderWitness, and ValueOfVerifyingKey(innerVK).
  5. Call plonk.Prove for the outer circuit using the natively valid proofs and witnesses via ValueOfProof and ValueOfWitness.
  6. Watch it fail with the qL⋅xa + qR⋅xb + ... != 0 mismatch.

Context

I'm migrating a ZK machine learning pipeline from emulated BN254 to natively folded BLS12-377 -> BW6-761 to avoid blowing up memory boundaries (BN254 emulated recursion was taking ~8M constraints per proof). The new curve pipeline compiles perfectly and is exactly what we need, but dynamic mapping currently hard-blocks the final root SNARK generation.

Your Environment

gnark version used: v0.11.0
gnark-crypto version used: v0.14.0
go version: go1.22+
Operating System and version: macOS (Apple Silicon M-Series)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions