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: added HashToG2 and BLS G2 signature verification circuit for BLS12-381 #1024

Closed
wants to merge 11 commits into from
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)

replace github.com/consensys/gnark-crypto => github.com/lightec-xyz/gnark-crypto v0.0.0-20240131005458-e9584bf28ef7
34 changes: 34 additions & 0 deletions std/algebra/emulated/sw_bls12381/bls_sig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package sw_bls12381

import (
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/math/uints"
)

const g2_dst = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"

func BlsAssertG2Verification(api frontend.API, pub G1Affine, sig G2Affine, msg []uints.U8) error {
pairing, e := NewPairing(api)
if e != nil {
return e
}

// public key cannot be infinity
xtest := pairing.g1.curveF.IsZero(&pub.X)
ytest := pairing.g1.curveF.IsZero(&pub.Y)
pubTest := api.Or(xtest, ytest)
api.AssertIsEqual(pubTest, 0)

// prime order subgroup checks
pairing.AssertIsOnG1(&pub)
pairing.AssertIsOnG2(&sig)

var g1GNeg bls12381.G1Affine
_, _, g1Gen, _ := bls12381.Generators()
g1GNeg.Neg(&g1Gen)
g1GN := NewG1Affine(g1GNeg)

h, e := HashToG2(api, msg, []byte(g2_dst))
return pairing.PairingCheck([]*G1Affine{&g1GN, &pub}, []*G2Affine{&sig, h})
}
83 changes: 83 additions & 0 deletions std/algebra/emulated/sw_bls12381/bls_sig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package sw_bls12381

import (
"encoding/hex"
"testing"

"github.com/consensys/gnark-crypto/ecc"
bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/math/uints"
"github.com/consensys/gnark/test"
)

type blsG2SigCircuit struct {
Pub bls12381.G1Affine
msg []byte
Sig bls12381.G2Affine
}

func (c *blsG2SigCircuit) Define(api frontend.API) error {
msg := uints.NewU8Array(c.msg)
return BlsAssertG2Verification(api, NewG1Affine(c.Pub), NewG2Affine(c.Sig), msg)
}

// "pubkey": "0xa491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a",
// "message": "0x5656565656565656565656565656565656565656565656565656565656565656",
// "signature": "0x882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb"},
// "output": true}
func TestBlsSigTestSolve(t *testing.T) {
assert := test.NewAssert(t)

msgHex := "5656565656565656565656565656565656565656565656565656565656565656"
pubHex := "a491d1b0ecd9bb917989f0e74f0dea0422eac4a873e5e2644f368dffb9a6e20fd6e10c1b77654d067c0618f6e5a7f79a"
sigHex := "882730e5d03f6b42c3abc26d3372625034e1d871b65a8a6b900a56dae22da98abbe1b68f85e49fe7652a55ec3d0591c20767677e33e5cbb1207315c41a9ac03be39c2e7668edc043d6cb1d9fd93033caa8a1c5b0e84bedaeb6c64972503a43eb"

msgBytes := make([]byte, len(msgHex)>>1)
hex.Decode(msgBytes, []byte(msgHex))
pubBytes := make([]byte, len(pubHex)>>1)
hex.Decode(pubBytes, []byte(pubHex))
sigBytes := make([]byte, len(sigHex)>>1)
hex.Decode(sigBytes, []byte(sigHex))

var pub bls12381.G1Affine
_, e := pub.SetBytes(pubBytes)
if e != nil {
t.Fail()
}
var sig bls12381.G2Affine
_, e = sig.SetBytes(sigBytes)
if e != nil {
t.Fail()
}

var g1GNeg bls12381.G1Affine
_, _, g1Gen, _ := bls12381.Generators()
g1GNeg.Neg(&g1Gen)

h, e := bls12381.HashToG2(msgBytes, []byte(g2_dst))
if e != nil {
t.Fail()
}

b, e := bls12381.PairingCheck([]bls12381.G1Affine{g1GNeg, pub}, []bls12381.G2Affine{sig, h})
if e != nil {
t.Fail()
}
if !b {
t.Fail() // invalid inputs, won't verify
}

circuit := blsG2SigCircuit{
Pub: pub,
msg: msgBytes,
Sig: sig,
}
witness := blsG2SigCircuit{
Pub: pub,
msg: msgBytes,
Sig: sig,
}
err := test.IsSolved(&circuit, &witness, ecc.BN254.ScalarField())
assert.NoError(err)
}
68 changes: 68 additions & 0 deletions std/algebra/emulated/sw_bls12381/g2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type G2 struct {
*fields_bls12381.Ext2
u1, w *emulated.Element[BaseField]
v *fields_bls12381.E2
api frontend.API
}

