diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index 404b6fc5c6..c8467a479d 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -647,7 +647,7 @@ func (c *Curve[B, S]) MultiScalarMul(p []*AffinePoint[B], s []*emulated.Element[ res := c.ScalarMul(p[0], s[0]) for i := 1; i < len(p); i++ { q := c.ScalarMul(p[i], s[i]) - c.AddUnified(res, q) + res = c.AddUnified(res, q) } return res, nil } diff --git a/std/algebra/emulated/sw_emulated/point_test.go b/std/algebra/emulated/sw_emulated/point_test.go index 267ccbf3c6..0a3cd8326f 100644 --- a/std/algebra/emulated/sw_emulated/point_test.go +++ b/std/algebra/emulated/sw_emulated/point_test.go @@ -19,6 +19,7 @@ import ( fr_secp "github.com/consensys/gnark-crypto/ecc/secp256k1/fr" "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/math/emulated/emparams" "github.com/consensys/gnark/test" ) @@ -920,3 +921,69 @@ func TestJointScalarMulBase(t *testing.T) { err := test.IsSolved(&circuit, &witness, testCurve.ScalarField()) assert.NoError(err) } + +type MultiScalarMulTest[T, S emulated.FieldParams] struct { + Points []AffinePoint[T] + Scalars []emulated.Element[S] + Res AffinePoint[T] +} + +func (c *MultiScalarMulTest[T, S]) Define(api frontend.API) error { + cr, err := New[T, S](api, GetCurveParams[T]()) + if err != nil { + return err + } + ps := make([]*AffinePoint[T], len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*emulated.Element[S], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res, err := cr.MultiScalarMul(ps, ss) + if err != nil { + return err + } + cr.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMultiScalarMul(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 4 + P := make([]secp256k1.G1Affine, nbLen) + S := make([]fr_secp.Element, nbLen) + for i := 0; i < nbLen; i++ { + S[i].SetRandom() + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res secp256k1.G1Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]AffinePoint[emulated.Secp256k1Fp], len(P)) + for i := range cP { + cP[i] = AffinePoint[emparams.Secp256k1Fp]{ + X: emulated.ValueOf[emparams.Secp256k1Fp](P[i].X), + Y: emulated.ValueOf[emparams.Secp256k1Fp](P[i].Y), + } + } + cS := make([]emulated.Element[emparams.Secp256k1Fr], len(S)) + for i := range cS { + cS[i] = emulated.ValueOf[emparams.Secp256k1Fr](S[i]) + } + assignment := MultiScalarMulTest[emparams.Secp256k1Fp, emparams.Secp256k1Fr]{ + Points: cP, + Scalars: cS, + Res: AffinePoint[emparams.Secp256k1Fp]{ + X: emulated.ValueOf[emparams.Secp256k1Fp](res.X), + Y: emulated.ValueOf[emparams.Secp256k1Fp](res.Y), + }, + } + err = test.IsSolved(&MultiScalarMulTest[emparams.Secp256k1Fp, emparams.P256Fr]{ + Points: make([]AffinePoint[emparams.Secp256k1Fp], nbLen), + Scalars: make([]emulated.Element[emparams.P256Fr], nbLen), + }, &assignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} diff --git a/std/algebra/native/sw_bls12377/g1_test.go b/std/algebra/native/sw_bls12377/g1_test.go index ce10d40732..eb52626127 100644 --- a/std/algebra/native/sw_bls12377/g1_test.go +++ b/std/algebra/native/sw_bls12377/g1_test.go @@ -27,6 +27,7 @@ import ( "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/cs/r1cs" "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/test" bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377" @@ -487,6 +488,66 @@ func TestVarScalarMulBaseG1(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_761)) } +type MultiScalarMulTest struct { + Points []G1Affine + Scalars []emulated.Element[ScalarField] + Res G1Affine +} + +func (c *MultiScalarMulTest) Define(api frontend.API) error { + cr, err := NewCurve(api) + if err != nil { + return err + } + ps := make([]*G1Affine, len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*emulated.Element[ScalarField], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res, err := cr.MultiScalarMul(ps, ss) + if err != nil { + return err + } + cr.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMultiScalarMul(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 4 + P := make([]bls12377.G1Affine, nbLen) + S := make([]fr.Element, nbLen) + for i := 0; i < nbLen; i++ { + S[i].SetRandom() + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res bls12377.G1Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]G1Affine, len(P)) + for i := range cP { + cP[i] = NewG1Affine(P[i]) + } + cS := make([]emulated.Element[ScalarField], len(S)) + for i := range cS { + cS[i] = NewScalar(S[i]) + } + assignment := MultiScalarMulTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(res), + } + err = test.IsSolved(&MultiScalarMulTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment, ecc.BW6_761.ScalarField()) + assert.NoError(err) +} + func randomPointG1() bls12377.G1Jac { p1, _, _, _ := bls12377.Generators() diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index 63d8b1d614..a75740febd 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -76,7 +76,6 @@ func (c *Curve) Add(P, Q *G1Affine) *G1Affine { // AssertIsEqual asserts the equality of P and Q. func (c *Curve) AssertIsEqual(P, Q *G1Affine) { P.AssertIsEqual(c.api, *Q) - panic("todo") } // Neg negates P and returns the result. Does not modify P. @@ -126,7 +125,12 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar) (*G1Affine, err res := c.ScalarMul(P[0], scalars[0]) for i := 1; i < len(P); i++ { q := c.ScalarMul(P[i], scalars[i]) - c.Add(res, q) + + // check for infinity + isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) + tmp := c.Add(res, q) + res.X = c.api.Select(isInfinity, res.X, tmp.X) + res.Y = c.api.Select(isInfinity, res.Y, tmp.Y) } return res, nil } diff --git a/std/algebra/native/sw_bls24315/g1_test.go b/std/algebra/native/sw_bls24315/g1_test.go index 524442fbe3..0cff2372d3 100644 --- a/std/algebra/native/sw_bls24315/g1_test.go +++ b/std/algebra/native/sw_bls24315/g1_test.go @@ -27,6 +27,7 @@ import ( "github.com/consensys/gnark/frontend" "github.com/consensys/gnark/frontend/cs/r1cs" "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/test" bls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315" @@ -489,6 +490,66 @@ func TestVarScalarMulBaseG1(t *testing.T) { assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BW6_633), test.NoProverChecks()) } +type MultiScalarMulTest struct { + Points []G1Affine + Scalars []emulated.Element[ScalarField] + Res G1Affine +} + +func (c *MultiScalarMulTest) Define(api frontend.API) error { + cr, err := NewCurve(api) + if err != nil { + return err + } + ps := make([]*G1Affine, len(c.Points)) + for i := range c.Points { + ps[i] = &c.Points[i] + } + ss := make([]*emulated.Element[ScalarField], len(c.Scalars)) + for i := range c.Scalars { + ss[i] = &c.Scalars[i] + } + res, err := cr.MultiScalarMul(ps, ss) + if err != nil { + return err + } + cr.AssertIsEqual(res, &c.Res) + return nil +} + +func TestMultiScalarMul(t *testing.T) { + assert := test.NewAssert(t) + nbLen := 4 + P := make([]bls24315.G1Affine, nbLen) + S := make([]fr.Element, nbLen) + for i := 0; i < nbLen; i++ { + S[i].SetRandom() + P[i].ScalarMultiplicationBase(S[i].BigInt(new(big.Int))) + } + var res bls24315.G1Affine + _, err := res.MultiExp(P, S, ecc.MultiExpConfig{}) + + assert.NoError(err) + cP := make([]G1Affine, len(P)) + for i := range cP { + cP[i] = NewG1Affine(P[i]) + } + cS := make([]emulated.Element[ScalarField], len(S)) + for i := range cS { + cS[i] = NewScalar(S[i]) + } + assignment := MultiScalarMulTest{ + Points: cP, + Scalars: cS, + Res: NewG1Affine(res), + } + err = test.IsSolved(&MultiScalarMulTest{ + Points: make([]G1Affine, nbLen), + Scalars: make([]emulated.Element[ScalarField], nbLen), + }, &assignment, ecc.BW6_633.ScalarField()) + assert.NoError(err) +} + func randomPointG1() bls24315.G1Jac { p1, _, _, _ := bls24315.Generators() diff --git a/std/algebra/native/sw_bls24315/pairing2.go b/std/algebra/native/sw_bls24315/pairing2.go index 21441f926d..a84808ab07 100644 --- a/std/algebra/native/sw_bls24315/pairing2.go +++ b/std/algebra/native/sw_bls24315/pairing2.go @@ -76,7 +76,6 @@ func (c *Curve) Add(P, Q *G1Affine) *G1Affine { // AssertIsEqual asserts the equality of P and Q. func (c *Curve) AssertIsEqual(P, Q *G1Affine) { P.AssertIsEqual(c.api, *Q) - panic("todo") } // Neg negates P and returns the result. Does not modify P. @@ -126,7 +125,12 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar) (*G1Affine, err res := c.ScalarMul(P[0], scalars[0]) for i := 1; i < len(P); i++ { q := c.ScalarMul(P[i], scalars[i]) - c.Add(res, q) + + // check for infinity... + isInfinity := c.api.And(c.api.IsZero(P[i].X), c.api.IsZero(P[i].Y)) + tmp := c.Add(res, q) + res.X = c.api.Select(isInfinity, res.X, tmp.X) + res.Y = c.api.Select(isInfinity, res.Y, tmp.Y) } return res, nil } diff --git a/std/commitments/kzg/verifier.go b/std/commitments/kzg/verifier.go index 3b077a6354..eb29938b21 100644 --- a/std/commitments/kzg/verifier.go +++ b/std/commitments/kzg/verifier.go @@ -36,6 +36,7 @@ import ( "github.com/consensys/gnark/std/algebra/emulated/sw_bw6761" "github.com/consensys/gnark/std/algebra/native/sw_bls12377" "github.com/consensys/gnark/std/algebra/native/sw_bls24315" + "github.com/consensys/gnark/std/math/bits" "github.com/consensys/gnark/std/math/emulated" "github.com/consensys/gnark/std/recursion" ) @@ -364,7 +365,8 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) CheckOpeningProof(commitment Commitment return nil } -func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifySinglePoint(digests []Commitment[G1El], batchOpeningProof BatchOpeningProof[FR, G1El], point emulated.Element[FR], vk VerifyingKey[G1El, G2El], dataTranscript ...frontend.Variable) error { +// BatchVerifySinglePoint verifies multiple opening proofs at a single point. +func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifySinglePoint(digests []Commitment[G1El], batchOpeningProof BatchOpeningProof[FR, G1El], point emulated.Element[FR], vk VerifyingKey[G1El, G2El], dataTranscript ...emulated.Element[FR]) error { // fold the proof foldedProof, foldedDigest, err := v.FoldProof(digests, batchOpeningProof, point, dataTranscript...) if err != nil { @@ -378,6 +380,7 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifySinglePoint(digests []Commit return nil } +// BatchVerifyMultiPoints verifies multiple opening proofs at different points. func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commitment[G1El], proofs []OpeningProof[FR, G1El], points []emulated.Element[FR], vk VerifyingKey[G1El, G2El]) error { var fr FR @@ -418,7 +421,7 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit } seed := whSnark.Sum() - binSeed := v.api.ToBinary(seed) + binSeed := bits.ToBinary(v.api, seed, bits.WithNbDigits(fr.Modulus().BitLen())) randomNumbers[1] = v.scalarApi.FromBits(binSeed...) for i := 2; i < len(randomNumbers); i++ { @@ -489,7 +492,8 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit return err } -func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], batchOpeningProof BatchOpeningProof[FR, G1El], point emulated.Element[FR], dataTranscript ...frontend.Variable) (OpeningProof[FR, G1El], Commitment[G1El], error) { +// FoldProof folds multiple commitments and a batch opening proof for a single opening check. +func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], batchOpeningProof BatchOpeningProof[FR, G1El], point emulated.Element[FR], dataTranscript ...emulated.Element[FR]) (OpeningProof[FR, G1El], Commitment[G1El], error) { var retP OpeningProof[FR, G1El] var retC Commitment[G1El] nbDigests := len(digests) @@ -525,7 +529,7 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], b // deriveGamma derives a challenge using Fiat Shamir to fold proofs. // dataTranscript are supposed to be bits. -func (v *Verifier[FR, G1El, G2El, GTEl]) deriveGamma(point emulated.Element[FR], digests []Commitment[G1El], claimedValues []emulated.Element[FR], dataTranscript ...frontend.Variable) (*emulated.Element[FR], error) { +func (v *Verifier[FR, G1El, G2El, GTEl]) deriveGamma(point emulated.Element[FR], digests []Commitment[G1El], claimedValues []emulated.Element[FR], dataTranscript ...emulated.Element[FR]) (*emulated.Element[FR], error) { var fr FR fs, err := recursion.NewTranscript(v.api, fr.Modulus(), []string{"gamma"}) if err != nil { @@ -546,15 +550,17 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) deriveGamma(point emulated.Element[FR], } } - if err := fs.Bind("gamma", dataTranscript); err != nil { - return nil, fmt.Errorf("bind data transcript: %w", err) + for i := range dataTranscript { + if err := fs.Bind("gamma", v.curve.MarshalScalar(dataTranscript[i])); err != nil { + return nil, fmt.Errorf("bind %d-ith data transcript: %w", i, err) + } } gamma, err := fs.ComputeChallenge("gamma") if err != nil { return nil, fmt.Errorf("compute challenge: %w", err) } - bGamma := v.api.ToBinary(gamma) + bGamma := bits.ToBinary(v.api, gamma, bits.WithNbDigits(fr.Modulus().BitLen())) gammaS := v.scalarApi.FromBits(bGamma...) return gammaS, nil diff --git a/std/recursion/plonk/doc.go b/std/recursion/plonk/doc.go new file mode 100644 index 0000000000..955477ac83 --- /dev/null +++ b/std/recursion/plonk/doc.go @@ -0,0 +1,2 @@ +// Package plonk implements in-circuit PLONK verifier. +package plonk diff --git a/std/recursion/plonk/native_doc_test.go b/std/recursion/plonk/native_doc_test.go new file mode 100644 index 0000000000..89e7f1aa32 --- /dev/null +++ b/std/recursion/plonk/native_doc_test.go @@ -0,0 +1,82 @@ +package plonk_test + +import ( + "github.com/consensys/gnark-crypto/ecc" + native_plonk "github.com/consensys/gnark/backend/plonk" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/std/algebra/native/sw_bls12377" + "github.com/consensys/gnark/std/recursion/plonk" + "github.com/consensys/gnark/test" +) + +// Example of verifying recursively BLS12-371 PLONK proof in BW6-761 PLONK circuit using field emulation +func Example_native() { + // compute the proof which we want to verify recursively + innerCcs, innerVK, innerWitness, innerProof := computeInnerProof(ecc.BW6_761.ScalarField(), ecc.BN254.ScalarField()) + + // initialize the witness elements + circuitVk, err := plonk.ValueOfVerifyingKey[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerVK) + if err != nil { + panic(err) + } + circuitWitness, err := plonk.ValueOfWitness[sw_bls12377.ScalarField](innerWitness) + if err != nil { + panic(err) + } + circuitProof, err := plonk.ValueOfProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerProof) + if err != nil { + panic(err) + } + + outerCircuit := &OuterCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{ + InnerWitness: plonk.PlaceholderWitness[sw_bls12377.ScalarField](innerCcs), + Proof: plonk.PlaceholderProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerCcs), + VerifyingKey: plonk.PlaceholderVerifyingKey[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerCcs), + } + outerAssignment := &OuterCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + VerifyingKey: circuitVk, + } + // compile the outer circuit + ccs, err := frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, outerCircuit) + if err != nil { + panic("compile failed: " + err.Error()) + } + + // NB! UNSAFE! Use MPC. + srs, err := test.NewKZGSRS(innerCcs) + if err != nil { + panic(err) + } + // create PLONK setup. NB! UNSAFE + pk, vk, err := native_plonk.Setup(ccs, srs) // UNSAFE! Use MPC + if err != nil { + panic("setup failed: " + err.Error()) + } + + // create prover witness from the assignment + secretWitness, err := frontend.NewWitness(outerAssignment, ecc.BN254.ScalarField()) + if err != nil { + panic("secret witness failed: " + err.Error()) + } + + // create public witness from the assignment + publicWitness, err := secretWitness.Public() + if err != nil { + panic("public witness failed: " + err.Error()) + } + + // construct the PLONK proof of verifying PLONK proof in-circuit + outerProof, err := native_plonk.Prove(ccs, pk, secretWitness) + if err != nil { + panic("proving failed: " + err.Error()) + } + + // verify the Groth16 proof + err = native_plonk.Verify(outerProof, vk, publicWitness) + if err != nil { + panic("circuit verification failed: " + err.Error()) + } +} diff --git a/std/recursion/plonk/nonnative_doc_test.go b/std/recursion/plonk/nonnative_doc_test.go new file mode 100644 index 0000000000..d9d34e326b --- /dev/null +++ b/std/recursion/plonk/nonnative_doc_test.go @@ -0,0 +1,167 @@ +package plonk_test + +import ( + "fmt" + "math/big" + + "github.com/consensys/gnark-crypto/ecc" + native_plonk "github.com/consensys/gnark/backend/plonk" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/std/algebra" + "github.com/consensys/gnark/std/algebra/emulated/sw_bw6761" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/recursion/plonk" + "github.com/consensys/gnark/test" +) + +// InnerCircuitNative is the definition of the inner circuit we want to +// recursively verify inside an outer circuit. The circuit proves the knowledge +// of a factorisation of a semiprime. +type InnerCircuitNative struct { + P, Q frontend.Variable + N frontend.Variable `gnark:",public"` +} + +func (c *InnerCircuitNative) Define(api frontend.API) error { + // prove that P*Q == N + res := api.Mul(c.P, c.Q) + api.AssertIsEqual(res, c.N) + // we must also enforce that P != 1 and Q != 1 + api.AssertIsDifferent(c.P, 1) + api.AssertIsDifferent(c.Q, 1) + return nil +} + +// computeInnerProof computes the proof for the inner circuit we want to verify +// recursively. In this example the PLONK keys are generated on the fly, but +// in practice should be genrated once and using MPC. +func computeInnerProof(field, outer *big.Int) (constraint.ConstraintSystem, native_plonk.VerifyingKey, witness.Witness, native_plonk.Proof) { + innerCcs, err := frontend.Compile(field, scs.NewBuilder, &InnerCircuitNative{}) + if err != nil { + panic(err) + } + // NB! UNSAFE! Use MPC. + srs, err := test.NewKZGSRS(innerCcs) + if err != nil { + panic(err) + } + innerPK, innerVK, err := native_plonk.Setup(innerCcs, srs) + if err != nil { + panic(err) + } + + // inner proof + innerAssignment := &InnerCircuitNative{ + P: 3, + Q: 5, + N: 15, + } + innerWitness, err := frontend.NewWitness(innerAssignment, field) + if err != nil { + panic(err) + } + innerProof, err := native_plonk.Prove(innerCcs, innerPK, innerWitness, plonk.GetNativeProverOptions(outer, field)) + if err != nil { + panic(err) + } + innerPubWitness, err := innerWitness.Public() + if err != nil { + panic(err) + } + err = native_plonk.Verify(innerProof, innerVK, innerPubWitness, plonk.GetNativeVerifierOptions(outer, field)) + if err != nil { + panic(err) + } + return innerCcs, innerVK, innerPubWitness, innerProof +} + +// OuterCircuit is the generic outer circuit which can verify PLONK proofs +// using field emulation or 2-chains of curves. +type OuterCircuit[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.GtElementT] struct { + Proof plonk.Proof[FR, G1El, G2El] + VerifyingKey plonk.VerifyingKey[FR, G1El, G2El] + InnerWitness plonk.Witness[FR] +} + +func (c *OuterCircuit[FR, G1El, G2El, GtEl]) Define(api frontend.API) error { + verifier, err := plonk.NewVerifier[FR, G1El, G2El, GtEl](api) + if err != nil { + return fmt.Errorf("new verifier: %w", err) + } + err = verifier.AssertProof(c.VerifyingKey, c.Proof, c.InnerWitness) + return err +} + +// Example of verifying recursively BW6-761 PLONK proof in BN254 PLONK circuit using field emulation +func Example_emulated() { + // compute the proof which we want to verify recursively + innerCcs, innerVK, innerWitness, innerProof := computeInnerProof(ecc.BW6_761.ScalarField(), ecc.BN254.ScalarField()) + + // initialize the witness elements + circuitVk, err := plonk.ValueOfVerifyingKey[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerVK) + if err != nil { + panic(err) + } + circuitWitness, err := plonk.ValueOfWitness[sw_bw6761.ScalarField](innerWitness) + if err != nil { + panic(err) + } + circuitProof, err := plonk.ValueOfProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerProof) + if err != nil { + panic(err) + } + + outerCircuit := &OuterCircuit[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]{ + InnerWitness: plonk.PlaceholderWitness[sw_bw6761.ScalarField](innerCcs), + Proof: plonk.PlaceholderProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerCcs), + VerifyingKey: plonk.PlaceholderVerifyingKey[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerCcs), + } + outerAssignment := &OuterCircuit[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + VerifyingKey: circuitVk, + } + // compile the outer circuit + ccs, err := frontend.Compile(ecc.BN254.ScalarField(), scs.NewBuilder, outerCircuit) + if err != nil { + panic("compile failed: " + err.Error()) + } + + // NB! UNSAFE! Use MPC. + srs, err := test.NewKZGSRS(innerCcs) + if err != nil { + panic(err) + } + // create PLONK setup. NB! UNSAFE + pk, vk, err := native_plonk.Setup(ccs, srs) // UNSAFE! Use MPC + if err != nil { + panic("setup failed: " + err.Error()) + } + + // create prover witness from the assignment + secretWitness, err := frontend.NewWitness(outerAssignment, ecc.BN254.ScalarField()) + if err != nil { + panic("secret witness failed: " + err.Error()) + } + + // create public witness from the assignment + publicWitness, err := secretWitness.Public() + if err != nil { + panic("public witness failed: " + err.Error()) + } + + // construct the PLONK proof of verifying PLONK proof in-circuit + outerProof, err := native_plonk.Prove(ccs, pk, secretWitness) + if err != nil { + panic("proving failed: " + err.Error()) + } + + // verify the PLONK proof + err = native_plonk.Verify(outerProof, vk, publicWitness) + if err != nil { + panic("circuit verification failed: " + err.Error()) + } +} diff --git a/std/recursion/plonk/opts.go b/std/recursion/plonk/opts.go new file mode 100644 index 0000000000..7941e2b3d1 --- /dev/null +++ b/std/recursion/plonk/opts.go @@ -0,0 +1,74 @@ +package plonk + +import ( + "fmt" + "math/big" + + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/std/recursion" +) + +// GetNativeProverOptions returns PLONK prover options for the native prover to +// initialize the configuration suitable for in-circuit verification. +func GetNativeProverOptions(outer, field *big.Int) backend.ProverOption { + return func(pc *backend.ProverConfig) error { + fsProverHasher, err := recursion.NewShort(outer, field) + if err != nil { + return fmt.Errorf("get prover fs hash: %w", err) + } + kzgProverHasher, err := recursion.NewShort(outer, field) + if err != nil { + return fmt.Errorf("get prover kzg hash: %w", err) + } + htfProverHasher, err := recursion.NewShort(outer, field) + if err != nil { + return fmt.Errorf("get hash to field: %w", err) + } + fsOpt := backend.WithProverChallengeHashFunction(fsProverHasher) + if err = fsOpt(pc); err != nil { + return fmt.Errorf("apply prover fs hash option: %w", err) + } + kzgOpt := backend.WithProverKZGFoldingHashFunction(kzgProverHasher) + if err = kzgOpt(pc); err != nil { + return fmt.Errorf("apply prover kzg folding hash option: %w", err) + } + htfOpt := backend.WithProverHashToFieldFunction(htfProverHasher) + if err = htfOpt(pc); err != nil { + return fmt.Errorf("apply prover htf option: %w", err) + } + return nil + + } +} + +// GetNativeVerifierOptions returns PLONK verifier options to initialize the +// configuration to be compatible with in-circuit verification. +func GetNativeVerifierOptions(outer, field *big.Int) backend.VerifierOption { + return func(vc *backend.VerifierConfig) error { + fsVerifierHasher, err := recursion.NewShort(outer, field) + if err != nil { + return fmt.Errorf("get verifier fs hash: %w", err) + } + kzgVerifierHasher, err := recursion.NewShort(outer, field) + if err != nil { + return fmt.Errorf("get verifier kzg hash: %w", err) + } + htfVerifierHasher, err := recursion.NewShort(outer, field) + if err != nil { + return fmt.Errorf("get hash to field: %w", err) + } + fsOpt := backend.WithVerifierChallengeHashFunction(fsVerifierHasher) + if err = fsOpt(vc); err != nil { + return fmt.Errorf("apply verifier fs hash option: %w", err) + } + kzgOpt := backend.WithVerifierKZGFoldingHashFunction(kzgVerifierHasher) + if err = kzgOpt(vc); err != nil { + return fmt.Errorf("apply verifier kzg folding hash option: %w", err) + } + htfOpt := backend.WithVerifierHashToFieldFunction(htfVerifierHasher) + if err = htfOpt(vc); err != nil { + return fmt.Errorf("apply verifier htf option: %w", err) + } + return nil + } +} diff --git a/std/recursion/plonk/verifier.go b/std/recursion/plonk/verifier.go new file mode 100644 index 0000000000..cc912dccc0 --- /dev/null +++ b/std/recursion/plonk/verifier.go @@ -0,0 +1,1055 @@ +package plonk + +import ( + "fmt" + stdbits "math/bits" + + fr_bls12377 "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + fr_bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + fr_bls24315 "github.com/consensys/gnark-crypto/ecc/bls24-315/fr" + fr_bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr" + fr_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + backend_plonk "github.com/consensys/gnark/backend/plonk" + plonkbackend_bls12377 "github.com/consensys/gnark/backend/plonk/bls12-377" + plonkbackend_bls12381 "github.com/consensys/gnark/backend/plonk/bls12-381" + plonkbackend_bls24315 "github.com/consensys/gnark/backend/plonk/bls24-315" + plonkbackend_bn254 "github.com/consensys/gnark/backend/plonk/bn254" + plonkbackend_bw6761 "github.com/consensys/gnark/backend/plonk/bw6-761" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/std/algebra" + "github.com/consensys/gnark/std/algebra/emulated/sw_bls12381" + "github.com/consensys/gnark/std/algebra/emulated/sw_bn254" + "github.com/consensys/gnark/std/algebra/emulated/sw_bw6761" + "github.com/consensys/gnark/std/algebra/native/sw_bls12377" + "github.com/consensys/gnark/std/algebra/native/sw_bls24315" + "github.com/consensys/gnark/std/commitments/kzg" + fiatshamir "github.com/consensys/gnark/std/fiat-shamir" + "github.com/consensys/gnark/std/math/bits" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/std/recursion" +) + +// Proof is a typed PLONK proof of SNARK. Use [ValueOfProof] to initialize the +// witness from the native proof. Use [PlaceholderProof] to initialize the +// placeholder witness for compiling the circuit. +type Proof[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT] struct { + + // Commitments to the solution vectors + LRO [3]kzg.Commitment[G1El] + + // Commitment to Z, the permutation polynomial + Z kzg.Commitment[G1El] + + // Commitments to h1, h2, h3 such that h = h1 + Xh2 + X**2h3 is the quotient polynomial + H [3]kzg.Commitment[G1El] + + Bsb22Commitments []kzg.Commitment[G1El] + + // Batch opening proof of h1 + zeta*h2 + zeta**2h3, linearizedPolynomial, l, r, o, s1, s2, qCPrime + BatchedProof kzg.BatchOpeningProof[FR, G1El] + + // Opening proof of Z at zeta*mu + ZShiftedOpening kzg.OpeningProof[FR, G1El] +} + +// ValueOfProof returns the typed witness of the native proof. It returns an +// error if there is a mismatch between the type parameters and the provided +// native proof. +func ValueOfProof[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT](proof backend_plonk.Proof) (Proof[FR, G1El, G2El], error) { + var ret Proof[FR, G1El, G2El] + var err error + switch r := any(&ret).(type) { + case *Proof[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine]: + tProof, ok := proof.(*plonkbackend_bls12377.Proof) + if !ok { + return ret, fmt.Errorf("expected sw_bls12377.Proof, got %T", proof) + } + for i := range r.LRO { + r.LRO[i], err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tProof.LRO[i]) + if err != nil { + return ret, fmt.Errorf("commitment LRO[%d] value assignment: %w", i, err) + } + } + r.Z, err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tProof.Z) + if err != nil { + return ret, fmt.Errorf("commitment Z value assignment: %w", err) + } + for i := range r.H { + r.H[i], err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tProof.H[i]) + if err != nil { + return ret, fmt.Errorf("commitment H[%d] value assignment: %w", i, err) + } + } + r.Bsb22Commitments = make([]kzg.Commitment[sw_bls12377.G1Affine], len(tProof.Bsb22Commitments)) + for i := range r.Bsb22Commitments { + r.Bsb22Commitments[i], err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tProof.Bsb22Commitments[i]) + if err != nil { + return ret, fmt.Errorf("bsb22 commitment %d value assignment: %w", i, err) + } + } + // TODO: actually we compute the opening point later. Maybe we can precompute it here and later assert its correctness? + r.BatchedProof, err = kzg.ValueOfBatchOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](tProof.BatchedProof) + if err != nil { + return ret, fmt.Errorf("batch opening proof value assignment: %w", err) + } + r.ZShiftedOpening, err = kzg.ValueOfOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](tProof.ZShiftedOpening) + if err != nil { + return ret, fmt.Errorf("z shifted opening proof value assignment: %w", err) + } + case *Proof[sw_bls12381.ScalarField, sw_bls12381.G1Affine, sw_bls12381.G2Affine]: + tProof, ok := proof.(*plonkbackend_bls12381.Proof) + if !ok { + return ret, fmt.Errorf("expected sw_bls12381.Proof, got %T", proof) + } + for i := range r.LRO { + r.LRO[i], err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tProof.LRO[i]) + if err != nil { + return ret, fmt.Errorf("commitment LRO[%d] value assignment: %w", i, err) + } + } + r.Z, err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tProof.Z) + if err != nil { + return ret, fmt.Errorf("commitment Z value assignment: %w", err) + } + for i := range r.H { + r.H[i], err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tProof.H[i]) + if err != nil { + return ret, fmt.Errorf("commitment H[%d] value assignment: %w", i, err) + } + } + r.Bsb22Commitments = make([]kzg.Commitment[sw_bls12381.G1Affine], len(tProof.Bsb22Commitments)) + for i := range r.Bsb22Commitments { + r.Bsb22Commitments[i], err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tProof.Bsb22Commitments[i]) + if err != nil { + return ret, fmt.Errorf("bsb22 commitment %d value assignment: %w", i, err) + } + } + // TODO: actually we compute the opening point later. Maybe we can precompute it here and later assert its correctness? + r.BatchedProof, err = kzg.ValueOfBatchOpeningProof[sw_bls12381.ScalarField, sw_bls12381.G1Affine](tProof.BatchedProof) + if err != nil { + return ret, fmt.Errorf("batch opening proof value assignment: %w", err) + } + r.ZShiftedOpening, err = kzg.ValueOfOpeningProof[sw_bls12381.ScalarField, sw_bls12381.G1Affine](tProof.ZShiftedOpening) + if err != nil { + return ret, fmt.Errorf("z shifted opening proof value assignment: %w", err) + } + case *Proof[sw_bls24315.ScalarField, sw_bls24315.G1Affine, sw_bls24315.G2Affine]: + tProof, ok := proof.(*plonkbackend_bls24315.Proof) + if !ok { + return ret, fmt.Errorf("expected sw_bls24315.Proof, got %T", proof) + } + for i := range r.LRO { + r.LRO[i], err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tProof.LRO[i]) + if err != nil { + return ret, fmt.Errorf("commitment LRO[%d] value assignment: %w", i, err) + } + } + r.Z, err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tProof.Z) + if err != nil { + return ret, fmt.Errorf("commitment Z value assignment: %w", err) + } + for i := range r.H { + r.H[i], err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tProof.H[i]) + if err != nil { + return ret, fmt.Errorf("commitment H[%d] value assignment: %w", i, err) + } + } + r.Bsb22Commitments = make([]kzg.Commitment[sw_bls24315.G1Affine], len(tProof.Bsb22Commitments)) + for i := range r.Bsb22Commitments { + r.Bsb22Commitments[i], err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tProof.Bsb22Commitments[i]) + if err != nil { + return ret, fmt.Errorf("bsb22 commitment %d value assignment: %w", i, err) + } + } + // TODO: actually we compute the opening point later. Maybe we can precompute it here and later assert its correctness? + r.BatchedProof, err = kzg.ValueOfBatchOpeningProof[sw_bls24315.ScalarField, sw_bls24315.G1Affine](tProof.BatchedProof) + if err != nil { + return ret, fmt.Errorf("batch opening proof value assignment: %w", err) + } + r.ZShiftedOpening, err = kzg.ValueOfOpeningProof[sw_bls24315.ScalarField, sw_bls24315.G1Affine](tProof.ZShiftedOpening) + if err != nil { + return ret, fmt.Errorf("z shifted opening proof value assignment: %w", err) + } + + case *Proof[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine]: + tProof, ok := proof.(*plonkbackend_bw6761.Proof) + if !ok { + return ret, fmt.Errorf("expected sw_bls12377.Proof, got %T", proof) + } + for i := range r.LRO { + r.LRO[i], err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tProof.LRO[i]) + if err != nil { + return ret, fmt.Errorf("commitment LRO[%d] value assignment: %w", i, err) + } + } + r.Z, err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tProof.Z) + if err != nil { + return ret, fmt.Errorf("commitment Z value assignment: %w", err) + } + for i := range r.H { + r.H[i], err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tProof.H[i]) + if err != nil { + return ret, fmt.Errorf("commitment H[%d] value assignment: %w", i, err) + } + } + r.Bsb22Commitments = make([]kzg.Commitment[sw_bw6761.G1Affine], len(tProof.Bsb22Commitments)) + for i := range r.Bsb22Commitments { + r.Bsb22Commitments[i], err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tProof.Bsb22Commitments[i]) + if err != nil { + return ret, fmt.Errorf("bsb22 commitment %d value assignment: %w", i, err) + } + } + // TODO: actually we compute the opening point later. Maybe we can precompute it here and later assert its correctness? + r.BatchedProof, err = kzg.ValueOfBatchOpeningProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine](tProof.BatchedProof) + if err != nil { + return ret, fmt.Errorf("batch opening proof value assignment: %w", err) + } + r.ZShiftedOpening, err = kzg.ValueOfOpeningProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine](tProof.ZShiftedOpening) + if err != nil { + return ret, fmt.Errorf("z shifted opening proof value assignment: %w", err) + } + case *Proof[sw_bn254.ScalarField, sw_bn254.G1Affine, sw_bn254.G2Affine]: + tProof, ok := proof.(*plonkbackend_bn254.Proof) + if !ok { + return ret, fmt.Errorf("expected sw_bls12377.Proof, got %T", proof) + } + for i := range r.LRO { + r.LRO[i], err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tProof.LRO[i]) + if err != nil { + return ret, fmt.Errorf("commitment LRO[%d] value assignment: %w", i, err) + } + } + r.Z, err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tProof.Z) + if err != nil { + return ret, fmt.Errorf("commitment Z value assignment: %w", err) + } + for i := range r.H { + r.H[i], err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tProof.H[i]) + if err != nil { + return ret, fmt.Errorf("commitment H[%d] value assignment: %w", i, err) + } + } + r.Bsb22Commitments = make([]kzg.Commitment[sw_bn254.G1Affine], len(tProof.Bsb22Commitments)) + for i := range r.Bsb22Commitments { + r.Bsb22Commitments[i], err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tProof.Bsb22Commitments[i]) + if err != nil { + return ret, fmt.Errorf("bsb22 commitment %d value assignment: %w", i, err) + } + } + // TODO: actually we compute the opening point later. Maybe we can precompute it here and later assert its correctness? + r.BatchedProof, err = kzg.ValueOfBatchOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine](tProof.BatchedProof) + if err != nil { + return ret, fmt.Errorf("batch opening proof value assignment: %w", err) + } + r.ZShiftedOpening, err = kzg.ValueOfOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine](tProof.ZShiftedOpening) + if err != nil { + return ret, fmt.Errorf("z shifted opening proof value assignment: %w", err) + } + // TODO: missing bls12-381, bls24315, bn254, bls24317 + default: + return ret, fmt.Errorf("unknown parametric type combination: %T", ret) + } + return ret, nil +} + +// PlaceholderProof returns a placeholder proof witness to be use for compiling +// the outer circuit for witness alignment. For actual witness assignment use +// [ValueOfProof]. +func PlaceholderProof[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT](ccs constraint.ConstraintSystem) Proof[FR, G1El, G2El] { + nbCommitments := len(ccs.GetCommitments().CommitmentIndexes()) + ret := Proof[FR, G1El, G2El]{ + BatchedProof: kzg.BatchOpeningProof[FR, G1El]{ + ClaimedValues: make([]emulated.Element[FR], 7+nbCommitments), + }, + Bsb22Commitments: make([]kzg.Commitment[G1El], nbCommitments), + } + return ret +} + +// VerifyingKey is a typed PLONK verification key. Use [ValueOfVerifyingKey] or +// [PlaceholderVerifyingKey] for initializing. +type VerifyingKey[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT] struct { + + // Size circuit + Size uint64 + SizeInv emulated.Element[FR] + Generator emulated.Element[FR] + NbPublicVariables uint64 + + // Commitment scheme that is used for an instantiation of PLONK + Kzg kzg.VerifyingKey[G1El, G2El] + + // cosetShift generator of the coset on the small domain + CosetShift emulated.Element[FR] + + // S commitments to S1, S2, S3 + S [3]kzg.Commitment[G1El] + + // Commitments to ql, qr, qm, qo, qcp prepended with as many zeroes (ones for l) as there are public inputs. + // In particular Qk is not complete. + Ql, Qr, Qm, Qo, Qk kzg.Commitment[G1El] + + Qcp []kzg.Commitment[G1El] + + CommitmentConstraintIndexes []uint64 +} + +// ValueOfVerifyingKey initializes witness from the given PLONK verifying key. +// It returns an error if there is a mismatch between the type parameters and +// the provided native verifying key. +func ValueOfVerifyingKey[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT](vk backend_plonk.VerifyingKey) (VerifyingKey[FR, G1El, G2El], error) { + var ret VerifyingKey[FR, G1El, G2El] + var err error + switch r := any(&ret).(type) { + case *VerifyingKey[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine]: + tVk, ok := vk.(*plonkbackend_bls12377.VerifyingKey) + if !ok { + return ret, fmt.Errorf("expected bls12377.VerifyingKey, got %T", vk) + } + r.Size = tVk.Size + r.SizeInv = sw_bls12377.NewScalar(tVk.SizeInv) + r.Generator = sw_bls12377.NewScalar(tVk.Generator) + r.NbPublicVariables = tVk.NbPublicVariables + r.Kzg, err = kzg.ValueOfVerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine](tVk.Kzg) + if err != nil { + return ret, fmt.Errorf("verifying key witness assignment: %w", err) + } + r.CosetShift = sw_bls12377.NewScalar(tVk.CosetShift) + for i := range r.S { + r.S[i], err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tVk.S[i]) + if err != nil { + return ret, fmt.Errorf("commitment S[%d] witness assignment: %w", i, err) + } + } + r.Ql, err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tVk.Ql) + if err != nil { + return ret, fmt.Errorf("commitment Ql witness assignment: %w", err) + } + r.Qr, err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tVk.Qr) + if err != nil { + return ret, fmt.Errorf("commitment Qr witness assignment: %w", err) + } + r.Qm, err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tVk.Qm) + if err != nil { + return ret, fmt.Errorf("commitment Qm witness assignment: %w", err) + } + r.Qo, err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tVk.Qo) + if err != nil { + return ret, fmt.Errorf("commitment Qo witness assignment: %w", err) + } + r.Qk, err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tVk.Qk) + if err != nil { + return ret, fmt.Errorf("commitment Qk witness assignment: %w", err) + } + r.Qcp = make([]kzg.Commitment[sw_bls12377.G1Affine], len(tVk.Qcp)) + for i := range r.Qcp { + r.Qcp[i], err = kzg.ValueOfCommitment[sw_bls12377.G1Affine](tVk.Qcp[i]) + if err != nil { + return ret, fmt.Errorf("commitment Qcp[%d] witness assignment: %w", i, err) + } + } + r.CommitmentConstraintIndexes = make([]uint64, len(tVk.CommitmentConstraintIndexes)) + copy(r.CommitmentConstraintIndexes, tVk.CommitmentConstraintIndexes) + case *VerifyingKey[sw_bls12381.ScalarField, sw_bls12381.G1Affine, sw_bls12381.G2Affine]: + tVk, ok := vk.(*plonkbackend_bls12381.VerifyingKey) + if !ok { + return ret, fmt.Errorf("expected bls12381.VerifyingKey, got %T", vk) + } + r.Size = tVk.Size + r.SizeInv = sw_bls12381.NewScalar(tVk.SizeInv) + r.Generator = sw_bls12381.NewScalar(tVk.Generator) + r.NbPublicVariables = tVk.NbPublicVariables + r.Kzg, err = kzg.ValueOfVerifyingKey[sw_bls12381.G1Affine, sw_bls12381.G2Affine](tVk.Kzg) + if err != nil { + return ret, fmt.Errorf("verifying key witness assignment: %w", err) + } + r.CosetShift = sw_bls12381.NewScalar(tVk.CosetShift) + for i := range r.S { + r.S[i], err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tVk.S[i]) + if err != nil { + return ret, fmt.Errorf("commitment S[%d] witness assignment: %w", i, err) + } + } + r.Ql, err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tVk.Ql) + if err != nil { + return ret, fmt.Errorf("commitment Ql witness assignment: %w", err) + } + r.Qr, err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tVk.Qr) + if err != nil { + return ret, fmt.Errorf("commitment Qr witness assignment: %w", err) + } + r.Qm, err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tVk.Qm) + if err != nil { + return ret, fmt.Errorf("commitment Qm witness assignment: %w", err) + } + r.Qo, err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tVk.Qo) + if err != nil { + return ret, fmt.Errorf("commitment Qo witness assignment: %w", err) + } + r.Qk, err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tVk.Qk) + if err != nil { + return ret, fmt.Errorf("commitment Qk witness assignment: %w", err) + } + r.Qcp = make([]kzg.Commitment[sw_bls12381.G1Affine], len(tVk.Qcp)) + for i := range r.Qcp { + r.Qcp[i], err = kzg.ValueOfCommitment[sw_bls12381.G1Affine](tVk.Qcp[i]) + if err != nil { + return ret, fmt.Errorf("commitment Qcp[%d] witness assignment: %w", i, err) + } + } + r.CommitmentConstraintIndexes = make([]uint64, len(tVk.CommitmentConstraintIndexes)) + copy(r.CommitmentConstraintIndexes, tVk.CommitmentConstraintIndexes) + case *VerifyingKey[sw_bls24315.ScalarField, sw_bls24315.G1Affine, sw_bls24315.G2Affine]: + tVk, ok := vk.(*plonkbackend_bls24315.VerifyingKey) + if !ok { + return ret, fmt.Errorf("expected bls24315.VerifyingKey, got %T", vk) + } + r.Size = tVk.Size + r.SizeInv = sw_bls24315.NewScalar(tVk.SizeInv) + r.Generator = sw_bls24315.NewScalar(tVk.Generator) + r.NbPublicVariables = tVk.NbPublicVariables + r.Kzg, err = kzg.ValueOfVerifyingKey[sw_bls24315.G1Affine, sw_bls24315.G2Affine](tVk.Kzg) + if err != nil { + return ret, fmt.Errorf("verifying key witness assignment: %w", err) + } + r.CosetShift = sw_bls24315.NewScalar(tVk.CosetShift) + for i := range r.S { + r.S[i], err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tVk.S[i]) + if err != nil { + return ret, fmt.Errorf("commitment S[%d] witness assignment: %w", i, err) + } + } + r.Ql, err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tVk.Ql) + if err != nil { + return ret, fmt.Errorf("commitment Ql witness assignment: %w", err) + } + r.Qr, err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tVk.Qr) + if err != nil { + return ret, fmt.Errorf("commitment Qr witness assignment: %w", err) + } + r.Qm, err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tVk.Qm) + if err != nil { + return ret, fmt.Errorf("commitment Qm witness assignment: %w", err) + } + r.Qo, err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tVk.Qo) + if err != nil { + return ret, fmt.Errorf("commitment Qo witness assignment: %w", err) + } + r.Qk, err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tVk.Qk) + if err != nil { + return ret, fmt.Errorf("commitment Qk witness assignment: %w", err) + } + r.Qcp = make([]kzg.Commitment[sw_bls24315.G1Affine], len(tVk.Qcp)) + for i := range r.Qcp { + r.Qcp[i], err = kzg.ValueOfCommitment[sw_bls24315.G1Affine](tVk.Qcp[i]) + if err != nil { + return ret, fmt.Errorf("commitment Qcp[%d] witness assignment: %w", i, err) + } + } + r.CommitmentConstraintIndexes = make([]uint64, len(tVk.CommitmentConstraintIndexes)) + copy(r.CommitmentConstraintIndexes, tVk.CommitmentConstraintIndexes) + case *VerifyingKey[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine]: + tVk, ok := vk.(*plonkbackend_bw6761.VerifyingKey) + if !ok { + return ret, fmt.Errorf("expected bls12377.VerifyingKey, got %T", vk) + } + r.Size = tVk.Size + r.SizeInv = sw_bw6761.NewScalar(tVk.SizeInv) + r.Generator = sw_bw6761.NewScalar(tVk.Generator) + r.NbPublicVariables = tVk.NbPublicVariables + r.Kzg, err = kzg.ValueOfVerifyingKey[sw_bw6761.G1Affine, sw_bw6761.G2Affine](tVk.Kzg) + if err != nil { + return ret, fmt.Errorf("verifying key witness assignment: %w", err) + } + r.CosetShift = sw_bw6761.NewScalar(tVk.CosetShift) + for i := range r.S { + r.S[i], err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tVk.S[i]) + if err != nil { + return ret, fmt.Errorf("commitment S[%d] witness assignment: %w", i, err) + } + } + r.Ql, err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tVk.Ql) + if err != nil { + return ret, fmt.Errorf("commitment Ql witness assignment: %w", err) + } + r.Qr, err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tVk.Qr) + if err != nil { + return ret, fmt.Errorf("commitment Qr witness assignment: %w", err) + } + r.Qm, err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tVk.Qm) + if err != nil { + return ret, fmt.Errorf("commitment Qm witness assignment: %w", err) + } + r.Qo, err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tVk.Qo) + if err != nil { + return ret, fmt.Errorf("commitment Qo witness assignment: %w", err) + } + r.Qk, err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tVk.Qk) + if err != nil { + return ret, fmt.Errorf("commitment Qk witness assignment: %w", err) + } + r.Qcp = make([]kzg.Commitment[sw_bw6761.G1Affine], len(tVk.Qcp)) + for i := range r.Qcp { + r.Qcp[i], err = kzg.ValueOfCommitment[sw_bw6761.G1Affine](tVk.Qcp[i]) + if err != nil { + return ret, fmt.Errorf("commitment Qcp[%d] witness assignment: %w", i, err) + } + } + r.CommitmentConstraintIndexes = make([]uint64, len(tVk.CommitmentConstraintIndexes)) + copy(r.CommitmentConstraintIndexes, tVk.CommitmentConstraintIndexes) + case *VerifyingKey[sw_bn254.ScalarField, sw_bn254.G1Affine, sw_bn254.G2Affine]: + tVk, ok := vk.(*plonkbackend_bn254.VerifyingKey) + if !ok { + return ret, fmt.Errorf("expected bn254.VerifyingKey, got %T", vk) + } + r.Size = tVk.Size + r.SizeInv = sw_bn254.NewScalar(tVk.SizeInv) + r.Generator = sw_bn254.NewScalar(tVk.Generator) + r.NbPublicVariables = tVk.NbPublicVariables + r.Kzg, err = kzg.ValueOfVerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine](tVk.Kzg) + if err != nil { + return ret, fmt.Errorf("verifying key witness assignment: %w", err) + } + r.CosetShift = sw_bn254.NewScalar(tVk.CosetShift) + for i := range r.S { + r.S[i], err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tVk.S[i]) + if err != nil { + return ret, fmt.Errorf("commitment S[%d] witness assignment: %w", i, err) + } + } + r.Ql, err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tVk.Ql) + if err != nil { + return ret, fmt.Errorf("commitment Ql witness assignment: %w", err) + } + r.Qr, err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tVk.Qr) + if err != nil { + return ret, fmt.Errorf("commitment Qr witness assignment: %w", err) + } + r.Qm, err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tVk.Qm) + if err != nil { + return ret, fmt.Errorf("commitment Qm witness assignment: %w", err) + } + r.Qo, err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tVk.Qo) + if err != nil { + return ret, fmt.Errorf("commitment Qo witness assignment: %w", err) + } + r.Qk, err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tVk.Qk) + if err != nil { + return ret, fmt.Errorf("commitment Qk witness assignment: %w", err) + } + r.Qcp = make([]kzg.Commitment[sw_bn254.G1Affine], len(tVk.Qcp)) + for i := range r.Qcp { + r.Qcp[i], err = kzg.ValueOfCommitment[sw_bn254.G1Affine](tVk.Qcp[i]) + if err != nil { + return ret, fmt.Errorf("commitment Qcp[%d] witness assignment: %w", i, err) + } + } + r.CommitmentConstraintIndexes = make([]uint64, len(tVk.CommitmentConstraintIndexes)) + copy(r.CommitmentConstraintIndexes, tVk.CommitmentConstraintIndexes) + default: + return ret, fmt.Errorf("unknown parametric type combination") + } + return ret, nil +} + +// PlaceholderVerifyingKey returns placeholder of the verification key for +// compiling the outer circuit. +func PlaceholderVerifyingKey[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT](ccs constraint.ConstraintSystem) VerifyingKey[FR, G1El, G2El] { + nbPublic := ccs.GetNbPublicVariables() + nbConstraints := ccs.GetNbConstraints() + sizeSystem := nbPublic + nbConstraints + nextPowerTwo := 1 << stdbits.Len(uint(sizeSystem)) + commitmentIndexes := ccs.GetCommitments().CommitmentIndexes() + cCommitmentIndexes := make([]uint64, len(commitmentIndexes)) + for i := range cCommitmentIndexes { + cCommitmentIndexes[i] = uint64(commitmentIndexes[i]) + } + return VerifyingKey[FR, G1El, G2El]{ + Size: uint64(nextPowerTwo), + NbPublicVariables: uint64(nbPublic), + CommitmentConstraintIndexes: cCommitmentIndexes, + Qcp: make([]kzg.Commitment[G1El], len(commitmentIndexes)), + } +} + +// Witness is a public witness to verify the SNARK proof against. For assigning +// witness use [ValueOfWitness] and to create stub witness for compiling use +// [PlaceholderWitness]. +type Witness[FR emulated.FieldParams] struct { + Public []emulated.Element[FR] +} + +// ValueOfWitness assigns a outer-circuit witness from the inner circuit +// witness. If there is a field mismatch then this method represents the witness +// inputs using field emulation. It returns an error if there is a mismatch +// between the type parameters and provided witness. +func ValueOfWitness[FR emulated.FieldParams](w witness.Witness) (Witness[FR], error) { + var ret Witness[FR] + pubw, err := w.Public() + if err != nil { + return ret, fmt.Errorf("get public witness: %w", err) + } + vec := pubw.Vector() + switch s := any(&ret).(type) { + case *Witness[sw_bls12377.ScalarField]: + vect, ok := vec.(fr_bls12377.Vector) + if !ok { + return ret, fmt.Errorf("expected fr_bls12377.Vector, got %T", vec) + } + for i := range vect { + s.Public = append(s.Public, sw_bls12377.NewScalar(vect[i])) + } + case *Witness[sw_bls12381.ScalarField]: + vect, ok := vec.(fr_bls12381.Vector) + if !ok { + return ret, fmt.Errorf("expected fr_bls12381.Vector, got %T", vec) + } + for i := range vect { + s.Public = append(s.Public, sw_bls12381.NewScalar(vect[i])) + } + case *Witness[sw_bls24315.ScalarField]: + vect, ok := vec.(fr_bls24315.Vector) + if !ok { + return ret, fmt.Errorf("expected fr_bls24315.Vector, got %T", vec) + } + for i := range vect { + s.Public = append(s.Public, sw_bls24315.NewScalar(vect[i])) + } + case *Witness[sw_bw6761.ScalarField]: + vect, ok := vec.(fr_bw6761.Vector) + if !ok { + return ret, fmt.Errorf("expected fr_bw6761.Vector, got %T", vec) + } + for i := range vect { + s.Public = append(s.Public, sw_bw6761.NewScalar(vect[i])) + } + case *Witness[sw_bn254.ScalarField]: + vect, ok := vec.(fr_bn254.Vector) + if !ok { + return ret, fmt.Errorf("expected fr_bn254.Vector, got %T", vec) + } + for i := range vect { + s.Public = append(s.Public, sw_bn254.NewScalar(vect[i])) + } + default: + return ret, fmt.Errorf("unknown parametric type combination") + } + return ret, nil +} + +// PlaceholderWitness creates a stub witness which can be used to allocate the +// variables in the circuit if the actual witness is not yet known. It takes +// into account the number of public inputs and number of used commitments. +func PlaceholderWitness[FR emulated.FieldParams](ccs constraint.ConstraintSystem) Witness[FR] { + return Witness[FR]{ + Public: make([]emulated.Element[FR], ccs.GetNbPublicVariables()), + } +} + +// Verifier verifies PLONK proofs. +type Verifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.GtElementT] struct { + api frontend.API + scalarApi *emulated.Field[FR] + curve algebra.Curve[FR, G1El] + pairing algebra.Pairing[G1El, G2El, GtEl] + kzg *kzg.Verifier[FR, G1El, G2El, GtEl] +} + +// NewVerifier returns a new [Verifier] instance. +func NewVerifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.GtElementT](api frontend.API) (*Verifier[FR, G1El, G2El, GtEl], error) { + curve, err := algebra.GetCurve[FR, G1El](api) + if err != nil { + return nil, fmt.Errorf("new curve: %w", err) + } + pairing, err := algebra.GetPairing[G1El, G2El, GtEl](api) + if err != nil { + return nil, fmt.Errorf("new pairing: %w", err) + } + f, err := emulated.NewField[FR](api) + if err != nil { + return nil, fmt.Errorf("new scalars: %w", err) + } + kzg, err := kzg.NewVerifier[FR, G1El, G2El, GtEl](api) + if err != nil { + return nil, fmt.Errorf("new kzg verifier: %w", err) + } + return &Verifier[FR, G1El, G2El, GtEl]{ + api: api, + scalarApi: f, + curve: curve, + pairing: pairing, + kzg: kzg, + }, nil +} + +// AssertProof asserts that the SNARK proof holds for the given witness and +// verifying key. +func (v *Verifier[FR, G1El, G2El, GtEl]) AssertProof(vk VerifyingKey[FR, G1El, G2El], proof Proof[FR, G1El, G2El], witness Witness[FR]) error { + + var fr FR + if len(proof.Bsb22Commitments) != len(vk.Qcp) { + return fmt.Errorf("BSB22 commitment number mismatch") + } + + fs, err := recursion.NewTranscript(v.api, fr.Modulus(), []string{"gamma", "beta", "alpha", "zeta"}) + if err != nil { + return fmt.Errorf("init new transcript: %w", err) + } + + if err := v.bindPublicData(fs, "gamma", vk, witness); err != nil { + return fmt.Errorf("bind public data: %w", err) + } + + // The first challenge is derived using the public data: the commitments to the permutation, + // the coefficients of the circuit, and the public inputs. + // derive gamma from the Comm(blinded cl), Comm(blinded cr), Comm(blinded co) + gamma, err := v.deriveRandomness(fs, "gamma", proof.LRO[0].G1El, proof.LRO[1].G1El, proof.LRO[2].G1El) + if err != nil { + return err + } + + // derive beta from Comm(l), Comm(r), Comm(o) + beta, err := v.deriveRandomness(fs, "beta") + if err != nil { + return err + } + + // derive alpha from Comm(l), Comm(r), Comm(o), Com(Z), Bsb22Commitments + alphaDeps := make([]G1El, len(proof.Bsb22Commitments)+1) + for i := range proof.Bsb22Commitments { + alphaDeps[i] = proof.Bsb22Commitments[i].G1El + } + alphaDeps[len(alphaDeps)-1] = proof.Z.G1El + alpha, err := v.deriveRandomness(fs, "alpha", alphaDeps...) + if err != nil { + return err + } + + // derive zeta, the point of evaluation + zeta, err := v.deriveRandomness(fs, "zeta", proof.H[0].G1El, proof.H[1].G1El, proof.H[2].G1El) + if err != nil { + return err + } + + // evaluation of Z=Xⁿ-1 at ζ + one := v.scalarApi.One() + zetaPowerM := v.fixedExpN(vk.Size, zeta) // ζⁿ + zzeta := v.scalarApi.Sub(zetaPowerM, one) // ζⁿ-1 + + // L1 = (1/n)(ζⁿ-1)/(ζ-1) + denom := v.scalarApi.Sub(zeta, one) + lagrangeOne := v.scalarApi.Div(zzeta, denom) + lagrangeOne = v.scalarApi.Mul(lagrangeOne, &vk.SizeInv) + lagrange := lagrangeOne + // compute PI = ∑_{i 0 { + hashToField, err := recursion.NewHash(v.api, fr.Modulus(), true) + if err != nil { + return err + } + for i := range vk.CommitmentConstraintIndexes { + li := v.computeIthLagrangeAtZeta(vk.CommitmentConstraintIndexes[i]+vk.NbPublicVariables, zeta, zetaPowerM, vk) + marshalledCommitment := v.curve.MarshalG1(proof.Bsb22Commitments[i].G1El) + hashToField.Write(marshalledCommitment...) + hashedCmt := hashToField.Sum() + hashedCmtBits := bits.ToBinary(v.api, hashedCmt, bits.WithNbDigits(fr.Modulus().BitLen())) + emulatedHashedCmt := v.scalarApi.FromBits(hashedCmtBits...) + xiLi := v.scalarApi.Mul(emulatedHashedCmt, li) + hashToField.Reset() + pi = v.scalarApi.Add(pi, xiLi) + } + } + + // linearizedpolynomial + pi(ζ) + α*(Z(μζ))*(l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(o(ζ)+γ) - α²*L₁(ζ) + zu := proof.ZShiftedOpening.ClaimedValue + claimedQuotient := &proof.BatchedProof.ClaimedValues[0] + linearizedPolynomialZeta := &proof.BatchedProof.ClaimedValues[1] + l := proof.BatchedProof.ClaimedValues[2] + r := proof.BatchedProof.ClaimedValues[3] + o := proof.BatchedProof.ClaimedValues[4] + s1 := proof.BatchedProof.ClaimedValues[5] + s2 := proof.BatchedProof.ClaimedValues[6] + + // _s1 = (l(ζ)+β*s1(ζ)+γ) + _s1 := v.scalarApi.Mul(&s1, beta) + _s1 = v.scalarApi.Add(_s1, &l) + _s1 = v.scalarApi.Add(_s1, gamma) + + // _s2 = (r(ζ)+β*s2(ζ)+γ) + _s2 := v.scalarApi.Mul(&s2, beta) + _s2 = v.scalarApi.Add(_s2, &r) + _s2 = v.scalarApi.Add(_s2, gamma) + + // _o = (o(ζ)+γ) + _o := v.scalarApi.Add(&o, gamma) + + // _s1 = α*(Z(μζ))*(l(ζ)+β*s1(ζ)+γ)*(r(ζ)+β*s2(ζ)+γ)*(o(ζ)+γ) + _s1 = v.scalarApi.Mul(_s1, _s2) + _s1 = v.scalarApi.Mul(_s1, _o) + _s1 = v.scalarApi.Mul(_s1, alpha) + _s1 = v.scalarApi.Mul(_s1, &zu) + + // alphaSquareLagrange = α²*L₁(ζ) + alphaSquareLagrange := v.scalarApi.Mul(lagrangeOne, alpha) + alphaSquareLagrange = v.scalarApi.Mul(alphaSquareLagrange, alpha) + + // linearizedPolynomialZeta = linearizedpolynomial+pi(zeta)+α*(Z(μζ))*(l(ζ)+s1(ζ)+γ)*(r(ζ)+s2(ζ)+γ)*(o(ζ)+γ)-α²*L₁(ζ) + linearizedPolynomialZeta = v.scalarApi.Add(linearizedPolynomialZeta, pi) + linearizedPolynomialZeta = v.scalarApi.Add(linearizedPolynomialZeta, _s1) + linearizedPolynomialZeta = v.scalarApi.Sub(linearizedPolynomialZeta, alphaSquareLagrange) + + // Compute H(ζ) using the previous result: H(ζ) = prev_result/(ζⁿ-1) + zetaPowerMMinusOne := v.scalarApi.Sub(zetaPowerM, one) + linearizedPolynomialZeta = v.scalarApi.Div(linearizedPolynomialZeta, zetaPowerMMinusOne) + + // check that H(ζ) is as claimed + v.scalarApi.AssertIsEqual(claimedQuotient, linearizedPolynomialZeta) + + // compute the folded commitment to H: Comm(h₁) + ζᵐ⁺²*Comm(h₂) + ζ²⁽ᵐ⁺²⁾*Comm(h₃) + zetaMPlusTwo := v.scalarApi.Mul(zetaPowerM, zeta) + zetaMPlusTwo = v.scalarApi.Mul(zetaMPlusTwo, zeta) + + foldedH := v.curve.ScalarMul(&proof.H[2].G1El, zetaMPlusTwo) + foldedH = v.curve.Add(foldedH, &proof.H[1].G1El) + foldedH = v.curve.ScalarMul(foldedH, zetaMPlusTwo) + foldedH = v.curve.Add(foldedH, &proof.H[0].G1El) + + // Compute the commitment to the linearized polynomial + // linearizedPolynomialDigest = + // l(ζ)*ql+r(ζ)*qr+r(ζ)l(ζ)*qm+o(ζ)*qo+qk+Σᵢqc'ᵢ(ζ)*BsbCommitmentᵢ + + // α*( Z(μζ)(l(ζ)+β*s₁(ζ)+γ)*(r(ζ)+β*s₂(ζ)+γ)*s₃(X)-Z(X)(l(ζ)+β*id_1(ζ)+γ)*(r(ζ)+β*id_2(ζ)+γ)*(o(ζ)+β*id_3(ζ)+γ) ) + + // α²*L₁(ζ)*Z + + // first part: individual constraints + rl := v.scalarApi.Mul(&r, &l) + + // second part: α*( Z(μζ)(l(ζ)+β*s₁(ζ)+γ)*(r(ζ)+β*s₂(ζ)+γ)*β*s₃(X)-Z(X)(l(ζ)+β*id_1(ζ)+γ)*(r(ζ)+β*id_2(ζ)+γ)*(o(ζ)+β*id_3(ζ)+γ) ) ) + + uu := v.scalarApi.Mul(&zu, beta) + + vv := v.scalarApi.Mul(beta, &s1) + vv = v.scalarApi.Add(vv, &l) + vv = v.scalarApi.Add(vv, gamma) + + ww := v.scalarApi.Mul(beta, &s2) + ww = v.scalarApi.Add(ww, &r) + ww = v.scalarApi.Add(ww, gamma) + + // α*Z(μζ)(l(ζ)+β*s₁(ζ)+γ)*(r(ζ)+β*s₂(ζ)+γ)*β + _s1 = v.scalarApi.Mul(uu, vv) + _s1 = v.scalarApi.Mul(_s1, ww) + _s1 = v.scalarApi.Mul(_s1, alpha) + + cosetsquare := v.scalarApi.Mul(&vk.CosetShift, &vk.CosetShift) + + // (l(ζ)+β*ζ+γ) + uu = v.scalarApi.Mul(beta, zeta) + uu = v.scalarApi.Add(uu, &l) + uu = v.scalarApi.Add(uu, gamma) + + // (r(ζ)+β*μ*ζ+γ) + vv = v.scalarApi.Mul(beta, zeta) + vv = v.scalarApi.Mul(vv, &vk.CosetShift) + vv = v.scalarApi.Add(vv, &r) + vv = v.scalarApi.Add(vv, gamma) + + // (o(ζ)+β*μ²*ζ+γ) + ww = v.scalarApi.Mul(beta, zeta) + ww = v.scalarApi.Mul(ww, cosetsquare) + ww = v.scalarApi.Add(ww, &o) + ww = v.scalarApi.Add(ww, gamma) + + // -(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ) + _s2 = v.scalarApi.Mul(uu, vv) + _s2 = v.scalarApi.Mul(_s2, ww) + _s2 = v.scalarApi.Neg(_s2) + + // note since third part = α²*L₁(ζ)*Z + // -α*(l(ζ)+β*ζ+γ)*(r(ζ)+β*u*ζ+γ)*(o(ζ)+β*u²*ζ+γ) + α²*L₁(ζ) + _s2 = v.scalarApi.Mul(_s2, alpha) + _s2 = v.scalarApi.Add(_s2, alphaSquareLagrange) + + points := make([]*G1El, len(proof.Bsb22Commitments)) + for i := range proof.Bsb22Commitments { + points[i] = &proof.Bsb22Commitments[i].G1El + } + points = append(points, + &vk.Ql.G1El, &vk.Qr.G1El, &vk.Qm.G1El, &vk.Qo.G1El, &vk.Qk.G1El, // first part + &vk.S[2].G1El, &proof.Z.G1El, // second & third part + ) + + qC := make([]*emulated.Element[FR], len(proof.Bsb22Commitments)) + for i := range proof.BatchedProof.ClaimedValues[7:] { + qC[i] = &proof.BatchedProof.ClaimedValues[7+i] + } + scalars := append(qC, + &l, &r, rl, &o, one, // first part + _s1, _s2, // second & third part + ) + + linearizedPolynomialDigest, err := v.curve.MultiScalarMul(points, scalars) + if err != nil { + return fmt.Errorf("linearized polynomial digest MSM: %w", err) + } + + // Fold the first proof + digestsToFold := make([]kzg.Commitment[G1El], len(vk.Qcp)+7) + copy(digestsToFold[7:], vk.Qcp) + digestsToFold[0] = kzg.Commitment[G1El]{G1El: *foldedH} + digestsToFold[1] = kzg.Commitment[G1El]{G1El: *linearizedPolynomialDigest} + digestsToFold[2] = proof.LRO[0] + digestsToFold[3] = proof.LRO[1] + digestsToFold[4] = proof.LRO[2] + digestsToFold[5] = vk.S[0] + digestsToFold[6] = vk.S[1] + foldedProof, foldedDigest, err := v.kzg.FoldProof( + digestsToFold, + proof.BatchedProof, + *zeta, + zu, + ) + if err != nil { + return fmt.Errorf("fold kzg proof: %w", err) + } + shiftedZeta := v.scalarApi.Mul(zeta, &vk.Generator) + err = v.kzg.BatchVerifyMultiPoints( + []kzg.Commitment[G1El]{ + foldedDigest, + proof.Z, + }, + []kzg.OpeningProof[FR, G1El]{ + foldedProof, + proof.ZShiftedOpening, + }, + []emulated.Element[FR]{ + *zeta, + *shiftedZeta, + }, + vk.Kzg, + ) + if err != nil { + return fmt.Errorf("batch verify kzg: %w", err) + } + return nil +} + +func (v *Verifier[FR, G1El, G2El, GtEl]) bindPublicData(fs *fiatshamir.Transcript, challenge string, vk VerifyingKey[FR, G1El, G2El], witness Witness[FR]) error { + + // permutation + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.S[0].G1El)); err != nil { + return err + } + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.S[1].G1El)); err != nil { + return err + } + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.S[2].G1El)); err != nil { + return err + } + + // coefficients + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.Ql.G1El)); err != nil { + return err + } + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.Qr.G1El)); err != nil { + return err + } + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.Qm.G1El)); err != nil { + return err + } + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.Qo.G1El)); err != nil { + return err + } + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.Qk.G1El)); err != nil { + return err + } + for i := range vk.Qcp { + if err := fs.Bind(challenge, v.curve.MarshalG1(vk.Qcp[i].G1El)); err != nil { + return err + } + } + + // public inputs + for i := 0; i < len(witness.Public); i++ { + if err := fs.Bind(challenge, v.curve.MarshalScalar(witness.Public[i])); err != nil { + return err + } + } + + return nil +} + +func (v *Verifier[FR, G1El, G2El, GtEl]) deriveRandomness(fs *fiatshamir.Transcript, challenge string, points ...G1El) (*emulated.Element[FR], error) { + var fr FR + for i := range points { + if err := fs.Bind(challenge, v.curve.MarshalG1(points[i])); err != nil { + return nil, fmt.Errorf("bind challenge %d: %w", i, err) + } + } + b, err := fs.ComputeChallenge(challenge) + if err != nil { + return nil, fmt.Errorf("compute challenge: %w", err) + } + bbits := bits.ToBinary(v.api, b, bits.WithNbDigits(fr.Modulus().BitLen())) + ret := v.scalarApi.FromBits(bbits...) + return ret, nil +} + +func (v *Verifier[FR, G1El, G2El, GtEl]) fixedExpN(n uint64, s *emulated.Element[FR]) *emulated.Element[FR] { + nlen := stdbits.Len64(n) + res := s + for i := 1; i < nlen; i++ { + res = v.scalarApi.Mul(res, res) + } + return res +} + +// computeIthLagrangeAtZeta computes L_{i}(\omega) = \omega^{i}/n (\zeta^{n}-1)/(\zeta-\omega^{i}) +func (v *Verifier[FR, G1El, G2El, GtEl]) computeIthLagrangeAtZeta(i uint64, zeta, zetaPowerM *emulated.Element[FR], vk VerifyingKey[FR, G1El, G2El]) *emulated.Element[FR] { + + one := v.scalarApi.One() + num := v.scalarApi.Sub(zetaPowerM, one) + + // \omega^{i} + omegai := one + irev := stdbits.Reverse(uint(i)) + // skip first zeroes + s := irev % 2 + nbBitsUint := 64 + for s == 0 { + irev = irev >> 1 + s = irev % 2 + nbBitsUint-- + } + for nbBitsUint != 0 { + omegai = v.scalarApi.Mul(omegai, omegai) + if irev%2 == 1 { + omegai = v.scalarApi.Mul(omegai, &vk.Generator) + } + nbBitsUint-- + irev = irev >> 1 + } + + den := v.scalarApi.Sub(zeta, omegai) + + li := v.scalarApi.Div(num, den) + li = v.scalarApi.Mul(li, &vk.SizeInv) + li = v.scalarApi.Mul(li, omegai) + + return li +} diff --git a/std/recursion/plonk/verifier_test.go b/std/recursion/plonk/verifier_test.go new file mode 100644 index 0000000000..924ac141f7 --- /dev/null +++ b/std/recursion/plonk/verifier_test.go @@ -0,0 +1,238 @@ +package plonk + +import ( + "fmt" + "math/big" + "testing" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/plonk" + "github.com/consensys/gnark/backend/witness" + "github.com/consensys/gnark/constraint" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/scs" + "github.com/consensys/gnark/std/algebra" + "github.com/consensys/gnark/std/algebra/emulated/sw_bw6761" + "github.com/consensys/gnark/std/algebra/native/sw_bls12377" + "github.com/consensys/gnark/std/math/emulated" + "github.com/consensys/gnark/test" +) + +//----------------------------------------------------------------- +// Without api.Commit + +type InnerCircuitNativeWoCommit struct { + P, Q frontend.Variable + N frontend.Variable `gnark:",public"` +} + +func (c *InnerCircuitNativeWoCommit) Define(api frontend.API) error { + res := api.Mul(c.P, c.Q) + api.AssertIsEqual(res, c.N) + return nil +} + +func getInnerWoCommit(assert *test.Assert, field, outer *big.Int) (constraint.ConstraintSystem, plonk.VerifyingKey, witness.Witness, plonk.Proof) { + innerCcs, err := frontend.Compile(field, scs.NewBuilder, &InnerCircuitNativeWoCommit{}) + assert.NoError(err) + srs, err := test.NewKZGSRS(innerCcs) + assert.NoError(err) + innerPK, innerVK, err := plonk.Setup(innerCcs, srs) + assert.NoError(err) + + // inner proof + innerAssignment := &InnerCircuitNativeWoCommit{ + P: 3, + Q: 5, + N: 15, + } + innerWitness, err := frontend.NewWitness(innerAssignment, field) + assert.NoError(err) + innerProof, err := plonk.Prove(innerCcs, innerPK, innerWitness, GetNativeProverOptions(outer, field)) + assert.NoError(err) + innerPubWitness, err := innerWitness.Public() + assert.NoError(err) + err = plonk.Verify(innerProof, innerVK, innerPubWitness, GetNativeVerifierOptions(outer, field)) + assert.NoError(err) + return innerCcs, innerVK, innerPubWitness, innerProof +} + +type OuterCircuit[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.GtElementT] struct { + Proof Proof[FR, G1El, G2El] + VerifyingKey VerifyingKey[FR, G1El, G2El] + InnerWitness Witness[FR] +} + +func (c *OuterCircuit[FR, G1El, G2El, GtEl]) Define(api frontend.API) error { + verifier, err := NewVerifier[FR, G1El, G2El, GtEl](api) + if err != nil { + return fmt.Errorf("new verifier: %w", err) + } + err = verifier.AssertProof(c.VerifyingKey, c.Proof, c.InnerWitness) + return err +} + +func TestBLS12InBW6WoCommit(t *testing.T) { + + assert := test.NewAssert(t) + innerCcs, innerVK, innerWitness, innerProof := getInnerWoCommit(assert, ecc.BLS12_377.ScalarField(), ecc.BW6_761.ScalarField()) + + // outer proof + circuitVk, err := ValueOfVerifyingKey[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerVK) + assert.NoError(err) + circuitWitness, err := ValueOfWitness[sw_bls12377.ScalarField](innerWitness) + assert.NoError(err) + circuitProof, err := ValueOfProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerProof) + assert.NoError(err) + + outerCircuit := &OuterCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{ + InnerWitness: PlaceholderWitness[sw_bls12377.ScalarField](innerCcs), + Proof: PlaceholderProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerCcs), + VerifyingKey: PlaceholderVerifyingKey[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerCcs), + } + outerAssignment := &OuterCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + VerifyingKey: circuitVk, + } + err = test.IsSolved(outerCircuit, outerAssignment, ecc.BW6_761.ScalarField()) + assert.NoError(err) +} + +func TestBW6InBN254WoCommit(t *testing.T) { + + assert := test.NewAssert(t) + innerCcs, innerVK, innerWitness, innerProof := getInnerWoCommit(assert, ecc.BW6_761.ScalarField(), ecc.BN254.ScalarField()) + + // outer proof + 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) + 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: PlaceholderVerifyingKey[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerCcs), + } + outerAssignment := &OuterCircuit[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + VerifyingKey: circuitVk, + } + err = test.IsSolved(outerCircuit, outerAssignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} + +//----------------------------------------------------------------- +// With api.Commit + +type InnerCircuitCommit struct { + P, Q frontend.Variable + N frontend.Variable `gnark:",public"` +} + +func (c *InnerCircuitCommit) Define(api frontend.API) error { + + x := api.Mul(c.P, c.P) + y := api.Mul(c.Q, c.Q) + z := api.Add(x, y) + + committer, ok := api.(frontend.Committer) + if !ok { + return fmt.Errorf("builder does not implement frontend.Committer") + } + u, err := committer.Commit(x, z) + if err != nil { + return err + } + api.AssertIsDifferent(u, c.N) + return nil +} + +func getInnerCommit(assert *test.Assert, field, outer *big.Int) (constraint.ConstraintSystem, plonk.VerifyingKey, witness.Witness, plonk.Proof) { + + innerCcs, err := frontend.Compile(field, scs.NewBuilder, &InnerCircuitCommit{}) + assert.NoError(err) + + srs, err := test.NewKZGSRS(innerCcs) + assert.NoError(err) + + innerPK, innerVK, err := plonk.Setup(innerCcs, srs) + assert.NoError(err) + + // inner proof + innerAssignment := &InnerCircuitCommit{ + P: 3, + Q: 5, + N: 15, + } + innerWitness, err := frontend.NewWitness(innerAssignment, field) + assert.NoError(err) + innerProof, err := plonk.Prove(innerCcs, innerPK, innerWitness, GetNativeProverOptions(outer, field)) + + assert.NoError(err) + innerPubWitness, err := innerWitness.Public() + assert.NoError(err) + err = plonk.Verify(innerProof, innerVK, innerPubWitness, GetNativeVerifierOptions(outer, field)) + + assert.NoError(err) + return innerCcs, innerVK, innerPubWitness, innerProof +} + +func TestBLS12InBW6Commit(t *testing.T) { + + assert := test.NewAssert(t) + innerCcs, innerVK, innerWitness, innerProof := getInnerCommit(assert, ecc.BLS12_377.ScalarField(), ecc.BW6_761.ScalarField()) + + // outer proof + circuitVk, err := ValueOfVerifyingKey[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerVK) + assert.NoError(err) + circuitWitness, err := ValueOfWitness[sw_bls12377.ScalarField](innerWitness) + assert.NoError(err) + circuitProof, err := ValueOfProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerProof) + assert.NoError(err) + + outerCircuit := &OuterCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{ + InnerWitness: PlaceholderWitness[sw_bls12377.ScalarField](innerCcs), + Proof: PlaceholderProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerCcs), + VerifyingKey: PlaceholderVerifyingKey[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerCcs), + } + outerAssignment := &OuterCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + VerifyingKey: circuitVk, + } + err = test.IsSolved(outerCircuit, outerAssignment, ecc.BW6_761.ScalarField()) + assert.NoError(err) + +} + +func TestBW6InBN254Commit(t *testing.T) { + + assert := test.NewAssert(t) + innerCcs, innerVK, innerWitness, innerProof := getInnerCommit(assert, ecc.BW6_761.ScalarField(), ecc.BN254.ScalarField()) + + // outer proof + 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) + 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: PlaceholderVerifyingKey[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerCcs), + } + outerAssignment := &OuterCircuit[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + VerifyingKey: circuitVk, + } + err = test.IsSolved(outerCircuit, outerAssignment, ecc.BN254.ScalarField()) + assert.NoError(err) +} diff --git a/std/recursion/wrapped_hash.go b/std/recursion/wrapped_hash.go index fff9075c28..a4f8c0893e 100644 --- a/std/recursion/wrapped_hash.go +++ b/std/recursion/wrapped_hash.go @@ -126,7 +126,7 @@ func (h *shortNativeHash) Reset() { } func (h *shortNativeHash) Size() int { - return (int(h.outSize) + 6) / 8 + return (int(h.outSize)+7)/8 - 1 } func (h *shortNativeHash) BlockSize() int { diff --git a/std/recursion/wrapped_hash_test.go b/std/recursion/wrapped_hash_test.go index 66f4737455..6f2e662cad 100644 --- a/std/recursion/wrapped_hash_test.go +++ b/std/recursion/wrapped_hash_test.go @@ -215,6 +215,21 @@ func TestHashMarshalScalar(t *testing.T) { } assert.CheckCircuit(circuit, test.WithCurves(ecc.BW6_761), test.WithValidAssignment(assignment), test.NoFuzzing(), test.NoSerializationChecks(), test.NoSolidityChecks(), test.NoProverChecks()) }) + assert.Run(func(assert *test.Assert) { + var s fr_bls24315.Element + s.SetRandom() + h, err := recursion.NewShort(ecc.BW6_633.ScalarField(), ecc.BLS24_315.ScalarField()) + assert.NoError(err) + marshalled := s.Marshal() + h.Write(marshalled) + hashed := h.Sum(nil) + circuit := &hashMarshalScalarCircuit[sw_bls24315.ScalarField, sw_bls24315.G1Affine]{} + assignment := &hashMarshalScalarCircuit[sw_bls24315.ScalarField, sw_bls24315.G1Affine]{ + Scalar: sw_bls24315.NewScalar(s), + Expected: hashed, + } + assert.CheckCircuit(circuit, test.WithCurves(ecc.BW6_633), test.WithValidAssignment(assignment), test.NoFuzzing(), test.NoSerializationChecks(), test.NoSolidityChecks(), test.NoProverChecks()) + }) } type transcriptCircuit[FR emulated.FieldParams, G1El algebra.G1ElementT] struct {