-
Notifications
You must be signed in to change notification settings - Fork 517
[std/recursion/plonk] Outer Prove fails with constraint mismatch (qL⋅xa + qR⋅xb... != 0) when inner circuit uses 2D slices #1745
Description
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"`
// ...
}
- Compile the inner BatchCircuit over BLS12-377 and run test/unsafekzg to get keys.
- Generate proofs natively and verify them with plonk.Verify (they pass).
- Set up the AggregationCircuit struct containing standard recursive_plonk slices.
- Compile the outer circuit over BW6-761 using PlaceholderProof, PlaceholderWitness, and ValueOfVerifyingKey(innerVK).
- Call plonk.Prove for the outer circuit using the natively valid proofs and witnesses via ValueOfProof and ValueOfWitness.
- 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)