Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

feature: Use curve25519-voi #66

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi
- [mempool] \#6466 The original mempool reactor has been versioned as `v0` and moved to a sub-package under the root `mempool` package.
Some core types have been kept in the `mempool` package such as `TxCache` and it's implementations, the `Mempool` interface itself
and `TxInfo`. (@alexanderbez)
- [crypto/sr25519] \#6526 Do not re-execute the Ed25519-style key derivation step when doing signing and verification. The derivation is now done once and only once. This breaks `sr25519.GenPrivKeyFromSecret` output compatibility. (@Yawning)

- Blockchain Protocol

Expand Down Expand Up @@ -95,6 +96,8 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi
- [statesync] \#6566 Allow state sync fetchers and request timeout to be configurable. (@alexanderbez)
- [types] \#6478 Add `block_id` to `newblock` event (@jeebster)
- [crypto/ed25519] \#5632 Adopt zip215 `ed25519` verification. (@marbar3778)
- [crypto/ed25519] \#6526 Use [curve25519-voi](https://github.com/oasisprotocol/curve25519-voi) for `ed25519` signing and verification. (@Yawning)
- [crypto/sr25519] \#6526 Use [curve25519-voi](https://github.com/oasisprotocol/curve25519-voi) for `sr25519` signing and verification. (@Yawning)
- [privval] \#5603 Add `--key` to `init`, `gen_validator`, `testnet` & `unsafe_reset_priv_validator` for use in generating `secp256k1` keys.
- [privval] \#5725 Add gRPC support to private validator.
- [privval] \#5876 `tendermint show-validator` will query the remote signer if gRPC is being used (@marbar3778)
Expand Down
11 changes: 11 additions & 0 deletions crypto/batch/batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,14 @@ func CreateBatchVerifier(pk crypto.PubKey) (crypto.BatchVerifier, bool) {
// case where the key does not support batch verification
return nil, false
}

// SupportsBatchVerifier checks if a key type implements the batch verifier
// interface.
func SupportsBatchVerifier(pk crypto.PubKey) bool {
switch pk.Type() {
case ed25519.KeyType, sr25519.KeyType:
return true
}

return false
}
8 changes: 5 additions & 3 deletions crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ type Symmetric interface {
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
// Verify verifies all the entries in the BatchVerifier, and returns
// if every signature in the batch is valid, and a vector of bools
// indicating the verification status of each signature (in the order
// that signatures were added to the batch).
Verify() (bool, []bool)
}
29 changes: 22 additions & 7 deletions crypto/ed25519/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,37 @@ func BenchmarkVerification(b *testing.B) {
}

func BenchmarkVerifyBatch(b *testing.B) {
msg := []byte("BatchVerifyTest")

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()
// Pre-generate all of the keys, and signatures, but do not
// benchmark key-generation and signing.
pubs := make([]crypto.PubKey, 0, sigsCount)
sigs := make([][]byte, 0, sigsCount)
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)
pubs = append(pubs, priv.PubKey().(PubKey))
sigs = append(sigs, sig)
}
b.ResetTimer()

