crypto: use decred secp256k1 directly (#30595)

Use `github.com/decred/dcrd/dcrec/secp256k1/v4` directly rather than
`github.com/btcsuite/btcd/btcec/v2` which is just a wrapper around the
underlying decred library. Inspired by
https://github.com/cosmos/cosmos-sdk/pull/15018

`github.com/btcsuite/btcd/btcec/v2` has a very annoying breaking change
when upgrading from `v2.3.3` to `v2.3.4`. The easiest way to workaround
this is to just remove the wrapper.

Would be very nice if you could backport this to the release branches.

References:
- https://github.com/btcsuite/btcd/issues/2221
- https://github.com/cometbft/cometbft/pull/4294
- https://github.com/cometbft/cometbft/pull/3728
- https://github.com/zeta-chain/node/pull/2934
This commit is contained in:
Alex Gartner 2024-10-15 01:49:08 -07:00 committed by GitHub
parent 4b9c7821b9
commit 30ce17386b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 21 additions and 26 deletions

View File

@ -25,8 +25,8 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"github.com/btcsuite/btcd/btcec/v2" "github.com/decred/dcrd/dcrec/secp256k1/v4"
btc_ecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa" decred_ecdsa "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa"
) )
// Ecrecover returns the uncompressed public key that created the given signature. // Ecrecover returns the uncompressed public key that created the given signature.
@ -39,16 +39,16 @@ func Ecrecover(hash, sig []byte) ([]byte, error) {
return bytes, err return bytes, err
} }
func sigToPub(hash, sig []byte) (*btcec.PublicKey, error) { func sigToPub(hash, sig []byte) (*secp256k1.PublicKey, error) {
if len(sig) != SignatureLength { if len(sig) != SignatureLength {
return nil, errors.New("invalid signature") return nil, errors.New("invalid signature")
} }
// Convert to btcec input format with 'recovery id' v at the beginning. // Convert to secp256k1 input format with 'recovery id' v at the beginning.
btcsig := make([]byte, SignatureLength) btcsig := make([]byte, SignatureLength)
btcsig[0] = sig[RecoveryIDOffset] + 27 btcsig[0] = sig[RecoveryIDOffset] + 27
copy(btcsig[1:], sig) copy(btcsig[1:], sig)
pub, _, err := btc_ecdsa.RecoverCompact(btcsig, hash) pub, _, err := decred_ecdsa.RecoverCompact(btcsig, hash)
return pub, err return pub, err
} }
@ -82,13 +82,13 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
if prv.Curve != S256() { if prv.Curve != S256() {
return nil, errors.New("private key curve is not secp256k1") return nil, errors.New("private key curve is not secp256k1")
} }
// ecdsa.PrivateKey -> btcec.PrivateKey // ecdsa.PrivateKey -> secp256k1.PrivateKey
var priv btcec.PrivateKey var priv secp256k1.PrivateKey
if overflow := priv.Key.SetByteSlice(prv.D.Bytes()); overflow || priv.Key.IsZero() { if overflow := priv.Key.SetByteSlice(prv.D.Bytes()); overflow || priv.Key.IsZero() {
return nil, errors.New("invalid private key") return nil, errors.New("invalid private key")
} }
defer priv.Zero() defer priv.Zero()
sig := btc_ecdsa.SignCompact(&priv, hash, false) // ref uncompressed pubkey sig := decred_ecdsa.SignCompact(&priv, hash, false) // ref uncompressed pubkey
// Convert to Ethereum signature format with 'recovery id' v at the end. // Convert to Ethereum signature format with 'recovery id' v at the end.
v := sig[0] - 27 v := sig[0] - 27
copy(sig, sig[1:]) copy(sig, sig[1:])
@ -103,19 +103,19 @@ func VerifySignature(pubkey, hash, signature []byte) bool {
if len(signature) != 64 { if len(signature) != 64 {
return false return false
} }
var r, s btcec.ModNScalar var r, s secp256k1.ModNScalar
if r.SetByteSlice(signature[:32]) { if r.SetByteSlice(signature[:32]) {
return false // overflow return false // overflow
} }
if s.SetByteSlice(signature[32:]) { if s.SetByteSlice(signature[32:]) {
return false return false
} }
sig := btc_ecdsa.NewSignature(&r, &s) sig := decred_ecdsa.NewSignature(&r, &s)
key, err := btcec.ParsePubKey(pubkey) key, err := secp256k1.ParsePubKey(pubkey)
if err != nil { if err != nil {
return false return false
} }
// Reject malleable signatures. libsecp256k1 does this check but btcec doesn't. // Reject malleable signatures. libsecp256k1 does this check but decred doesn't.
if s.IsOverHalfOrder() { if s.IsOverHalfOrder() {
return false return false
} }
@ -127,7 +127,7 @@ func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) {
if len(pubkey) != 33 { if len(pubkey) != 33 {
return nil, errors.New("invalid compressed public key length") return nil, errors.New("invalid compressed public key length")
} }
key, err := btcec.ParsePubKey(pubkey) key, err := secp256k1.ParsePubKey(pubkey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -148,20 +148,20 @@ func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) {
// when constructing a PrivateKey. // when constructing a PrivateKey.
func CompressPubkey(pubkey *ecdsa.PublicKey) []byte { func CompressPubkey(pubkey *ecdsa.PublicKey) []byte {
// NOTE: the coordinates may be validated with // NOTE: the coordinates may be validated with
// btcec.ParsePubKey(FromECDSAPub(pubkey)) // secp256k1.ParsePubKey(FromECDSAPub(pubkey))
var x, y btcec.FieldVal var x, y secp256k1.FieldVal
x.SetByteSlice(pubkey.X.Bytes()) x.SetByteSlice(pubkey.X.Bytes())
y.SetByteSlice(pubkey.Y.Bytes()) y.SetByteSlice(pubkey.Y.Bytes())
return btcec.NewPublicKey(&x, &y).SerializeCompressed() return secp256k1.NewPublicKey(&x, &y).SerializeCompressed()
} }
// S256 returns an instance of the secp256k1 curve. // S256 returns an instance of the secp256k1 curve.
func S256() EllipticCurve { func S256() EllipticCurve {
return btCurve{btcec.S256()} return btCurve{secp256k1.S256()}
} }
type btCurve struct { type btCurve struct {
*btcec.KoblitzCurve *secp256k1.KoblitzCurve
} }
// Marshal converts a point given as (x, y) into a byte slice. // Marshal converts a point given as (x, y) into a byte slice.

3
go.mod
View File

@ -12,7 +12,6 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.18.45 github.com/aws/aws-sdk-go-v2/config v1.18.45
github.com/aws/aws-sdk-go-v2/credentials v1.13.43 github.com/aws/aws-sdk-go-v2/credentials v1.13.43
github.com/aws/aws-sdk-go-v2/service/route53 v1.30.2 github.com/aws/aws-sdk-go-v2/service/route53 v1.30.2
github.com/btcsuite/btcd/btcec/v2 v2.3.4
github.com/cespare/cp v0.1.0 github.com/cespare/cp v0.1.0
github.com/cloudflare/cloudflare-go v0.79.0 github.com/cloudflare/cloudflare-go v0.79.0
github.com/cockroachdb/pebble v1.1.2 github.com/cockroachdb/pebble v1.1.2
@ -21,6 +20,7 @@ require (
github.com/crate-crypto/go-kzg-4844 v1.0.0 github.com/crate-crypto/go-kzg-4844 v1.0.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set/v2 v2.6.0 github.com/deckarep/golang-set/v2 v2.6.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0 github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
github.com/ethereum/c-kzg-4844 v1.0.0 github.com/ethereum/c-kzg-4844 v1.0.0
@ -102,7 +102,6 @@ require (
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/bavard v0.1.13 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/deepmap/oapi-codegen v1.6.0 // indirect github.com/deepmap/oapi-codegen v1.6.0 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect

4
go.sum
View File

@ -92,10 +92,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=

View File

@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/btcsuite/btcd/btcec/v2" dcred_secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/crypto/secp256k1"
) )
@ -38,7 +38,7 @@ func Fuzz(f *testing.F) {
func fuzz(dataP1, dataP2 []byte) { func fuzz(dataP1, dataP2 []byte) {
var ( var (
curveA = secp256k1.S256() curveA = secp256k1.S256()
curveB = btcec.S256() curveB = dcred_secp256k1.S256()
) )
// first point // first point
x1, y1 := curveB.ScalarBaseMult(dataP1) x1, y1 := curveB.ScalarBaseMult(dataP1)