From 763622a08a6dc9aba1783716122b7f7212243507 Mon Sep 17 00:00:00 2001 From: armfazh Date: Wed, 8 Mar 2023 20:26:06 -0800 Subject: [PATCH 1/6] Addding BLS signature scheme. --- sign/bls/bls.go | 258 +++++++++++++++++++++++++++++++++++++++++++ sign/bls/bls_test.go | 33 ++++++ 2 files changed, 291 insertions(+) create mode 100644 sign/bls/bls.go create mode 100644 sign/bls/bls_test.go diff --git a/sign/bls/bls.go b/sign/bls/bls.go new file mode 100644 index 00000000..980e5aa4 --- /dev/null +++ b/sign/bls/bls.go @@ -0,0 +1,258 @@ +// Package bls provides BLS signatures instantiated with the BLS12-381 pairing curve. +package bls + +import ( + "crypto" + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "errors" + "io" + + GG "github.com/cloudflare/circl/ecc/bls12381" + "golang.org/x/crypto/hkdf" +) + +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 } +) + +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() } + +func (k *PrivateKey[K]) PublicKey() *PublicKey[K] { + if k.pub == nil { + k.pub = new(PublicKey[K]) + switch (interface{})(k).(type) { + case *PrivateKey[G1]: + kk := (interface{})(&k.pub.key).(*G1) + kk.g.ScalarMult(&k.key, GG.G1Generator()) + case *PrivateKey[G2]: + kk := (interface{})(&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]) + switch (interface{})(k).(type) { + case *PrivateKey[G1], *PrivateKey[G2]: + return ok && k.key.IsEqual(&xx.key) == 1 + default: + panic(ErrInvalid) + } +} + +func (k *PublicKey[K]) Validate() bool { + switch (interface{})(k).(type) { + case *PublicKey[G1]: + kk := (interface{})(k.key).(G1) + return !kk.g.IsIdentity() && kk.g.IsOnG1() + case *PublicKey[G2]: + kk := (interface{})(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]) + switch (interface{})(k).(type) { + case *PublicKey[G1]: + xxx := (interface{})(xx.key).(G1) + kk := (interface{})(k.key).(G1) + return ok && kk.g.IsEqual(&xxx.g) + case *PublicKey[G2]: + xxx := (interface{})(xx.key).(G1) + kk := (interface{})(k.key).(G1) + return ok && kk.g.IsEqual(&xxx.g) + default: + panic(ErrInvalid) + } +} + +func KeyGen[K KeyGroup](ikm, salt, keyInfo []byte) (*PrivateKey[K], error) { + 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 +} + +func Sign[K KeyGroup](k *PrivateKey[K], msg []byte) Signature { + switch (interface{})(k).(type) { + case *PrivateKey[G1]: + var Q G2 + Q.hash(msg) + Q.g.ScalarMult(&k.key, &Q.g) + return Q.g.BytesCompressed() + case *PrivateKey[G2]: + var Q G1 + Q.hash(msg) + Q.g.ScalarMult(&k.key, &Q.g) + return Q.g.BytesCompressed() + default: + panic(ErrInvalid) + } +} + +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 (interface{})(pub).(type) { + case *PublicKey[G1]: + aa, bb := new(G2), new(G2) + a, b = aa, bb + k := (interface{})(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 := (interface{})(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() +} + +func Aggregate[K KeyGroup](sigs []Signature) (Signature, error) { + if len(sigs) == 0 { + return nil, ErrAggregate + } + + return nil, nil + // switch (interface{})(Aggregate[K]).(type) { + // case *func([]Signature) (Signature, error): + // 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 *func([]Signature) (Signature, error): + // 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) + // } +} + +func VerifyAggregate[K KeyGroup](pubs []PublicKey[K], msgs [][]byte, sig Signature) bool { + if len(pubs) != len(msgs) || len(pubs) == 0 || len(msgs) == 0 { + return false + } + + setMsgs := make(map[string][]PublicKey[K], len(pubs)) + switch (interface{})(pubs).(type) { + case []PublicKey[G1]: + for i := range msgs { + s := hex.EncodeToString(msgs[i]) + setMsgs[s] = append(setMsgs[s], pubs[i]) + } + return false + + case []PublicKey[G2]: + return false + + default: + panic(ErrInvalid) + } +} + +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 new file mode 100644 index 00000000..d2522e71 --- /dev/null +++ b/sign/bls/bls_test.go @@ -0,0 +1,33 @@ +package bls_test + +import ( + "crypto/rand" + "testing" + + "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/sign/bls" +) + +func TestSuite(t *testing.T) { + t.Run("BLS12381G1", func(t *testing.T) { check[bls.G1](t) }) + + t.Run("BLS12381G2", func(t *testing.T) { check[bls.G2](t) }) +} + +func check[K bls.KeyGroup](t *testing.T) { + const testTimes = 1 << 7 + msg := []byte("BLS signing") + salt := []byte{23, 23, 232, 32, 32} + keyInfo := []byte{23, 23, 232, 32, 32} + ikm := [32]byte{} + + 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.PublicKey() + test.CheckOk(bls.Verify(pub, msg, signature), "failed verification", t) + } +} From a8dadbe511e4b5308b8a414c6dd77293b1ce66db Mon Sep 17 00:00:00 2001 From: armfazh Date: Wed, 21 Jun 2023 14:31:41 -0700 Subject: [PATCH 2/6] Addding test vectors from reference implementation. --- sign/bls/bls.go | 13 ++++ sign/bls/testdata/sig_g1_basic_P256.txt.zip | Bin 0 -> 14898 bytes sign/bls/testdata/sig_g1_basic_P521.txt.zip | Bin 0 -> 17303 bytes sign/bls/testdata/sig_g2_basic_P256.txt.zip | Bin 0 -> 18266 bytes sign/bls/testdata/sig_g2_basic_P521.txt.zip | Bin 0 -> 20636 bytes sign/bls/vectors_test.go | 67 ++++++++++++++++++++ 6 files changed, 80 insertions(+) create mode 100644 sign/bls/testdata/sig_g1_basic_P256.txt.zip create mode 100644 sign/bls/testdata/sig_g1_basic_P521.txt.zip create mode 100644 sign/bls/testdata/sig_g2_basic_P256.txt.zip create mode 100644 sign/bls/testdata/sig_g2_basic_P521.txt.zip create mode 100644 sign/bls/vectors_test.go diff --git a/sign/bls/bls.go b/sign/bls/bls.go index 980e5aa4..a7868446 100644 --- a/sign/bls/bls.go +++ b/sign/bls/bls.go @@ -20,6 +20,10 @@ type ( 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) } @@ -69,6 +73,15 @@ func (k *PrivateKey[K]) Equal(x crypto.PrivateKey) bool { } } +func (k *PrivateKey[K]) UnmarshalBinary(data []byte) error { + switch (interface{})(k).(type) { + case *PrivateKey[G1], *PrivateKey[G2]: + return k.key.UnmarshalBinary(data) + default: + panic(ErrInvalid) + } +} + func (k *PublicKey[K]) Validate() bool { switch (interface{})(k).(type) { case *PublicKey[G1]: 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 0000000000000000000000000000000000000000..087d6a18d77c4118578b5bdf49564f26785384f1 GIT binary patch literal 14898 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 a=Ks(e3uCrRe=jjzl*7f`ybyE%fd2yR|8R={ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c80da1c19a3d6622a972050b12c8d751a91a18b9 GIT binary patch literal 20636 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 Date: Wed, 21 Jun 2023 16:57:21 -0700 Subject: [PATCH 3/6] Adding more tests and benchmarks. --- sign/bls/bls.go | 39 +++++++++++++- sign/bls/bls_test.go | 119 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 146 insertions(+), 12 deletions(-) diff --git a/sign/bls/bls.go b/sign/bls/bls.go index a7868446..c247dcce 100644 --- a/sign/bls/bls.go +++ b/sign/bls/bls.go @@ -73,6 +73,15 @@ func (k *PrivateKey[K]) Equal(x crypto.PrivateKey) bool { } } +func (k *PrivateKey[K]) MarshalBinary() ([]byte, error) { + switch (interface{})(k).(type) { + case *PrivateKey[G1], *PrivateKey[G2]: + return k.key.MarshalBinary() + default: + panic(ErrInvalid) + } +} + func (k *PrivateKey[K]) UnmarshalBinary(data []byte) error { switch (interface{})(k).(type) { case *PrivateKey[G1], *PrivateKey[G2]: @@ -103,14 +112,40 @@ func (k *PublicKey[K]) Equal(x crypto.PublicKey) bool { kk := (interface{})(k.key).(G1) return ok && kk.g.IsEqual(&xxx.g) case *PublicKey[G2]: - xxx := (interface{})(xx.key).(G1) - kk := (interface{})(k.key).(G1) + xxx := (interface{})(xx.key).(G2) + kk := (interface{})(k.key).(G2) return ok && kk.g.IsEqual(&xxx.g) default: panic(ErrInvalid) } } +func (k *PublicKey[K]) MarshalBinary() ([]byte, error) { + switch (interface{})(k).(type) { + case *PublicKey[G1]: + kk := (interface{})(k.key).(G1) + return kk.g.BytesCompressed(), nil + case *PublicKey[G2]: + kk := (interface{})(k.key).(G2) + return kk.g.BytesCompressed(), nil + default: + panic(ErrInvalid) + } +} + +func (k *PublicKey[K]) UnmarshalBinary(data []byte) error { + switch (interface{})(k).(type) { + case *PublicKey[G1]: + kk := (interface{})(&k.key).(*G1) + return kk.setBytes(data) + case *PublicKey[G2]: + kk := (interface{})(&k.key).(*G2) + return kk.setBytes(data) + default: + panic(ErrInvalid) + } +} + func KeyGen[K KeyGroup](ikm, salt, keyInfo []byte) (*PrivateKey[K], error) { if len(ikm) < 32 { return nil, ErrShortIKM diff --git a/sign/bls/bls_test.go b/sign/bls/bls_test.go index d2522e71..4b464534 100644 --- a/sign/bls/bls_test.go +++ b/sign/bls/bls_test.go @@ -1,33 +1,132 @@ package bls_test import ( + "bytes" "crypto/rand" + "encoding" "testing" "github.com/cloudflare/circl/internal/test" "github.com/cloudflare/circl/sign/bls" ) -func TestSuite(t *testing.T) { - t.Run("BLS12381G1", func(t *testing.T) { check[bls.G1](t) }) - - t.Run("BLS12381G2", func(t *testing.T) { check[bls.G2](t) }) +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]) } -func check[K bls.KeyGroup](t *testing.T) { +func testBls[K bls.KeyGroup](t *testing.T) { const testTimes = 1 << 7 - msg := []byte("BLS signing") - salt := []byte{23, 23, 232, 32, 32} - keyInfo := []byte{23, 23, 232, 32, 32} + 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) + priv, err := bls.KeyGen[K](ikm[:], salt[:], keyInfo) test.CheckNoErr(t, err, "failed to keygen") signature := bls.Sign(priv, msg) - pub := priv.PublicKey() + 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) + } +} + +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) +} + +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) + + 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) + } + }) +} From 6e109377b379215cd7f7c69d432aafa668e6b133 Mon Sep 17 00:00:00 2001 From: armfazh Date: Wed, 21 Jun 2023 18:03:33 -0700 Subject: [PATCH 4/6] Implementing aggregation. --- sign/bls/bls.go | 121 +++++++++++++++++++++++++++---------------- sign/bls/bls_test.go | 56 ++++++++++++++++++++ 2 files changed, 132 insertions(+), 45 deletions(-) diff --git a/sign/bls/bls.go b/sign/bls/bls.go index c247dcce..52bb93dd 100644 --- a/sign/bls/bls.go +++ b/sign/bls/bls.go @@ -5,7 +5,6 @@ import ( "crypto" "crypto/sha256" "encoding/binary" - "encoding/hex" "errors" "io" @@ -183,15 +182,15 @@ func KeyGen[K KeyGroup](ikm, salt, keyInfo []byte) (*PrivateKey[K], error) { func Sign[K KeyGroup](k *PrivateKey[K], msg []byte) Signature { switch (interface{})(k).(type) { case *PrivateKey[G1]: - var Q G2 - Q.hash(msg) - Q.g.ScalarMult(&k.key, &Q.g) - return Q.g.BytesCompressed() + var Q GG.G2 + Q.Hash(msg, []byte(dstG2)) + Q.ScalarMult(&k.key, &Q) + return Q.BytesCompressed() case *PrivateKey[G2]: - var Q G1 - Q.hash(msg) - Q.g.ScalarMult(&k.key, &Q.g) - return Q.g.BytesCompressed() + var Q GG.G1 + Q.Hash(msg, []byte(dstG1)) + Q.ScalarMult(&k.key, &Q) + return Q.BytesCompressed() default: panic(ErrInvalid) } @@ -237,60 +236,92 @@ func Verify[K KeyGroup](pub *PublicKey[K], msg []byte, sig Signature) bool { return res.IsIdentity() } -func Aggregate[K KeyGroup](sigs []Signature) (Signature, error) { +func Aggregate[K KeyGroup](k K, sigs []Signature) (Signature, error) { if len(sigs) == 0 { return nil, ErrAggregate } - return nil, nil - // switch (interface{})(Aggregate[K]).(type) { - // case *func([]Signature) (Signature, error): - // 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 *func([]Signature) (Signature, error): - // 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) - // } + switch (interface{})(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) + } } -func VerifyAggregate[K KeyGroup](pubs []PublicKey[K], msgs [][]byte, sig Signature) bool { +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 } - setMsgs := make(map[string][]PublicKey[K], len(pubs)) + n := len(pubs) + listG1 := make([]*GG.G1, n+1) + listG2 := make([]*GG.G2, n+1) + listExp := make([]int, n+1) + switch (interface{})(pubs).(type) { - case []PublicKey[G1]: + case []*PublicKey[G1]: for i := range msgs { - s := hex.EncodeToString(msgs[i]) - setMsgs[s] = append(setMsgs[s], pubs[i]) + listG2[i] = new(GG.G2) + listG2[i].Hash(msgs[i], []byte(dstG2)) + + xP := (interface{})(pubs[i].key).(G1) + listG1[i] = &xP.g + listExp[i] = 1 } - return false - case []PublicKey[G2]: - return false + 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 { + listG1[i] = new(GG.G1) + listG1[i].Hash(msgs[i], []byte(dstG1)) + + xP := (interface{})(pubs[i].key).(G2) + listG2[i] = &xP.g + 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) } + + C := GG.ProdPairFrac(listG1, listG2, listExp) + return C.IsIdentity() } var ( diff --git a/sign/bls/bls_test.go b/sign/bls/bls_test.go index 4b464534..4b961a64 100644 --- a/sign/bls/bls_test.go +++ b/sign/bls/bls_test.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/rand" "encoding" + "fmt" "testing" "github.com/cloudflare/circl/internal/test" @@ -17,6 +18,8 @@ func TestBls(t *testing.T) { 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) { @@ -92,6 +95,32 @@ func testErrors[K bls.KeyGroup](t *testing.T) { test.CheckOk(bls.Verify(pub, nil, nil) == false, "should fail: bad 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]) @@ -107,6 +136,18 @@ func benchmarkBls[K bls.KeyGroup](b *testing.B) { 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[:]) @@ -129,4 +170,19 @@ func benchmarkBls[K bls.KeyGroup](b *testing.B) { 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) + } + }) } From fc7511c6ab5ddecbfd436e7063893c6833ca33f7 Mon Sep 17 00:00:00 2001 From: armfazh Date: Mon, 26 Jun 2023 16:07:24 -0700 Subject: [PATCH 5/6] 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 52bb93dd..04f9ec90 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]. +// 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/draft-irtf-cfrg-bls-signature/ +// +// [2] https://github.com/zkcrypto/bls12_381/blob/0.7.0/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 4b961a64..a99a4778 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) { From bcbc992d755bafe5836fe3617574e7a92865c6a5 Mon Sep 17 00:00:00 2001 From: armfazh Date: Fri, 14 Jul 2023 13:24:11 -0700 Subject: [PATCH 6/6] Applying changes after Bas' review. --- sign/bls/bls.go | 78 ++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/sign/bls/bls.go b/sign/bls/bls.go index 04f9ec90..2af63625 100644 --- a/sign/bls/bls.go +++ b/sign/bls/bls.go @@ -22,7 +22,7 @@ // // # References // -// [1] https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/ +// [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 @@ -88,12 +88,12 @@ func (k *PrivateKey[K]) Public() crypto.PublicKey { return k.PublicKey() } func (k *PrivateKey[K]) PublicKey() *PublicKey[K] { if k.pub == nil { k.pub = new(PublicKey[K]) - switch (interface{})(k).(type) { + switch any(k).(type) { case *PrivateKey[G1]: - kk := (interface{})(&k.pub.key).(*G1) + kk := any(&k.pub.key).(*G1) kk.g.ScalarMult(&k.key, GG.G1Generator()) case *PrivateKey[G2]: - kk := (interface{})(&k.pub.key).(*G2) + kk := any(&k.pub.key).(*G2) kk.g.ScalarMult(&k.key, GG.G2Generator()) default: panic(ErrInvalid) @@ -109,7 +109,7 @@ func (k *PrivateKey[K]) Equal(x crypto.PrivateKey) bool { return false } - switch (interface{})(k).(type) { + switch any(k).(type) { case *PrivateKey[G1], *PrivateKey[G2]: return k.key.IsEqual(&xx.key) == 1 default: @@ -119,7 +119,7 @@ func (k *PrivateKey[K]) Equal(x crypto.PrivateKey) bool { // Validate explicitly determines if a private key is valid. func (k *PrivateKey[K]) Validate() bool { - switch (interface{})(k).(type) { + switch any(k).(type) { case *PrivateKey[G1], *PrivateKey[G2]: return k.key.IsZero() == 0 default: @@ -130,7 +130,7 @@ func (k *PrivateKey[K]) Validate() bool { // 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) { + switch any(k).(type) { case *PrivateKey[G1], *PrivateKey[G2]: return k.key.MarshalBinary() default: @@ -139,7 +139,7 @@ func (k *PrivateKey[K]) MarshalBinary() ([]byte, error) { } func (k *PrivateKey[K]) UnmarshalBinary(data []byte) error { - switch (interface{})(k).(type) { + switch any(k).(type) { case *PrivateKey[G1], *PrivateKey[G2]: if err := k.key.UnmarshalBinary(data); err != nil { return err @@ -156,12 +156,12 @@ func (k *PrivateKey[K]) UnmarshalBinary(data []byte) error { // Validate explicitly determines if a public key is valid. func (k *PublicKey[K]) Validate() bool { - switch (interface{})(k).(type) { + switch any(k).(type) { case *PublicKey[G1]: - kk := (interface{})(k.key).(G1) + kk := any(k.key).(G1) return !kk.g.IsIdentity() && kk.g.IsOnG1() case *PublicKey[G2]: - kk := (interface{})(k.key).(G2) + kk := any(k.key).(G2) return !kk.g.IsIdentity() && kk.g.IsOnG2() default: panic(ErrInvalid) @@ -174,14 +174,14 @@ func (k *PublicKey[K]) Equal(x crypto.PublicKey) bool { return false } - switch (interface{})(k).(type) { + switch any(k).(type) { case *PublicKey[G1]: - xxx := (interface{})(xx.key).(G1) - kk := (interface{})(k.key).(G1) + xxx := any(xx.key).(G1) + kk := any(k.key).(G1) return kk.g.IsEqual(&xxx.g) case *PublicKey[G2]: - xxx := (interface{})(xx.key).(G2) - kk := (interface{})(k.key).(G2) + xxx := any(xx.key).(G2) + kk := any(k.key).(G2) return kk.g.IsEqual(&xxx.g) default: panic(ErrInvalid) @@ -191,12 +191,12 @@ func (k *PublicKey[K]) Equal(x crypto.PublicKey) bool { // 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) { + switch any(k).(type) { case *PublicKey[G1]: - kk := (interface{})(k.key).(G1) + kk := any(k.key).(G1) return kk.g.BytesCompressed(), nil case *PublicKey[G2]: - kk := (interface{})(k.key).(G2) + kk := any(k.key).(G2) return kk.g.BytesCompressed(), nil default: panic(ErrInvalid) @@ -204,12 +204,12 @@ func (k *PublicKey[K]) MarshalBinary() ([]byte, error) { } func (k *PublicKey[K]) UnmarshalBinary(data []byte) error { - switch (interface{})(k).(type) { + switch any(k).(type) { case *PublicKey[G1]: - kk := (interface{})(&k.key).(*G1) + kk := any(&k.key).(*G1) return kk.setBytes(data) case *PublicKey[G2]: - kk := (interface{})(&k.key).(*G2) + kk := any(&k.key).(*G2) return kk.setBytes(data) default: panic(ErrInvalid) @@ -263,7 +263,7 @@ func Sign[K KeyGroup](k *PrivateKey[K], msg []byte) Signature { panic(ErrInvalidKey) } - switch (interface{})(k).(type) { + switch any(k).(type) { case *PrivateKey[G1]: var Q GG.G2 Q.Hash(msg, []byte(dstG2)) @@ -291,17 +291,17 @@ func Verify[K KeyGroup](pub *PublicKey[K], msg []byte, sig Signature) bool { listG2 [2]*GG.G2 ) - switch (interface{})(pub).(type) { + switch any(pub).(type) { case *PublicKey[G1]: aa, bb := new(G2), new(G2) a, b = aa, bb - k := (interface{})(pub.key).(G1) + 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 := (interface{})(pub.key).(G2) + k := any(pub.key).(G2) listG2[0], listG2[1] = &k.g, GG.G2Generator() listG1[0], listG1[1] = &aa.g, &bb.g default: @@ -329,7 +329,7 @@ func Aggregate[K KeyGroup](k K, sigs []Signature) (Signature, error) { return nil, ErrAggregate } - switch (interface{})(k).(type) { + switch any(k).(type) { case G1: var P, Q GG.G2 P.SetIdentity() @@ -361,28 +361,34 @@ func Aggregate[K KeyGroup](k K, sigs []Signature) (Signature, error) { // 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 { + 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) - listExp := make([]int, n+1) + listSigns := make([]int, n+1) listG1[n] = GG.G1Generator() listG2[n] = GG.G2Generator() - listExp[n] = -1 + listSigns[n] = -1 - switch (interface{})(pubs).(type) { + switch any(pubs).(type) { case []*PublicKey[G1]: for i := range msgs { listG2[i] = new(GG.G2) listG2[i].Hash(msgs[i], []byte(dstG2)) - xP := (interface{})(pubs[i].key).(G1) + xP := any(pubs[i].key).(G1) listG1[i] = &xP.g - listExp[i] = 1 + listSigns[i] = 1 } err := listG2[n].SetBytes(aggSig) @@ -395,9 +401,9 @@ func VerifyAggregate[K KeyGroup](pubs []*PublicKey[K], msgs [][]byte, aggSig Sig listG1[i] = new(GG.G1) listG1[i].Hash(msgs[i], []byte(dstG1)) - xP := (interface{})(pubs[i].key).(G2) + xP := any(pubs[i].key).(G2) listG2[i] = &xP.g - listExp[i] = 1 + listSigns[i] = 1 } err := listG1[n].SetBytes(aggSig) @@ -409,6 +415,6 @@ func VerifyAggregate[K KeyGroup](pubs []*PublicKey[K], msgs [][]byte, aggSig Sig panic(ErrInvalid) } - C := GG.ProdPairFrac(listG1, listG2, listExp) + C := GG.ProdPairFrac(listG1, listG2, listSigns) return C.IsIdentity() }