From cab8d185f80de71599cc2294c878e3f54d899765 Mon Sep 17 00:00:00 2001 From: Forostovec Date: Mon, 29 Sep 2025 16:24:09 +0300 Subject: [PATCH 1/2] Handle zero public inputs in PI computation to avoid panic --- std/recursion/plonk/verifier.go | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/std/recursion/plonk/verifier.go b/std/recursion/plonk/verifier.go index 8f09b4a7a..f6c3a4c5e 100644 --- a/std/recursion/plonk/verifier.go +++ b/std/recursion/plonk/verifier.go @@ -840,26 +840,29 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) PrepareVerification(vk VerifyingKey[FR, lagrange := lagrangeOne // compute PI = ∑_{i 0 { + wPowI := one + xiLi := v.scalarApi.Mul(lagrange, &witness.Public[0]) pi = v.scalarApi.Add(pi, xiLi) - if i+1 != len(witness.Public) { + if len(witness.Public) != 1 { lagrange = v.scalarApi.Mul(lagrange, &vk.Generator) lagrange = v.scalarApi.Mul(lagrange, denom) - wPowI = v.scalarApi.Mul(wPowI, &vk.Generator) + wPowI = &vk.Generator denom = v.scalarApi.Sub(zeta, wPowI) lagrange = v.scalarApi.Div(lagrange, denom) } + for i := 1; i < len(witness.Public); i++ { + xiLi := v.scalarApi.Mul(lagrange, &witness.Public[i]) + pi = v.scalarApi.Add(pi, xiLi) + if i+1 != len(witness.Public) { + lagrange = v.scalarApi.Mul(lagrange, &vk.Generator) + lagrange = v.scalarApi.Mul(lagrange, denom) + wPowI = v.scalarApi.Mul(wPowI, &vk.Generator) + denom = v.scalarApi.Sub(zeta, wPowI) + lagrange = v.scalarApi.Div(lagrange, denom) + } + } } if len(vk.CommitmentConstraintIndexes) > 0 { From 4a99cc4541556b35510575fdfb1808b0e70285ab Mon Sep 17 00:00:00 2001 From: Forostovec Date: Mon, 29 Sep 2025 16:24:48 +0300 Subject: [PATCH 2/2] Add test covering zero public inputs in recursive PLONK verifier --- std/recursion/plonk/verifier_test.go | 63 ++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/std/recursion/plonk/verifier_test.go b/std/recursion/plonk/verifier_test.go index 124d50f91..345400ff2 100644 --- a/std/recursion/plonk/verifier_test.go +++ b/std/recursion/plonk/verifier_test.go @@ -801,3 +801,66 @@ func TestAggregationDiff(t *testing.T) { err = test.IsSolved(aggCircuit, aggAssignment, ecc.BW6_761.ScalarField()) assert.NoError(err) } + +// ----------------------------------------------------------------- +// Zero public inputs + +type InnerCircuitZeroPublic struct { + A, B frontend.Variable +} + +func (c *InnerCircuitZeroPublic) Define(api frontend.API) error { + api.AssertIsEqual(api.Mul(c.A, c.B), 6) + return nil +} + +func getInnerZeroPublic(assert *test.Assert, field, outer *big.Int) (constraint.ConstraintSystem, native_plonk.VerifyingKey, witness.Witness, native_plonk.Proof) { + innerCcs, err := frontend.Compile(field, scs.NewBuilder, &InnerCircuitZeroPublic{}) + assert.NoError(err) + srs, srsLagrange, err := unsafekzg.NewSRS(innerCcs) + assert.NoError(err) + + innerPK, innerVK, err := native_plonk.Setup(innerCcs, srs, srsLagrange) + assert.NoError(err) + + // inner proof + innerAssignment := &InnerCircuitZeroPublic{A: 2, B: 3} + innerWitness, err := frontend.NewWitness(innerAssignment, field) + assert.NoError(err) + innerProof, err := native_plonk.Prove(innerCcs, innerPK, innerWitness, GetNativeProverOptions(outer, field)) + assert.NoError(err) + innerPubWitness, err := innerWitness.Public() + assert.NoError(err) + // ensure zero public inputs indeed + assert.Equal(0, innerCcs.GetNbPublicVariables()) + err = native_plonk.Verify(innerProof, innerVK, innerPubWitness, GetNativeVerifierOptions(outer, field)) + assert.NoError(err) + return innerCcs, innerVK, innerPubWitness, innerProof +} + +func TestZeroPublicInputsBW6InBN254(t *testing.T) { + assert := test.NewAssert(t) + innerCcs, innerVK, innerWitness, innerProof := getInnerZeroPublic(assert, ecc.BW6_761.ScalarField(), ecc.BN254.ScalarField()) + + // outer proof (recursive verification) + circuitVk, err := ValueOfVerifyingKey[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerVK) + assert.NoError(err) + circuitWitness, err := ValueOfWitness[sw_bw6761.ScalarField](innerWitness) + assert.NoError(err) + // sanity: witness.Public should be empty after conversion + assert.Equal(0, len(circuitWitness.Public)) + circuitProof, err := ValueOfProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerProof) + assert.NoError(err) + + outerCircuit := &OuterCircuit[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]{ + InnerWitness: PlaceholderWitness[sw_bw6761.ScalarField](innerCcs), + Proof: PlaceholderProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerCcs), + VerifyingKey: circuitVk, + } + outerAssignment := &OuterCircuit[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + } + err = test.IsSolved(outerCircuit, outerAssignment, ecc.BN254.ScalarField()) + assert.NoError(err) +}