type g2AffP struct {
Expand Down Expand Up @@ -50,6 +51,7 @@ func NewG2(api frontend.API) *G2 {
w: &w,
u1: &u1,
v: &v,
api: api,
}
}

Expand Down Expand Up @@ -96,6 +98,18 @@ func (g2 *G2) psi(q *G2Affine) *G2Affine {
}
}

func (g2 *G2) psi2(q *G2Affine) *G2Affine {
x := g2.Ext2.MulByElement(&q.P.X, g2.w)
y := g2.Ext2.Neg(&q.P.Y)

return &G2Affine{
P: g2AffP{
X: *x,
Y: *y,
},
}
}

func (g2 *G2) scalarMulBySeed(q *G2Affine) *G2Affine {

z := g2.triple(q)
Expand Down Expand Up @@ -136,6 +150,60 @@ func (g2 G2) add(p, q *G2Affine) *G2Affine {
}
}

// Follow sw_emulated.Curve.AddUnified to implement the Brier and Joye algorithm
// to handle edge cases, i.e., p == q, p == 0 or/and q == 0
func (g2 G2) addUnified(p, q *G2Affine) *G2Affine {

// selector1 = 1 when p is (0,0) and 0 otherwise
selector1 := g2.api.And(g2.Ext2.IsZero(&p.P.X), g2.Ext2.IsZero(&p.P.Y))
// selector2 = 1 when q is (0,0) and 0 otherwise
selector2 := g2.api.And(g2.Ext2.IsZero(&q.P.X), g2.Ext2.IsZero(&q.P.Y))

// λ = ((p.x+q.x)² - p.x*q.x + a)/(p.y + q.y)
pxqx := g2.Ext2.Mul(&p.P.X, &q.P.X)
pxplusqx := g2.Ext2.Add(&p.P.X, &q.P.X)
num := g2.Ext2.Mul(pxplusqx, pxplusqx)
num = g2.Ext2.Sub(num, pxqx)
denum := g2.Ext2.Add(&p.P.Y, &q.P.Y)
// if p.y + q.y = 0, assign dummy 1 to denum and continue
selector3 := g2.Ext2.IsZero(denum)
denum = g2.Ext2.Select(selector3, g2.Ext2.One(), denum)
λ := g2.Ext2.DivUnchecked(num, denum) // we already know that denum won't be zero

// x = λ^2 - p.x - q.x
xr := g2.Ext2.Mul(λ, λ)
xr = g2.Ext2.Sub(xr, pxplusqx)

// y = λ(p.x - xr) - p.y
yr := g2.Ext2.Sub(&p.P.X, xr)
yr = g2.Ext2.Mul(yr, λ)
yr = g2.Ext2.Sub(yr, &p.P.Y)
result := &G2Affine{
P: g2AffP{
X: *xr,
Y: *yr,
},
}

zero := g2.Ext2.Zero()
// if p=(0,0) return q
resultX := *g2.Select(selector1, &q.P.X, &result.P.X)
resultY := *g2.Select(selector1, &q.P.Y, &result.P.Y)
// if q=(0,0) return p
resultX = *g2.Select(selector2, &p.P.X, &resultX)
resultY = *g2.Select(selector2, &p.P.Y, &resultY)
// if p.y + q.y = 0, return (0, 0)
resultX = *g2.Select(selector3, zero, &resultX)
resultY = *g2.Select(selector3, zero, &resultY)

return &G2Affine{
P: g2AffP{
X: resultX,
Y: resultY,
},
}
}

