diff --git a/CHANGELOG.md b/CHANGELOG.md index bc72dcf241..ccc208b085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (docs) [\#1120](https://github.com/Finschia/finschia-sdk/pull/1120) Update links in x/foundation README.md * (feat) [\#1121](https://github.com/Finschia/finschia-sdk/pull/1121) Add update-censorship cmd to x/foundation cli * (server) [#1153](https://github.com/Finschia/finschia-sdk/pull/1153) remove grpc replace directive +* (crypto) [\#1163](https://github.com/Finschia/finschia-sdk/pull/1163) Update some secp256k1 logics with latest `dcrec` + ### Bug Fixes * (x/auth) [#1281](https://github.com/Finschia/finschia-sdk/pull/1281) `ModuleAccount.Validate` now reports a nil `.BaseAccount` instead of panicking. (backport #1274) * (x/foundation) [\#1283](https://github.com/Finschia/finschia-sdk/pull/1283) add init logic of foundation module accounts to InitGenesis in order to eliminate potential panic (backport #1277) diff --git a/crypto/hd/hdpath.go b/crypto/hd/hdpath.go index a4683a97c6..60460b9238 100644 --- a/crypto/hd/hdpath.go +++ b/crypto/hd/hdpath.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/btcsuite/btcd/btcec" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) // BIP44Params wraps BIP 44 params (5 level BIP 32 path). @@ -237,7 +237,7 @@ func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, h data = append([]byte{byte(0)}, privKeyBytes[:]...) } else { // this can't return an error: - _, ecPub := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes[:]) + ecPub := secp.PrivKeyFromBytes(privKeyBytes[:]).PubKey() pubkeyBytes := ecPub.SerializeCompressed() data = pubkeyBytes @@ -260,7 +260,7 @@ func addScalars(a []byte, b []byte) [32]byte { aInt := new(big.Int).SetBytes(a) bInt := new(big.Int).SetBytes(b) sInt := new(big.Int).Add(aInt, bInt) - x := sInt.Mod(sInt, btcec.S256().N).Bytes() + x := sInt.Mod(sInt, secp.S256().N).Bytes() x2 := [32]byte{} copy(x2[32-len(x):], x) diff --git a/crypto/keys/secp256k1/secp256k1.go b/crypto/keys/secp256k1/secp256k1.go index f5d42a07d4..fbb6ede895 100644 --- a/crypto/keys/secp256k1/secp256k1.go +++ b/crypto/keys/secp256k1/secp256k1.go @@ -8,7 +8,7 @@ import ( "io" "math/big" - secp256k1 "github.com/btcsuite/btcd/btcec" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "golang.org/x/crypto/ripemd160" //nolint: staticcheck // necessary for Bitcoin address format "github.com/Finschia/ostracon/crypto" @@ -38,7 +38,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() cryptotypes.PubKey { - _, pubkeyObject := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey.Key) + pubkeyObject := secp256k1.PrivKeyFromBytes(privKey.Key).PubKey() pk := pubkeyObject.SerializeCompressed() return &PubKey{Key: pk} } diff --git a/crypto/keys/secp256k1/secp256k1_internal_test.go b/crypto/keys/secp256k1/secp256k1_internal_test.go index 7cbe5949f7..6dca83d01f 100644 --- a/crypto/keys/secp256k1/secp256k1_internal_test.go +++ b/crypto/keys/secp256k1/secp256k1_internal_test.go @@ -5,7 +5,7 @@ import ( "math/big" "testing" - btcSecp256k1 "github.com/btcsuite/btcd/btcec" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/stretchr/testify/require" ) @@ -23,7 +23,7 @@ func Test_genPrivKey(t *testing.T) { shouldPanic bool }{ {"empty bytes (panics because 1st 32 bytes are zero and 0 is not a valid field element)", empty, true}, - {"curve order: N", btcSecp256k1.S256().N.Bytes(), true}, + {"curve order: N", secp.S256().N.Bytes(), true}, {"valid because 0 < 1 < N", validOne, false}, } for _, tt := range tests { @@ -37,7 +37,7 @@ func Test_genPrivKey(t *testing.T) { } got := genPrivKey(bytes.NewReader(tt.notSoRand)) fe := new(big.Int).SetBytes(got[:]) - require.True(t, fe.Cmp(btcSecp256k1.S256().N) < 0) + require.True(t, fe.Cmp(secp.S256().N) < 0) require.True(t, fe.Sign() > 0) }) } diff --git a/crypto/keys/secp256k1/secp256k1_nocgo.go b/crypto/keys/secp256k1/secp256k1_nocgo.go index 767c25074e..1822f409d0 100644 --- a/crypto/keys/secp256k1/secp256k1_nocgo.go +++ b/crypto/keys/secp256k1/secp256k1_nocgo.go @@ -4,29 +4,22 @@ package secp256k1 import ( - "math/big" + "errors" - secp256k1 "github.com/btcsuite/btcd/btcec" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/Finschia/ostracon/crypto" ) -// 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.Key) - sig, err := priv.Sign(crypto.Sha256(msg)) - if err != nil { - return nil, err - } - sigBytes := serializeSig(sig) - return sigBytes, nil + priv := secp256k1.PrivKeyFromBytes(privKey.Key) + sig := ecdsa.SignCompact(priv, crypto.Sha256(msg), false) + + // remove the first byte which is compactSigRecoveryCode + return sig[1:], nil } // VerifyBytes verifies a signature of the form R || S. @@ -35,15 +28,13 @@ func (pubKey *PubKey) VerifySignature(msg []byte, sigStr []byte) bool { if len(sigStr) != 64 { return false } - pub, err := secp256k1.ParsePubKey(pubKey.Key, secp256k1.S256()) + pub, err := secp256k1.ParsePubKey(pubKey.Key) if err != nil { return false } - // parse the signature: - 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 { + // parse the signature, will return error if it is not in lower-S form + signature, err := signatureFromBytes(sigStr) + if err != nil { return false } return signature.Verify(crypto.Sha256(msg), pub) @@ -51,21 +42,15 @@ 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]), +// Rejects malleable signatures (if S value if it is over half order). +func signatureFromBytes(sigStr []byte) (*ecdsa.Signature, error) { + var r secp256k1.ModNScalar + r.SetByteSlice(sigStr[:32]) + var s secp256k1.ModNScalar + s.SetByteSlice(sigStr[32:64]) + if s.IsOverHalfOrder() { + return nil, errors.New("signature is not in lower-S form") } -} -// 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 + return ecdsa.NewSignature(&r, &s), nil } diff --git a/crypto/keys/secp256k1/secp256k1_nocgo_test.go b/crypto/keys/secp256k1/secp256k1_nocgo_test.go index 060b2815a0..dc8e64a238 100644 --- a/crypto/keys/secp256k1/secp256k1_nocgo_test.go +++ b/crypto/keys/secp256k1/secp256k1_nocgo_test.go @@ -6,7 +6,7 @@ package secp256k1 import ( "testing" - secp256k1 "github.com/btcsuite/btcd/btcec" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/stretchr/testify/require" ) @@ -19,20 +19,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, ) } diff --git a/crypto/keys/secp256k1/secp256k1_test.go b/crypto/keys/secp256k1/secp256k1_test.go index 7e593d77ba..6c46ec785b 100644 --- a/crypto/keys/secp256k1/secp256k1_test.go +++ b/crypto/keys/secp256k1/secp256k1_test.go @@ -7,8 +7,9 @@ import ( "math/big" "testing" - btcSecp256k1 "github.com/btcsuite/btcd/btcec" "github.com/cosmos/btcutil/base58" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" + btcecdsa "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,12 +28,137 @@ type keyData struct { addr string } +/* +The following code snippet has been used to generate test vectors. The purpose of these vectors are to check our +implementation of secp256k1 against go-ethereum's one. It has been commented to avoid dependencies. + + github.com/btcsuite/btcutil v1.0.2 + github.com/ethereum/go-ethereum v1.10.26 + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 + +--- + + import ( + "crypto/ecdsa" + "crypto/sha256" + "encoding/hex" + "fmt" + "github.com/btcsuite/btcutil/base58" + "github.com/ethereum/go-ethereum/crypto" + "golang.org/x/crypto/ripemd160" + ) + + func ethereumKeys() keyData { + // Generate private key with the go-ethereum + priv, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + encPriv := make([]byte, len(priv.D.Bytes())*2) + hex.Encode(encPriv, priv.D.Bytes()) + + // Get go-ethereum public key + ethPub, ok := priv.Public().(*ecdsa.PublicKey) + if !ok { + panic(err) + } + ethPublicKeyBytes := crypto.FromECDSAPub(ethPub) + + // Format byte depending on the oddness of the Y coordinate. + format := 0x02 + if ethPub.Y.Bit(0) != 0 { + format = 0x03 + } + + // Public key in the 33-byte compressed format. + pub := ethPublicKeyBytes[:33] + encPub := make([]byte, len(pub)*2) + pub[0] = byte(format) + hex.Encode(encPub, pub) + + // Bitcoin style addresses + sha := sha256.Sum256(pub) + hasherRIPEMD160 := ripemd160.New() + hasherRIPEMD160.Write(sha[:]) + addr := hasherRIPEMD160.Sum(nil) + return keyData{ + priv: string(encPriv), + pub: string(encPub), + addr: base58.CheckEncode(addr[:], 0), + } + } +*/ + +/* +generateKeyForCheckingConsistency was used to create test vectors that matches consistency against prior versions. +Here are the specific versions used to generate the vectors. + +github.com/cosmos/btcutil v1.0.5 +github.com/cosmos/cosmos-sdk v0.46.8 +*/ +var _ = func() keyData { + priv := secp256k1.GenPrivKey() + encPriv := make([]byte, len(priv.Key)*2) + hex.Encode(encPriv, priv.Key) + pub := priv.PubKey() + encPub := make([]byte, len(pub.Bytes())*2) + hex.Encode(encPub, pub.Bytes()) + addr := pub.Address() + return keyData{ + priv: string(encPriv), + pub: string(encPub), + addr: base58.CheckEncode(addr, 0), + } +} + var secpDataTable = []keyData{ { priv: "a96e62ed3955e65be32703f12d87b6b5cf26039ecfa948dc5107a495418e5330", pub: "02950e1cdfcb133d6024109fd489f734eeb4502418e538c28481f22bce276f248c", addr: "1CKZ9Nx4zgds8tU7nJHotKSDr4a9bYJCa3", }, + // matches consistency against a prior version of this library. Generated with generateKeyForCheckingConsistency + { + priv: "9af074dc32fe3e7173802cd72dcb1110582879a1990c90bdac60f2739986aa06", + pub: "0285592121e2a5e0eb970a1a9d1879c5fa7b33badf7dbb61c44b1bfced94649efb", + addr: "1Q4mWVk2hotRVDEdGGtGf6waz622rEwvib", + }, + // matches consistency against a prior version of this library. Generated with generateKeyForCheckingConsistency + { + priv: "ef9edc836bc4d47e9bc3cfab446836a737c41d60abb1d5f76a6d53ffe5b35f76", + pub: "02f5bf88d72172cc2f9a52919b6b1b74a01ca606cad75d5f4f93aa1a6ff0374aaf", + addr: "1KtiSApteeKdLi5cdZVpnkNW1t5Eteksvf", + }, + // matches consistency against a prior version of this library. Generated with generateKeyForCheckingConsistency + { + priv: "ab7715a1dd7cea7898c45b1f291550b83a6897fbdf0ec48330dd50187059b74b", + pub: "028f3003b3e6cb40897138dba5858207357a6d116cc5bf556c942cf6081b58d5fe", + addr: "RnM1o5grgCHAmm45wt5vzGsQoCJdPK2n2", + }, + // matches consistency against a prior version of this library. Generated with generateKeyForCheckingConsistency + { + priv: "db6b914d9a2d6ae4bab8f9b43de3b1e83940e1a309521128b13fdaf3cd15009a", + pub: "022f8e4e07ae2705a3c425eafea16027041bcdc87a193b01ea6c36c1c7a0bfc300", + addr: "16MpKTksSpGABuHqMqU9RPBz26DfwY8cLY", + }, + // matches consistency against go-ethereum's implementation. Generated with ethereumKeys + { + priv: "42ba4249f6fd9f1e31f8876a8d3d3bdef989fcc906164290c0be237f69f53718", + pub: "033c2f6ea7a678f0afbb43d0fe7a2b2706a75c2fdea08c3b90fd038c8219b42959", + addr: "18iz5wdTdwzq6cGzoVhooZCPRAx61GfUMR", + }, + // matches consistency against go-ethereum's implementation. Generated with ethereumKeys + { + priv: "86192b60369616574daabe8d7d6067f14ec3f0648cded5633c566c25c48e1f31", + pub: "03ad9e97842d0f6f57804f29f55aac9bba207d2b24b98aaabc7d106250389e6d46", + addr: "1K31NqmdMBZiLeUiP4kfjLNnWSmx17a9aE", + }, + // matches consistency against go-ethereum's implementation. Generated with ethereumKeys + { + priv: "1856b3a581aa1bf83daf61b1f8f4bb52b5223033f710e61d7e0b3086f48ba09a", + pub: "03d681bb11e5ebc14d5d2f72881cb0b2a693ef12bc72fe863f980fc6542eafbd40", + addr: "1K29nsfH6qwmE3MzsoHpLcWLA4mQLstGgx", + }, } func TestPubKeySecp256k1Address(t *testing.T) { @@ -65,7 +191,8 @@ func TestSignAndValidateSecp256k1(t *testing.T) { // ---- // Test cross packages verification msgHash := crypto.Sha256(msg) - btcPrivKey, btcPubKey := btcSecp256k1.PrivKeyFromBytes(btcSecp256k1.S256(), privKey.Key) + btcPrivKey := secp.PrivKeyFromBytes(privKey.Key) + btcPubKey := btcPrivKey.PubKey() // This fails: malformed signature: no header magic // btcSig, err := secp256k1.ParseSignature(sig, secp256k1.S256()) // require.NoError(t, err) @@ -78,9 +205,11 @@ func TestSignAndValidateSecp256k1(t *testing.T) { ok := ecdsa.Verify(btcPubKey.ToECDSA(), msgHash, r, s) require.True(t, ok) - sig2, err := btcPrivKey.Sign(msgHash) + sig2 := btcecdsa.SignCompact(btcPrivKey, msgHash, false) + // Chop off compactSigRecoveryCode. + sig2 = sig2[1:] require.NoError(t, err) - pubKey.VerifySignature(msg, sig2.Serialize()) + pubKey.VerifySignature(msg, sig2) // ---- // Mutate the signature, just one bit. @@ -99,7 +228,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, _ := btcSecp256k1.PrivKeyFromBytes(btcSecp256k1.S256(), privKeyBytes[:]) + priv := secp.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 @@ -111,7 +240,7 @@ func TestSecp256k1LoadPrivkeyAndSerializeIsIdentity(t *testing.T) { func TestGenPrivKeyFromSecret(t *testing.T) { // curve oder N - N := btcSecp256k1.S256().N + N := secp.S256().N tests := []struct { name string secret []byte @@ -132,7 +261,7 @@ func TestGenPrivKeyFromSecret(t *testing.T) { gotPrivKey := secp256k1.GenPrivKeyFromSecret(tt.secret) require.NotNil(t, gotPrivKey) // interpret as a big.Int and make sure it is a valid field element: - fe := new(big.Int).SetBytes(gotPrivKey.Key[:]) + fe := new(big.Int).SetBytes(gotPrivKey.Key) require.True(t, fe.Cmp(N) < 0) require.True(t, fe.Sign() > 0) }) @@ -272,7 +401,7 @@ func TestMarshalAmino(t *testing.T) { func TestMarshalAmino_BackwardsCompatibility(t *testing.T) { aminoCdc := codec.NewLegacyAmino() - // Create Tendermint keys. + // Create Ostracon keys. ostPrivKey := ostsecp256k1.GenPrivKey() ostPubKey := ostPrivKey.PubKey() // Create our own keys, with the same private key as Tendermint's. @@ -281,7 +410,7 @@ func TestMarshalAmino_BackwardsCompatibility(t *testing.T) { testCases := []struct { desc string - ostKey interface{} + tmKey interface{} ourKey interface{} marshalFn func(o interface{}) ([]byte, error) }{ @@ -314,7 +443,7 @@ func TestMarshalAmino_BackwardsCompatibility(t *testing.T) { for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { // Make sure Amino encoding override is not breaking backwards compatibility. - bz1, err := tc.marshalFn(tc.ostKey) + bz1, err := tc.marshalFn(tc.tmKey) require.NoError(t, err) bz2, err := tc.marshalFn(tc.ourKey) require.NoError(t, err) diff --git a/crypto/ledger/ledger_mock.go b/crypto/ledger/ledger_mock.go index f1127e3475..4c1f2c3aed 100644 --- a/crypto/ledger/ledger_mock.go +++ b/crypto/ledger/ledger_mock.go @@ -4,15 +4,13 @@ package ledger import ( + "errors" "fmt" - "github.com/btcsuite/btcd/btcec" - "github.com/pkg/errors" - - "github.com/cosmos/go-bip39" - secp256k1 "github.com/tendermint/btcd/btcec" - "github.com/Finschia/ostracon/crypto" + "github.com/cosmos/go-bip39" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/Finschia/finschia-sdk/crypto/hd" csecp256k1 "github.com/Finschia/finschia-sdk/crypto/keys/secp256k1" @@ -39,11 +37,11 @@ func (mock LedgerSECP256K1Mock) Close() error { // as per the original API, it returns an uncompressed key func (mock LedgerSECP256K1Mock) GetPublicKeySECP256K1(derivationPath []uint32) ([]byte, error) { if derivationPath[0] != 44 { - return nil, errors.New("Invalid derivation path") + return nil, errors.New("invalid derivation path") } if derivationPath[1] != sdk.GetConfig().GetCoinType() { - return nil, errors.New("Invalid derivation path") + return nil, errors.New("invalid derivation path") } seed, err := bip39.NewSeedWithErrorChecking(testdata.TestMnemonic, "") @@ -58,7 +56,7 @@ func (mock LedgerSECP256K1Mock) GetPublicKeySECP256K1(derivationPath []uint32) ( return nil, err } - _, pubkeyObject := secp256k1.PrivKeyFromBytes(secp256k1.S256(), derivedPriv[:]) + pubkeyObject := secp.PrivKeyFromBytes(derivedPriv).PubKey() return pubkeyObject.SerializeUncompressed(), nil } @@ -72,7 +70,7 @@ func (mock LedgerSECP256K1Mock) GetAddressPubKeySECP256K1(derivationPath []uint3 } // re-serialize in the 33-byte compressed format - cmp, err := btcec.ParsePubKey(pk[:], btcec.S256()) + cmp, err := secp.ParsePubKey(pk) if err != nil { return nil, "", fmt.Errorf("error parsing public key: %v", err) } @@ -80,7 +78,7 @@ func (mock LedgerSECP256K1Mock) GetAddressPubKeySECP256K1(derivationPath []uint3 compressedPublicKey := make([]byte, csecp256k1.PubKeySize) copy(compressedPublicKey, cmp.SerializeCompressed()) - // Generate the bech32 addr using existing occrypto/etc. + // Generate the bech32 addr using existing ostcrypto/etc. pub := &csecp256k1.PubKey{Key: compressedPublicKey} addr := sdk.AccAddress(pub.Address()).String() return pk, addr, err @@ -99,16 +97,10 @@ func (mock LedgerSECP256K1Mock) SignSECP256K1(derivationPath []uint32, message [ return nil, err } - priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), derivedPriv[:]) - - sig, err := priv.Sign(crypto.Sha256(message)) - if err != nil { - return nil, err - } + priv := secp.PrivKeyFromBytes(derivedPriv) + sig := ecdsa.Sign(priv, crypto.Sha256(message)) - // Need to return DER as the ledger does - sig2 := btcec.Signature{R: sig.R, S: sig.S} - return sig2.Serialize(), nil + return sig.Serialize(), nil } // ShowAddressSECP256K1 shows the address for the corresponding bip32 derivation path diff --git a/crypto/ledger/ledger_notavail.go b/crypto/ledger/ledger_notavail.go index 578c33d436..4cc53e211c 100644 --- a/crypto/ledger/ledger_notavail.go +++ b/crypto/ledger/ledger_notavail.go @@ -6,7 +6,7 @@ package ledger import ( - "github.com/pkg/errors" + "errors" ) // If ledger support (build tag) has been enabled, which implies a CGO dependency, diff --git a/crypto/ledger/ledger_secp256k1.go b/crypto/ledger/ledger_secp256k1.go index d93bd24a4b..f6f65589ec 100644 --- a/crypto/ledger/ledger_secp256k1.go +++ b/crypto/ledger/ledger_secp256k1.go @@ -1,13 +1,13 @@ package ledger import ( + "errors" "fmt" + "math/big" "os" - "github.com/btcsuite/btcd/btcec" - "github.com/pkg/errors" - - tmbtcec "github.com/tendermint/btcd/btcec" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "github.com/Finschia/finschia-sdk/crypto/hd" "github.com/Finschia/finschia-sdk/crypto/keys/secp256k1" @@ -174,12 +174,31 @@ func warnIfErrors(f func() error) { } func convertDERtoBER(signatureDER []byte) ([]byte, error) { - sigDER, err := btcec.ParseDERSignature(signatureDER, btcec.S256()) + sigDER, err := ecdsa.ParseDERSignature(signatureDER) if err != nil { return nil, err } - sigBER := tmbtcec.Signature{R: sigDER.R, S: sigDER.S} - return sigBER.Serialize(), nil + + sigStr := sigDER.Serialize() + // The format of a DER encoded signature is as follows: + // 0x30 0x02 0x02 + r, s := new(big.Int), new(big.Int) + r.SetBytes(sigStr[4 : 4+sigStr[3]]) + s.SetBytes(sigStr[4+sigStr[3]+2:]) + + sModNScalar := new(secp.ModNScalar) + sModNScalar.SetByteSlice(s.Bytes()) + // based on https://github.com/tendermint/btcd/blob/ec996c5/btcec/signature.go#L33-L50 + if sModNScalar.IsOverHalfOrder() { + s = new(big.Int).Sub(secp.S256().N, s) + } + + sigBytes := make([]byte, 64) + // 0 pad the byte arrays from the left if they aren't big enough. + copy(sigBytes[32-len(r.Bytes()):32], r.Bytes()) + copy(sigBytes[64-len(s.Bytes()):64], s.Bytes()) + + return sigBytes, nil } func getDevice() (SECP256K1, error) { @@ -189,7 +208,7 @@ func getDevice() (SECP256K1, error) { device, err := discoverLedger() if err != nil { - return nil, errors.Wrap(err, "ledger nano S") + return nil, fmt.Errorf("ledger nano S: %w", err) } return device, nil @@ -243,7 +262,7 @@ func getPubKeyUnsafe(device SECP256K1, path hd.BIP44Params) (types.PubKey, error } // re-serialize in the 33-byte compressed format - cmp, err := btcec.ParsePubKey(publicKey, btcec.S256()) + cmp, err := secp.ParsePubKey(publicKey) if err != nil { return nil, fmt.Errorf("error parsing public key: %v", err) } @@ -267,7 +286,7 @@ func getPubKeyAddrSafe(device SECP256K1, path hd.BIP44Params, hrp string) (types } // re-serialize in the 33-byte compressed format - cmp, err := btcec.ParsePubKey(publicKey, btcec.S256()) + cmp, err := secp.ParsePubKey(publicKey) if err != nil { return nil, "", fmt.Errorf("error parsing public key: %v", err) } diff --git a/go.mod b/go.mod index 82f49b4f27..5912aec0e6 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.1 github.com/armon/go-metrics v0.4.1 github.com/bgentry/speakeasy v0.1.0 - github.com/btcsuite/btcd v0.22.1 github.com/coinbase/rosetta-sdk-go v0.8.3 github.com/coinbase/rosetta-sdk-go/types v1.0.0 github.com/confio/ics23/go v0.9.0 @@ -16,6 +15,7 @@ require ( github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/iavl v0.19.4 github.com/cosmos/ledger-cosmos-go v0.13.2 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/go-kit/kit v0.13.0 github.com/gogo/gateway v1.1.0 github.com/gogo/protobuf v1.3.3 @@ -42,7 +42,6 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.17.0 github.com/stretchr/testify v1.8.4 - github.com/tendermint/btcd v0.1.1 github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 github.com/tendermint/go-amino v0.16.0 github.com/tendermint/tendermint v0.34.24 @@ -62,6 +61,7 @@ require ( github.com/Finschia/r2ishiguro_vrf v0.1.2 // indirect github.com/Workiva/go-datastructures v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcd v0.22.1 // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index 63bb3d8544..981761cf5b 100644 --- a/go.sum +++ b/go.sum @@ -155,7 +155,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= @@ -661,8 +663,6 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= -github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI= github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= diff --git a/server/rosetta/converter.go b/server/rosetta/converter.go index ee6f1db2f3..1df0173fca 100644 --- a/server/rosetta/converter.go +++ b/server/rosetta/converter.go @@ -6,8 +6,8 @@ import ( "fmt" "reflect" - "github.com/btcsuite/btcd/btcec" rosettatypes "github.com/coinbase/rosetta-sdk-go/types" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" abci "github.com/tendermint/tendermint/abci/types" @@ -650,7 +650,7 @@ func (c converter) PubKey(pubKey *rosettatypes.PublicKey) (cryptotypes.PubKey, e return nil, crgerrs.WrapError(crgerrs.ErrUnsupportedCurve, "only secp256k1 supported") } - cmp, err := btcec.ParsePubKey(pubKey.Bytes, btcec.S256()) + cmp, err := secp.ParsePubKey(pubKey.Bytes) if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error()) }