Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PLONK in-circuit verifier #880

Merged
merged 27 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
53fee2d
test: add recursion hash tests
ivokub Nov 3, 2023
e29589c
fix: accumulate MSM result
ivokub Nov 20, 2023
395a6d7
refactor: take emulated element for additional data
ivokub Nov 20, 2023
3945e90
fix: handled infinity point in native multi scalar exp
ThomasPiellard Nov 10, 2023
daae22c
fix: use only nbBits when creating scalar
ivokub Nov 20, 2023
7a41420
feat: add PLONK verifier
ivokub Nov 20, 2023
ed6ce0a
feat: PlaceholderVerifyingKey takes the vk as argument
ThomasPiellard Nov 15, 2023
c72177b
feat: f -> scalarApi
ThomasPiellard Nov 15, 2023
e727a92
feat: addition of computeIthLagrangeAtZeta
ThomasPiellard Nov 15, 2023
9595331
feat: bsb commitments are added to pi
ThomasPiellard Nov 16, 2023
e958ce6
refactor: PlaceholderProof takes the proof as argument
ThomasPiellard Nov 16, 2023
330904c
fix: compute ith lagrange ok, hashToField failing
ThomasPiellard Nov 16, 2023
dbff073
fix: native short hash output size
ivokub Nov 17, 2023
5f8a060
feat: add bw6
ivokub Nov 17, 2023
b7cc8fb
docs: add package documentation
ivokub Nov 20, 2023
75bf361
refactor: describe error in panic
ivokub Nov 20, 2023
01f28ce
refactor: init curve and pairing implicitly
ivokub Nov 20, 2023
e608d57
refactor: remove comments
ivokub Nov 20, 2023
16d88db
docs: add package examples
ivokub Nov 20, 2023
5860e3a
feat: add all supported witness assignments
ivokub Nov 20, 2023
74145f9
test: add MSM test
ivokub Nov 23, 2023
f4fc463
fix: remove todo panic
ivokub Nov 23, 2023
51846bf
feat: add option shortcuts
ivokub Nov 23, 2023
9e3d250
fix: include hash to field in shortcut option
ivokub Nov 28, 2023
a3ec483
feat: use only CCS for placeholder proof and verifyingkey
ivokub Nov 28, 2023
3608b29
chore: typos and cleanup
ivokub Nov 28, 2023
f31664b
docs: add KZG package documentation
ivokub Nov 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion std/algebra/emulated/sw_emulated/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
ivokub marked this conversation as resolved.
Show resolved Hide resolved
}
return res, nil
}
7 changes: 6 additions & 1 deletion std/algebra/native/sw_bls12377/pairing2.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,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)

yelhousni marked this conversation as resolved.
Show resolved Hide resolved
// 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
}
Expand Down
7 changes: 6 additions & 1 deletion std/algebra/native/sw_bls24315/pairing2.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,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)

ivokub marked this conversation as resolved.
Show resolved Hide resolved
// 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
}
Expand Down
17 changes: 10 additions & 7 deletions std/commitments/kzg/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -364,7 +365,7 @@ 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 {
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 {
Expand Down Expand Up @@ -418,7 +419,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++ {
Expand Down Expand Up @@ -489,7 +490,7 @@ 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) {
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)
Expand Down Expand Up @@ -525,7 +526,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 {
Expand All @@ -546,15 +547,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
Expand Down
2 changes: 2 additions & 0 deletions std/recursion/plonk/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package plonk implements in-circuit PLONK verifier.
package plonk
82 changes: 82 additions & 0 deletions std/recursion/plonk/native_doc_test.go
Original file line number Diff line number Diff line change
@@ -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](innerProof),
VerifyingKey: plonk.PlaceholderVerifyingKey[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerVK),
}
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())
}
}
191 changes: 191 additions & 0 deletions std/recursion/plonk/nonnative_doc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package plonk_test

import (
"fmt"
"math/big"

"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark/backend"
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"
"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)
}
fsProverHasher, err := recursion.NewShort(outer, field)
if err != nil {
panic(err)
}
kzgProverHasher, err := recursion.NewShort(outer, field)
if err != nil {
panic(err)
}
innerProof, err := native_plonk.Prove(innerCcs, innerPK, innerWitness,
backend.WithProverChallengeHashFunction(fsProverHasher),
backend.WithProverKZGFoldingHashFunction(kzgProverHasher),
)
if err != nil {
panic(err)
}
innerPubWitness, err := innerWitness.Public()
if err != nil {
panic(err)
}
fsVerifierHasher, err := recursion.NewShort(outer, field)
if err != nil {
panic(err)
}
kzgVerifierHash, err := recursion.NewShort(outer, field)
if err != nil {
panic(err)
}
err = native_plonk.Verify(innerProof, innerVK, innerPubWitness,
backend.WithVerifierChallengeHashFunction(fsVerifierHasher),
backend.WithVerifierKZGFoldingHashFunction(kzgVerifierHash),
)
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](innerProof),
VerifyingKey: plonk.PlaceholderVerifyingKey[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine](innerVK),
}
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 Groth16 proof
err = native_plonk.Verify(outerProof, vk, publicWitness)
if err != nil {
panic("circuit verification failed: " + err.Error())
}
}
Loading