func (g2 G2) neg(p *G2Affine) *G2Affine {
xr := &p.P.X
yr := g2.Ext2.Neg(&p.P.Y)
Expand Down
110 changes: 110 additions & 0 deletions std/algebra/emulated/sw_bls12381/g2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,116 @@ func TestAddG2TestSolve(t *testing.T) {
assert.NoError(err)
}

func TestAddG2FailureCaseTestSolve(t *testing.T) {
assert := test.NewAssert(t)
_, in1 := randomG1G2Affines()
var res bls12381.G2Affine
res.Double(&in1)
witness := addG2Circuit{
In1: NewG2Affine(in1),
In2: NewG2Affine(in1),
Res: NewG2Affine(res),
}
err := test.IsSolved(&addG2Circuit{}, &witness, ecc.BN254.ScalarField())
// the add() function cannot handle identical inputs
assert.Error(err)
}

type addG2UnifiedCircuit struct {
In1, In2 G2Affine
Res G2Affine
}

func (c *addG2UnifiedCircuit) Define(api frontend.API) error {
g2 := NewG2(api)
res := g2.addUnified(&c.In1, &c.In2)
g2.AssertIsEqual(res, &c.Res)
return nil
}

func TestAddG2UnifiedTestSolveAdd(t *testing.T) {
assert := test.NewAssert(t)
_, in1 := randomG1G2Affines()
_, in2 := randomG1G2Affines()
var res bls12381.G2Affine
res.Add(&in1, &in2)
witness := addG2UnifiedCircuit{
In1: NewG2Affine(in1),
In2: NewG2Affine(in2),
Res: NewG2Affine(res),
}
err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField())
assert.NoError(err)
}

func TestAddG2UnifiedTestSolveDbl(t *testing.T) {
assert := test.NewAssert(t)
_, in1 := randomG1G2Affines()
var res bls12381.G2Affine
res.Double(&in1)
witness := addG2UnifiedCircuit{
In1: NewG2Affine(in1),
In2: NewG2Affine(in1),
Res: NewG2Affine(res),
}
err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField())
assert.NoError(err)
}

func TestAddG2UnifiedTestSolveEdgeCases(t *testing.T) {
assert := test.NewAssert(t)
_, p := randomG1G2Affines()
var np, zero bls12381.G2Affine
np.Neg(&p)
zero.Sub(&p, &p)

// p + (-p) == (0, 0)
witness := addG2UnifiedCircuit{
In1: NewG2Affine(p),
In2: NewG2Affine(np),
Res: NewG2Affine(zero),
}
err := test.IsSolved(&addG2UnifiedCircuit{}, &witness, ecc.BN254.ScalarField())
assert.NoError(err)

// (-p) + p == (0, 0)
witness2 := addG2UnifiedCircuit{
In1: NewG2Affine(np),
In2: NewG2Affine(p),
Res: NewG2Affine(zero),
}
err2 := test.IsSolved(&addG2UnifiedCircuit{}, &witness2, ecc.BN254.ScalarField())
assert.NoError(err2)

// p + (0, 0) == p
witness3 := addG2UnifiedCircuit{
In1: NewG2Affine(p),
In2: NewG2Affine(zero),
Res: NewG2Affine(p),
}
err3 := test.IsSolved(&addG2UnifiedCircuit{}, &witness3, ecc.BN254.ScalarField())
assert.NoError(err3)

// (0, 0) + p == p
witness4 := addG2UnifiedCircuit{
In1: NewG2Affine(zero),
In2: NewG2Affine(p),
Res: NewG2Affine(p),
}
err4 := test.IsSolved(&addG2UnifiedCircuit{}, &witness4, ecc.BN254.ScalarField())
assert.NoError(err4)

// (0, 0) + (0, 0) == (0, 0)
witness5 := addG2UnifiedCircuit{
In1: NewG2Affine(zero),
In2: NewG2Affine(zero),
Res: NewG2Affine(zero),
}
err5 := test.IsSolved(&addG2UnifiedCircuit{}, &witness5, ecc.BN254.ScalarField())
assert.NoError(err5)

}

type doubleG2Circuit struct {
In1 G2Affine
Res G2Affine
Expand Down
Loading