Skip to content

Commit

Permalink
Feat/plonk verifier batching (#960)
Browse files Browse the repository at this point in the history
* feat: add test

* feat: split BatchVerifyMultiPoints in two to retrieve pairing arguments

* feat: test circuit for BatchVerifyMultiPoints

* feat: split plonk verifier in two

* feat: batch verify kzg folded proofs in plonk verifier

* feat: cleaning

* feat: hash public data in final circuit

* feat: use different circuits

* feat: BatchVerifyCircuits instantiation

* feat: multi circuit (wo hashing public inputs) ok

* feat: public inputs hash ok

* feat: cleaning

* feat: clean comment

* refactor: use references instead of values

* docs: add method docs

* chore: use new unsafe SRS generation for test

* feat: use native prove/verifier option aggregator

* fix: return signature

* fix: return value

* feat: use constant verification key in tests

* docs: constant verification key in example

* chore: make inner witness public

* chore: fix typo

* perf: make verification key constant

* feat: add batch verifier

* chore: remove test.Assert in non-test

* refactor: use constant verification key and simplify witness assignment

* feat: setting up circuit with selectors

* feat: placeholder, assignment ok

* feat: restores batchVerifyMultPoints

* feat: split new files

* feat: move getInnerData

* feat: added **_bis.go files

* feat: add lookup and select to curve

* feat: split PLONK verification key

* fix: missing argument

* feat: perform generic exponentiation

* chore: linter warning

* feat: add base and circuit verification key init

* feat: implement verification key switching

* feat: add switched verification

* fix: missing return

* feat: hash works

* test: add aggregation test

* test: fix aggregation circuit test

* chore: simplify placeholder

* test: use bigger padding to ensure constant size of two

* docs: AssertDifferentProofs

* test: add test with public input digest

* test: return only public witness

* chore: remove old files

---------

Co-authored-by: Ivo Kubjas <[email protected]>
  • Loading branch information
ThomasPiellard and ivokub authored Dec 15, 2023
1 parent 17fa8eb commit a1bb3ee
Show file tree
Hide file tree
Showing 9 changed files with 774 additions and 152 deletions.
11 changes: 11 additions & 0 deletions std/algebra/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ type Curve[FR emulated.FieldParams, G1El G1ElementT] interface {

// MarshalScalar returns the binary decomposition of the argument.
MarshalScalar(emulated.Element[FR]) []frontend.Variable

// Select sets p1 if b=1, p2 if b=0, and returns it. b must be boolean constrained
Select(b frontend.Variable, p1 *G1El, p2 *G1El) *G1El

// Lookup2 performs a 2-bit lookup between p1, p2, p3, p4 based on bits b0 and b1.
// Returns:
// - p1 if b0=0 and b1=0,
// - p2 if b0=1 and b1=0,
// - p3 if b0=0 and b1=1,
// - p4 if b0=1 and b1=1.
Lookup2(b1 frontend.Variable, b2 frontend.Variable, p1 *G1El, p2 *G1El, p3 *G1El, p4 *G1El) *G1El
}

// Pairing allows to compute the bi-linear pairing of G1 and G2 elements.
Expand Down
21 changes: 21 additions & 0 deletions std/algebra/native/sw_bls12377/pairing2.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,27 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts
}
}

// Select sets p1 if b=1, p2 if b=0, and returns it. b must be boolean constrained
func (c *Curve) Select(b frontend.Variable, p1, p2 *G1Affine) *G1Affine {
return &G1Affine{
X: c.api.Select(b, p1.X, p2.X),
Y: c.api.Select(b, p1.Y, p2.Y),
}
}

// Lookup2 performs a 2-bit lookup between p1, p2, p3, p4 based on bits b0 and b1.
// Returns:
// - p1 if b0=0 and b1=0,
// - p2 if b0=1 and b1=0,
// - p3 if b0=0 and b1=1,
// - p4 if b0=1 and b1=1.
func (c *Curve) Lookup2(b1, b2 frontend.Variable, p1, p2, p3, p4 *G1Affine) *G1Affine {
return &G1Affine{
X: c.api.Lookup2(b1, b2, p1.X, p2.X, p3.X, p4.X),
Y: c.api.Lookup2(b1, b2, p1.Y, p2.Y, p3.Y, p4.Y),
}
}

// Pairing allows computing pairing-related operations in BLS12-377.
type Pairing struct {
api frontend.API
Expand Down
21 changes: 21 additions & 0 deletions std/algebra/native/sw_bls24315/pairing2.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,27 @@ func (c *Curve) MultiScalarMul(P []*G1Affine, scalars []*Scalar, opts ...algopts
}
}

