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 #1040

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
840a867
added circuit implementation of ExpandMsgXmd
weijiguo Jan 8, 2024
7fb1b95
implemented HashToG2 for bls12-381
weijiguo Jan 27, 2024
620585f
Merge branch 'Consensys:master' into feat/Hash2G2
weijiguo Jan 27, 2024
b0b11d2
revised G2.addUnified function based on the Brier and Joye algorithm
weijiguo Jan 28, 2024
3cf48fd
Merge pull request #2 from lightec-xyz/feat/Hash2G2
weijiguo Jan 28, 2024
b4da768
fixed G2.sgn0 function and associated unit tests for BLS12-381
weijiguo Jan 29, 2024
b6574fc
Merge pull request #4 from lightec-xyz/feat/Hash2G2
weijiguo Jan 29, 2024
86d12ce
implemented BLS signature verification for BLS12-381/G2
weijiguo Jan 29, 2024
78bdcf6
added benchmarks for HashToG2/BLS12-381
weijiguo Jan 30, 2024
04c1677
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Feb 10, 2024
8e2fc0e
gofmt
weijiguo Feb 10, 2024
bae5ff4
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Feb 14, 2024
4a2f9b0
golangci-lint
weijiguo Feb 19, 2024
50cca4b
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Feb 20, 2024
17fc881
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Feb 23, 2024
c60b775
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Mar 10, 2024
5719545
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Mar 12, 2024
3f6bbef
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Mar 21, 2024
8f38fba
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Mar 24, 2024
f0856e9
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo May 4, 2024
34a664c
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo May 16, 2024
a25777f
Merge branch 'master' into feat/BLSSigAndHashToG2
weijiguo Sep 27, 2024
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
38 changes: 38 additions & 0 deletions std/algebra/emulated/sw_bls12381/bls_sig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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))
if e != nil {
return e
}

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
Loading