Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions std/algebra/emulated/sw_bls12381/pairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions std/algebra/emulated/sw_bn254/pairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 29 additions & 6 deletions std/algebra/emulated/sw_bw6761/pairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down
7 changes: 6 additions & 1 deletion std/algebra/emulated/sw_emulated/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions std/algebra/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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.
Expand Down
45 changes: 39 additions & 6 deletions std/algebra/native/sw_bls12377/pairing2.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand Down
31 changes: 24 additions & 7 deletions std/commitments/pedersen/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}

Expand All @@ -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.
Expand All @@ -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
21 changes: 10 additions & 11 deletions std/recursion/groth16/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)...)
Expand All @@ -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")
Expand All @@ -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
Expand Down
Loading