// Select sets p1 if b=1, p2 if b=0, and returns it. b must be boolean constrained
func (c *Curve) Select(b frontend.Variable, p1, p2 *G1Affine) *G1Affine {
return &G1Affine{
X: c.api.Select(b, p1.X, p2.X),
Y: c.api.Select(b, p1.Y, p2.Y),
}
}

// Lookup2 performs a 2-bit lookup between p1, p2, p3, p4 based on bits b0 and b1.
// Returns:
// - p1 if b0=0 and b1=0,
// - p2 if b0=1 and b1=0,
// - p3 if b0=0 and b1=1,
// - p4 if b0=1 and b1=1.
func (c *Curve) Lookup2(b1, b2 frontend.Variable, p1, p2, p3, p4 *G1Affine) *G1Affine {
return &G1Affine{
X: c.api.Lookup2(b1, b2, p1.X, p2.X, p3.X, p4.X),
Y: c.api.Lookup2(b1, b2, p1.Y, p2.Y, p3.Y, p4.Y),
}
}

// Pairing allows computing pairing-related operations in BLS24-315.
type Pairing struct {
api frontend.API
Expand Down
43 changes: 29 additions & 14 deletions std/commitments/kzg/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,34 +462,32 @@ 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 {
// FoldProofsMultiPoint folds multiple proofs with openings at multiple points.
// Used for batch verification of different opening proofs. See also
// [Verifier.BatchVerifyMultiPoints].
func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProofsMultiPoint(digests []Commitment[G1El], proofs []OpeningProof[FR, G1El], points []emulated.Element[FR], vk VerifyingKey[G1El, G2El]) (*G1El, *G1El, error) {

var fr FR

// check consistency nb proogs vs nb digests
if len(digests) != len(proofs) {
return fmt.Errorf("number of commitments doesn't match number of proofs")
return nil, nil, fmt.Errorf("number of commitments doesn't match number of proofs")
}
if len(digests) != len(points) {
return fmt.Errorf("number of commitments doesn't match number of points ")
return nil, nil, fmt.Errorf("number of commitments doesn't match number of points ")
}

// len(digests) should be nonzero because of randomNumbers
if len(digests) == 0 {
return fmt.Errorf("number of digests should be nonzero")
}

// if only one digest, call Verify
if len(digests) == 1 {
return v.CheckOpeningProof(digests[0], proofs[0], points[0], vk)
return nil, nil, fmt.Errorf("number of digests should be nonzero")
}

// sample random numbers λᵢ for sampling
randomNumbers := make([]*emulated.Element[FR], len(digests))
randomNumbers[0] = v.scalarApi.One()
whSnark, err := recursion.NewHash(v.api, fr.Modulus(), true)
if err != nil {
return err
return nil, nil, err
}
for i := 0; i < len(digests); i++ {
marshalledG1 := v.curve.MarshalG1(digests[i].G1El)
Expand Down Expand Up @@ -525,11 +523,11 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit
}
foldedQuotients, err := v.curve.MultiScalarMul(quotients, []*emulated.Element[FR]{randomNumbers[1]}, algopts.WithFoldingScalarMul(), algopts.WithNbScalarBits(nbScalarBits))
if err != nil {
return fmt.Errorf("fold quotients: %w", err)
return nil, nil, fmt.Errorf("fold quotients: %w", err)
}
foldedPointsQuotients, err := v.curve.MultiScalarMul(quotients, randomPointNumbers)
if err != nil {
return fmt.Errorf("fold point quotients: %w", err)
return nil, nil, fmt.Errorf("fold point quotients: %w", err)
}

// fold digests and evals
Expand All @@ -543,7 +541,7 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit

foldedDigests, foldedEvals, err := v.fold(digests, evals, randomNumbers)
if err != nil {
return fmt.Errorf("fold: %w", err)
return nil, nil, fmt.Errorf("fold: %w", err)
}

// compute commitment to folded Eval [∑ᵢλᵢfᵢ(aᵢ)]G₁
Expand All @@ -562,6 +560,23 @@ func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commit
// foldedQuotients.Neg(&foldedQuotients)
foldedQuotients = v.curve.Neg(foldedQuotients)

return foldedDigest, foldedQuotients, 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 {

// if only one proof go to base case
if len(digests) == 1 {
return v.CheckOpeningProof(digests[0], proofs[0], points[0], vk)
}

// fold the proofs
foldedDigest, foldedQuotients, err := v.FoldProofsMultiPoint(digests, proofs, points, vk)
if err != nil {
return err
}

// pairing check
err = v.pairing.PairingCheck(
[]*G1El{foldedDigest, foldedQuotients},
Expand Down
83 changes: 83 additions & 0 deletions std/commitments/kzg/verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
const (
kzgSize = 128
polynomialSize = 100
nbPolynomials = 5
)

type KZGVerificationCircuit[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GTEl algebra.GtElementT] struct {
Expand Down Expand Up @@ -231,6 +232,88 @@ func TestKZGVerificationTwoChain(t *testing.T) {
assert.CheckCircuit(&KZGVerificationCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{}, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BW6_761))
}

type KZGBatcheVerificationMultiPointCircuit[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GTEl algebra.GtElementT] struct {
Vk VerifyingKey[G1El, G2El]
Commitments []Commitment[G1El]
Proofs []OpeningProof[FR, G1El]
Points []emulated.Element[FR]
}

func (c *KZGBatcheVerificationMultiPointCircuit[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)
}
if err := verifier.BatchVerifyMultiPoints(c.Commitments, c.Proofs, c.Points, c.Vk); err != nil {
return fmt.Errorf("assert proof: %w", err)
}
return nil
}

func TestKZGBatchVerificationMultiPointsTwoChain(t *testing.T) {

assert := test.NewAssert(t)

alpha, err := rand.Int(rand.Reader, ecc.BLS12_377.ScalarField())
assert.NoError(err)
srs, err := kzg_bls12377.NewSRS(kzgSize, alpha)
assert.NoError(err)

// sample random polynomials, random points

f := make([][]fr_bls12377.Element, nbPolynomials)
points := make([]fr_bls12377.Element, nbPolynomials)
proofs := make([]kzg_bls12377.OpeningProof, nbPolynomials)
commitments := make([]kzg_bls12377.Digest, nbPolynomials)
for i := 0; i < nbPolynomials; i++ {
f[i] = make([]fr_bls12377.Element, polynomialSize)
for j := 0; j < polynomialSize; j++ {
f[i][j].SetRandom()
}

commitments[i], err = kzg_bls12377.Commit(f[i], srs.Pk)
assert.NoError(err)

points[i].SetRandom()
proofs[i], err = kzg_bls12377.Open(f[i], points[i], srs.Pk)
assert.NoError(err)
}

if err = kzg_bls12377.BatchVerifyMultiPoints(commitments, proofs, points, srs.Vk); err != nil {
t.Fatal("verify proof", err)
}

var assignment KZGBatcheVerificationMultiPointCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]
assignment.Commitments = make([]Commitment[sw_bls12377.G1Affine], nbPolynomials)
assignment.Proofs = make([]OpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine], nbPolynomials)
assignment.Points = make([]emulated.Element[sw_bls12377.ScalarField], nbPolynomials)
for i := 0; i < nbPolynomials; i++ {
wCmt, err := ValueOfCommitment[sw_bls12377.G1Affine](commitments[i])
assert.NoError(err)
wProof, err := ValueOfOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](proofs[i])
assert.NoError(err)
wPt, err := ValueOfScalar[sw_bls12377.ScalarField](points[i])
assert.NoError(err)
assignment.Commitments[i] = wCmt
assignment.Proofs[i] = wProof
assignment.Points[i] = wPt
}
wVk, err := ValueOfVerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine](srs.Vk)
assert.NoError(err)
assignment.Vk = wVk

var circuit KZGBatcheVerificationMultiPointCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]
circuit.Commitments = make([]Commitment[sw_bls12377.G1Affine], nbPolynomials)
circuit.Proofs = make([]OpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine], nbPolynomials)
circuit.Points = make([]emulated.Element[sw_bls12377.ScalarField], nbPolynomials)

assert.CheckCircuit(
&circuit,
test.WithValidAssignment(&assignment),
test.WithCurves(ecc.BW6_761),
)
}

func TestKZGVerificationTwoChain2(t *testing.T) {
assert := test.NewAssert(t)

Expand Down
3 changes: 1 addition & 2 deletions std/recursion/plonk/native_doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ func Example_native() {
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),
VerifyingKey: circuitVk,
}
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)
Expand Down
7 changes: 3 additions & 4 deletions std/recursion/plonk/nonnative_doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ func computeInnerProof(field, outer *big.Int) (constraint.ConstraintSystem, nati
// 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]
VerifyingKey plonk.VerifyingKey[FR, G1El, G2El] `gnark:"-"` // constant verification key
InnerWitness plonk.Witness[FR] `gnark:",public"`
}

func (c *OuterCircuit[FR, G1El, G2El, GtEl]) Define(api frontend.API) error {
Expand Down Expand Up @@ -118,12 +118,11 @@ func Example_emulated() {
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),
VerifyingKey: circuitVk,
}
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)
Expand Down
Loading

0 comments on commit a1bb3ee

Please sign in to comment.