b.ReportAllocs()
// NOTE: dividing by n so that metrics are per-signature
for i := 0; i < b.N/sigsCount; i++ {
if !v.Verify() {
// The benchmark could just benchmark the Verify()
// routine, but there is non-trivial overhead associated
// with BatchVerifier.Add(), which should be included
// in the benchmark.
v := NewBatchVerifier()
for i := 0; i < sigsCount; i++ {
err := v.Add(pubs[i], msg, sigs[i])
require.NoError(b, err)
}

if ok, _ := v.Verify(); !ok {
b.Fatal("signature set failed batch verification")
}
}
Expand Down
57 changes: 40 additions & 17 deletions crypto/ed25519/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package ed25519

import (
"bytes"
"crypto/ed25519"
"crypto/subtle"
"errors"
"fmt"
"io"

"github.com/hdevalence/ed25519consensus"
"github.com/oasisprotocol/curve25519-voi/primitives/ed25519"
"github.com/oasisprotocol/curve25519-voi/primitives/ed25519/extra/cache"

"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/tmhash"
Expand All @@ -17,7 +17,18 @@ import (

//-------------------------------------

var _ crypto.PrivKey = PrivKey{}
var (
_ crypto.PrivKey = PrivKey{}

// curve25519-voi's Ed25519 implementation supports configurable
// verification behavior, and tendermint uses the ZIP-215 verification
// semantics.
verifyOptions = &ed25519.Options{
Verify: ed25519.VerifyOptionsZIP_215,
}

cachingVerifier = cache.NewVerifier(cache.NewLRUCache(cacheSize))
)

const (
PrivKeyName = "tendermint/PrivKeyEd25519"
Expand All @@ -34,6 +45,14 @@ const (
SeedSize = 32

KeyType = "ed25519"

// cacheSize is the number of public keys that will be cached in
// an expanded format for repeated signature verification.
//
// TODO/perf: Either this should exclude single verification, or be
// tuned to `> validatorSize + maxTxnsPerBlock` to avoid cache
// thrashing.
cacheSize = 4096
)

func init() {
Expand Down Expand Up @@ -107,14 +126,12 @@ func GenPrivKey() PrivKey {

// genPrivKey generates a new ed25519 private key using the provided reader.
func genPrivKey(rand io.Reader) PrivKey {
seed := make([]byte, SeedSize)

_, err := io.ReadFull(rand, seed)
_, priv, err := ed25519.GenerateKey(rand)
if err != nil {
panic(err)
}

return PrivKey(ed25519.NewKeyFromSeed(seed))
return PrivKey(priv)
}

// GenPrivKeyFromSecret hashes the secret with SHA2, and uses
Expand Down Expand Up @@ -153,7 +170,7 @@ func (pubKey PubKey) VerifySignature(msg []byte, sig []byte) bool {
return false
}

return ed25519consensus.Verify(ed25519.PublicKey(pubKey), msg, sig)
return cachingVerifier.VerifyWithOptions(ed25519.PublicKey(pubKey), msg, sig, verifyOptions)
}

func (pubKey PubKey) String() string {
Expand All @@ -175,30 +192,36 @@ func (pubKey PubKey) Equals(other crypto.PubKey) bool {
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
*ed25519.BatchVerifier
}

func NewBatchVerifier() crypto.BatchVerifier {
return &BatchVerifier{ed25519consensus.NewBatchVerifier()}
return &BatchVerifier{ed25519.NewBatchVerifier()}
}

func (b *BatchVerifier) Add(key crypto.PubKey, msg, signature []byte) error {
if l := len(key.Bytes()); l != PubKeySize {
pkEd, ok := key.(PubKey)
if !ok {
return fmt.Errorf("pubkey is not Ed25519")
}

pkBytes := pkEd.Bytes()

if l := len(pkBytes); 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 {
// check that the signature is the correct length
if len(signature) != SignatureSize {
return errors.New("invalid signature")
}

b.BatchVerifier.Add(ed25519.PublicKey(key.Bytes()), msg, signature)
cachingVerifier.AddWithOptions(b.BatchVerifier, ed25519.PublicKey(pkBytes), msg, signature, verifyOptions)

return nil
}

func (b *BatchVerifier) Verify() bool {
return b.BatchVerifier.Verify()
func (b *BatchVerifier) Verify() (bool, []bool) {
return b.BatchVerifier.Verify(crypto.CReader())
}
3 changes: 2 additions & 1 deletion crypto/ed25519/ed25519_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ func TestBatchSafe(t *testing.T) {
require.NoError(t, err)
}

require.True(t, v.Verify())
ok, _ := v.Verify()
require.True(t, ok)
}
40 changes: 22 additions & 18 deletions crypto/sr25519/batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,44 @@ package sr25519
import (
"fmt"

schnorrkel "github.com/ChainSafe/go-schnorrkel"
"github.com/oasisprotocol/curve25519-voi/primitives/sr25519"

"github.com/tendermint/tendermint/crypto"
)

var _ crypto.BatchVerifier = BatchVerifier{}
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
*sr25519.BatchVerifier
}

func NewBatchVerifier() crypto.BatchVerifier {
return BatchVerifier{schnorrkel.NewBatchVerifier()}
return &BatchVerifier{sr25519.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)
func (b *BatchVerifier) Add(key crypto.PubKey, msg, signature []byte) error {
pk, ok := key.(PubKey)
if !ok {
return fmt.Errorf("sr25519: pubkey is not sr25519")
}

signingContext := schnorrkel.NewSigningContext([]byte{}, msg)
var srpk sr25519.PublicKey
if err := srpk.UnmarshalBinary(pk); err != nil {
return fmt.Errorf("sr25519: invalid public key: %w", err)
}

var sig sr25519.Signature
if err := sig.UnmarshalBinary(signature); err != nil {
return fmt.Errorf("sr25519: unable to decode signature: %w", err)
}

var pk [PubKeySize]byte
copy(pk[:], key.Bytes())
st := signingCtx.NewTranscriptBytes(msg)
b.BatchVerifier.Add(&srpk, st, &sig)

return b.BatchVerifier.Add(signingContext, signature, schnorrkel.NewPublicKey(pk))
return nil
}

func (b BatchVerifier) Verify() bool {
return b.BatchVerifier.Verify()
func (b *BatchVerifier) Verify() (bool, []bool) {
return b.BatchVerifier.Verify(crypto.CReader())
}
39 changes: 27 additions & 12 deletions crypto/sr25519/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,37 @@ func BenchmarkVerification(b *testing.B) {
}

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++ {
msg := []byte("BatchVerifyTest")

for _, sigsCount := range []int{1, 8, 64, 1024} {
sigsCount := sigsCount
b.Run(fmt.Sprintf("sig-count-%d", sigsCount), func(b *testing.B) {
// Pre-generate all of the keys, and signatures, but do not
// benchmark key-generation and signing.
pubs := make([]crypto.PubKey, 0, sigsCount)
sigs := make([][]byte, 0, sigsCount)
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)
pubs = append(pubs, priv.PubKey().(PubKey))
sigs = append(sigs, sig)
}
b.ResetTimer()

b.ReportAllocs()
// NOTE: dividing by n so that metrics are per-signature
for i := 0; i < b.N/n; i++ {
if !v.Verify() {
for i := 0; i < b.N/sigsCount; i++ {
// The benchmark could just benchmark the Verify()
// routine, but there is non-trivial overhead associated
// with BatchVerifier.Add(), which should be included
// in the benchmark.
v := NewBatchVerifier()
for i := 0; i < sigsCount; i++ {
err := v.Add(pubs[i], msg, sigs[i])
require.NoError(b, err)
}

if ok, _ := v.Verify(); !ok {
b.Fatal("signature set failed batch verification")
}
}
Expand Down
12 changes: 1 addition & 11 deletions crypto/sr25519/encoding.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
package sr25519

import (
"github.com/tendermint/tendermint/crypto"
tmjson "github.com/tendermint/tendermint/libs/json"
)

var _ crypto.PrivKey = PrivKey{}
import tmjson "github.com/tendermint/tendermint/libs/json"

const (
PrivKeyName = "tendermint/PrivKeySr25519"
PubKeyName = "tendermint/PubKeySr25519"

// SignatureSize is the size of an Edwards25519 signature. Namely the size of a compressed
// Sr25519 point, and a field element. Both of which are 32 bytes.
SignatureSize = 64
)

func init() {

tmjson.RegisterType(PubKey{}, PubKeyName)
tmjson.RegisterType(PrivKey{}, PrivKeyName)
}
Loading