diff --git a/sign/bls/bls.go b/sign/bls/bls.go new file mode 100644 index 000000000..2af636253 --- /dev/null +++ b/sign/bls/bls.go @@ -0,0 +1,420 @@ +// Package bls provides BLS signatures using the BLS12-381 pairing curve. +// +// This packages implements the IETF/CFRG draft for BLS signatures [1]. +// Currently 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/html/draft-irtf-cfrg-bls-signature-05 +// +// [2] https://github.com/zkcrypto/bls12_381/blob/0.7.0/src/notes/serialization.rs +package bls + +import ( + "crypto" + "crypto/sha256" + "encoding/binary" + "errors" + "io" + + GG "github.com/cloudflare/circl/ecc/bls12381" + "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 ( + // G1 group used for keys defined in pairing group G1. + G1 struct{ g GG.G1 } + // G2 group used for keys defined in pairing group G2. + G2 struct{ g GG.G2 } + // KeyG1SigG2 sets the keys to G1 and signatures to G2. + KeyG1SigG2 = G1 + // KeyG2SigG1 sets the keys to G2 and signatures to G1. + KeyG2SigG1 = G2 +) + +func (f *G1) setBytes(b []byte) error { return f.g.SetBytes(b) } +func (f *G2) setBytes(b []byte) error { return f.g.SetBytes(b) } + +func (f *G1) hash(msg []byte) { f.g.Hash(msg, []byte(dstG1)) } +func (f *G2) hash(msg []byte) { f.g.Hash(msg, []byte(dstG2)) } + +// KeyGroup determines the group used for keys, while the other +// group is used for signatures. +type KeyGroup interface{ G1 | G2 } + +type PrivateKey[K KeyGroup] struct { + key GG.Scalar + pub *PublicKey[K] +} + +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]) + switch any(k).(type) { + case *PrivateKey[G1]: + kk := any(&k.pub.key).(*G1) + kk.g.ScalarMult(&k.key, GG.G1Generator()) + case *PrivateKey[G2]: + kk := any(&k.pub.key).(*G2) + kk.g.ScalarMult(&k.key, GG.G2Generator()) + default: + panic(ErrInvalid) + } + } + + return k.pub +} + +func (k *PrivateKey[K]) Equal(x crypto.PrivateKey) bool { + xx, ok := x.(*PrivateKey[K]) + if !ok { + return false + } + + switch any(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 any(k).(type) { + case *PrivateKey[G1], *PrivateKey[G2]: + 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 any(k).(type) { + case *PrivateKey[G1], *PrivateKey[G2]: + return k.key.MarshalBinary() + default: + panic(ErrInvalid) + } +} + +func (k *PrivateKey[K]) UnmarshalBinary(data []byte) error { + switch any(k).(type) { + case *PrivateKey[G1], *PrivateKey[G2]: + 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 any(k).(type) { + case *PublicKey[G1]: + kk := any(k.key).(G1) + return !kk.g.IsIdentity() && kk.g.IsOnG1() + case *PublicKey[G2]: + kk := any(k.key).(G2) + return !kk.g.IsIdentity() && kk.g.IsOnG2() + default: + panic(ErrInvalid) + } +} + +func (k *PublicKey[K]) Equal(x crypto.PublicKey) bool { + xx, ok := x.(*PublicKey[K]) + if !ok { + return false + } + + switch any(k).(type) { + case *PublicKey[G1]: + xxx := any(xx.key).(G1) + kk := any(k.key).(G1) + return kk.g.IsEqual(&xxx.g) + case *PublicKey[G2]: + xxx := any(xx.key).(G2) + kk := any(k.key).(G2) + 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 any(k).(type) { + case *PublicKey[G1]: + kk := any(k.key).(G1) + return kk.g.BytesCompressed(), nil + case *PublicKey[G2]: + kk := any(k.key).(G2) + return kk.g.BytesCompressed(), nil + default: + panic(ErrInvalid) + } +} + +func (k *PublicKey[K]) UnmarshalBinary(data []byte) error { + switch any(k).(type) { + case *PublicKey[G1]: + kk := any(&k.key).(*G1) + return kk.setBytes(data) + case *PublicKey[G2]: + kk := any(&k.key).(*G2) + return kk.setBytes(data) + default: + panic(ErrInvalid) + } +} + +// 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 + } + + ikmZero := make([]byte, len(ikm)+1) + keyInfoTwo := make([]byte, len(keyInfo)+2) + copy(ikmZero, ikm) + copy(keyInfoTwo, keyInfo) + const L = uint16(48) + binary.BigEndian.PutUint16(keyInfoTwo[len(keyInfo):], L) + OKM := make([]byte, L) + + var ss GG.Scalar + for tries := 8; tries > 0; tries-- { + rd := hkdf.New(sha256.New, ikmZero, salt, keyInfoTwo) + n, err := io.ReadFull(rd, OKM) + if n != len(OKM) || err != nil { + return nil, err + } + + ss.SetBytes(OKM) + + if ss.IsZero() == 1 { + digest := sha256.Sum256(salt) + salt = digest[:] + } else { + return &PrivateKey[K]{key: ss, pub: nil}, nil + } + } + + 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 any(k).(type) { + case *PrivateKey[G1]: + var Q GG.G2 + Q.Hash(msg, []byte(dstG2)) + Q.ScalarMult(&k.key, &Q) + return Q.BytesCompressed() + case *PrivateKey[G2]: + var Q GG.G1 + Q.Hash(msg, []byte(dstG1)) + Q.ScalarMult(&k.key, &Q) + return Q.BytesCompressed() + default: + panic(ErrInvalid) + } +} + +// 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 { + setBytes([]byte) error + hash([]byte) + } + listG1 [2]*GG.G1 + listG2 [2]*GG.G2 + ) + + switch any(pub).(type) { + case *PublicKey[G1]: + aa, bb := new(G2), new(G2) + a, b = aa, bb + k := any(pub.key).(G1) + listG1[0], listG1[1] = &k.g, GG.G1Generator() + listG2[0], listG2[1] = &aa.g, &bb.g + case *PublicKey[G2]: + aa, bb := new(G1), new(G1) + a, b = aa, bb + k := any(pub.key).(G2) + listG2[0], listG2[1] = &k.g, GG.G2Generator() + listG1[0], listG1[1] = &aa.g, &bb.g + default: + panic(ErrInvalid) + } + + err := b.setBytes(sig) + if err != nil { + return false + } + if !pub.Validate() { + return false + } + a.hash(msg) + + res := GG.ProdPairFrac(listG1[:], listG2[:], []int{1, -1}) + 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 + } + + switch any(k).(type) { + case G1: + var P, Q GG.G2 + P.SetIdentity() + for _, sig := range sigs { + if err := Q.SetBytes(sig); err != nil { + return nil, err + } + P.Add(&P, &Q) + } + return P.BytesCompressed(), nil + + case G2: + var P, Q GG.G1 + P.SetIdentity() + for _, sig := range sigs { + if err := Q.SetBytes(sig); err != nil { + return nil, err + } + P.Add(&P, &Q) + } + return P.BytesCompressed(), nil + + default: + panic(ErrInvalid) + } +} + +// 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 { + return false + } + + for _, p := range pubs { + if !p.Validate() { + return false + } + } + + n := len(pubs) + listG1 := make([]*GG.G1, n+1) + listG2 := make([]*GG.G2, n+1) + listSigns := make([]int, n+1) + + listG1[n] = GG.G1Generator() + listG2[n] = GG.G2Generator() + listSigns[n] = -1 + + switch any(pubs).(type) { + case []*PublicKey[G1]: + for i := range msgs { + listG2[i] = new(GG.G2) + listG2[i].Hash(msgs[i], []byte(dstG2)) + + xP := any(pubs[i].key).(G1) + listG1[i] = &xP.g + listSigns[i] = 1 + } + + err := listG2[n].SetBytes(aggSig) + if err != nil { + return false + } + + case []*PublicKey[G2]: + for i := range msgs { + listG1[i] = new(GG.G1) + listG1[i].Hash(msgs[i], []byte(dstG1)) + + xP := any(pubs[i].key).(G2) + listG2[i] = &xP.g + listSigns[i] = 1 + } + + err := listG1[n].SetBytes(aggSig) + if err != nil { + return false + } + + default: + panic(ErrInvalid) + } + + C := GG.ProdPairFrac(listG1, listG2, listSigns) + return C.IsIdentity() +} diff --git a/sign/bls/bls_test.go b/sign/bls/bls_test.go new file mode 100644 index 000000000..a99a4778b --- /dev/null +++ b/sign/bls/bls_test.go @@ -0,0 +1,219 @@ +package bls_test + +import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "encoding" + "fmt" + "testing" + + "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/sign/bls" +) + +func TestBls(t *testing.T) { + t.Run("G1/API", testBls[bls.G1]) + t.Run("G2/API", testBls[bls.G2]) + t.Run("G1/Marshal", testMarshalKeys[bls.G1]) + t.Run("G2/Marshal", testMarshalKeys[bls.G2]) + t.Run("G1/Errors", testErrors[bls.G1]) + t.Run("G2/Errors", testErrors[bls.G2]) + t.Run("G1/Aggregation", testAggregation[bls.G1]) + t.Run("G2/Aggregation", testAggregation[bls.G2]) +} + +func testBls[K bls.KeyGroup](t *testing.T) { + const testTimes = 1 << 7 + msg := []byte("hello world") + keyInfo := []byte("KeyInfo for BLS") + salt := [32]byte{} + ikm := [32]byte{} + _, _ = rand.Reader.Read(ikm[:]) + _, _ = rand.Reader.Read(salt[:]) + + for i := 0; i < testTimes; i++ { + _, _ = rand.Reader.Read(ikm[:]) + + priv, err := bls.KeyGen[K](ikm[:], salt[:], keyInfo) + test.CheckNoErr(t, err, "failed to keygen") + signature := bls.Sign(priv, msg) + pub := priv.Public().(*bls.PublicKey[K]) + test.CheckOk(bls.Verify(pub, msg, signature), "failed verification", t) + } +} + +func testMarshalKeys[K bls.KeyGroup](t *testing.T) { + ikm := [32]byte{} + priv, err := bls.KeyGen[K](ikm[:], nil, nil) + test.CheckNoErr(t, err, "failed to keygen") + pub := priv.PublicKey() + + auxPriv := new(bls.PrivateKey[K]) + auxPub := new(bls.PublicKey[K]) + + t.Run("PrivateKey", func(t *testing.T) { + testMarshal[K](t, priv, auxPriv) + test.CheckOk(priv.Equal(auxPriv), "private keys do not match", t) + }) + t.Run("PublicKey", func(t *testing.T) { + testMarshal[K](t, pub, auxPub) + test.CheckOk(pub.Equal(auxPub), "public keys do not match", t) + }) +} + +func testMarshal[K bls.KeyGroup]( + t *testing.T, + left, right interface { + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler + }, +) { + want, err := left.MarshalBinary() + test.CheckNoErr(t, err, "failed to marshal") + + err = right.UnmarshalBinary(want) + test.CheckNoErr(t, err, "failed to unmarshal") + + got, err := right.MarshalBinary() + test.CheckNoErr(t, err, "failed to marshal") + + 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) { + // Short IKM + _, err := bls.KeyGen[K](nil, nil, nil) + test.CheckIsErr(t, err, "should fail: short ikm") + + // Bad Signature size + ikm := [32]byte{} + priv, err := bls.KeyGen[K](ikm[:], nil, nil) + 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) { + const N = 3 + + ikm := [32]byte{} + _, _ = rand.Reader.Read(ikm[:]) + + msgs := make([][]byte, N) + sigs := make([]bls.Signature, N) + pubKeys := make([]*bls.PublicKey[K], N) + + for i := range sigs { + priv, err := bls.KeyGen[K](ikm[:], nil, nil) + test.CheckNoErr(t, err, "failed to keygen") + pubKeys[i] = priv.PublicKey() + + msgs[i] = []byte(fmt.Sprintf("Message number: %v", i)) + sigs[i] = bls.Sign(priv, msgs[i]) + } + + aggSig, err := bls.Aggregate(*new(K), sigs) + test.CheckNoErr(t, err, "failed to aggregate") + + ok := bls.VerifyAggregate(pubKeys, msgs, aggSig) + test.CheckOk(ok, "failed to verify aggregated signature", t) +} + +func BenchmarkBls(b *testing.B) { + b.Run("G1", benchmarkBls[bls.G1]) + b.Run("G2", benchmarkBls[bls.G2]) +} + +func benchmarkBls[K bls.KeyGroup](b *testing.B) { + msg := []byte("hello world") + keyInfo := []byte("KeyInfo for BLS") + salt := [32]byte{} + ikm := [32]byte{} + _, _ = rand.Reader.Read(ikm[:]) + _, _ = rand.Reader.Read(salt[:]) + + priv, _ := bls.KeyGen[K](ikm[:], salt[:], keyInfo) + + const N = 3 + msgs := make([][]byte, N) + sigs := make([]bls.Signature, N) + pubKeys := make([]*bls.PublicKey[K], N) + + for i := range sigs { + pubKeys[i] = priv.PublicKey() + + msgs[i] = []byte(fmt.Sprintf("Message number: %v", i)) + sigs[i] = bls.Sign(priv, msgs[i]) + } + + b.Run("Keygen", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = rand.Reader.Read(ikm[:]) + _, _ = bls.KeyGen[K](ikm[:], salt[:], keyInfo) + } + }) + + b.Run("Sign", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = bls.Sign(priv, msg) + } + }) + + b.Run("Verify", func(b *testing.B) { + pub := priv.PublicKey() + signature := bls.Sign(priv, msg) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + bls.Verify(pub, msg, signature) + } + }) + + b.Run("Aggregate3", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = bls.Aggregate(*new(K), sigs) + } + }) + + b.Run("VerifyAggregate3", func(b *testing.B) { + aggSig, _ := bls.Aggregate(*new(K), sigs) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = bls.VerifyAggregate(pubKeys, msgs, aggSig) + } + }) +} diff --git a/sign/bls/testdata/sig_g1_basic_P256.txt.zip b/sign/bls/testdata/sig_g1_basic_P256.txt.zip new file mode 100644 index 000000000..087d6a18d Binary files /dev/null and b/sign/bls/testdata/sig_g1_basic_P256.txt.zip differ diff --git a/sign/bls/testdata/sig_g1_basic_P521.txt.zip b/sign/bls/testdata/sig_g1_basic_P521.txt.zip new file mode 100644 index 000000000..32088846f Binary files /dev/null and b/sign/bls/testdata/sig_g1_basic_P521.txt.zip differ diff --git a/sign/bls/testdata/sig_g2_basic_P256.txt.zip b/sign/bls/testdata/sig_g2_basic_P256.txt.zip new file mode 100644 index 000000000..20919fe1b Binary files /dev/null and b/sign/bls/testdata/sig_g2_basic_P256.txt.zip differ diff --git a/sign/bls/testdata/sig_g2_basic_P521.txt.zip b/sign/bls/testdata/sig_g2_basic_P521.txt.zip new file mode 100644 index 000000000..c80da1c19 Binary files /dev/null and b/sign/bls/testdata/sig_g2_basic_P521.txt.zip differ diff --git a/sign/bls/vectors_test.go b/sign/bls/vectors_test.go new file mode 100644 index 000000000..9a86ead9b --- /dev/null +++ b/sign/bls/vectors_test.go @@ -0,0 +1,67 @@ +package bls_test + +import ( + "archive/zip" + "bufio" + "encoding/hex" + "fmt" + "strings" + "testing" + + "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/sign/bls" +) + +func TestVectors(t *testing.T) { + // Test vectors taken from: + // Repository: https://github.com/kwantam/bls_sigs_ref/tree/sgn0_fix/test-vectors + // Branch: sig0_fix + // Path: /test-vectors/sig_[g1|g2]_basic/[name] + // Compression: zip + + for _, name := range []string{"P256", "P521"} { + t.Run(name+"/G1", func(t *testing.T) { testVector[bls.KeyG2SigG1](t, "g1", name) }) + t.Run(name+"/G2", func(t *testing.T) { testVector[bls.KeyG1SigG2](t, "g2", name) }) + } +} + +func testVector[K bls.KeyGroup](t *testing.T, group, name string) { + nameFile := fmt.Sprintf("./testdata/sig_%v_basic_%v.txt.zip", group, name) + zipFile, err := zip.OpenReader(nameFile) + test.CheckNoErr(t, err, "error opening zipped file") + + for _, f := range zipFile.File { + unzipped, err := f.Open() + test.CheckNoErr(t, err, "error opening unzipped file") + + scanner := bufio.NewScanner(unzipped) + for scanner.Scan() { + line := scanner.Text() + inputs := strings.Split(line, " ") + if len(inputs) != 3 { + t.Fatalf("bad input length") + } + + msg, err := hex.DecodeString(inputs[0]) + test.CheckNoErr(t, err, "error decoding msg") + seed, err := hex.DecodeString(inputs[1]) + test.CheckNoErr(t, err, "error decoding sk") + wantSig := inputs[2] + + salt := []byte("BLS-SIG-KEYGEN-SALT-") + keyInfo := []byte("") + priv, err := bls.KeyGen[K](seed, salt, keyInfo) + test.CheckNoErr(t, err, "error generating priv key") + + sig := bls.Sign(priv, msg) + gotSig := hex.EncodeToString(sig) + + if gotSig != wantSig { + test.ReportError(t, gotSig, wantSig, msg) + } + + pub := priv.PublicKey() + test.CheckOk(bls.Verify(pub, msg, sig), "cannot verify", t) + } + } +}