From 2bd0a6527e96f4000f2dfe1ac7ed76a67cb194e9 Mon Sep 17 00:00:00 2001 From: Daniel Cason Date: Tue, 19 Apr 2022 14:41:32 +0200 Subject: [PATCH] BLS library from Ostracon (Tendermint fork) * Files retrieved from Ostracon implementation on Github, master branch, commit 051475802c94f46763cae4ef4c18e01d4cfa952e. --- crypto/bls/go.mod | 6 + crypto/bls/ostracon/bls.go | 233 ++++++++++++++++++++ crypto/bls/ostracon/bls_test.go | 373 ++++++++++++++++++++++++++++++++ 3 files changed, 612 insertions(+) create mode 100644 crypto/bls/ostracon/bls.go create mode 100644 crypto/bls/ostracon/bls_test.go diff --git a/crypto/bls/go.mod b/crypto/bls/go.mod index 744d12516..5f47b9690 100644 --- a/crypto/bls/go.mod +++ b/crypto/bls/go.mod @@ -3,13 +3,19 @@ module github.com/tendermint/tendermint/crypto/bls go 1.17 require ( + github.com/herumi/bls-eth-go-binary v0.0.0-20220216073600-600054663ec1 + github.com/line/ostracon v1.0.4 + github.com/stretchr/testify v1.7.1 github.com/supranational/blst v0.3.7 github.com/tendermint/tendermint v0.35.4 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/crypto/bls/ostracon/bls.go b/crypto/bls/ostracon/bls.go new file mode 100644 index 000000000..48ba34924 --- /dev/null +++ b/crypto/bls/ostracon/bls.go @@ -0,0 +1,233 @@ +package bls + +import ( + "bytes" + "crypto/sha512" + "crypto/subtle" + "fmt" + + tmjson "github.com/line/ostracon/libs/json" + + "github.com/herumi/bls-eth-go-binary/bls" + + "github.com/line/ostracon/crypto" + "github.com/line/ostracon/crypto/tmhash" +) + +var _ crypto.PrivKey = PrivKey{} + +const ( + PrivKeyName = "ostracon/PrivKeyBLS12" + PubKeyName = "ostracon/PubKeyBLS12" + PrivKeySize = 32 + PubKeySize = 48 + SignatureSize = 96 + KeyType = "bls12-381" +) + +func init() { + tmjson.RegisterType(PubKey{}, PubKeyName) + tmjson.RegisterType(PrivKey{}, PrivKeyName) + + err := bls.Init(bls.BLS12_381) + if err != nil { + panic(fmt.Sprintf("ERROR: %s", err)) + } + err = bls.SetETHmode(bls.EthModeLatest) + if err != nil { + panic(fmt.Sprintf("ERROR: %s", err)) + } +} + +// PrivKey implements crypto.PrivKey. +type PrivKey [PrivKeySize]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 []PubKey, 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 + } + 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() PrivKey { + sigKey := bls.SecretKey{} + sigKey.SetByCSPRNG() + sigKeyBinary := PrivKey{} + binary := sigKey.Serialize() + if len(binary) != PrivKeySize { + panic(fmt.Sprintf("unexpected BLS private key size: %d != %d", len(binary), PrivKeySize)) + } + copy(sigKeyBinary[:], binary) + return sigKeyBinary +} + +// Bytes marshals the privkey using amino encoding. +func (privKey PrivKey) Bytes() []byte { + return privKey[:] +} + +// Sign produces a signature on the provided message. +func (privKey PrivKey) Sign(msg []byte) ([]byte, error) { + if msg == nil { + panic("Nil specified as the message") + } + blsKey := bls.SecretKey{} + err := blsKey.Deserialize(privKey[:]) + if err != nil { + return nil, err + } + hash := sha512.Sum512_256(msg) + sign := blsKey.SignHash(hash[:]) + return sign.Serialize(), nil +} + +// VRFProve is not supported in BLS12. +func (privKey PrivKey) VRFProve(seed []byte) (crypto.Proof, error) { + return nil, fmt.Errorf("VRF prove is not supported by the BLS12") +} + +// PubKey gets the corresponding public key from the private key. +func (privKey PrivKey) PubKey() crypto.PubKey { + blsKey := bls.SecretKey{} + err := blsKey.Deserialize(privKey[:]) + if err != nil { + panic(fmt.Sprintf("Not a BLS12-381 private key: %X", privKey[:])) + } + pubKey := blsKey.GetPublicKey() + pubKeyBinary := PubKey{} + binary := pubKey.Serialize() + if len(binary) != PubKeySize { + panic(fmt.Sprintf("unexpected BLS public key size: %d != %d", len(binary), PubKeySize)) + } + copy(pubKeyBinary[:], binary) + return pubKeyBinary +} + +// Equals - you probably don't need to use this. +// Runs in constant time based on length of the keys. +func (privKey PrivKey) Equals(other crypto.PrivKey) bool { + if otherEd, ok := other.(PrivKey); ok { + return subtle.ConstantTimeCompare(privKey[:], otherEd[:]) == 1 + } + return false +} + +// Type returns information to identify the type of this key. +func (privKey PrivKey) Type() string { + return KeyType +} + +var _ crypto.PubKey = PubKey{} + +// PubKey implements crypto.PubKey for the BLS12-381 signature scheme. +type PubKey [PubKeySize]byte + +// Address is the SHA256-20 of the raw pubkey bytes. +func (pubKey PubKey) Address() crypto.Address { + return tmhash.SumTruncated(pubKey[:]) +} + +// Bytes marshals the PubKey using amino encoding. +func (pubKey PubKey) Bytes() []byte { + return pubKey[:] +} + +func (pubKey PubKey) VerifySignature(msg []byte, sig []byte) bool { + // make sure we use the same algorithm to sign + if len(sig) != SignatureSize { + return false + } + blsPubKey := bls.PublicKey{} + err := blsPubKey.Deserialize(pubKey[:]) + if err != nil { + return false + } + blsSign := bls.Sign{} + err = blsSign.Deserialize(sig) + if err != nil { + fmt.Printf("Signature Deserialize failed: %s", err) + return false + } + hash := sha512.Sum512_256(msg) + return blsSign.VerifyHash(&blsPubKey, hash[:]) +} + +// VRFVerify is not supported in BLS12. +func (pubKey PubKey) VRFVerify(proof crypto.Proof, seed []byte) (crypto.Output, error) { + return nil, fmt.Errorf("VRF verify is not supported by the BLS12") +} + +func (pubKey PubKey) String() string { + return fmt.Sprintf("PubKeyBLS12{%X}", pubKey[:]) +} + +func (pubKey PubKey) Equals(other crypto.PubKey) bool { + if otherEd, ok := other.(PubKey); ok { + return bytes.Equal(pubKey[:], otherEd[:]) + } + return false +} + +// Type returns information to identify the type of this key. +func (pubKey PubKey) Type() string { + return KeyType +} diff --git a/crypto/bls/ostracon/bls_test.go b/crypto/bls/ostracon/bls_test.go new file mode 100644 index 000000000..702325c01 --- /dev/null +++ b/crypto/bls/ostracon/bls_test.go @@ -0,0 +1,373 @@ +package bls_test + +import ( + "bytes" + "crypto/sha256" + "fmt" + "math/rand" + "reflect" + "testing" + "time" + + b "github.com/herumi/bls-eth-go-binary/bls" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/line/ostracon/crypto" + "github.com/line/ostracon/crypto/bls" +) + +func TestBasicSignatureFunctions(t *testing.T) { + privateKey := b.SecretKey{} + privateKey.SetByCSPRNG() + publicKey := privateKey.GetPublicKey() + + duplicatedPrivateKey := b.SecretKey{} + err := duplicatedPrivateKey.Deserialize(privateKey.Serialize()) + if err != nil { + t.Fatalf("Private key deserialization failed.") + } + + if len(privateKey.Serialize()) != bls.PrivKeySize { + t.Fatalf("The constant size %d of the private key is different from the actual size %d.", + bls.PrivKeySize, len(privateKey.Serialize())) + } + + duplicatedPublicKey := b.PublicKey{} + err = duplicatedPublicKey.Deserialize(publicKey.Serialize()) + if err != nil { + t.Fatalf("Public key deserialization failed.") + } + + if len(publicKey.Serialize()) != bls.PubKeySize { + t.Fatalf("The constant size %d of the public key is different from the actual size %d.", + bls.PubKeySize, len(publicKey.Serialize())) + } + + duplicatedSignature := func(sig *b.Sign) *b.Sign { + duplicatedSign := b.Sign{} + err := duplicatedSign.Deserialize(sig.Serialize()) + if err != nil { + t.Fatalf("Signature deserialization failed.") + } + + if len(sig.Serialize()) != bls.SignatureSize { + t.Fatalf("The constant size %d of the signature is different from the actual size %d.", + bls.SignatureSize, len(sig.Serialize())) + } + return &duplicatedSign + } + + msg := []byte("hello, world") + for _, privKey := range []b.SecretKey{privateKey, duplicatedPrivateKey} { + for _, pubKey := range []*b.PublicKey{publicKey, &duplicatedPublicKey} { + signature := privKey.SignByte(msg) + if !signature.VerifyByte(pubKey, msg) { + t.Errorf("Signature verification failed.") + } + + if !duplicatedSignature(signature).VerifyByte(pubKey, msg) { + t.Errorf("Signature verification failed.") + } + + for i := 0; i < len(msg); i++ { + for j := 0; j < 8; j++ { + garbled := make([]byte, len(msg)) + copy(garbled, msg) + garbled[i] ^= 1 << (8 - j - 1) + if bytes.Equal(msg, garbled) { + t.Fatalf("Not a barbled message") + } + if signature.VerifyByte(pubKey, garbled) { + t.Errorf("Signature verification was successful against a garbled byte sequence.") + } + if duplicatedSignature(signature).VerifyByte(pubKey, garbled) { + t.Errorf("Signature verification was successful against a garbled byte sequence.") + } + } + } + } + } +} + +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.PubKey, [][]byte, [][]byte) { + pubKeys := make([]bls.PubKey, 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].VerifySignature(msgs[i], sigs[i]) { + t.Fatal("fail to verify signature") + } + } + return pubKeys, msgs, sigs +} + +func blsPublicKey(t *testing.T, pubKey crypto.PubKey) bls.PubKey { + blsPubKey, ok := pubKey.(bls.PubKey) + if !ok { + var keyType string + if t := reflect.TypeOf(pubKey); t.Kind() == reflect.Ptr { + keyType = "*" + t.Elem().Name() + } else { + keyType = t.Name() + } + t.Fatal(fmt.Sprintf("specified public key is not for BLS: %s", keyType)) + } + 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.Errorf("fail to aggregate BLS signatures: %s", err) + } + if len(aggrSig) != bls.SignatureSize { + t.Errorf("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.PubKey, 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.PubKey, 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.PubKey) + replacedPubKeys := make([]bls.PubKey, 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.PubKey, 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{} + aggregatedPublicKey := b.PublicKey{} + msg := []byte("hello, world") + for i := 0; i < len(publicKeys); i++ { + privateKey := b.SecretKey{} + privateKey.SetByCSPRNG() + publicKeys[i] = *privateKey.GetPublicKey() + aggregatedSignature.Add(privateKey.SignByte(msg)) + aggregatedPublicKey.Add(&publicKeys[i]) + } + + if !aggregatedSignature.FastAggregateVerify(publicKeys, msg) { + t.Errorf("Aggregated signature verification failed.") + } + if !aggregatedSignature.VerifyByte(&aggregatedPublicKey, msg) { + t.Errorf("Aggregated signature verification failed.") + } +} + +func TestSignAndValidateBLS12(t *testing.T) { + privKey := bls.GenPrivKey() + pubKey := privKey.PubKey() + + msg := crypto.CRandBytes(128) + sig, err := privKey.Sign(msg) + require.Nil(t, err) + fmt.Printf("restoring signature: %x\n", sig) + + // Test the signature + assert.True(t, pubKey.VerifySignature(msg, sig)) + + // Mutate the signature, just one bit. + // TODO: Replace this with a much better fuzzer, tendermint/ed25519/issues/10 + sig[7] ^= byte(0x01) + + assert.False(t, pubKey.VerifySignature(msg, sig)) +}