crypto: ed25519 & sr25519 batch verification (#6120)

Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
This commit is contained in:
Marko
2021-03-15 10:58:49 +00:00
committed by GitHub
co-authored by Aleksandr Bezobchuk Anton Kaliaev
parent bf8cce83db
commit 6ffdf181f2
15 changed files with 553 additions and 68 deletions
+22
View File
@@ -0,0 +1,22 @@
package batch
import (
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/sr25519"
)
// CreateBatchVerifier checks if a key type implements the batch verifier interface.
// Currently only ed25519 & sr25519 supports batch verification.
func CreateBatchVerifier(pk crypto.PubKey) (crypto.BatchVerifier, bool) {
switch pk.Type() {
case ed25519.KeyType:
return ed25519.NewBatchVerifier(), true
case sr25519.KeyType:
return sr25519.NewBatchVerifier(), true
}
// case where the key does not support batch verification
return nil, false
}
+10
View File
@@ -40,3 +40,13 @@ type Symmetric interface {
Encrypt(plaintext []byte, secret []byte) (ciphertext []byte)
Decrypt(ciphertext []byte, secret []byte) (plaintext []byte, err error)
}
// If a new key type implements batch verification,
// the key type must be registered in github.com/tendermint/tendermint/crypto/batch
type BatchVerifier interface {
// Add appends an entry into the BatchVerifier.
Add(key PubKey, message, signature []byte) error
// Verify verifies all the entries in the BatchVerifier.
// If the verification fails it is unknown which entry failed and each entry will need to be verified individually.
Verify() bool
}
+26
View File
@@ -1,9 +1,11 @@
package ed25519
import (
"fmt"
"io"
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/internal/benchmarking"
)
@@ -24,3 +26,27 @@ func BenchmarkVerification(b *testing.B) {
priv := GenPrivKey()
benchmarking.BenchmarkVerification(b, priv)
}
func BenchmarkVerifyBatch(b *testing.B) {
for _, sigsCount := range []int{1, 8, 64, 1024} {
sigsCount := sigsCount
b.Run(fmt.Sprintf("sig-count-%d", sigsCount), func(b *testing.B) {
b.ReportAllocs()
v := NewBatchVerifier()
for i := 0; i < sigsCount; i++ {
priv := GenPrivKey()
pub := priv.PubKey()
msg := []byte("BatchVerifyTest")
sig, _ := priv.Sign(msg)
err := v.Add(pub, msg, sig)
require.NoError(b, err)
}
// NOTE: dividing by n so that metrics are per-signature
for i := 0; i < b.N/sigsCount; i++ {
if !v.Verify() {
b.Fatal("signature set failed batch verification")
}
}
})
}
}
+32
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"crypto/ed25519"
"crypto/subtle"
"errors"
"fmt"
"io"
@@ -170,3 +171,34 @@ func (pubKey PubKey) Equals(other crypto.PubKey) bool {
return false
}
var _ crypto.BatchVerifier = &BatchVerifier{}
// BatchVerifier implements batch verification for ed25519.
// https://github.com/hdevalence/ed25519consensus is used for batch verification
type BatchVerifier struct {
ed25519consensus.BatchVerifier
}
func NewBatchVerifier() crypto.BatchVerifier {
return &BatchVerifier{ed25519consensus.NewBatchVerifier()}
}
func (b *BatchVerifier) Add(key crypto.PubKey, msg, signature []byte) error {
if l := len(key.Bytes()); l != PubKeySize {
return fmt.Errorf("pubkey size is incorrect; expected: %d, got %d", PubKeySize, l)
}
// check that the signature is the correct length & the last byte is set correctly
if len(signature) != SignatureSize || signature[63]&224 != 0 {
return errors.New("invalid signature")
}
b.BatchVerifier.Add(ed25519.PublicKey(key.Bytes()), msg, signature)
return nil
}
func (b *BatchVerifier) Verify() bool {
return b.BatchVerifier.Verify()
}
+24
View File
@@ -28,3 +28,27 @@ func TestSignAndValidateEd25519(t *testing.T) {
assert.False(t, pubKey.VerifySignature(msg, sig))
}
func TestBatchSafe(t *testing.T) {
v := ed25519.NewBatchVerifier()
for i := 0; i <= 38; i++ {
priv := ed25519.GenPrivKey()
pub := priv.PubKey()
var msg []byte
if i%2 == 0 {
msg = []byte("easter")
} else {
msg = []byte("egg")
}
sig, err := priv.Sign(msg)
require.NoError(t, err)
err = v.Add(pub, msg, sig)
require.NoError(t, err)
}
require.True(t, v.Verify())
}
+42
View File
@@ -0,0 +1,42 @@
package sr25519
import (
"fmt"
schnorrkel "github.com/ChainSafe/go-schnorrkel"
"github.com/tendermint/tendermint/crypto"
)
var _ crypto.BatchVerifier = BatchVerifier{}
// BatchVerifier implements batch verification for sr25519.
// https://github.com/ChainSafe/go-schnorrkel is used for batch verification
type BatchVerifier struct {
*schnorrkel.BatchVerifier
}
func NewBatchVerifier() crypto.BatchVerifier {
return BatchVerifier{schnorrkel.NewBatchVerifier()}
}
func (b BatchVerifier) Add(key crypto.PubKey, msg, sig []byte) error {
var sig64 [SignatureSize]byte
copy(sig64[:], sig)
signature := new(schnorrkel.Signature)
err := signature.Decode(sig64)
if err != nil {
return fmt.Errorf("unable to decode signature: %w", err)
}
signingContext := schnorrkel.NewSigningContext([]byte{}, msg)
var pk [PubKeySize]byte
copy(pk[:], key.Bytes())
return b.BatchVerifier.Add(signingContext, signature, schnorrkel.NewPublicKey(pk))
}
func (b BatchVerifier) Verify() bool {
return b.BatchVerifier.Verify()
}
+26
View File
@@ -1,9 +1,11 @@
package sr25519
import (
"fmt"
"io"
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/internal/benchmarking"
)
@@ -24,3 +26,27 @@ func BenchmarkVerification(b *testing.B) {
priv := GenPrivKey()
benchmarking.BenchmarkVerification(b, priv)
}
func BenchmarkVerifyBatch(b *testing.B) {
for _, n := range []int{1, 8, 64, 1024} {
n := n
b.Run(fmt.Sprintf("sig-count-%d", n), func(b *testing.B) {
b.ReportAllocs()
v := NewBatchVerifier()
for i := 0; i < n; i++ {
priv := GenPrivKey()
pub := priv.PubKey()
msg := []byte("BatchVerifyTest")
sig, _ := priv.Sign(msg)
err := v.Add(pub, msg, sig)
require.NoError(b, err)
}
// NOTE: dividing by n so that metrics are per-signature
for i := 0; i < b.N/n; i++ {
if !v.Verify() {
b.Fatal("signature set failed batch verification")
}
}
})
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ func (privKey PrivKey) Equals(other crypto.PrivKey) bool {
}
func (privKey PrivKey) Type() string {
return keyType
return KeyType
}
// GenPrivKey generates a new sr25519 private key.
+2 -2
View File
@@ -15,7 +15,7 @@ var _ crypto.PubKey = PubKey{}
// PubKeySize is the number of bytes in an Sr25519 public key.
const (
PubKeySize = 32
keyType = "sr25519"
KeyType = "sr25519"
)
// PubKeySr25519 implements crypto.PubKey for the Sr25519 signature scheme.
@@ -72,6 +72,6 @@ func (pubKey PubKey) Equals(other crypto.PubKey) bool {
}
func (pubKey PubKey) Type() string {
return keyType
return KeyType
}
+25
View File
@@ -29,3 +29,28 @@ func TestSignAndValidateSr25519(t *testing.T) {
assert.False(t, pubKey.VerifySignature(msg, sig))
}
func TestBatchSafe(t *testing.T) {
v := sr25519.NewBatchVerifier()
for i := 0; i <= 38; i++ {
priv := sr25519.GenPrivKey()
pub := priv.PubKey()
var msg []byte
if i%2 == 0 {
msg = []byte("easter")
} else {
msg = []byte("egg")
}
sig, err := priv.Sign(msg)
require.NoError(t, err)
err = v.Add(pub, msg, sig)
require.NoError(t, err)
}
if !v.Verify() {
t.Error("failed batch verification")
}
}