diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go index 12f453b674..661a028f23 100644 --- a/std/algebra/emulated/sw_bls12381/pairing.go +++ b/std/algebra/emulated/sw_bls12381/pairing.go @@ -365,6 +365,10 @@ func (pr *Pairing) IsOnG2(Q *G2Affine) frontend.Variable { return pr.api.And(isOnCurve, isInSubgroup) } +func (pr *Pairing) One() *GTEl { + return pr.Ext12.One() +} + // loopCounter = seed in binary // // seed=-15132376222941642752 diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go index 7eec342480..db2cbdd2e7 100644 --- a/std/algebra/emulated/sw_bn254/pairing.go +++ b/std/algebra/emulated/sw_bn254/pairing.go @@ -319,6 +319,14 @@ func (pr *Pairing) AssertIsOnCurve(P *G1Affine) { pr.curve.AssertIsOnCurve(P) } +func (pr *Pairing) IsOnG1(P *G1Affine) frontend.Variable { + return pr.curve.IsOnCurve(P) +} + +func (pr *Pairing) One() *GTEl { + return pr.Ext12.One() +} + func (pr *Pairing) MuxG2(sel frontend.Variable, inputs ...*G2Affine) *G2Affine { if len(inputs) == 0 { return nil diff --git a/std/algebra/emulated/sw_bw6761/pairing.go b/std/algebra/emulated/sw_bw6761/pairing.go index 8c17bd7126..8ce7fd7828 100644 --- a/std/algebra/emulated/sw_bw6761/pairing.go +++ b/std/algebra/emulated/sw_bw6761/pairing.go @@ -321,7 +321,15 @@ func (pr *Pairing) AssertIsOnCurve(P *G1Affine) { pr.curve.AssertIsOnCurve(P) } +func (pr *Pairing) IsOnCurve(P *G1Affine) frontend.Variable { + return pr.curve.IsOnCurve(P) +} + func (pr *Pairing) AssertIsOnTwist(Q *G2Affine) { + pr.api.AssertIsEqual(pr.IsOnTwist(Q), 1) +} + +func (pr *Pairing) IsOnTwist(Q *G2Affine) frontend.Variable { // Twist: Y² == X³ + aX + b, where a=0 and b=4 // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) @@ -334,13 +342,16 @@ func (pr *Pairing) AssertIsOnTwist(Q *G2Affine) { right := pr.curveF.Mul(&Q.P.X, &Q.P.X) right = pr.curveF.Mul(right, &Q.P.X) right = pr.curveF.Add(right, b) - pr.curveF.AssertIsEqual(left, right) + return pr.curveF.IsZero(pr.curveF.Sub(left, right)) } func (pr *Pairing) AssertIsOnG1(P *G1Affine) { - // 1- Check P is on the curve - pr.AssertIsOnCurve(P) + pr.api.AssertIsEqual(pr.IsOnG1(P), 1) +} +func (pr *Pairing) IsOnG1(P *G1Affine) frontend.Variable { + // 1- Check P is on the curve + isOnCurve := pr.IsOnCurve(P) // 2- Check P has the right subgroup order // we check that [x₀+1]P == [-x₀³+x₀²-1]ϕ(P) xP := pr.g1.scalarMulBySeed(P) @@ -353,12 +364,20 @@ func (pr *Pairing) AssertIsOnG1(P *G1Affine) { right = pr.g1.phi(right) // [r]P == 0 <==> [x₀+1]P == [-x₀³+x₀²-1]ϕ(P) - pr.curve.AssertIsEqual(left, right) + isEqual := pr.api.And( + pr.curveF.IsZero(pr.curveF.Sub(&left.X, &right.X)), + pr.curveF.IsZero(pr.curveF.Sub(&left.Y, &right.Y)), + ) + return pr.api.And(isOnCurve, isEqual) } func (pr *Pairing) AssertIsOnG2(Q *G2Affine) { + pr.api.AssertIsEqual(pr.IsOnG2(Q), 1) +} + +func (pr *Pairing) IsOnG2(Q *G2Affine) frontend.Variable { // 1- Check Q is on the curve - pr.AssertIsOnTwist(Q) + isOnCurve := pr.IsOnTwist(Q) // 2- Check Q has the right subgroup order // we check that [x₀+1]Q == [-x₀³+x₀²-1]ϕ(Q) @@ -372,7 +391,11 @@ func (pr *Pairing) AssertIsOnG2(Q *G2Affine) { right = pr.g2.phi(right) // [r]Q == 0 <==> [x₀+1]Q == [-x₀³+x₀²-1]ϕ(Q) - pr.g2.AssertIsEqual(left, right) + isEqual := pr.api.And( + pr.curveF.IsZero(pr.curveF.Sub(&left.P.X, &right.P.X)), + pr.curveF.IsZero(pr.curveF.Sub(&left.P.Y, &right.P.Y)), + ) + return pr.api.And(isOnCurve, isEqual) } // seed x₀=9586122913090633729 diff --git a/std/algebra/emulated/sw_emulated/point.go b/std/algebra/emulated/sw_emulated/point.go index e66823728b..f6387781a0 100644 --- a/std/algebra/emulated/sw_emulated/point.go +++ b/std/algebra/emulated/sw_emulated/point.go @@ -207,6 +207,11 @@ func (c *Curve[B, S]) add(p, q *AffinePoint[B]) *AffinePoint[B] { // AssertIsOnCurve asserts if p belongs to the curve. It doesn't modify p. func (c *Curve[B, S]) AssertIsOnCurve(p *AffinePoint[B]) { + c.api.AssertIsEqual(c.IsOnCurve(p), 1) +} + +// IsOnCurve returns a boolean indicating if p belongs to the curve. +func (c *Curve[B, S]) IsOnCurve(p *AffinePoint[B]) frontend.Variable { // (X,Y) ∈ {Y² == X³ + aX + b} U (0,0) // if p=(0,0) we assign b=0 and continue @@ -219,7 +224,7 @@ func (c *Curve[B, S]) AssertIsOnCurve(p *AffinePoint[B]) { } else { check = c.baseApi.Eval([][]*emulated.Element[B]{{&p.X, &p.X, &p.X}, {&c.a, &p.X}, {b}, {&p.Y, &p.Y}}, []int{1, 1, 1, -1}) } - c.baseApi.AssertIsEqual(check, c.baseApi.Zero()) + return c.baseApi.IsZero(check) } // AddUnified adds p and q and returns it. It doesn't modify p nor q. diff --git a/std/algebra/interfaces.go b/std/algebra/interfaces.go index 2dba477b08..f7c2224061 100644 --- a/std/algebra/interfaces.go +++ b/std/algebra/interfaces.go @@ -99,6 +99,9 @@ type Pairing[G1El G1ElementT, G2El G2ElementT, GtEl GtElementT] interface { // when the inputs are of mismatching length. It does not modify the inputs. PairingCheck([]*G1El, []*G2El) error + // One returns the identity element of the target group. + One() *GtEl + // AssertIsEqual asserts the equality of the inputs. AssertIsEqual(*GtEl, *GtEl) @@ -108,6 +111,12 @@ type Pairing[G1El G1ElementT, G2El G2ElementT, GtEl GtElementT] interface { // AssertIsOnG2 asserts that the input is on the G2 curve. AssertIsOnG2(*G2El) + // IsOnG1 returns a boolean indicating whether the input is on G1. + IsOnG1(*G1El) frontend.Variable + + // IsOnG2 returns a boolean indicating whether the input is on G2. + IsOnG2(*G2El) frontend.Variable + // MuxG2 performs a lookup from the G2 inputs and returns inputs[sel]. It is // most efficient for power of two lengths of the inputs, but works for any // number of inputs. diff --git a/std/algebra/native/sw_bls12377/pairing2.go b/std/algebra/native/sw_bls12377/pairing2.go index 09451a117f..7bc69b4b30 100644 --- a/std/algebra/native/sw_bls12377/pairing2.go +++ b/std/algebra/native/sw_bls12377/pairing2.go @@ -343,6 +343,12 @@ func (pr *Pairing) PairingCheck(P []*G1Affine, Q []*G2Affine) error { return nil } +func (pr *Pairing) One() *GT { + var res GT + res.SetOne() + return &res +} + // AssertIsEqual asserts the equality of the target group elements. func (pr *Pairing) AssertIsEqual(e1, e2 *GT) { e1.AssertIsEqual(pr.api, *e2) @@ -469,6 +475,11 @@ func (pr *Pairing) MuxGt(sel frontend.Variable, inputs ...*GT) *GT { // AssertIsOnCurve asserts if p belongs to the curve. It doesn't modify p. func (pr *Pairing) AssertIsOnCurve(p *G1Affine) { + pr.api.AssertIsEqual(pr.IsOnCurve(p), 1) +} + +// IsOnCurve returns a boolean indicating if p belongs to the curve. +func (pr *Pairing) IsOnCurve(p *G1Affine) frontend.Variable { // (X,Y) ∈ {Y² == X³ + 1} U (0,0) // if p=(0,0) we assign b=0 and continue @@ -478,12 +489,16 @@ func (pr *Pairing) AssertIsOnCurve(p *G1Affine) { left := pr.api.Mul(p.Y, p.Y) right := pr.api.Mul(p.X, pr.api.Mul(p.X, p.X)) right = pr.api.Add(right, b) - pr.api.AssertIsEqual(left, right) + return pr.api.IsZero(pr.api.Sub(left, right)) } func (pr *Pairing) AssertIsOnG1(P *G1Affine) { + pr.api.AssertIsEqual(pr.IsOnG1(P), 1) +} + +func (pr *Pairing) IsOnG1(P *G1Affine) frontend.Variable { // 1- Check P is on the curve - pr.AssertIsOnCurve(P) + isOnCurve := pr.IsOnCurve(P) // 2- Check P has the right subgroup order // [x²]ϕ(P) @@ -497,11 +512,19 @@ func (pr *Pairing) AssertIsOnG1(P *G1Affine) { _P.Neg(pr.api, _P) // [r]Q == 0 <==> P = -[x²]ϕ(P) - P.AssertIsEqual(pr.api, _P) + isEqual := pr.api.And( + pr.api.IsZero(pr.api.Sub(P.X, _P.X)), + pr.api.IsZero(pr.api.Sub(P.Y, _P.Y)), + ) + return pr.api.And(isOnCurve, isEqual) } // AssertIsOnTwist asserts if p belongs to the curve. It doesn't modify p. func (pr *Pairing) AssertIsOnTwist(p *G2Affine) { + pr.api.AssertIsEqual(pr.IsOnTwist(p), 1) +} + +func (pr *Pairing) IsOnTwist(p *G2Affine) frontend.Variable { // (X,Y) ∈ {Y² == X³ + 1/u} U (0,0) // if p=(0,0) we assign b=0 and continue @@ -519,12 +542,18 @@ func (pr *Pairing) AssertIsOnTwist(p *G2Affine) { right.Square(pr.api, p.P.X) right.Mul(pr.api, right, p.P.X) right.Add(pr.api, right, b) - left.AssertIsEqual(pr.api, right) + var diff fields_bls12377.E2 + diff.Sub(pr.api, left, right) + return diff.IsZero(pr.api) } func (pr *Pairing) AssertIsOnG2(P *G2Affine) { + pr.api.AssertIsEqual(pr.IsOnG2(P), 1) +} + +func (pr *Pairing) IsOnG2(P *G2Affine) frontend.Variable { // 1- Check P is on the curve - pr.AssertIsOnTwist(P) + isOnTwist := pr.IsOnTwist(P) // 2- Check P has the right subgroup order // [x₀]Q @@ -534,7 +563,11 @@ func (pr *Pairing) AssertIsOnG2(P *G2Affine) { psiP.psi(pr.api, &P.P) // [r]Q == 0 <==> ψ(Q) == [x₀]Q - xP.AssertIsEqual(pr.api, psiP) + var diffX, diffY fields_bls12377.E2 + diffX.Sub(pr.api, xP.X, psiP.X) + diffY.Sub(pr.api, xP.Y, psiP.Y) + isEqual := pr.api.And(diffX.IsZero(pr.api), diffY.IsZero(pr.api)) + return pr.api.And(isOnTwist, isEqual) } // NewG1Affine allocates a witness from the native G1 element and returns it. diff --git a/std/commitments/pedersen/verifier.go b/std/commitments/pedersen/verifier.go index 77cd596be6..35b5c53ed2 100644 --- a/std/commitments/pedersen/verifier.go +++ b/std/commitments/pedersen/verifier.go @@ -27,6 +27,7 @@ type VerifyingKey[G2El algebra.G2ElementT] struct { // Verifier verifies the knowledge proofs for a Pedersen commitments type Verifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.GtElementT] struct { + api frontend.API pairing algebra.Pairing[G1El, G2El, GtEl] } @@ -36,7 +37,7 @@ func NewVerifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra. if err != nil { return nil, fmt.Errorf("get pairing: %w", err) } - return &Verifier[FR, G1El, G2El, GtEl]{pairing: pairing}, nil + return &Verifier[FR, G1El, G2El, GtEl]{api: api, pairing: pairing}, nil } // FoldCommitments folds the given commitments into a single commitment for efficient verification. @@ -54,19 +55,35 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) FoldCommitments(commitments []Commitmen // AssertCommitment verifies the given commitment and knowledge proof against the given verifying key. func (v *Verifier[FR, G1El, G2El, GtEl]) AssertCommitment(commitment Commitment[G1El], knowledgeProof KnowledgeProof[G1El], vk VerifyingKey[G2El], opts ...VerifierOption) error { + isValid, err := v.IsCommitmentValid(commitment, knowledgeProof, vk, opts...) + if err != nil { + return err + } + v.api.AssertIsEqual(isValid, 1) + return nil +} + +// IsCommitmentValid returns a variable that is 1 if the commitment and +// knowledge proof are valid and 0 otherwise. +func (v *Verifier[FR, G1El, G2El, GtEl]) IsCommitmentValid(commitment Commitment[G1El], knowledgeProof KnowledgeProof[G1El], vk VerifyingKey[G2El], opts ...VerifierOption) (frontend.Variable, error) { cfg, err := newCfg(opts...) if err != nil { - return fmt.Errorf("apply options: %w", err) + return 0, fmt.Errorf("apply options: %w", err) } + + isValid := frontend.Variable(1) if cfg.subgroupCheck { - v.pairing.AssertIsOnG1(&commitment.G1El) - v.pairing.AssertIsOnG1(&knowledgeProof.G1El) + isValid = v.api.Mul(isValid, v.pairing.IsOnG1(&commitment.G1El)) + isValid = v.api.Mul(isValid, v.pairing.IsOnG1(&knowledgeProof.G1El)) } - if err = v.pairing.PairingCheck([]*G1El{&commitment.G1El, &knowledgeProof.G1El}, []*G2El{&vk.GSigmaNeg, &vk.G}); err != nil { - return fmt.Errorf("pairing check failed: %w", err) + res, err := v.pairing.Pair([]*G1El{&commitment.G1El, &knowledgeProof.G1El}, []*G2El{&vk.GSigmaNeg, &vk.G}) + if err != nil { + return 0, fmt.Errorf("pairing: %w", err) } - return nil + + isValid = v.api.Mul(isValid, v.pairing.IsEqual(res, v.pairing.One())) + return isValid, nil } // TODO: add asserting with switches between different keys diff --git a/std/recursion/groth16/verifier.go b/std/recursion/groth16/verifier.go index 13fb60c218..bf1f173079 100644 --- a/std/recursion/groth16/verifier.go +++ b/std/recursion/groth16/verifier.go @@ -559,11 +559,6 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) IsValidProof(vk VerifyingKey[G1El, G2El return 0, fmt.Errorf("hash to field: %w", err) } - maxNbPublicCommitted := 0 - for _, s := range vk.PublicAndCommitmentCommitted { // iterate over commitments - maxNbPublicCommitted = max(maxNbPublicCommitted, len(s)) - } - commitmentAuxData := make([]*emulated.Element[FR], len(vk.PublicAndCommitmentCommitted)) for i := range vk.PublicAndCommitmentCommitted { // solveCommitmentWire hashToField.Write(v.curve.MarshalG1(proof.Commitments[i].G1El)...) @@ -580,13 +575,16 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) IsValidProof(vk VerifyingKey[G1El, G2El commitmentAuxData[i] = res } + isValid := frontend.Variable(1) switch len(vk.CommitmentKeys) { case 0: // explicitly do not verify the commitment as there is nothing case 1: - if err = v.commitment.AssertCommitment(proof.Commitments[0], proof.CommitmentPok, vk.CommitmentKeys[0], opt.pedopt...); err != nil { - return 0, fmt.Errorf("assert commitment: %w", err) + commitmentValid, err := v.commitment.IsCommitmentValid(proof.Commitments[0], proof.CommitmentPok, vk.CommitmentKeys[0], opt.pedopt...) + if err != nil { + return 0, fmt.Errorf("check commitment: %w", err) } + isValid = v.api.Mul(isValid, commitmentValid) default: // TODO: we support only a single commitment in the recursion for now return 0, fmt.Errorf("multiple commitments are not supported") @@ -603,15 +601,16 @@ func (v *Verifier[FR, G1El, G2El, GtEl]) IsValidProof(vk VerifyingKey[G1El, G2El } if opt.forceSubgroupCheck { - v.pairing.AssertIsOnG1(&proof.Ar) - v.pairing.AssertIsOnG1(&proof.Krs) - v.pairing.AssertIsOnG2(&proof.Bs) + isValid = v.api.Mul(isValid, v.pairing.IsOnG1(&proof.Ar)) + isValid = v.api.Mul(isValid, v.pairing.IsOnG1(&proof.Krs)) + isValid = v.api.Mul(isValid, v.pairing.IsOnG2(&proof.Bs)) } pairing, err := v.pairing.Pair([]*G1El{kSum, &proof.Krs, &proof.Ar}, []*G2El{&vk.G2.GammaNeg, &vk.G2.DeltaNeg, &proof.Bs}) if err != nil { return 0, fmt.Errorf("pairing: %w", err) } - return v.pairing.IsEqual(pairing, &vk.E), nil + isValid = v.api.Mul(isValid, v.pairing.IsEqual(pairing, &vk.E)) + return isValid, nil } // SwitchVerification key switches the verification key based on the provided diff --git a/std/recursion/groth16/verifier_test.go b/std/recursion/groth16/verifier_test.go index 1ec5a76229..490ec9b572 100644 --- a/std/recursion/groth16/verifier_test.go +++ b/std/recursion/groth16/verifier_test.go @@ -342,6 +342,7 @@ func getInnerCommitment(assert *test.Assert, field, outer *big.Int) (constraint. assert.NoError(err) return innerCcs, innerVK, innerPubWitness, innerProof } + func TestBN254InBN254Commitment(t *testing.T) { assert := test.NewAssert(t) @@ -672,3 +673,83 @@ func TestBLS12InBW6InvalidProof(t *testing.T) { assert.NoError(err) }, "invalid=witness") } + +type OuterCircuitIsValidWithSubgroupCheck[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.GtElementT] struct { + Proof Proof[G1El, G2El] + VerifyingKey VerifyingKey[G1El, G2El, GtEl] + InnerWitness Witness[FR] + Res frontend.Variable `gnark:",public"` +} + +func (c *OuterCircuitIsValidWithSubgroupCheck[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) + } + res, err := verifier.IsValidProof(c.VerifyingKey, c.Proof, c.InnerWitness, WithSubgroupCheck()) + if err != nil { + return fmt.Errorf("proof check: %w", err) + } + api.AssertIsEqual(res, c.Res) + return nil +} + +func TestBLS12InBW6CommitmentIsValidWithSubgroupCheck(t *testing.T) { + assert := test.NewAssert(t) + + innerCcs, innerVK, innerWitness, innerProof := getInnerCommitment(assert, ecc.BLS12_377.ScalarField(), ecc.BW6_761.ScalarField()) + assert.Equal(len(innerCcs.GetCommitments().CommitmentIndexes()), 1) + + circuitVk, err := ValueOfVerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT](innerVK) + assert.NoError(err) + circuitWitness, err := ValueOfWitness[sw_bls12377.ScalarField](innerWitness) + assert.NoError(err) + circuitProof, err := ValueOfProof[sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerProof) + assert.NoError(err) + + outerCircuit := &OuterCircuitIsValidWithSubgroupCheck[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{ + Proof: PlaceholderProof[sw_bls12377.G1Affine, sw_bls12377.G2Affine](innerCcs), + InnerWitness: PlaceholderWitness[sw_bls12377.ScalarField](innerCcs), + VerifyingKey: PlaceholderVerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT](innerCcs), + } + + outerAssignment := &OuterCircuitIsValidWithSubgroupCheck[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + VerifyingKey: circuitVk, + Res: 1, + } + err = test.IsSolved(outerCircuit, outerAssignment, ecc.BW6_761.ScalarField()) + assert.NoError(err) +} + +func TestBN254InBN254CommitmentIsValidWithSubgroupCheck(t *testing.T) { + assert := test.NewAssert(t) + + innerCcs, innerVK, innerWitness, innerProof := getInnerCommitment(assert, ecc.BN254.ScalarField(), ecc.BN254.ScalarField()) + assert.Equal(len(innerCcs.GetCommitments().CommitmentIndexes()), 1) + + circuitVk, err := ValueOfVerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl](innerVK) + assert.NoError(err) + circuitWitness, err := ValueOfWitness[sw_bn254.ScalarField](innerWitness) + assert.NoError(err) + circuitProof, err := ValueOfProof[sw_bn254.G1Affine, sw_bn254.G2Affine](innerProof) + assert.NoError(err) + + outerCircuit := &OuterCircuitIsValidWithSubgroupCheck[sw_bn254.ScalarField, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{ + Proof: PlaceholderProof[sw_bn254.G1Affine, sw_bn254.G2Affine](innerCcs), + InnerWitness: PlaceholderWitness[sw_bn254.ScalarField](innerCcs), + VerifyingKey: PlaceholderVerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl](innerCcs), + } + + assert.Run(func(assert *test.Assert) { + outerAssignment := &OuterCircuitIsValidWithSubgroupCheck[sw_bn254.ScalarField, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{ + InnerWitness: circuitWitness, + Proof: circuitProof, + VerifyingKey: circuitVk, + Res: 1, + } + err = test.IsSolved(outerCircuit, outerAssignment, ecc.BN254.ScalarField()) + assert.NoError(err) + }, "valid") +}