From 36749b63d185b21d695077e120513f12fa9450fe Mon Sep 17 00:00:00 2001 From: armfazh Date: Mon, 26 Jun 2023 16:07:24 -0700 Subject: [PATCH] Applying changes after Thibault's review. --- sign/bls/bls.go | 123 +++++++++++++++++++++++++++++++++++-------- sign/bls/bls_test.go | 31 +++++++++++ 2 files changed, 131 insertions(+), 23 deletions(-) diff --git a/sign/bls/bls.go b/sign/bls/bls.go index 52bb93ddc..4d2c27e22 100644 --- a/sign/bls/bls.go +++ b/sign/bls/bls.go @@ -1,4 +1,30 @@ -// Package bls provides BLS signatures instantiated with the BLS12-381 pairing curve. +// Package bls provides BLS signatures using the BLS12-381 pairing curve. +// +// This packages implements the IETF/CFRG draft for BLS signatures [1]. +// Curently only the BASIC mode (one of the three modes specified +// in the draft) is supported. The pairing function is instantiated +// with the BLS12-381 curve. +// +// # Groups +// +// The BLS signature scheme can be instantiated with keys in one of the +// two groups: G1 or G2, which correspond to the input domain of a pairing +// function e(G1,G2) -> Gt. +// Thus, choosing keys in G1 implies that signature values are internally +// represented in G2; or viceversa. Use the types KeyG1SigG2 or KeyG2SigG1 +// to express this preference. +// +// # Serialization +// +// The serialization of elements in G1 and G2 follows the recommendation +// given in [2], in order to be compatible with other implementations of +// BLS12-381 curve. +// +// # References +// +// [1] https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/ +// +// [2] https://github.com/zkcrypto/bls12_381/blob/main/src/notes/serialization.rs package bls import ( @@ -12,6 +38,19 @@ import ( "golang.org/x/crypto/hkdf" ) +var ( + ErrInvalid = errors.New("bls: invalid BLS instance") + ErrInvalidKey = errors.New("bls: invalid key") + ErrKeyGen = errors.New("bls: too many unsuccessful key generation tries") + ErrShortIKM = errors.New("bls: IKM material shorter than 32 bytes") + ErrAggregate = errors.New("bls: error while aggregating signatures") +) + +const ( + dstG1 = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_" + dstG2 = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_" +) + type Signature = []byte type ( @@ -44,6 +83,8 @@ type PublicKey[K KeyGroup] struct{ key K } func (k *PrivateKey[K]) Public() crypto.PublicKey { return k.PublicKey() } +// PublicKey computes the corresponding public key. The key is cached +// for further invocations to this function. func (k *PrivateKey[K]) PublicKey() *PublicKey[K] { if k.pub == nil { k.pub = new(PublicKey[K]) @@ -64,14 +105,30 @@ func (k *PrivateKey[K]) PublicKey() *PublicKey[K] { func (k *PrivateKey[K]) Equal(x crypto.PrivateKey) bool { xx, ok := x.(*PrivateKey[K]) + if !ok { + return false + } + + switch (interface{})(k).(type) { + case *PrivateKey[G1], *PrivateKey[G2]: + return k.key.IsEqual(&xx.key) == 1 + default: + panic(ErrInvalid) + } +} + +// Validate explicitly determines if a private key is valid. +func (k *PrivateKey[K]) Validate() bool { switch (interface{})(k).(type) { case *PrivateKey[G1], *PrivateKey[G2]: - return ok && k.key.IsEqual(&xx.key) == 1 + return k.key.IsZero() == 0 default: panic(ErrInvalid) } } +// MarshalBinary returns a slice with the representation of +// the underlying PrivateKey scalar (in big-endian order). func (k *PrivateKey[K]) MarshalBinary() ([]byte, error) { switch (interface{})(k).(type) { case *PrivateKey[G1], *PrivateKey[G2]: @@ -84,12 +141,20 @@ func (k *PrivateKey[K]) MarshalBinary() ([]byte, error) { func (k *PrivateKey[K]) UnmarshalBinary(data []byte) error { switch (interface{})(k).(type) { case *PrivateKey[G1], *PrivateKey[G2]: - return k.key.UnmarshalBinary(data) + if err := k.key.UnmarshalBinary(data); err != nil { + return err + } + if !k.Validate() { + return ErrInvalidKey + } + k.pub = nil + return nil default: panic(ErrInvalid) } } +// Validate explicitly determines if a public key is valid. func (k *PublicKey[K]) Validate() bool { switch (interface{})(k).(type) { case *PublicKey[G1]: @@ -105,20 +170,26 @@ func (k *PublicKey[K]) Validate() bool { func (k *PublicKey[K]) Equal(x crypto.PublicKey) bool { xx, ok := x.(*PublicKey[K]) + if !ok { + return false + } + switch (interface{})(k).(type) { case *PublicKey[G1]: xxx := (interface{})(xx.key).(G1) kk := (interface{})(k.key).(G1) - return ok && kk.g.IsEqual(&xxx.g) + return kk.g.IsEqual(&xxx.g) case *PublicKey[G2]: xxx := (interface{})(xx.key).(G2) kk := (interface{})(k.key).(G2) - return ok && kk.g.IsEqual(&xxx.g) + return kk.g.IsEqual(&xxx.g) default: panic(ErrInvalid) } } +// MarshalBinary returns a slice with the compressed +// representation of the underlying element in G1 or G2. func (k *PublicKey[K]) MarshalBinary() ([]byte, error) { switch (interface{})(k).(type) { case *PublicKey[G1]: @@ -145,7 +216,13 @@ func (k *PublicKey[K]) UnmarshalBinary(data []byte) error { } } +// KeyGen derives a private key for the specified group (G1 or G2). +// The length of ikm material should be at least 32 bytes length. +// The salt value should be either empty or a uniformly random +// bytes whose length equals the output length of SHA-256. func KeyGen[K KeyGroup](ikm, salt, keyInfo []byte) (*PrivateKey[K], error) { + // Implements recommended method at: + // https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#name-keygen if len(ikm) < 32 { return nil, ErrShortIKM } @@ -179,7 +256,13 @@ func KeyGen[K KeyGroup](ikm, salt, keyInfo []byte) (*PrivateKey[K], error) { return nil, ErrKeyGen } +// Sign computes a signature of a message using a key (defined in +// G1 or G1). func Sign[K KeyGroup](k *PrivateKey[K], msg []byte) Signature { + if !k.Validate() { + panic(ErrInvalidKey) + } + switch (interface{})(k).(type) { case *PrivateKey[G1]: var Q GG.G2 @@ -196,6 +279,8 @@ func Sign[K KeyGroup](k *PrivateKey[K], msg []byte) Signature { } } +// Verify returns true if the signature of a message is valid for the +// corresponding public key. func Verify[K KeyGroup](pub *PublicKey[K], msg []byte, sig Signature) bool { var ( a, b interface { @@ -236,6 +321,9 @@ func Verify[K KeyGroup](pub *PublicKey[K], msg []byte, sig Signature) bool { return res.IsIdentity() } +// Aggregate produces a unified signature given a list of signatures. +// To specify the group of keys pass either G1{} or G2{} as the first +// parameter. func Aggregate[K KeyGroup](k K, sigs []Signature) (Signature, error) { if len(sigs) == 0 { return nil, ErrAggregate @@ -269,6 +357,9 @@ func Aggregate[K KeyGroup](k K, sigs []Signature) (Signature, error) { } } +// VerifyAggregate returns true if the aggregated signature is valid for +// the list of messages and public keys provided. The slices must have +// equal size and have at least one element. func VerifyAggregate[K KeyGroup](pubs []*PublicKey[K], msgs [][]byte, aggSig Signature) bool { if len(pubs) != len(msgs) || len(pubs) == 0 || len(msgs) == 0 { return false @@ -279,6 +370,10 @@ func VerifyAggregate[K KeyGroup](pubs []*PublicKey[K], msgs [][]byte, aggSig Sig listG2 := make([]*GG.G2, n+1) listExp := make([]int, n+1) + listG1[n] = GG.G1Generator() + listG2[n] = GG.G2Generator() + listExp[n] = -1 + switch (interface{})(pubs).(type) { case []*PublicKey[G1]: for i := range msgs { @@ -290,13 +385,10 @@ func VerifyAggregate[K KeyGroup](pubs []*PublicKey[K], msgs [][]byte, aggSig Sig listExp[i] = 1 } - listG2[n] = new(GG.G2) err := listG2[n].SetBytes(aggSig) if err != nil { return false } - listG1[n] = GG.G1Generator() - listExp[n] = -1 case []*PublicKey[G2]: for i := range msgs { @@ -308,13 +400,10 @@ func VerifyAggregate[K KeyGroup](pubs []*PublicKey[K], msgs [][]byte, aggSig Sig listExp[i] = 1 } - listG1[n] = new(GG.G1) err := listG1[n].SetBytes(aggSig) if err != nil { return false } - listG2[n] = GG.G2Generator() - listExp[n] = -1 default: panic(ErrInvalid) @@ -323,15 +412,3 @@ func VerifyAggregate[K KeyGroup](pubs []*PublicKey[K], msgs [][]byte, aggSig Sig C := GG.ProdPairFrac(listG1, listG2, listExp) return C.IsIdentity() } - -var ( - ErrInvalid = errors.New("bls: invalid BLS instance") - ErrKeyGen = errors.New("bls: too many unsuccessful key generation tries") - ErrShortIKM = errors.New("bls: IKM material shorter than 32 bytes") - ErrAggregate = errors.New("bls: error while aggregating signatures") -) - -const ( - dstG1 = "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_" - dstG2 = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_" -) diff --git a/sign/bls/bls_test.go b/sign/bls/bls_test.go index 4b961a641..a99a4778b 100644 --- a/sign/bls/bls_test.go +++ b/sign/bls/bls_test.go @@ -3,6 +3,7 @@ package bls_test import ( "bytes" "crypto/rand" + "crypto/rsa" "encoding" "fmt" "testing" @@ -80,6 +81,9 @@ func testMarshal[K bls.KeyGroup]( if !bytes.Equal(got, want) { test.ReportError(t, got, want) } + + err = right.UnmarshalBinary(nil) + test.CheckIsErr(t, err, "should fail: empty input") } func testErrors[K bls.KeyGroup](t *testing.T) { @@ -93,6 +97,33 @@ func testErrors[K bls.KeyGroup](t *testing.T) { test.CheckNoErr(t, err, "failed to keygen") pub := priv.PublicKey() test.CheckOk(bls.Verify(pub, nil, nil) == false, "should fail: bad signature", t) + + // Bad public key + msg := []byte("hello") + sig := bls.Sign[K](priv, msg) + pub = new(bls.PublicKey[K]) + test.CheckOk(pub.Validate() == false, "should fail: bad public key", t) + test.CheckOk(bls.Verify(pub, msg, sig) == false, "should fail: bad signature", t) + + // Bad private key + priv = new(bls.PrivateKey[K]) + test.CheckOk(priv.Validate() == false, "should fail: bad private key", t) + err = test.CheckPanic(func() { bls.Sign(priv, msg) }) + test.CheckNoErr(t, err, "sign should panic") + + // Wrong comparisons + test.CheckOk(priv.Equal(new(rsa.PrivateKey)) == false, "should fail: bad private key types", t) + test.CheckOk(pub.Equal(new(rsa.PublicKey)) == false, "should fail: bad public key types", t) + + // Aggregate nil + _, err = bls.Aggregate[K](*new(K), nil) + test.CheckIsErr(t, err, "should fail: empty signatures") + + // VerifyAggregate nil + test.CheckOk(bls.VerifyAggregate([]*bls.PublicKey[K]{}, nil, nil) == false, "should fail: empty keys", t) + + // VerifyAggregate empty signature + test.CheckOk(bls.VerifyAggregate([]*bls.PublicKey[K]{pub}, [][]byte{msg}, nil) == false, "should fail: empty signature", t) } func testAggregation[K bls.KeyGroup](t *testing.T) {