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:
Wachiu Siu
2022-09-09 07:32:23 -04:00
committed by GitHub
co-authored by Thane Thomson
parent 5013bc3f4a
commit 0f45086c5f
6 changed files with 81 additions and 60 deletions
+21 -31
View File
@@ -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)
}
+17 -7
View File
@@ -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,
)
}
+3 -3
View File
@@ -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