From 9628c92f51ff21b40bdaada88bf496ffcc4aad75 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 17 Sep 2024 00:02:04 +0200 Subject: [PATCH 1/2] Add gnark as bls-12-381 backend Signed-off-by: Jakub Sztandera --- pairing/bls12381/bls12381_test.go | 43 +++++++- pairing/bls12381/gnark/adapter.go | 48 +++++++++ pairing/bls12381/gnark/adapter_test.go | 28 +++++ pairing/bls12381/gnark/g1.go | 143 +++++++++++++++++++++++++ pairing/bls12381/gnark/g2.go | 143 +++++++++++++++++++++++++ pairing/bls12381/gnark/group.go | 23 ++++ pairing/bls12381/gnark/gt.go | 108 +++++++++++++++++++ pairing/bls12381/gnark/scalar.go | 109 +++++++++++++++++++ pairing/bls12381/gnark/suite.go | 83 ++++++++++++++ pairing/bls12381/gnark/suite_test.go | 49 +++++++++ sign/test/bls_test.go | 13 +++ suites/all.go | 2 + 12 files changed, 790 insertions(+), 2 deletions(-) create mode 100644 pairing/bls12381/gnark/adapter.go create mode 100644 pairing/bls12381/gnark/adapter_test.go create mode 100644 pairing/bls12381/gnark/g1.go create mode 100644 pairing/bls12381/gnark/g2.go create mode 100644 pairing/bls12381/gnark/group.go create mode 100644 pairing/bls12381/gnark/gt.go create mode 100644 pairing/bls12381/gnark/scalar.go create mode 100644 pairing/bls12381/gnark/suite.go create mode 100644 pairing/bls12381/gnark/suite_test.go diff --git a/pairing/bls12381/bls12381_test.go b/pairing/bls12381/bls12381_test.go index 46ec9eb6e..3a611cfb7 100644 --- a/pairing/bls12381/bls12381_test.go +++ b/pairing/bls12381/bls12381_test.go @@ -18,6 +18,7 @@ import ( "go.dedis.ch/kyber/v4/internal/test" "go.dedis.ch/kyber/v4/pairing" circl "go.dedis.ch/kyber/v4/pairing/bls12381/circl" + "go.dedis.ch/kyber/v4/pairing/bls12381/gnark" kilic "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" "go.dedis.ch/kyber/v4/sign/bls" "go.dedis.ch/kyber/v4/sign/tbls" @@ -38,6 +39,7 @@ func TestScalarEndianess(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } seed := "TestScalarEndianess" @@ -109,6 +111,16 @@ func TestZKCryptoVectorsG1Compressed(t *testing.T) { if err != nil && testCaseValid { panic("Circl: err should be nil") } + + // Test gnark + g3 := gnark.G1Elt{} + err = g3.UnmarshalBinary(byts) + if err == nil && !testCaseValid { + panic("Gnark: err should not be nil") + } + if err != nil && testCaseValid { + panic("Gnark: err should be nil") + } }) } } @@ -156,6 +168,16 @@ func TestZKCryptoVectorsG2Compressed(t *testing.T) { if err != nil && testCaseValid { panic("Circl: err should be nil") } + + // Test gnark + g3 := gnark.G2Elt{} + err = g3.UnmarshalBinary(byts) + if err == nil && !testCaseValid { + panic("Gnark: err should not be nil") + } + if err != nil && testCaseValid { + panic("Gnark: err should be nil") + } }) } } @@ -400,6 +422,7 @@ func TestKyberG1(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, suite := range suites { @@ -411,6 +434,7 @@ func TestKyberG2(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, suite := range suites { @@ -422,6 +446,7 @@ func TestKyberPairingG2(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, s := range suites { @@ -449,6 +474,7 @@ func TestRacePairings(_ *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, s := range suites { @@ -473,6 +499,7 @@ func TestKyberBLSG2(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, suite := range suites { @@ -485,6 +512,7 @@ func TestKyberBLSG1(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, suite := range suites { @@ -497,6 +525,7 @@ func TestKyberThresholdG2(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, suite := range suites { @@ -509,6 +538,7 @@ func TestKyberThresholdG1(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, suite := range suites { @@ -521,6 +551,7 @@ func TestIsValidGroup(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, suite := range suites { @@ -549,6 +580,7 @@ func TestBasicPairing(t *testing.T) { suites := []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, suite := range suites { @@ -601,6 +633,7 @@ func BenchmarkPairingSeparate(bb *testing.B) { var suites = []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, s := range suites { @@ -630,6 +663,7 @@ func BenchmarkPairingInv(bb *testing.B) { var suites = []pairing.Suite{ kilic.NewBLS12381Suite(), circl.NewSuiteBLS12381(), + gnark.NewSuiteBLS12381(), } for _, s := range suites { @@ -657,7 +691,7 @@ func BenchmarkPairingInv(bb *testing.B) { var ( dataSize = 32 numSigs = []int{1, 10, 100, 1000, 10000} - curveOptions = []string{"kilic", "circl"} + curveOptions = []string{"kilic", "circl", "gnark"} ) // Used to avoid compiler optimizations @@ -671,6 +705,9 @@ func BenchmarkKilic(b *testing.B) { func BenchmarkCircl(b *testing.B) { BLSBenchmark(b, "circl") } +func BenchmarkGnark(b *testing.B) { + BLSBenchmark(b, "gnark") +} func BLSBenchmark(b *testing.B, curveOption string) { b.Logf("----------------------") @@ -696,6 +733,8 @@ func BLSBenchmark(b *testing.B, curveOption string) { suite = kilic.NewBLS12381Suite() case "circl": suite = circl.NewSuiteBLS12381() + case "gnark": + suite = gnark.NewSuiteBLS12381() default: panic(fmt.Errorf("invalid curve option: %s", curveOption)) } @@ -752,7 +791,7 @@ func BLSBenchmark(b *testing.B, curveOption string) { } } }) - b.Run(fmt.Sprintf("AggregateSign-G1 on %d signs", n), func(bb *testing.B) { + b.Run(fmt.Sprintf("AggregateSign-G2 on %d signs", n), func(bb *testing.B) { for j := 0; j < bb.N; j++ { result, err = schemeOnG2.AggregateSignatures(sigsOnG2[:n]...) if err != nil { diff --git a/pairing/bls12381/gnark/adapter.go b/pairing/bls12381/gnark/adapter.go new file mode 100644 index 000000000..cca42e0bc --- /dev/null +++ b/pairing/bls12381/gnark/adapter.go @@ -0,0 +1,48 @@ +package gnark + +import ( + "go.dedis.ch/kyber/v4" +) + +// SuiteBLS12381 is an adapter that implements the suites.Suite interface so that +// bls12381 can be used as a common suite to generate key pairs for instance but +// still preserves the properties of the pairing (e.g. the Pair function). +// +// It's important to note that the Point function will generate a point +// compatible with public keys only (group G2) where the signature must be +// used as a point from the group G1. +type SuiteBLS12381 struct { + Suite + kyber.Group +} + +// NewSuiteBLS12381 makes a new BN256 suite +func NewSuiteBLS12381() *SuiteBLS12381 { + return &SuiteBLS12381{} +} + +// Point generates a point from the G2 group that can only be used +// for public keys +func (s *SuiteBLS12381) Point() kyber.Point { + return s.G2().Point() +} + +// PointLen returns the length of a G2 point +func (s *SuiteBLS12381) PointLen() int { + return s.G2().PointLen() +} + +// Scalar generates a scalar +func (s *SuiteBLS12381) Scalar() kyber.Scalar { + return s.G1().Scalar() +} + +// ScalarLen returns the length of a scalar +func (s *SuiteBLS12381) ScalarLen() int { + return s.G1().ScalarLen() +} + +// String returns the name of the suite +func (s *SuiteBLS12381) String() string { + return "gnark.adapter" +} diff --git a/pairing/bls12381/gnark/adapter_test.go b/pairing/bls12381/gnark/adapter_test.go new file mode 100644 index 000000000..1e457aeba --- /dev/null +++ b/pairing/bls12381/gnark/adapter_test.go @@ -0,0 +1,28 @@ +package gnark + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.dedis.ch/kyber/v4/util/key" +) + +func TestAdapter_SuiteBLS12381(t *testing.T) { + suite := NewSuiteBLS12381() + + pair := key.NewKeyPair(suite) + pubkey, err := pair.Public.MarshalBinary() + require.Nil(t, err) + privkey, err := pair.Private.MarshalBinary() + require.Nil(t, err) + + pubhex := suite.Point() + err = pubhex.UnmarshalBinary(pubkey) + require.Nil(t, err) + + privhex := suite.Scalar() + err = privhex.UnmarshalBinary(privkey) + require.Nil(t, err) + + require.Equal(t, "gnark.adapter", suite.String()) +} diff --git a/pairing/bls12381/gnark/g1.go b/pairing/bls12381/gnark/g1.go new file mode 100644 index 000000000..94e38a9d2 --- /dev/null +++ b/pairing/bls12381/gnark/g1.go @@ -0,0 +1,143 @@ +//nolint:dupl // unavoidable duplication between g1 and g2 +package gnark + +import ( + "crypto/cipher" + "fmt" + "io" + "math/big" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "go.dedis.ch/kyber/v4" +) + +var _ kyber.SubGroupElement = &G1Elt{} + +// G1Elt is a wrapper around a G1 point on the BLS12-381 Gnark curve. +type G1Elt struct{ inner bls12381.G1Jac } + +// MarshalBinary returns a compressed point, without any domain separation tag information +func (p *G1Elt) MarshalBinary() (data []byte, err error) { + var g1aff bls12381.G1Affine + g1aff.FromJacobian(&p.inner) + res := g1aff.Bytes() + return res[:], nil +} + +// UnmarshalBinary populates the point from a compressed point representation. +func (p *G1Elt) UnmarshalBinary(data []byte) error { + var g1aff bls12381.G1Affine + _, err := g1aff.SetBytes(data) + if err != nil { + return fmt.Errorf("setting affine representation: %w", err) + } + + p.inner.FromAffine(&g1aff) + return nil +} + +func (p *G1Elt) String() string { return p.inner.String() } + +func (p *G1Elt) MarshalSize() int { return bls12381.SizeOfG1AffineCompressed } + +// MarshalTo writes a compressed point to the Writer, without any domain separation tag information +func (p *G1Elt) MarshalTo(w io.Writer) (int, error) { + buf, err := p.MarshalBinary() + if err != nil { + return 0, err + } + return w.Write(buf) +} + +// UnmarshalFrom populates the point from a compressed point representation read from the Reader. +func (p *G1Elt) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, p.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + return n, p.UnmarshalBinary(buf) +} + +func (p *G1Elt) Equal(p2 kyber.Point) bool { x := p2.(*G1Elt); return p.inner.Equal(&x.inner) } + +func (p *G1Elt) Null() kyber.Point { + p.inner.X.SetZero() + p.inner.Y.SetOne() + p.inner.Z.SetZero() + return p +} + +func (p *G1Elt) Base() kyber.Point { + p.inner, _, _, _ = bls12381.Generators() + return p +} + +func (p *G1Elt) Pick(rand cipher.Stream) kyber.Point { + var buf [32]byte + rand.XORKeyStream(buf[:], buf[:]) + return p.Hash(buf[:]) +} + +func (p *G1Elt) Set(p2 kyber.Point) kyber.Point { p.inner = p2.(*G1Elt).inner; return p } + +func (p *G1Elt) Clone() kyber.Point { return new(G1Elt).Set(p) } + +func (p *G1Elt) EmbedLen() int { + panic("bls12-381: unsupported operation") +} + +func (p *G1Elt) Embed(_ []byte, _ cipher.Stream) kyber.Point { + panic("bls12-381: unsupported operation") +} + +func (p *G1Elt) Data() ([]byte, error) { + panic("bls12-381: unsupported operation") +} + +func (p *G1Elt) Add(a, b kyber.Point) kyber.Point { + aa, bb := a.(*G1Elt), b.(*G1Elt) + p.inner.Set(&aa.inner) + p.inner.AddAssign(&bb.inner) + return p +} + +func (p *G1Elt) Sub(a, b kyber.Point) kyber.Point { + aa, bb := a.(*G1Elt), b.(*G1Elt) + p.inner.Set(&aa.inner) + p.inner.SubAssign(&bb.inner) + return p +} + +func (p *G1Elt) Neg(a kyber.Point) kyber.Point { + p.inner.Neg(&a.(*G1Elt).inner) + return p +} + +func (p *G1Elt) Mul(s kyber.Scalar, q kyber.Point) kyber.Point { + if q == nil { + q = new(G1Elt).Base() + } + ss, qq := s.(*Scalar), q.(*G1Elt) + var scalar big.Int + ss.inner.BigInt(&scalar) + p.inner.ScalarMultiplication(&qq.inner, &scalar) + return p +} + +func (p *G1Elt) IsInCorrectGroup() bool { + return !(p.inner.X.IsZero() && p.inner.Y.IsZero() && p.inner.X.IsZero()) && + p.inner.IsOnCurve() && p.inner.IsInSubGroup() +} + +var domainG1 = []byte("BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_") + +func (p *G1Elt) Hash(msg []byte) kyber.Point { return p.Hash2(msg, domainG1) } +func (p *G1Elt) Hash2(msg, dst []byte) kyber.Point { + g1aff, err := bls12381.HashToG1(msg, dst) + if err != nil { + panic(fmt.Errorf("error while hashing: %w", err)) + } + p.inner.FromAffine(&g1aff) + return p +} diff --git a/pairing/bls12381/gnark/g2.go b/pairing/bls12381/gnark/g2.go new file mode 100644 index 000000000..97f7ec889 --- /dev/null +++ b/pairing/bls12381/gnark/g2.go @@ -0,0 +1,143 @@ +//nolint:dupl // unavoidable duplication between g1 and g2 +package gnark + +import ( + "crypto/cipher" + "fmt" + "io" + "math/big" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "go.dedis.ch/kyber/v4" +) + +var _ kyber.SubGroupElement = &G2Elt{} + +// G2Elt is a wrapper around the Gnark G2 point type. +type G2Elt struct{ inner bls12381.G2Jac } + +// MarshalBinary returns a compressed point, without any domain separation tag information +func (p *G2Elt) MarshalBinary() (data []byte, err error) { + var g2aff bls12381.G2Affine + g2aff.FromJacobian(&p.inner) + res := g2aff.Bytes() + return res[:], nil +} + +// UnmarshalBinary populates the point from a compressed point representation. +func (p *G2Elt) UnmarshalBinary(data []byte) error { + var g2aff bls12381.G2Affine + _, err := g2aff.SetBytes(data) + if err != nil { + return fmt.Errorf("setting affine representation: %w", err) + } + + p.inner.FromAffine(&g2aff) + return nil +} + +func (p *G2Elt) String() string { return p.inner.String() } + +func (p *G2Elt) MarshalSize() int { return bls12381.SizeOfG2AffineCompressed } + +// MarshalTo writes a compressed point to the Writer, without any domain separation tag information +func (p *G2Elt) MarshalTo(w io.Writer) (int, error) { + buf, err := p.MarshalBinary() + if err != nil { + return 0, err + } + return w.Write(buf) +} + +// UnmarshalFrom populates the point from a compressed point representation read from the Reader. +func (p *G2Elt) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, p.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + return n, p.UnmarshalBinary(buf) +} + +func (p *G2Elt) Equal(p2 kyber.Point) bool { x := p2.(*G2Elt); return p.inner.Equal(&x.inner) } + +func (p *G2Elt) Null() kyber.Point { + p.inner.X.SetZero() + p.inner.Y.SetOne() + p.inner.Z.SetZero() + return p +} + +func (p *G2Elt) Base() kyber.Point { + _, p.inner, _, _ = bls12381.Generators() + return p +} + +func (p *G2Elt) Pick(rand cipher.Stream) kyber.Point { + var buf [32]byte + rand.XORKeyStream(buf[:], buf[:]) + return p.Hash(buf[:]) +} + +func (p *G2Elt) Set(p2 kyber.Point) kyber.Point { p.inner = p2.(*G2Elt).inner; return p } + +func (p *G2Elt) Clone() kyber.Point { return new(G2Elt).Set(p) } + +func (p *G2Elt) EmbedLen() int { + panic("bls12-381: unsupported operation") +} + +func (p *G2Elt) Embed(_ []byte, _ cipher.Stream) kyber.Point { + panic("bls12-381: unsupported operation") +} + +func (p *G2Elt) Data() ([]byte, error) { + panic("bls12-381: unsupported operation") +} + +func (p *G2Elt) Add(a, b kyber.Point) kyber.Point { + aa, bb := a.(*G2Elt), b.(*G2Elt) + p.inner.Set(&aa.inner) + p.inner.AddAssign(&bb.inner) + return p +} + +func (p *G2Elt) Sub(a, b kyber.Point) kyber.Point { + aa, bb := a.(*G2Elt), b.(*G2Elt) + p.inner.Set(&aa.inner) + p.inner.SubAssign(&bb.inner) + return p +} + +func (p *G2Elt) Neg(a kyber.Point) kyber.Point { + p.inner.Neg(&a.(*G2Elt).inner) + return p +} + +func (p *G2Elt) Mul(s kyber.Scalar, q kyber.Point) kyber.Point { + if q == nil { + q = new(G2Elt).Base() + } + ss, qq := s.(*Scalar), q.(*G2Elt) + var scalar big.Int + ss.inner.BigInt(&scalar) + p.inner.ScalarMultiplication(&qq.inner, &scalar) + return p +} + +func (p *G2Elt) IsInCorrectGroup() bool { + return !(p.inner.X.IsZero() && p.inner.Y.IsZero() && p.inner.X.IsZero()) && + p.inner.IsOnCurve() && p.inner.IsInSubGroup() +} + +var domainG2 = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_") + +func (p *G2Elt) Hash(msg []byte) kyber.Point { return p.Hash2(msg, domainG2) } +func (p *G2Elt) Hash2(msg, dst []byte) kyber.Point { + g1aff, err := bls12381.HashToG2(msg, dst) + if err != nil { + panic(fmt.Errorf("error while hashing: %w", err)) + } + p.inner.FromAffine(&g1aff) + return p +} diff --git a/pairing/bls12381/gnark/group.go b/pairing/bls12381/gnark/group.go new file mode 100644 index 000000000..e08a4cf95 --- /dev/null +++ b/pairing/bls12381/gnark/group.go @@ -0,0 +1,23 @@ +package gnark + +import ( + fr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "go.dedis.ch/kyber/v4" +) + +var ( + G1 kyber.Group = &groupBls{name: "bls12-381.G1", newPoint: func() kyber.Point { return new(G1Elt).Null() }} + G2 kyber.Group = &groupBls{name: "bls12-381.G2", newPoint: func() kyber.Point { return new(G2Elt).Null() }} + GT kyber.Group = &groupBls{name: "bls12-381.GT", newPoint: func() kyber.Point { return new(GTElt).Null() }} +) + +type groupBls struct { + name string + newPoint func() kyber.Point +} + +func (g groupBls) String() string { return g.name } +func (g groupBls) ScalarLen() int { return fr.Bytes } +func (g groupBls) Scalar() kyber.Scalar { return new(Scalar).SetInt64(0) } +func (g groupBls) PointLen() int { return g.newPoint().MarshalSize() } +func (g groupBls) Point() kyber.Point { return g.newPoint() } diff --git a/pairing/bls12381/gnark/gt.go b/pairing/bls12381/gnark/gt.go new file mode 100644 index 000000000..13f684bca --- /dev/null +++ b/pairing/bls12381/gnark/gt.go @@ -0,0 +1,108 @@ +package gnark + +import ( + "crypto/cipher" + "io" + "math/big" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "go.dedis.ch/kyber/v4" +) + +var gtBase *bls12381.GT + +func init() { + _, _, g1, g2 := bls12381.Generators() + gt, err := bls12381.Pair([]bls12381.G1Affine{g1}, []bls12381.G2Affine{g2}) + if err != nil { + panic(err) + } + gtBase = > +} + +var _ kyber.Point = >Elt{} + +// GTElt is a wrapper around the Circl Gt point type. +type GTElt struct{ inner bls12381.GT } + +// MarshalBinary returns a compressed point, without any domain separation tag information +func (p *GTElt) MarshalBinary() (data []byte, err error) { + res := p.inner.Bytes() + return res[:], nil +} + +// UnmarshalBinary populates the point from a compressed point representation. +func (p *GTElt) UnmarshalBinary(data []byte) error { return p.inner.Unmarshal(data) } + +func (p *GTElt) String() string { return p.inner.String() } + +func (p *GTElt) MarshalSize() int { return bls12381.SizeOfGT } + +// MarshalTo writes a compressed point to the Writer, without any domain separation tag information +func (p *GTElt) MarshalTo(w io.Writer) (int, error) { + buf, err := p.MarshalBinary() + if err != nil { + return 0, err + } + return w.Write(buf) +} + +// UnmarshalFrom populates the point from a compressed point representation read from the Reader. +func (p *GTElt) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, p.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + return n, p.UnmarshalBinary(buf) +} + +func (p *GTElt) Equal(p2 kyber.Point) bool { x := p2.(*GTElt); return p.inner.Equal(&x.inner) } + +func (p *GTElt) Null() kyber.Point { p.inner.SetOne(); return p } + +func (p *GTElt) Base() kyber.Point { p.inner = *gtBase; return p } + +func (p *GTElt) Pick(_ cipher.Stream) kyber.Point { + panic("bls12-381: unsupported operation") +} + +func (p *GTElt) Set(p2 kyber.Point) kyber.Point { p.inner = p2.(*GTElt).inner; return p } + +func (p *GTElt) Clone() kyber.Point { return new(GTElt).Set(p) } + +func (p *GTElt) EmbedLen() int { + panic("bls12-381: unsupported operation") +} + +func (p *GTElt) Embed(_ []byte, _ cipher.Stream) kyber.Point { + panic("bls12-381: unsupported operation") +} + +func (p *GTElt) Data() ([]byte, error) { + panic("bls12-381: unsupported operation") +} + +func (p *GTElt) Add(a, b kyber.Point) kyber.Point { + aa, bb := a.(*GTElt), b.(*GTElt) + p.inner.Mul(&aa.inner, &bb.inner) + return p +} + +func (p *GTElt) Sub(a, b kyber.Point) kyber.Point { + return p.Add(a, new(GTElt).Neg(b)) +} + +func (p *GTElt) Neg(a kyber.Point) kyber.Point { + aa := a.(*GTElt) + p.inner.Inverse(&aa.inner) + return p +} + +func (p *GTElt) Mul(s kyber.Scalar, q kyber.Point) kyber.Point { + qq, ss := q.(*GTElt), s.(*Scalar) + var scalar big.Int + ss.inner.BigInt(&scalar) + p.inner.Exp(qq.inner, &scalar) + return p +} diff --git a/pairing/bls12381/gnark/scalar.go b/pairing/bls12381/gnark/scalar.go new file mode 100644 index 000000000..38047b52c --- /dev/null +++ b/pairing/bls12381/gnark/scalar.go @@ -0,0 +1,109 @@ +package gnark + +import ( + "crypto/cipher" + "io" + "math/big" + + fr "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/util/random" +) + +var _ kyber.Scalar = &Scalar{} + +type Scalar struct{ inner fr.Element } + +func (s *Scalar) MarshalBinary() (data []byte, err error) { res := s.inner.Bytes(); return res[:], nil } + +func (s *Scalar) UnmarshalBinary(data []byte) error { s.inner.SetBytes(data); return nil } + +func (s *Scalar) String() string { return s.inner.String() } + +func (s *Scalar) MarshalSize() int { return fr.Bytes } + +func (s *Scalar) MarshalTo(w io.Writer) (int, error) { + buf := s.inner.Bytes() + return w.Write(buf[:]) +} + +func (s *Scalar) UnmarshalFrom(r io.Reader) (int, error) { + buf := make([]byte, s.MarshalSize()) + n, err := io.ReadFull(r, buf) + if err != nil { + return n, err + } + s.inner.SetBytes(buf) + return n, nil +} + +func (s *Scalar) Equal(s2 kyber.Scalar) bool { + x := s2.(*Scalar) + return s.inner.Cmp(&x.inner) == 0 +} + +func (s *Scalar) Set(a kyber.Scalar) kyber.Scalar { + aa := a.(*Scalar) + s.inner.Set(&aa.inner) + return s +} + +func (s *Scalar) Clone() kyber.Scalar { return new(Scalar).Set(s) } + +func (s *Scalar) SetInt64(v int64) kyber.Scalar { + s.inner.SetInt64(v) + + return s +} + +func (s *Scalar) Zero() kyber.Scalar { s.inner.SetUint64(0); return s } + +func (s *Scalar) Add(a, b kyber.Scalar) kyber.Scalar { + aa, bb := a.(*Scalar), b.(*Scalar) + s.inner.Add(&aa.inner, &bb.inner) + return s +} + +func (s *Scalar) Sub(a, b kyber.Scalar) kyber.Scalar { + aa, bb := a.(*Scalar), b.(*Scalar) + s.inner.Sub(&aa.inner, &bb.inner) + return s +} + +func (s *Scalar) Neg(a kyber.Scalar) kyber.Scalar { + s.Set(a) + s.inner.Neg(&s.inner) + return s +} + +func (s *Scalar) One() kyber.Scalar { s.inner.SetUint64(1); return s } + +func (s *Scalar) Mul(a, b kyber.Scalar) kyber.Scalar { + aa, bb := a.(*Scalar), b.(*Scalar) + s.inner.Mul(&aa.inner, &bb.inner) + return s +} + +func (s *Scalar) Div(a, b kyber.Scalar) kyber.Scalar { return s.Mul(new(Scalar).Inv(b), a) } + +func (s *Scalar) Inv(a kyber.Scalar) kyber.Scalar { + aa := a.(*Scalar) + s.inner.Inverse(&aa.inner) + return s +} + +func (s *Scalar) Pick(stream cipher.Stream) kyber.Scalar { + n := random.Int(fr.Modulus(), stream) + s.inner.SetBigInt(n) + return s +} + +func (s *Scalar) SetBytes(data []byte) kyber.Scalar { s.inner.SetBytes(data); return s } + +func (s *Scalar) ByteOrder() kyber.ByteOrder { + return kyber.BigEndian +} + +func (s *Scalar) GroupOrder() *big.Int { + return fr.Modulus() +} diff --git a/pairing/bls12381/gnark/suite.go b/pairing/bls12381/gnark/suite.go new file mode 100644 index 000000000..f500f33bc --- /dev/null +++ b/pairing/bls12381/gnark/suite.go @@ -0,0 +1,83 @@ +package gnark + +import ( + "crypto/cipher" + "crypto/sha256" + "fmt" + "hash" + "io" + + bls12381 "github.com/consensys/gnark-crypto/ecc/bls12-381" + "go.dedis.ch/kyber/v4" + "go.dedis.ch/kyber/v4/pairing" + "go.dedis.ch/kyber/v4/util/random" + "go.dedis.ch/kyber/v4/xof/blake2xb" +) + +var _ pairing.Suite = Suite{} + +type Suite struct{} + +func NewSuite() (s Suite) { return } + +func (s Suite) String() string { return "bls12381" } +func (s Suite) G1() kyber.Group { return G1 } +func (s Suite) G2() kyber.Group { return G2 } +func (s Suite) GT() kyber.Group { return GT } + +func (s Suite) Pair(p1, p2 kyber.Point) kyber.Point { + aa, bb := p1.(*G1Elt), p2.(*G2Elt) + var g1aff bls12381.G1Affine + g1aff.FromJacobian(&aa.inner) + var g2aff bls12381.G2Affine + g2aff.FromJacobian(&bb.inner) + gt, err := bls12381.Pair([]bls12381.G1Affine{g1aff}, []bls12381.G2Affine{g2aff}) + if err != nil { + panic(fmt.Errorf("error in gnark pairing: %w", err)) + } + + return >Elt{gt} +} + +func (s Suite) ValidatePairing(p1, p2, p3, p4 kyber.Point) bool { + a, b := p1.(*G1Elt), p2.(*G2Elt) + c, d := p3.(*G1Elt), p4.(*G2Elt) + + var aAff, cAff bls12381.G1Affine + var bAff, dAff bls12381.G2Affine + aAff.FromJacobian(&a.inner) + bAff.FromJacobian(&b.inner) + cAff.FromJacobian(&c.inner) + dAff.FromJacobian(&d.inner) + + cAff.Neg(&cAff) + + out, err := bls12381.PairingCheck( + []bls12381.G1Affine{aAff, cAff}, + []bls12381.G2Affine{bAff, dAff}, + ) + if err != nil { + panic(fmt.Errorf("error in gnark pairing: %w", err)) + } + return out +} + +func (s Suite) Read(_ io.Reader, _ ...interface{}) error { + panic("Suite.Read(): deprecated in drand") +} + +func (s Suite) Write(_ io.Writer, _ ...interface{}) error { + panic("Suite.Write(): deprecated in drand") +} + +func (s Suite) Hash() hash.Hash { + return sha256.New() +} + +func (s Suite) XOF(seed []byte) kyber.XOF { + return blake2xb.New(seed) +} + +func (s Suite) RandomStream() cipher.Stream { + return random.New() +} diff --git a/pairing/bls12381/gnark/suite_test.go b/pairing/bls12381/gnark/suite_test.go new file mode 100644 index 000000000..532441b2c --- /dev/null +++ b/pairing/bls12381/gnark/suite_test.go @@ -0,0 +1,49 @@ +package gnark + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "testing" + + "go.dedis.ch/kyber/v4/pairing" + + "go.dedis.ch/kyber/v4" +) + +func TestVerifySigOnG2(t *testing.T) { + pk := "868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31" + sig := "8d61d9100567de44682506aea1a7a6fa6e5491cd27a0a0ed349ef6910ac5ac20ff7bc3e09d7c046566c9f7f3c6f3b10104990e7cb424998203d8f7de586fb7fa5f60045417a432684f85093b06ca91c769f0e7ca19268375e659c2a2352b4655" + prevSig := "176f93498eac9ca337150b46d21dd58673ea4e3581185f869672e59fa4cb390a" + round := uint64(1) + + suite := NewSuite() + pkb, _ := hex.DecodeString(pk) + pubkeyP := suite.G1().Point() + pubkeyP.UnmarshalBinary(pkb) + sigb, _ := hex.DecodeString(sig) + sigP := suite.G2().Point() + sigP.UnmarshalBinary(sigb) + prev, _ := hex.DecodeString(prevSig) + h := sha256.New() + h.Write(prev) + _ = binary.Write(h, binary.BigEndian, round) + msg := h.Sum(nil) + + base := suite.G1().Point().Base().Clone() + MsgP := suite.G2().Point().(kyber.HashablePoint).Hash(msg) + if !suite.ValidatePairing(base, sigP, pubkeyP, MsgP) { + t.Fatalf("Error validating pairing") + } +} + +func TestImplementInterfaces(_ *testing.T) { + var _ kyber.Point = &G1Elt{} + var _ kyber.Point = &G2Elt{} + var _ kyber.Point = >Elt{} + var _ kyber.HashablePoint = &G1Elt{} + var _ kyber.HashablePoint = &G2Elt{} + // var _ kyber.hashablePoint = &KyberGT{} // GT is not hashable for now + var _ kyber.Group = &groupBls{} + var _ pairing.Suite = &Suite{} +} diff --git a/sign/test/bls_test.go b/sign/test/bls_test.go index 428bc9421..c2b5f17cb 100644 --- a/sign/test/bls_test.go +++ b/sign/test/bls_test.go @@ -5,6 +5,7 @@ import ( "go.dedis.ch/kyber/v4/internal/test" circl "go.dedis.ch/kyber/v4/pairing/bls12381/circl" + "go.dedis.ch/kyber/v4/pairing/bls12381/gnark" kilic "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" sign "go.dedis.ch/kyber/v4/sign/bls" ) @@ -20,3 +21,15 @@ func TestKilicBLS12381(t *testing.T) { scheme := sign.NewSchemeOnG2(suite) test.SchemeTesting(t, scheme) } + +func TestGnarkBLS12381G1(t *testing.T) { + suite := gnark.NewSuiteBLS12381() + scheme := sign.NewSchemeOnG1(suite) + test.SchemeTesting(t, scheme) +} + +func TestGnarkBLS12381G2(t *testing.T) { + suite := gnark.NewSuiteBLS12381() + scheme := sign.NewSchemeOnG2(suite) + test.SchemeTesting(t, scheme) +} diff --git a/suites/all.go b/suites/all.go index 02a65811b..493a22ae2 100644 --- a/suites/all.go +++ b/suites/all.go @@ -4,6 +4,7 @@ import ( "go.dedis.ch/kyber/v4/group/edwards25519" "go.dedis.ch/kyber/v4/group/p256" "go.dedis.ch/kyber/v4/pairing/bls12381/circl" + "go.dedis.ch/kyber/v4/pairing/bls12381/gnark" "go.dedis.ch/kyber/v4/pairing/bls12381/kilic" "go.dedis.ch/kyber/v4/pairing/bn254" "go.dedis.ch/kyber/v4/pairing/bn256" @@ -21,6 +22,7 @@ func init() { register(bn254.NewSuite()) register(circl.NewSuiteBLS12381()) register(kilic.NewSuiteBLS12381()) + register(gnark.NewSuiteBLS12381()) // This is a constant time implementation that should be // used as much as possible register(edwards25519.NewBlakeSHA256Ed25519()) From 1d434f5f219478bbf43cde7ea946230907392dba Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 20 Sep 2024 16:22:30 +0200 Subject: [PATCH 2/2] Fix lint issues and disable errcheck in bls12381 Errcheck was already not checking other impls Signed-off-by: Jakub Sztandera --- .golangci.yml | 2 +- pairing/bls12381/gnark/g1.go | 1 - pairing/bls12381/gnark/g2.go | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 28d5417d2..b7b3548d7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -277,7 +277,7 @@ issues: - path: "group/edwards25519/scalar.go" linters: - ineffassign - - path: "pairing/(circl_bls12381|bn254)/." + - path: "pairing/(bls12381|bn254)/." linters: - errcheck #TODO: proper error handling text: "Error return value is not checked" diff --git a/pairing/bls12381/gnark/g1.go b/pairing/bls12381/gnark/g1.go index 94e38a9d2..a40414625 100644 --- a/pairing/bls12381/gnark/g1.go +++ b/pairing/bls12381/gnark/g1.go @@ -1,4 +1,3 @@ -//nolint:dupl // unavoidable duplication between g1 and g2 package gnark import ( diff --git a/pairing/bls12381/gnark/g2.go b/pairing/bls12381/gnark/g2.go index 97f7ec889..48f6a291b 100644 --- a/pairing/bls12381/gnark/g2.go +++ b/pairing/bls12381/gnark/g2.go @@ -1,4 +1,3 @@ -//nolint:dupl // unavoidable duplication between g1 and g2 package gnark import (