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

pairing: Adds support for BLS12381 using CIRCL library #49

Merged
merged 3 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/drand/kyber
go 1.18

require (
github.com/cloudflare/circl v1.3.2
github.com/drand/kyber-bls12381 v0.2.5
github.com/jonboulle/clockwork v0.3.0
github.com/stretchr/testify v1.8.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/cloudflare/circl v1.3.2 h1:VWp8dY3yH69fdM7lM6A1+NhhVoDu9vqK0jOgmkQHFWk=
github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
48 changes: 48 additions & 0 deletions pairing/bls12381/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package bls12381

import (
"github.com/drand/kyber"
)

// 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 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by the need for this struct - why do you have it ? Why not just "Suite" is enough ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is enough, I was just mimicking what is already done in the other pairing package.

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 lenght 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 "bls12381.adapter"
}
28 changes: 28 additions & 0 deletions pairing/bls12381/adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package bls12381

import (
"testing"

"github.com/drand/kyber/util/key"
"github.com/stretchr/testify/require"
)

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, "bls12381.adapter", suite.String())
}
95 changes: 95 additions & 0 deletions pairing/bls12381/g1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package bls12381

import (
"crypto/cipher"
"io"

circl "github.com/cloudflare/circl/ecc/bls12381"
"github.com/drand/kyber"
)

var _ kyber.SubGroupElement = &G1Elt{}

type G1Elt struct{ inner circl.G1 }

func (p *G1Elt) MarshalBinary() (data []byte, err error) { return p.inner.BytesCompressed(), nil }

func (p *G1Elt) UnmarshalBinary(data []byte) error { return p.inner.SetBytes(data) }

func (p *G1Elt) String() string { return p.inner.String() }

func (p *G1Elt) MarshalSize() int { return circl.G1SizeCompressed }

func (p *G1Elt) MarshalTo(w io.Writer) (int, error) {
buf, err := p.MarshalBinary()
if err != nil {
return 0, err
}
return w.Write(buf)
}

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.IsEqual(&x.inner) }

func (p *G1Elt) Null() kyber.Point { p.inner.SetIdentity(); return p }

func (p *G1Elt) Base() kyber.Point { p.inner = *circl.G1Generator(); return p }

func (p *G1Elt) Pick(rand cipher.Stream) kyber.Point {
var buf [32]byte
rand.XORKeyStream(buf[:], buf[:])
p.inner.Hash(buf[:], nil)
return p
}

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(data []byte, r 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.Add(&aa.inner, &bb.inner)
return p
}

func (p *G1Elt) Sub(a, b kyber.Point) kyber.Point { return p.Add(a, new(G1Elt).Neg(b)) }

func (p *G1Elt) Neg(a kyber.Point) kyber.Point {
p.Set(a)
p.inner.Neg()
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)
p.inner.ScalarMult(&ss.inner, &qq.inner)
return p
}

func (p *G1Elt) IsInCorrectGroup() bool { return p.inner.IsOnG1() }

func (p *G1Elt) Hash(msg []byte) kyber.Point { p.inner.Hash(msg, nil); return p }
func (p *G1Elt) Hash2(msg, dst []byte) kyber.Point { p.inner.Hash(msg, dst); return p }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to make that generic, could we put the "dst" as a field in the struct, to avoid having multiple hash functions ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once #50 get fixed, there will be only one (correct) API for hashes.

95 changes: 95 additions & 0 deletions pairing/bls12381/g2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package bls12381

import (
"crypto/cipher"
"io"

circl "github.com/cloudflare/circl/ecc/bls12381"
"github.com/drand/kyber"
)

var _ kyber.SubGroupElement = &G2Elt{}

type G2Elt struct{ inner circl.G2 }

func (p *G2Elt) MarshalBinary() (data []byte, err error) { return p.inner.BytesCompressed(), nil }

func (p *G2Elt) UnmarshalBinary(data []byte) error { return p.inner.SetBytes(data) }

func (p *G2Elt) String() string { return p.inner.String() }

func (p *G2Elt) MarshalSize() int { return circl.G2SizeCompressed }

func (p *G2Elt) MarshalTo(w io.Writer) (int, error) {
buf, err := p.MarshalBinary()
if err != nil {
return 0, err
}
return w.Write(buf)
}

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.IsEqual(&x.inner) }

func (p *G2Elt) Null() kyber.Point { p.inner.SetIdentity(); return p }

func (p *G2Elt) Base() kyber.Point { p.inner = *circl.G2Generator(); return p }

func (p *G2Elt) Pick(rand cipher.Stream) kyber.Point {
var buf [32]byte
rand.XORKeyStream(buf[:], buf[:])
p.inner.Hash(buf[:], nil)
return p
}

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(data []byte, r 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.Add(&aa.inner, &bb.inner)
return p
}

func (p *G2Elt) Sub(a, b kyber.Point) kyber.Point { return p.Add(a, new(G2Elt).Neg(b)) }

func (p *G2Elt) Neg(a kyber.Point) kyber.Point {
p.Set(a)
p.inner.Neg()
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)
p.inner.ScalarMult(&ss.inner, &qq.inner)
return p
}

func (p *G2Elt) IsInCorrectGroup() bool { return p.inner.IsOnG2() }

func (p *G2Elt) Hash(msg []byte) kyber.Point { p.inner.Hash(msg, nil); return p }
func (p *G2Elt) Hash2(msg, dst []byte) kyber.Point { p.inner.Hash(msg, dst); return p }
23 changes: 23 additions & 0 deletions pairing/bls12381/group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package bls12381

import (
circl "github.com/cloudflare/circl/ecc/bls12381"
"github.com/drand/kyber"
)

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 circl.ScalarSize }
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() }
Loading