Skip to content

Commit

Permalink
added signature aggregation and verify to block
Browse files Browse the repository at this point in the history
  • Loading branch information
torao committed Sep 3, 2020
1 parent 5db66a2 commit 4242d3d
Show file tree
Hide file tree
Showing 9 changed files with 573 additions and 100 deletions.
73 changes: 71 additions & 2 deletions crypto/bls/bls.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bls

import (
"bytes"
"crypto/sha512"
"crypto/subtle"
"fmt"

Expand Down Expand Up @@ -46,6 +47,72 @@ func init() {
// PrivKeyBLS12 implements crypto.PrivKey.
type PrivKeyBLS12 [PrivKeyBLS12Size]byte

// AddSignature adds a BLS signature to the init. When the init is nil, then a new aggregate signature is built
// from specified signature.
func AddSignature(init []byte, signature []byte) (aggrSign []byte, err error) {
if init == nil {
blsSign := bls.Sign{}
init = blsSign.Serialize()
} else if len(init) != SignatureSize {
err = fmt.Errorf("invalid BLS signature: aggregated signature size %d is not valid size %d",
len(init), SignatureSize)
return
}
if len(signature) != SignatureSize {
err = fmt.Errorf("invalid BLS signature: signature size %d is not valid size %d",
len(signature), SignatureSize)
return
}
blsSign := bls.Sign{}
err = blsSign.Deserialize(signature)
if err != nil {
return
}
aggrBLSSign := bls.Sign{}
err = aggrBLSSign.Deserialize(init)
if err != nil {
return
}
aggrBLSSign.Add(&blsSign)
aggrSign = aggrBLSSign.Serialize()
return
}

func VerifyAggregatedSignature(aggregatedSignature []byte, pubKeys []PubKeyBLS12, msgs [][]byte) error {
if len(pubKeys) != len(msgs) {
return fmt.Errorf("the number of public keys %d doesn't match the one of messages %d",
len(pubKeys), len(msgs))
}
if aggregatedSignature == nil {
if len(pubKeys) == 0 {
return nil
} else {
return fmt.Errorf(
"the aggregate signature was omitted, even though %d public keys were specified", len(pubKeys))
}
}
aggrSign := bls.Sign{}
err := aggrSign.Deserialize(aggregatedSignature)
if err != nil {
return err
}
blsPubKeys := make([]bls.PublicKey, len(pubKeys))
hashes := make([][]byte, len(msgs))
for i := 0; i < len(pubKeys); i++ {
blsPubKeys[i] = bls.PublicKey{}
err = blsPubKeys[i].Deserialize(pubKeys[i][:])
if err != nil {
return err
}
hash := sha512.Sum512_256(msgs[i])
hashes[i] = hash[:]
}
if !aggrSign.VerifyAggregateHashes(blsPubKeys, hashes) {
return fmt.Errorf("failed to verify the aggregated hashes by %d public keys", len(blsPubKeys))
}
return nil
}

// GenPrivKey generates a new BLS12-381 private key.
func GenPrivKey() PrivKeyBLS12 {
sigKey := bls.SecretKey{}
Expand Down Expand Up @@ -74,7 +141,8 @@ func (privKey PrivKeyBLS12) Sign(msg []byte) ([]byte, error) {
if err != nil {
panic(fmt.Sprintf("Failed to copy the private key: %s", err))
}
sign := blsKey.SignByte(msg)
hash := sha512.Sum512_256(msg)
sign := blsKey.SignHash(hash[:])
return sign.Serialize(), nil
}

Expand Down Expand Up @@ -143,7 +211,8 @@ func (pubKey PubKeyBLS12) VerifyBytes(msg []byte, sig []byte) bool {
if err != nil {
return false
}
return blsSign.VerifyByte(&blsPubKey, msg)
hash := sha512.Sum512_256(msg)
return blsSign.VerifyHash(&blsPubKey, hash[:])
}

// VRFVerify is not supported in BLS12.
Expand Down
233 changes: 233 additions & 0 deletions crypto/bls/bls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package bls_test

import (
"bytes"
"crypto/sha256"
"fmt"
"math/rand"
"testing"
"time"

b "github.com/herumi/bls-eth-go-binary/bls"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -234,6 +237,236 @@ func TestBasicSignatureFunctions(t *testing.T) {
}
}

func TestSignatureAggregationAndVerify(t *testing.T) {
privKeys := make([]b.SecretKey, 25)
pubKeys := make([]b.PublicKey, len(privKeys))
msgs := make([][]byte, len(privKeys))
hash32s := make([][32]byte, len(privKeys))
signatures := make([]b.Sign, len(privKeys))
for i, privKey := range privKeys {
privKey.SetByCSPRNG()
pubKeys[i] = *privKey.GetPublicKey()
msgs[i] = []byte(fmt.Sprintf("hello, world #%d", i))
hash32s[i] = sha256.Sum256(msgs[i])
signatures[i] = *privKey.SignHash(hash32s[i][:])

// normal single-hash case
if !signatures[i].VerifyHash(&pubKeys[i], hash32s[i][:]) {
t.Fail()
}

// in case where 1-bit of hash was garbled
garbledHash := make([]byte, len(msgs[i]))
copy(garbledHash, msgs[i])
garbledHash[0] ^= 1 << 0
if garbledHash[0] == msgs[i][0] || signatures[i].VerifyByte(&pubKeys[i], garbledHash) {
t.Fail()
}

// Verification using an invalid public key
}

// aggregation
multiSig := b.Sign{}
multiSig.Aggregate(signatures)

// normal case
hashes := make([][]byte, len(privKeys))
for i := 0; i < len(hashes); i++ {
hashes[i] = hash32s[i][:]
}
if !multiSig.VerifyAggregateHashes(pubKeys, hashes) {
t.Fatalf("failed to validate the aggregate signature of the hashed message")
}

// in case where 1-bit of signature was garbled
multiSigBytes := multiSig.Serialize()
for i := range multiSigBytes {
for j := 0; j < 8; j++ {
garbledMultiSigBytes := make([]byte, len(multiSigBytes))
copy(garbledMultiSigBytes, multiSigBytes)
garbledMultiSigBytes[i] ^= 1 << j
if garbledMultiSigBytes[i] == multiSigBytes[i] {
t.Fail()
}
garbledMultiSig := b.Sign{}
err := garbledMultiSig.Deserialize(garbledMultiSigBytes)
if err == nil {
// Note that in some cases Deserialize() fails
if garbledMultiSig.VerifyAggregateHashes(pubKeys, hashes) {
t.Errorf("successfully verified the redacted signature")
}
}
}
}

// in case a public key used for verification is replaced
invalidPrivKey := b.SecretKey{}
invalidPrivKey.SetByCSPRNG()
invalidPubKeys := make([]b.PublicKey, len(pubKeys))
copy(invalidPubKeys, pubKeys)
invalidPubKeys[len(invalidPubKeys)-1] = *invalidPrivKey.GetPublicKey()
if multiSig.VerifyAggregateHashes(invalidPubKeys, hashes) {
t.Fatalf("successfully verified that it contains a public key that was not involved in the signing")
}

// in case a hash used for verification is replaced
invalidHashes := make([][]byte, len(hashes))
copy(invalidHashes, hashes)
invalidHash := sha256.Sum256([]byte("hello, world #99"))
invalidHashes[len(invalidHashes)-1] = invalidHash[:]
if multiSig.VerifyAggregateHashes(pubKeys, invalidHashes) {
t.Fatalf("successfully verified that it contains a hash that was not involved in the signing")
}
}

func generatePubKeysAndSigns(t *testing.T, size int) ([]bls.PubKeyBLS12, [][]byte, [][]byte) {
pubKeys := make([]bls.PubKeyBLS12, size)
msgs := make([][]byte, len(pubKeys))
sigs := make([][]byte, len(pubKeys))
for i := 0; i < len(pubKeys); i++ {
var err error
privKey := bls.GenPrivKey()
pubKeys[i] = blsPublicKey(t, privKey.PubKey())
msgs[i] = []byte(fmt.Sprintf("hello, workd #%d", i))
sigs[i], err = privKey.Sign(msgs[i])
if err != nil {
t.Fatal(fmt.Sprintf("fail to sign: %s", err))
}
if !pubKeys[i].VerifyBytes(msgs[i], sigs[i]) {
t.Fatal("fail to verify signature")
}
}
return pubKeys, msgs, sigs
}

func blsPublicKey(t *testing.T, pubKey crypto.PubKey) bls.PubKeyBLS12 {
blsPubKey, ok := pubKey.(bls.PubKeyBLS12)
if !ok {
t.Fatal(fmt.Sprintf("specified public key is not for BLS"))
}
return blsPubKey
}
func aggregateSignatures(init []byte, signatures [][]byte) (aggrSig []byte, err error) {
aggrSig = init
for _, sign := range signatures {
aggrSig, err = bls.AddSignature(aggrSig, sign)
if err != nil {
return
}
}
return
}

func TestAggregatedSignature(t *testing.T) {

// generate BLS signatures and public keys
pubKeys, msgs, sigs := generatePubKeysAndSigns(t, 25)

// aggregate signatures
aggrSig, err := aggregateSignatures(nil, sigs)
if err != nil {
t.Error(fmt.Sprintf("fail to aggregate BLS signatures: %s", err))
}
if len(aggrSig) != bls.SignatureSize {
t.Error(fmt.Sprintf("inconpatible signature size: %d != %d", len(aggrSig), bls.SignatureSize))
}

// validate the aggregated signature
if err := bls.VerifyAggregatedSignature(aggrSig, pubKeys, msgs); err != nil {
t.Errorf("fail to verify aggregated signature: %s", err)
}

// validate with the public keys and messages pair in random order
t.Run("Doesn't Depend on the Order of PublicKey-Message Pairs", func(t *testing.T) {
shuffledPubKeys := make([]bls.PubKeyBLS12, len(pubKeys))
shuffledMsgs := make([][]byte, len(msgs))
copy(shuffledPubKeys, pubKeys)
copy(shuffledMsgs, msgs)
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(shuffledPubKeys), func(i, j int) {
shuffledPubKeys[i], shuffledPubKeys[j] = shuffledPubKeys[j], shuffledPubKeys[i]
shuffledMsgs[i], shuffledMsgs[j] = shuffledMsgs[j], shuffledMsgs[i]
})
if err := bls.VerifyAggregatedSignature(aggrSig, shuffledPubKeys, shuffledMsgs); err != nil {
t.Errorf("fail to verify the aggregated signature with random order: %s", err)
}
})

// validate with the public keys in random order
t.Run("Incorrect Public Key Order", func(t *testing.T) {
shuffledPubKeys := make([]bls.PubKeyBLS12, len(pubKeys))
copy(shuffledPubKeys, pubKeys)
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(shuffledPubKeys), func(i, j int) { shuffledPubKeys[i], shuffledPubKeys[j] = shuffledPubKeys[j], shuffledPubKeys[i] })
if err := bls.VerifyAggregatedSignature(aggrSig, shuffledPubKeys, msgs); err == nil {
t.Error("successfully validated with public keys of different order")
}
})

// validate with the messages in random order
t.Run("Incorrect Message Order", func(t *testing.T) {
shuffledMsgs := make([][]byte, len(msgs))
copy(shuffledMsgs, msgs)
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(shuffledMsgs), func(i, j int) { shuffledMsgs[i], shuffledMsgs[j] = shuffledMsgs[j], shuffledMsgs[i] })
if err := bls.VerifyAggregatedSignature(aggrSig, pubKeys, shuffledMsgs); err == nil {
t.Error("successfully validated with messages of different order")
}
})

// replace one public key with another and detect
t.Run("Replace One Public Key", func(t *testing.T) {
pubKey, _ := bls.GenPrivKey().PubKey().(bls.PubKeyBLS12)
replacedPubKeys := make([]bls.PubKeyBLS12, len(pubKeys))
copy(replacedPubKeys, pubKeys)
replacedPubKeys[0] = pubKey
if err := bls.VerifyAggregatedSignature(aggrSig, replacedPubKeys, msgs); err == nil {
t.Error("verification with an invalid key was successful")
}
})

// replace one message with another and detect
t.Run("Replace One Message", func(t *testing.T) {
msg := []byte(fmt.Sprintf("hello, world #%d replaced", len(msgs)))
replacedMsgs := make([][]byte, len(msgs))
copy(replacedMsgs, msgs)
replacedMsgs[0] = msg
if err := bls.VerifyAggregatedSignature(aggrSig, pubKeys, replacedMsgs); err == nil {
t.Error("verification with an invalid message was successful")
}
})

// add new signature to existing aggregated signature and verify
t.Run("Incremental Update", func(t *testing.T) {
msg := []byte(fmt.Sprintf("hello, world #%d", len(msgs)))
privKey := bls.GenPrivKey()
pubKey := privKey.PubKey()
sig, err := privKey.Sign(msg)
assert.Nilf(t, err, "%s", err)
newAggrSig, _ := aggregateSignatures(aggrSig, [][]byte{sig})
newPubKeys := make([]bls.PubKeyBLS12, len(pubKeys))
copy(newPubKeys, pubKeys)
newPubKeys = append(newPubKeys, blsPublicKey(t, pubKey))
newMsgs := make([][]byte, len(msgs))
copy(newMsgs, msgs)
newMsgs = append(newMsgs, msg)
if err := bls.VerifyAggregatedSignature(newAggrSig, newPubKeys, newMsgs); err != nil {
t.Errorf("fail to verify the aggregate signature with the new signature: %s", err)
}
})

// nil is returned for nil and empty signature
nilSig, _ := aggregateSignatures(nil, [][]byte{})
assert.Nil(t, nilSig)

// a non-BLS signature contained
func() {
_, err = aggregateSignatures(nil, [][]byte{make([]byte, 0)})
assert.NotNil(t, err)
}()
}

func TestSignatureAggregation(t *testing.T) {
publicKeys := make([]b.PublicKey, 25)
aggregatedSignature := b.Sign{}
Expand Down
Loading

0 comments on commit 4242d3d

Please sign in to comment.