mirror of
https://github.com/tendermint/tendermint.git
synced 2026-10-01 04:05:45 +00:00
update btcec to v2 and use the latest btcutil (#9250)
* secp256k1: upgrade to latest btcec v2 and btcutil * Update CHANGELOG_PENDING.md Co-authored-by: Thane Thomson <connect@thanethomson.com> * fix rebase Co-authored-by: Thane Thomson <connect@thanethomson.com>
This commit is contained in:
co-authored by
Thane Thomson
parent
5013bc3f4a
commit
0f45086c5f
@@ -8,7 +8,8 @@ import (
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
secp256k1 "github.com/btcsuite/btcd/btcec"
|
||||
secp256k1 "github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
"golang.org/x/crypto/ripemd160" //nolint: staticcheck // necessary for Bitcoin address format
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
@@ -42,7 +43,7 @@ func (privKey PrivKey) Bytes() []byte {
|
||||
// PubKey performs the point-scalar multiplication from the privKey on the
|
||||
// generator point to get the pubkey.
|
||||
func (privKey PrivKey) PubKey() crypto.PubKey {
|
||||
_, pubkeyObject := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey)
|
||||
_, pubkeyObject := secp256k1.PrivKeyFromBytes(privKey)
|
||||
|
||||
pk := pubkeyObject.SerializeCompressed()
|
||||
|
||||
@@ -122,24 +123,18 @@ func GenPrivKeySecp256k1(secret []byte) PrivKey {
|
||||
return PrivKey(privKey32)
|
||||
}
|
||||
|
||||
// used to reject malleable signatures
|
||||
// see:
|
||||
// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93
|
||||
// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/crypto.go#L39
|
||||
var secp256k1halfN = new(big.Int).Rsh(secp256k1.S256().N, 1)
|
||||
|
||||
// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg.
|
||||
// The returned signature will be of the form R || S (in lower-S form).
|
||||
func (privKey PrivKey) Sign(msg []byte) ([]byte, error) {
|
||||
priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey)
|
||||
priv, _ := secp256k1.PrivKeyFromBytes(privKey)
|
||||
|
||||
sig, err := priv.Sign(crypto.Sha256(msg))
|
||||
sig, err := ecdsa.SignCompact(priv, crypto.Sha256(msg), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sigBytes := serializeSig(sig)
|
||||
return sigBytes, nil
|
||||
// remove the first byte which is compactSigRecoveryCode
|
||||
return sig[1:], nil
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
@@ -199,7 +194,7 @@ func (pubKey PubKey) VerifySignature(msg []byte, sigStr []byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
pub, err := secp256k1.ParsePubKey(pubKey, secp256k1.S256())
|
||||
pub, err := secp256k1.ParsePubKey(pubKey)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -208,7 +203,13 @@ func (pubKey PubKey) VerifySignature(msg []byte, sigStr []byte) bool {
|
||||
signature := signatureFromBytes(sigStr)
|
||||
// Reject malleable signatures. libsecp256k1 does this check but btcec doesn't.
|
||||
// see: https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93
|
||||
if signature.S.Cmp(secp256k1halfN) > 0 {
|
||||
// Serialize() would negate S value if it is over half order.
|
||||
// Hence, if the signature is different after Serialize() if should be rejected.
|
||||
var modifiedSignature, parseErr = ecdsa.ParseDERSignature(signature.Serialize())
|
||||
if parseErr != nil {
|
||||
return false
|
||||
}
|
||||
if !signature.IsEqual(modifiedSignature) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -217,21 +218,10 @@ func (pubKey PubKey) VerifySignature(msg []byte, sigStr []byte) bool {
|
||||
|
||||
// Read Signature struct from R || S. Caller needs to ensure
|
||||
// that len(sigStr) == 64.
|
||||
func signatureFromBytes(sigStr []byte) *secp256k1.Signature {
|
||||
return &secp256k1.Signature{
|
||||
R: new(big.Int).SetBytes(sigStr[:32]),
|
||||
S: new(big.Int).SetBytes(sigStr[32:64]),
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize signature to R || S.
|
||||
// R, S are padded to 32 bytes respectively.
|
||||
func serializeSig(sig *secp256k1.Signature) []byte {
|
||||
rBytes := sig.R.Bytes()
|
||||
sBytes := sig.S.Bytes()
|
||||
sigBytes := make([]byte, 64)
|
||||
// 0 pad the byte arrays from the left if they aren't big enough.
|
||||
copy(sigBytes[32-len(rBytes):32], rBytes)
|
||||
copy(sigBytes[64-len(sBytes):64], sBytes)
|
||||
return sigBytes
|
||||
func signatureFromBytes(sigStr []byte) *ecdsa.Signature {
|
||||
var r secp256k1.ModNScalar
|
||||
r.SetByteSlice(sigStr[:32])
|
||||
var s secp256k1.ModNScalar
|
||||
s.SetByteSlice(sigStr[32:64])
|
||||
return ecdsa.NewSignature(&r, &s)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
secp256k1 "github.com/btcsuite/btcd/btcec"
|
||||
secp256k1 "github.com/btcsuite/btcd/btcec/v2"
|
||||
)
|
||||
|
||||
func Test_genPrivKey(t *testing.T) {
|
||||
@@ -54,20 +54,30 @@ func TestSignatureVerificationAndRejectUpperS(t *testing.T) {
|
||||
priv := GenPrivKey()
|
||||
sigStr, err := priv.Sign(msg)
|
||||
require.NoError(t, err)
|
||||
sig := signatureFromBytes(sigStr)
|
||||
require.False(t, sig.S.Cmp(secp256k1halfN) > 0)
|
||||
var r secp256k1.ModNScalar
|
||||
r.SetByteSlice(sigStr[:32])
|
||||
var s secp256k1.ModNScalar
|
||||
s.SetByteSlice(sigStr[32:64])
|
||||
require.False(t, s.IsOverHalfOrder())
|
||||
|
||||
pub := priv.PubKey()
|
||||
require.True(t, pub.VerifySignature(msg, sigStr))
|
||||
|
||||
// malleate:
|
||||
sig.S.Sub(secp256k1.S256().CurveParams.N, sig.S)
|
||||
require.True(t, sig.S.Cmp(secp256k1halfN) > 0)
|
||||
malSigStr := serializeSig(sig)
|
||||
var S256 secp256k1.ModNScalar
|
||||
S256.SetByteSlice(secp256k1.S256().N.Bytes())
|
||||
s.Negate().Add(&S256)
|
||||
require.True(t, s.IsOverHalfOrder())
|
||||
|
||||
rBytes := r.Bytes()
|
||||
sBytes := s.Bytes()
|
||||
malSigStr := make([]byte, 64)
|
||||
copy(malSigStr[32-len(rBytes):32], rBytes[:])
|
||||
copy(malSigStr[64-len(sBytes):64], sBytes[:])
|
||||
|
||||
require.False(t, pub.VerifySignature(msg, malSigStr),
|
||||
"VerifyBytes incorrect with malleated & invalid S. sig=%v, key=%v",
|
||||
sig,
|
||||
malSigStr,
|
||||
priv,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcutil/base58"
|
||||
"github.com/btcsuite/btcd/btcutil/base58"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
|
||||
underlyingSecp256k1 "github.com/btcsuite/btcd/btcec"
|
||||
underlyingSecp256k1 "github.com/btcsuite/btcd/btcec/v2"
|
||||
)
|
||||
|
||||
type keyData struct {
|
||||
@@ -75,7 +75,7 @@ func TestSecp256k1LoadPrivkeyAndSerializeIsIdentity(t *testing.T) {
|
||||
|
||||
// This function creates a private and public key in the underlying libraries format.
|
||||
// The private key is basically calling new(big.Int).SetBytes(pk), which removes leading zero bytes
|
||||
priv, _ := underlyingSecp256k1.PrivKeyFromBytes(underlyingSecp256k1.S256(), privKeyBytes[:])
|
||||
priv, _ := underlyingSecp256k1.PrivKeyFromBytes(privKeyBytes[:])
|
||||
// this takes the bytes returned by `(big int).Bytes()`, and if the length is less than 32 bytes,
|
||||
// pads the bytes from the left with zero bytes. Therefore these two functions composed
|
||||
// result in the identity function on privKeyBytes, hence the following equality check
|
||||
|
||||
Reference in New Issue
Block a user