Skip to content

Commit 6f908eb

Browse files
committedOct 19, 2020
crypto: add in secp256k1 support (#5500)
Secp256k1 was removed in the protobuf migration, this pr adds it back in order to provide this functionality for users (band) Closes: #5495
1 parent b3238cd commit 6f908eb

16 files changed

+711
-100
lines changed
 

‎.markdownlintignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
docs/node_modules
22
CHANGELOG.md
33
docs/architecture/*
4+
crypto/secp256k1/**
45
scripts/*
56
.github

‎CHANGELOG.md

-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi
5151
- [evidence] [\#5319](https://github.com/tendermint/tendermint/issues/5319) Remove Amnesia & potentialAmnesia evidence types and removed POLC. (@marbar3778)
5252
- [evidence] [\#5361](https://github.com/tendermint/tendermint/pull/5361) Add LightClientAttackEvidence and change evidence interface (@cmwaters)
5353
- [params] [\#5319](https://github.com/tendermint/tendermint/issues/5319) Remove `ProofofTrialPeriod` from evidence params (@marbar3778)
54-
- [crypto/secp256k1] [\#5280](https://github.com/tendermint/tendermint/issues/5280) `secp256k1` has been removed from the Tendermint repo. (@marbar3778)
5554
- [light] [\#5347](https://github.com/tendermint/tendermint/issues/5347) `NewClient`, `NewHTTPClient`, `VerifyHeader` and `VerifyLightBlockAtHeight` now accept `context.Context` as 1st param (@melekes)
5655
- [state] [\#5348](https://github.com/tendermint/tendermint/issues/5348) Define an Interface for the state store. (@marbar3778)
5756

‎UPGRADING.md

+51-54
Original file line numberDiff line numberDiff line change
@@ -10,63 +10,63 @@ the encoding format (see "Protocol Buffers," below) and the block header (see "B
1010

1111
### ABCI Changes
1212

13-
* New ABCI methods (`ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk`)
14-
were added to support the new State Sync feature.
15-
Previously, syncing a new node to a preexisting network could take days; but with State Sync,
16-
new nodes are able to join a network in a matter of seconds.
17-
Read [the spec](https://docs.tendermint.com/master/spec/abci/apps.html#state-sync)
18-
if you want to learn more about State Sync, or if you'd like your application to use it.
19-
(If you don't want to support State Sync in your application, you can just implement these new
20-
ABCI methods as no-ops, leaving them empty.)
21-
22-
* `KV.Pair` has been replaced with `abci.EventAttribute`. The `EventAttribute.Index` field
13+
* New ABCI methods (`ListSnapshots`, `LoadSnapshotChunk`, `OfferSnapshot`, and `ApplySnapshotChunk`)
14+
were added to support the new State Sync feature.
15+
Previously, syncing a new node to a preexisting network could take days; but with State Sync,
16+
new nodes are able to join a network in a matter of seconds.
17+
Read [the spec](https://docs.tendermint.com/master/spec/abci/apps.html#state-sync)
18+
if you want to learn more about State Sync, or if you'd like your application to use it.
19+
(If you don't want to support State Sync in your application, you can just implement these new
20+
ABCI methods as no-ops, leaving them empty.)
21+
22+
* `KV.Pair` has been replaced with `abci.EventAttribute`. The `EventAttribute.Index` field
2323
allows ABCI applications to dictate which events should be indexed.
2424

25-
* The blockchain can now start from an arbitrary initial height,
25+
* The blockchain can now start from an arbitrary initial height,
2626
provided to the application via `RequestInitChain.InitialHeight`.
2727

28-
* ABCI evidence type is now an enum with two recognized types of evidence:
29-
`DUPLICATE_VOTE` and `LIGHT_CLIENT_ATTACK`.
30-
Applications should be able to handle these evidence types
28+
* ABCI evidence type is now an enum with two recognized types of evidence:
29+
`DUPLICATE_VOTE` and `LIGHT_CLIENT_ATTACK`.
30+
Applications should be able to handle these evidence types
3131
(i.e., through slashing or other accountability measures).
3232

33-
* The [`PublicKey` type](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/crypto/keys.proto#L13-L15)
34-
(used in ABCI as part of `ValidatorUpdate`) now uses a `oneof` protobuf type.
35-
Note that since Tendermint only supports ed25519 validator keys, there's only one
33+
* The [`PublicKey` type](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/crypto/keys.proto#L13-L15)
34+
(used in ABCI as part of `ValidatorUpdate`) now uses a `oneof` protobuf type.
35+
Note that since Tendermint only supports ed25519 validator keys, there's only one
3636
option in the `oneof`. For more, see "Protocol Buffers," below.
3737

38-
* The field `Proof`, on the ABCI type `ResponseQuery`, is now named `ProofOps`.
39-
For more, see "Crypto," below.
38+
* The field `Proof`, on the ABCI type `ResponseQuery`, is now named `ProofOps`.
39+
For more, see "Crypto," below.
4040

4141
### P2P Protocol
4242

4343
The default codec is now proto3, not amino. The schema files can be found in the `/proto`
44-
directory. For more, see "Protobuf," below.
44+
directory. For more, see "Protobuf," below.
4545

4646
### Blockchain Protocol
4747

48-
* `Header#LastResultsHash` previously was the root hash of a Merkle tree built from `ResponseDeliverTx(Code, Data)` responses.
48+
* `Header#LastResultsHash` previously was the root hash of a Merkle tree built from `ResponseDeliverTx(Code, Data)` responses.
4949
As of 0.34,`Header#LastResultsHash` is now the root hash of a Merkle tree built from:
5050
* `BeginBlock#Events`
5151
* Root hash of a Merkle tree built from `ResponseDeliverTx(Code, Data,
5252
GasWanted, GasUsed, Events)` responses
5353
* `BeginBlock#Events`
5454

5555
* Merkle hashes of empty trees previously returned nothing, but now return the hash of an empty input,
56-
to conform with [RFC-6962](https://tools.ietf.org/html/rfc6962).
56+
to conform with [RFC-6962](https://tools.ietf.org/html/rfc6962).
5757
This mainly affects `Header#DataHash`, `Header#LastResultsHash`, and
5858
`Header#EvidenceHash`, which are often empty. Non-empty hashes can also be affected, e.g. if their
5959
inputs depend on other (empty) Merkle hashes, giving different results.
6060

6161
### Transaction Indexing
6262

63-
Tendermint now relies on the application to tell it which transactions to index. This means that
64-
in the `config.toml`, generated by Tendermint, there is no longer a way to specify which
63+
Tendermint now relies on the application to tell it which transactions to index. This means that
64+
in the `config.toml`, generated by Tendermint, there is no longer a way to specify which
6565
transactions to index. `tx.height` & `tx.hash` will always be indexed when using the `kv` indexer.
6666

67-
Applications must now choose to either a) enable indexing for all transactions, or
67+
Applications must now choose to either a) enable indexing for all transactions, or
6868
b) allow node operators to decide which transactions to index.
69-
Applications can notify Tendermint to index a specific transaction by setting
69+
Applications can notify Tendermint to index a specific transaction by setting
7070
`Index: bool` to `true` in the Event Attribute:
7171

7272
```go
@@ -82,19 +82,19 @@ Applications can notify Tendermint to index a specific transaction by setting
8282

8383
### Protocol Buffers
8484

85-
Tendermint 0.34 replaces Amino with Protocol Buffers for encoding.
86-
This migration is extensive and results in a number of changes, however,
85+
Tendermint 0.34 replaces Amino with Protocol Buffers for encoding.
86+
This migration is extensive and results in a number of changes, however,
8787
Tendermint only uses the types generated from Protocol Buffers for disk and
88-
wire serialization.
88+
wire serialization.
8989
**This means that these changes should not affect you as a Tendermint user.**
9090

9191
However, Tendermint users and contributors may note the following changes:
9292

93-
* Directory layout changes: All proto files have been moved under one directory, `/proto`.
94-
This is in line with the recommended file layout by [Buf](https://buf.build).
93+
* Directory layout changes: All proto files have been moved under one directory, `/proto`.
94+
This is in line with the recommended file layout by [Buf](https://buf.build).
9595
For more, see the [Buf documentation](https://buf.build/docs/lint-checkers#file_layout).
96-
* ABCI Changes: As noted in the "ABCI Changes" section above, the `PublicKey` type now uses
97-
a `oneof` type.
96+
* ABCI Changes: As noted in the "ABCI Changes" section above, the `PublicKey` type now uses
97+
a `oneof` type.
9898

9999
For more on the Protobuf changes, please see our [blog post on this migration](https://medium.com/tendermint/tendermint-0-34-protocol-buffers-and-you-8c40558939ae).
100100

@@ -114,30 +114,27 @@ Tendermint 0.34 includes new and updated consensus parameters.
114114

115115
#### Keys
116116

117-
* Keys no longer include a type prefix. For example, ed25519 pubkeys have been renamed from
118-
`PubKeyEd25519` to `PubKey`. This reduces stutter (e.g., `ed25519.PubKey`).
117+
* Keys no longer include a type prefix. For example, ed25519 pubkeys have been renamed from
118+
`PubKeyEd25519` to `PubKey`. This reduces stutter (e.g., `ed25519.PubKey`).
119119
* Keys are now byte slices (`[]byte`) instead of byte arrays (`[<size>]byte`).
120-
* The multisig functionality that was previously in Tendermint now has
121-
a new home within the Cosmos SDK:
120+
* The multisig functionality that was previously in Tendermint now has
121+
a new home within the Cosmos SDK:
122122
[`cosmos/cosmos-sdk/types/multisig`](https://github.com/cosmos/cosmos-sdk/blob/master/crypto/types/multisig/multisignature.go).
123-
* Similarly, secp256k1 has been removed from the Tendermint repo.
124-
There is still [a secp256k1 implementation in the Cosmos SDK](https://github.com/cosmos/cosmos-sdk/tree/443e0c1f89bd3730a731aea30453bd732f7efa35/crypto/keys/secp256k1),
125-
and we recommend you use that package for all your secp256k1 needs.
126123

127124
#### `merkle` Package
128125

129126
* `SimpleHashFromMap()` and `SimpleProofsFromMap()` were removed.
130-
* The prefix `Simple` has been removed. (For example, `SimpleProof` is now called `Proof`.)
131-
* All protobuf messages have been moved to the `/proto` directory.
132-
* The protobuf message `Proof` that contained multiple ProofOp's has been renamed to `ProofOps`.
133-
As noted above, this affects the ABCI type `ResponseQuery`:
127+
* The prefix `Simple` has been removed. (For example, `SimpleProof` is now called `Proof`.)
128+
* All protobuf messages have been moved to the `/proto` directory.
129+
* The protobuf message `Proof` that contained multiple ProofOp's has been renamed to `ProofOps`.
130+
As noted above, this affects the ABCI type `ResponseQuery`:
134131
The field that was named Proof is now named `ProofOps`.
135132
* `HashFromByteSlices` and `ProofsFromByteSlices` now return a hash for empty inputs, to conform with
136133
[RFC-6962](https://tools.ietf.org/html/rfc6962).
137134

138135
### `libs` Package
139136

140-
The `bech32` package has moved to the Cosmos SDK:
137+
The `bech32` package has moved to the Cosmos SDK:
141138
[`cosmos/cosmos-sdk/types/bech32`](https://github.com/cosmos/cosmos-sdk/tree/4173ea5ebad906dd9b45325bed69b9c655504867/types/bech32).
142139

143140
### CLI
@@ -147,37 +144,37 @@ See [the docs](https://docs.tendermint.com/master/tendermint-core/light-client-p
147144

148145
### Light Client
149146

150-
We have a new, rewritten light client! You can
147+
We have a new, rewritten light client! You can
151148
[read more](https://medium.com/tendermint/everything-you-need-to-know-about-the-tendermint-light-client-f80d03856f98)
152-
about the justifications and details behind this change.
149+
about the justifications and details behind this change.
153150

154151
Other user-relevant changes include:
155152

156153
* The old `lite` package was removed; the new light client uses the `light` package.
157-
* The `Verifier` was broken up into two pieces:
158-
* Core verification logic (pure `VerifyX` functions)
154+
* The `Verifier` was broken up into two pieces:
155+
* Core verification logic (pure `VerifyX` functions)
159156
* `Client` object, which represents the complete light client
160-
* The RPC client can be found in the `/rpc` directory.
157+
* The RPC client can be found in the `/rpc` directory.
161158
* The HTTP(S) proxy is located in the `/proxy` directory.
162159

163160
### `state` Package
164161

165162
* A new field `State.InitialHeight` has been added to record the initial chain height, which must be `1`
166163
(not `0`) if starting from height `1`. This can be configured via the genesis field `initial_height`.
167-
* The `state` package now has a `Store` interface. All functions in
168-
[state/store.go](https://github.com/tendermint/tendermint/blob/56911ee35298191c95ef1c7d3d5ec508237aaff4/state/store.go#L42-L42)
164+
* The `state` package now has a `Store` interface. All functions in
165+
[state/store.go](https://github.com/tendermint/tendermint/blob/56911ee35298191c95ef1c7d3d5ec508237aaff4/state/store.go#L42-L42)
169166
are now part of the interface. The interface returns errors on all methods and can be used by calling `state.NewStore(dbm.DB)`.
170167

171168
### `privval` Package
172169

173170
All requests are now accompanied by the chain ID from the network.
174-
This is a optional field and can be ignored by key management systems.
171+
This is a optional field and can be ignored by key management systems.
175172
It is recommended to check the chain ID if using the same key management system for multiple chains.
176173

177174
### RPC
178175

179176
`/unsafe_start_cpu_profiler`, `/unsafe_stop_cpu_profiler` and
180-
`/unsafe_write_heap_profile` were removed.
177+
`/unsafe_write_heap_profile` were removed.
181178
For profiling, please use the pprof server, which can
182179
be enabled through `--rpc.pprof_laddr=X` flag or `pprof_laddr=X` config setting
183180
in the rpc section.

‎abci/example/kvstore/persistent_kvstore.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,11 @@ func (app *PersistentKVStoreApplication) execValidatorTx(tx []byte) types.Respon
243243

244244
// add, update, or remove a validator
245245
func (app *PersistentKVStoreApplication) updateValidator(v types.ValidatorUpdate) types.ResponseDeliverTx {
246-
key := []byte("val:" + string(v.PubKey.GetEd25519()))
247246
pubkey, err := cryptoenc.PubKeyFromProto(v.PubKey)
248247
if err != nil {
249248
panic(fmt.Errorf("can't decode public key: %w", err))
250249
}
250+
key := []byte("val:" + string(pubkey.Bytes()))
251251

252252
if v.Power == 0 {
253253
// remove validator

‎crypto/ed25519/ed25519.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const (
3131
// private key representations used by RFC 8032.
3232
SeedSize = 32
3333

34-
keyType = "ed25519"
34+
KeyType = "ed25519"
3535
)
3636

3737
func init() {
@@ -93,7 +93,7 @@ func (privKey PrivKey) Equals(other crypto.PrivKey) bool {
9393
}
9494

9595
func (privKey PrivKey) Type() string {
96-
return keyType
96+
return KeyType
9797
}
9898

9999
// GenPrivKey generates a new ed25519 private key.
@@ -159,7 +159,7 @@ func (pubKey PubKey) String() string {
159159
}
160160

161161
func (pubKey PubKey) Type() string {
162-
return keyType
162+
return KeyType
163163
}
164164

165165
func (pubKey PubKey) Equals(other crypto.PubKey) bool {

‎crypto/encoding/codec.go

+16
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import (
55

66
"github.com/tendermint/tendermint/crypto"
77
"github.com/tendermint/tendermint/crypto/ed25519"
8+
"github.com/tendermint/tendermint/crypto/secp256k1"
89
"github.com/tendermint/tendermint/libs/json"
910
pc "github.com/tendermint/tendermint/proto/tendermint/crypto"
1011
)
1112

1213
func init() {
1314
json.RegisterType((*pc.PublicKey)(nil), "tendermint.crypto.PublicKey")
1415
json.RegisterType((*pc.PublicKey_Ed25519)(nil), "tendermint.crypto.PublicKey_Ed25519")
16+
json.RegisterType((*pc.PublicKey_Secp256K1)(nil), "tendermint.crypto.PublicKey_Secp256K1")
1517
}
1618

1719
// PubKeyToProto takes crypto.PubKey and transforms it to a protobuf Pubkey
@@ -24,6 +26,12 @@ func PubKeyToProto(k crypto.PubKey) (pc.PublicKey, error) {
2426
Ed25519: k,
2527
},
2628
}
29+
case secp256k1.PubKey:
30+
kp = pc.PublicKey{
31+
Sum: &pc.PublicKey_Secp256K1{
32+
Secp256K1: k,
33+
},
34+
}
2735
default:
2836
return kp, fmt.Errorf("toproto: key type %v is not supported", k)
2937
}
@@ -41,6 +49,14 @@ func PubKeyFromProto(k pc.PublicKey) (crypto.PubKey, error) {
4149
pk := make(ed25519.PubKey, ed25519.PubKeySize)
4250
copy(pk, k.Ed25519)
4351
return pk, nil
52+
case *pc.PublicKey_Secp256K1:
53+
if len(k.Secp256K1) != secp256k1.PubKeySize {
54+
return nil, fmt.Errorf("invalid size for PubKeyEd25519. Got %d, expected %d",
55+
len(k.Secp256K1), secp256k1.PubKeySize)
56+
}
57+
pk := make(secp256k1.PubKey, secp256k1.PubKeySize)
58+
copy(pk, k.Secp256K1)
59+
return pk, nil
4460
default:
4561
return nil, fmt.Errorf("fromproto: key type %v is not supported", k)
4662
}

‎crypto/secp256k1/secp256k1.go

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package secp256k1
2+
3+
import (
4+
"bytes"
5+
"crypto/sha256"
6+
"crypto/subtle"
7+
"fmt"
8+
"io"
9+
"math/big"
10+
11+
secp256k1 "github.com/btcsuite/btcd/btcec"
12+
"golang.org/x/crypto/ripemd160" // nolint: staticcheck // necessary for Bitcoin address format
13+
14+
"github.com/tendermint/tendermint/crypto"
15+
tmjson "github.com/tendermint/tendermint/libs/json"
16+
)
17+
18+
//-------------------------------------
19+
const (
20+
PrivKeyName = "tendermint/PrivKeySecp256k1"
21+
PubKeyName = "tendermint/PubKeySecp256k1"
22+
23+
KeyType = "secp256k1"
24+
PrivKeySize = 32
25+
)
26+
27+
func init() {
28+
tmjson.RegisterType(PubKey{}, PubKeyName)
29+
tmjson.RegisterType(PrivKey{}, PrivKeyName)
30+
}
31+
32+
var _ crypto.PrivKey = PrivKey{}
33+
34+
// PrivKey implements PrivKey.
35+
type PrivKey []byte
36+
37+
// Bytes marshalls the private key using amino encoding.
38+
func (privKey PrivKey) Bytes() []byte {
39+
return []byte(privKey)
40+
}
41+
42+
// PubKey performs the point-scalar multiplication from the privKey on the
43+
// generator point to get the pubkey.
44+
func (privKey PrivKey) PubKey() crypto.PubKey {
45+
_, pubkeyObject := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey)
46+
47+
pk := pubkeyObject.SerializeCompressed()
48+
49+
return PubKey(pk)
50+
}
51+
52+
// Equals - you probably don't need to use this.
53+
// Runs in constant time based on length of the keys.
54+
func (privKey PrivKey) Equals(other crypto.PrivKey) bool {
55+
if otherSecp, ok := other.(PrivKey); ok {
56+
return subtle.ConstantTimeCompare(privKey[:], otherSecp[:]) == 1
57+
}
58+
return false
59+
}
60+
61+
func (privKey PrivKey) Type() string {
62+
return KeyType
63+
}
64+
65+
// GenPrivKey generates a new ECDSA private key on curve secp256k1 private key.
66+
// It uses OS randomness to generate the private key.
67+
func GenPrivKey() PrivKey {
68+
return genPrivKey(crypto.CReader())
69+
}
70+
71+
// genPrivKey generates a new secp256k1 private key using the provided reader.
72+
func genPrivKey(rand io.Reader) PrivKey {
73+
var privKeyBytes [PrivKeySize]byte
74+
d := new(big.Int)
75+
76+
for {
77+
privKeyBytes = [PrivKeySize]byte{}
78+
_, err := io.ReadFull(rand, privKeyBytes[:])
79+
if err != nil {
80+
panic(err)
81+
}
82+
83+
d.SetBytes(privKeyBytes[:])
84+
// break if we found a valid point (i.e. > 0 and < N == curverOrder)
85+
isValidFieldElement := 0 < d.Sign() && d.Cmp(secp256k1.S256().N) < 0
86+
if isValidFieldElement {
87+
break
88+
}
89+
}
90+
91+
return PrivKey(privKeyBytes[:])
92+
}
93+
94+
var one = new(big.Int).SetInt64(1)
95+
96+
// GenPrivKeySecp256k1 hashes the secret with SHA2, and uses
97+
// that 32 byte output to create the private key.
98+
//
99+
// It makes sure the private key is a valid field element by setting:
100+
//
101+
// c = sha256(secret)
102+
// k = (c mod (n − 1)) + 1, where n = curve order.
103+
//
104+
// NOTE: secret should be the output of a KDF like bcrypt,
105+
// if it's derived from user input.
106+
func GenPrivKeySecp256k1(secret []byte) PrivKey {
107+
secHash := sha256.Sum256(secret)
108+
// to guarantee that we have a valid field element, we use the approach of:
109+
// "Suite B Implementer’s Guide to FIPS 186-3", A.2.1
110+
// https://apps.nsa.gov/iaarchive/library/ia-guidance/ia-solutions-for-classified/algorithm-guidance/suite-b-implementers-guide-to-fips-186-3-ecdsa.cfm
111+
// see also https://github.com/golang/go/blob/0380c9ad38843d523d9c9804fe300cb7edd7cd3c/src/crypto/ecdsa/ecdsa.go#L89-L101
112+
fe := new(big.Int).SetBytes(secHash[:])
113+
n := new(big.Int).Sub(secp256k1.S256().N, one)
114+
fe.Mod(fe, n)
115+
fe.Add(fe, one)
116+
117+
feB := fe.Bytes()
118+
privKey32 := make([]byte, PrivKeySize)
119+
// copy feB over to fixed 32 byte privKey32 and pad (if necessary)
120+
copy(privKey32[32-len(feB):32], feB)
121+
122+
return PrivKey(privKey32)
123+
}
124+
125+
//-------------------------------------
126+
127+
var _ crypto.PubKey = PubKey{}
128+
129+
// PubKeySize is comprised of 32 bytes for one field element
130+
// (the x-coordinate), plus one byte for the parity of the y-coordinate.
131+
const PubKeySize = 33
132+
133+
// PubKey implements crypto.PubKey.
134+
// It is the compressed form of the pubkey. The first byte depends is a 0x02 byte
135+
// if the y-coordinate is the lexicographically largest of the two associated with
136+
// the x-coordinate. Otherwise the first byte is a 0x03.
137+
// This prefix is followed with the x-coordinate.
138+
type PubKey []byte
139+
140+
// Address returns a Bitcoin style addresses: RIPEMD160(SHA256(pubkey))
141+
func (pubKey PubKey) Address() crypto.Address {
142+
if len(pubKey) != PubKeySize {
143+
panic("length of pubkey is incorrect")
144+
}
145+
hasherSHA256 := sha256.New()
146+
_, _ = hasherSHA256.Write(pubKey) // does not error
147+
sha := hasherSHA256.Sum(nil)
148+
149+
hasherRIPEMD160 := ripemd160.New()
150+
_, _ = hasherRIPEMD160.Write(sha) // does not error
151+
152+
return crypto.Address(hasherRIPEMD160.Sum(nil))
153+
}
154+
155+
// Bytes returns the pubkey marshalled with amino encoding.
156+
func (pubKey PubKey) Bytes() []byte {
157+
return []byte(pubKey)
158+
}
159+
160+
func (pubKey PubKey) String() string {
161+
return fmt.Sprintf("PubKeySecp256k1{%X}", pubKey[:])
162+
}
163+
164+
func (pubKey PubKey) Equals(other crypto.PubKey) bool {
165+
if otherSecp, ok := other.(PubKey); ok {
166+
return bytes.Equal(pubKey[:], otherSecp[:])
167+
}
168+
return false
169+
}
170+
171+
func (pubKey PubKey) Type() string {
172+
return KeyType
173+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package secp256k1
2+
3+
import (
4+
"bytes"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
secp256k1 "github.com/btcsuite/btcd/btcec"
11+
)
12+
13+
func Test_genPrivKey(t *testing.T) {
14+
15+
empty := make([]byte, 32)
16+
oneB := big.NewInt(1).Bytes()
17+
onePadded := make([]byte, 32)
18+
copy(onePadded[32-len(oneB):32], oneB)
19+
t.Logf("one padded: %v, len=%v", onePadded, len(onePadded))
20+
21+
validOne := append(empty, onePadded...)
22+
tests := []struct {
23+
name string
24+
notSoRand []byte
25+
shouldPanic bool
26+
}{
27+
{"empty bytes (panics because 1st 32 bytes are zero and 0 is not a valid field element)", empty, true},
28+
{"curve order: N", secp256k1.S256().N.Bytes(), true},
29+
{"valid because 0 < 1 < N", validOne, false},
30+
}
31+
for _, tt := range tests {
32+
tt := tt
33+
t.Run(tt.name, func(t *testing.T) {
34+
if tt.shouldPanic {
35+
require.Panics(t, func() {
36+
genPrivKey(bytes.NewReader(tt.notSoRand))
37+
})
38+
return
39+
}
40+
got := genPrivKey(bytes.NewReader(tt.notSoRand))
41+
fe := new(big.Int).SetBytes(got[:])
42+
require.True(t, fe.Cmp(secp256k1.S256().N) < 0)
43+
require.True(t, fe.Sign() > 0)
44+
})
45+
}
46+
}
47+
48+
// Ensure that signature verification works, and that
49+
// non-canonical signatures fail.
50+
// Note: run with CGO_ENABLED=0 or go test -tags !cgo.
51+
func TestSignatureVerificationAndRejectUpperS(t *testing.T) {
52+
msg := []byte("We have lingered long enough on the shores of the cosmic ocean.")
53+
for i := 0; i < 500; i++ {
54+
priv := GenPrivKey()
55+
sigStr, err := priv.Sign(msg)
56+
require.NoError(t, err)
57+
sig := signatureFromBytes(sigStr)
58+
require.False(t, sig.S.Cmp(secp256k1halfN) > 0)
59+
60+
pub := priv.PubKey()
61+
require.True(t, pub.VerifySignature(msg, sigStr))
62+
63+
// malleate:
64+
sig.S.Sub(secp256k1.S256().CurveParams.N, sig.S)
65+
require.True(t, sig.S.Cmp(secp256k1halfN) > 0)
66+
malSigStr := serializeSig(sig)
67+
68+
require.False(t, pub.VerifySignature(msg, malSigStr),
69+
"VerifyBytes incorrect with malleated & invalid S. sig=%v, key=%v",
70+
sig,
71+
priv,
72+
)
73+
}
74+
}

‎crypto/secp256k1/secp256k1_nocgo.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// +build !libsecp256k1
2+
3+
package secp256k1
4+
5+
import (
6+
"math/big"
7+
8+
secp256k1 "github.com/btcsuite/btcd/btcec"
9+
10+
"github.com/tendermint/tendermint/crypto"
11+
)
12+
13+
// used to reject malleable signatures
14+
// see:
15+
// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93
16+
// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/crypto.go#L39
17+
var secp256k1halfN = new(big.Int).Rsh(secp256k1.S256().N, 1)
18+
19+
// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg.
20+
// The returned signature will be of the form R || S (in lower-S form).
21+
func (privKey PrivKey) Sign(msg []byte) ([]byte, error) {
22+
priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey)
23+
24+
sig, err := priv.Sign(crypto.Sha256(msg))
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
sigBytes := serializeSig(sig)
30+
return sigBytes, nil
31+
}
32+
33+
// VerifySignature verifies a signature of the form R || S.
34+
// It rejects signatures which are not in lower-S form.
35+
func (pubKey PubKey) VerifySignature(msg []byte, sigStr []byte) bool {
36+
if len(sigStr) != 64 {
37+
return false
38+
}
39+
40+
pub, err := secp256k1.ParsePubKey(pubKey, secp256k1.S256())
41+
if err != nil {
42+
return false
43+
}
44+
45+
// parse the signature:
46+
signature := signatureFromBytes(sigStr)
47+
// Reject malleable signatures. libsecp256k1 does this check but btcec doesn't.
48+
// see: https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93
49+
if signature.S.Cmp(secp256k1halfN) > 0 {
50+
return false
51+
}
52+
53+
return signature.Verify(crypto.Sha256(msg), pub)
54+
}
55+
56+
// Read Signature struct from R || S. Caller needs to ensure
57+
// that len(sigStr) == 64.
58+
func signatureFromBytes(sigStr []byte) *secp256k1.Signature {
59+
return &secp256k1.Signature{
60+
R: new(big.Int).SetBytes(sigStr[:32]),
61+
S: new(big.Int).SetBytes(sigStr[32:64]),
62+
}
63+
}
64+
65+
// Serialize signature to R || S.
66+
// R, S are padded to 32 bytes respectively.
67+
func serializeSig(sig *secp256k1.Signature) []byte {
68+
rBytes := sig.R.Bytes()
69+
sBytes := sig.S.Bytes()
70+
sigBytes := make([]byte, 64)
71+
// 0 pad the byte arrays from the left if they aren't big enough.
72+
copy(sigBytes[32-len(rBytes):32], rBytes)
73+
copy(sigBytes[64-len(sBytes):64], sBytes)
74+
return sigBytes
75+
}

‎crypto/secp256k1/secp256k1_test.go

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package secp256k1_test
2+
3+
import (
4+
"encoding/hex"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/btcsuite/btcutil/base58"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/tendermint/tendermint/crypto"
13+
"github.com/tendermint/tendermint/crypto/secp256k1"
14+
15+
underlyingSecp256k1 "github.com/btcsuite/btcd/btcec"
16+
)
17+
18+
type keyData struct {
19+
priv string
20+
pub string
21+
addr string
22+
}
23+
24+
var secpDataTable = []keyData{
25+
{
26+
priv: "a96e62ed3955e65be32703f12d87b6b5cf26039ecfa948dc5107a495418e5330",
27+
pub: "02950e1cdfcb133d6024109fd489f734eeb4502418e538c28481f22bce276f248c",
28+
addr: "1CKZ9Nx4zgds8tU7nJHotKSDr4a9bYJCa3",
29+
},
30+
}
31+
32+
func TestPubKeySecp256k1Address(t *testing.T) {
33+
for _, d := range secpDataTable {
34+
privB, _ := hex.DecodeString(d.priv)
35+
pubB, _ := hex.DecodeString(d.pub)
36+
addrBbz, _, _ := base58.CheckDecode(d.addr)
37+
addrB := crypto.Address(addrBbz)
38+
39+
var priv secp256k1.PrivKey = secp256k1.PrivKey(privB)
40+
41+
pubKey := priv.PubKey()
42+
pubT, _ := pubKey.(secp256k1.PubKey)
43+
pub := pubT
44+
addr := pubKey.Address()
45+
46+
assert.Equal(t, pub, secp256k1.PubKey(pubB), "Expected pub keys to match")
47+
assert.Equal(t, addr, addrB, "Expected addresses to match")
48+
}
49+
}
50+
51+
func TestSignAndValidateSecp256k1(t *testing.T) {
52+
privKey := secp256k1.GenPrivKey()
53+
pubKey := privKey.PubKey()
54+
55+
msg := crypto.CRandBytes(128)
56+
sig, err := privKey.Sign(msg)
57+
require.Nil(t, err)
58+
59+
assert.True(t, pubKey.VerifySignature(msg, sig))
60+
61+
// Mutate the signature, just one bit.
62+
sig[3] ^= byte(0x01)
63+
64+
assert.False(t, pubKey.VerifySignature(msg, sig))
65+
}
66+
67+
// This test is intended to justify the removal of calls to the underlying library
68+
// in creating the privkey.
69+
func TestSecp256k1LoadPrivkeyAndSerializeIsIdentity(t *testing.T) {
70+
numberOfTests := 256
71+
for i := 0; i < numberOfTests; i++ {
72+
// Seed the test case with some random bytes
73+
privKeyBytes := [32]byte{}
74+
copy(privKeyBytes[:], crypto.CRandBytes(32))
75+
76+
// This function creates a private and public key in the underlying libraries format.
77+
// The private key is basically calling new(big.Int).SetBytes(pk), which removes leading zero bytes
78+
priv, _ := underlyingSecp256k1.PrivKeyFromBytes(underlyingSecp256k1.S256(), privKeyBytes[:])
79+
// this takes the bytes returned by `(big int).Bytes()`, and if the length is less than 32 bytes,
80+
// pads the bytes from the left with zero bytes. Therefore these two functions composed
81+
// result in the identity function on privKeyBytes, hence the following equality check
82+
// always returning true.
83+
serializedBytes := priv.Serialize()
84+
require.Equal(t, privKeyBytes[:], serializedBytes)
85+
}
86+
}
87+
88+
func TestGenPrivKeySecp256k1(t *testing.T) {
89+
// curve oder N
90+
N := underlyingSecp256k1.S256().N
91+
tests := []struct {
92+
name string
93+
secret []byte
94+
}{
95+
{"empty secret", []byte{}},
96+
{
97+
"some long secret",
98+
[]byte("We live in a society exquisitely dependent on science and technology, " +
99+
"in which hardly anyone knows anything about science and technology."),
100+
},
101+
{"another seed used in cosmos tests #1", []byte{0}},
102+
{"another seed used in cosmos tests #2", []byte("mySecret")},
103+
{"another seed used in cosmos tests #3", []byte("")},
104+
}
105+
for _, tt := range tests {
106+
tt := tt
107+
t.Run(tt.name, func(t *testing.T) {
108+
gotPrivKey := secp256k1.GenPrivKeySecp256k1(tt.secret)
109+
require.NotNil(t, gotPrivKey)
110+
// interpret as a big.Int and make sure it is a valid field element:
111+
fe := new(big.Int).SetBytes(gotPrivKey[:])
112+
require.True(t, fe.Cmp(N) < 0)
113+
require.True(t, fe.Sign() > 0)
114+
})
115+
}
116+
}

‎go.mod

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ go 1.14
55
require (
66
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d
77
github.com/Workiva/go-datastructures v1.0.52
8+
github.com/btcsuite/btcd v0.21.0-beta
9+
github.com/btcsuite/btcutil v1.0.2
810
github.com/fortytw2/leaktest v1.3.0
911
github.com/go-kit/kit v0.10.0
1012
github.com/go-logfmt/logfmt v0.5.0
@@ -14,6 +16,7 @@ require (
1416
github.com/gtank/merlin v0.1.1
1517
github.com/libp2p/go-buffer-pool v0.0.2
1618
github.com/minio/highwayhash v1.0.1
19+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
1720
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
1821
github.com/pkg/errors v0.9.1
1922
github.com/prometheus/client_golang v1.7.1
@@ -25,7 +28,9 @@ require (
2528
github.com/spf13/viper v1.7.1
2629
github.com/stretchr/testify v1.6.1
2730
github.com/tendermint/tm-db v0.6.2
28-
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
31+
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
2932
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
33+
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
3034
google.golang.org/grpc v1.32.0
35+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
3136
)

‎go.sum

+41-24
Large diffs are not rendered by default.

‎proto/tendermint/crypto/keys.pb.go

+143-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎proto/tendermint/crypto/keys.proto

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ message PublicKey {
1111
option (gogoproto.equal) = true;
1212

1313
oneof sum {
14-
bytes ed25519 = 1;
14+
bytes ed25519 = 1;
15+
bytes secp256k1 = 2;
1516
}
1617
}

‎types/params_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import (
1313
)
1414

1515
var (
16-
valEd25519 = []string{ABCIPubKeyTypeEd25519}
16+
valEd25519 = []string{ABCIPubKeyTypeEd25519}
17+
valSecp256k1 = []string{ABCIPubKeyTypeSecp256k1}
1718
)
1819

1920
func TestConsensusParamsValidation(t *testing.T) {
@@ -127,10 +128,10 @@ func TestConsensusParamsUpdate(t *testing.T) {
127128
MaxBytes: 50,
128129
},
129130
Validator: &tmproto.ValidatorParams{
130-
PubKeyTypes: valEd25519,
131+
PubKeyTypes: valSecp256k1,
131132
},
132133
},
133-
makeParams(100, 200, 10, 300, 50, valEd25519),
134+
makeParams(100, 200, 10, 300, 50, valSecp256k1),
134135
},
135136
}
136137
for _, tc := range testCases {

‎types/protobuf.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@ import (
88
"github.com/tendermint/tendermint/crypto"
99
"github.com/tendermint/tendermint/crypto/ed25519"
1010
cryptoenc "github.com/tendermint/tendermint/crypto/encoding"
11+
"github.com/tendermint/tendermint/crypto/secp256k1"
1112
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
1213
)
1314

1415
//-------------------------------------------------------
1516
// Use strings to distinguish types in ABCI messages
1617

1718
const (
18-
ABCIPubKeyTypeEd25519 = "ed25519"
19+
ABCIPubKeyTypeEd25519 = ed25519.KeyType
20+
ABCIPubKeyTypeSecp256k1 = secp256k1.KeyType
1921
)
2022

2123
// TODO: Make non-global by allowing for registration of more pubkey types
2224

2325
var ABCIPubKeyTypesToNames = map[string]string{
24-
ABCIPubKeyTypeEd25519: ed25519.PubKeyName,
26+
ABCIPubKeyTypeEd25519: ed25519.PubKeyName,
27+
ABCIPubKeyTypeSecp256k1: secp256k1.PubKeyName,
2528
}
2629

2730
//-------------------------------------------------------

0 commit comments

Comments
 (0)
Please sign in to comment.