diff --git a/crypto/hd/algo.go b/crypto/hd/algo.go index f934ad08aec..b0255c09287 100644 --- a/crypto/hd/algo.go +++ b/crypto/hd/algo.go @@ -1,30 +1,32 @@ package hd import ( - bip39 "github.com/cosmos/go-bip39" + "crypto/elliptic" + + "github.com/btcsuite/btcd/btcec" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" "github.com/cosmos/cosmos-sdk/crypto/types" ) // PubKeyType defines an algorithm to derive key-pairs which can be used for cryptographic signing. type PubKeyType string +// Signature scheme types const ( - // MultiType implies that a pubkey is a multisignature - MultiType = PubKeyType("multi") - // Secp256k1Type uses the Bitcoin secp256k1 ECDSA parameters. + MultiType = PubKeyType("multi") Secp256k1Type = PubKeyType("secp256k1") - // Ed25519Type represents the Ed25519Type signature system. - // It is currently not supported for end-user keys (wallets/ledgers). + Secp256r1Type = PubKeyType(secp256r1.Name) + Ed25519Type = PubKeyType("ed25519") - // Sr25519Type represents the Sr25519Type signature system. Sr25519Type = PubKeyType("sr25519") ) +// Secp256 algos use the Bip39 derivation mechanism var ( - // Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters. - Secp256k1 = secp256k1Algo{} + Secp256k1 = ecdsaAlgo{btcec.S256(), Secp256k1Type, genSecp256k1} + Secp256r1 = ecdsaAlgo{elliptic.P256(), Secp256r1Type, genSecp256r1} ) type DeriveFn func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) @@ -35,37 +37,46 @@ type WalletGenerator interface { Generate(bz []byte) types.PrivKey } -type secp256k1Algo struct { +type ecdsaAlgo struct { + curve elliptic.Curve + name PubKeyType + gen GenerateFn } -func (s secp256k1Algo) Name() PubKeyType { - return Secp256k1Type -} +// Name returns signature scheme name +func (s ecdsaAlgo) Name() PubKeyType { return s.name } // Derive derives and returns the secp256k1 private key for the given seed and HD path. -func (s secp256k1Algo) Derive() DeriveFn { - return func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) { - seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) - if err != nil { - return nil, err - } - - masterPriv, ch := ComputeMastersFromSeed(seed) - if len(hdPath) == 0 { - return masterPriv[:], nil - } - derivedKey, err := DerivePrivateKeyForPath(masterPriv, ch, hdPath) - - return derivedKey, err +func (s ecdsaAlgo) Derive() DeriveFn { + return s.derive +} + +func (s ecdsaAlgo) derive(mnemonic, bip39Passphrase, hdPath string) ([]byte, error) { + m, ch, err := masterPrivKey(mnemonic, bip39Passphrase) + if err != nil { + return nil, err } + if len(hdPath) == 0 { + return m[:], nil + } + return DeriveECDSAPrivKey(s.curve, m, ch, hdPath) } // Generate generates a secp256k1 private key from the given bytes. -func (s secp256k1Algo) Generate() GenerateFn { - return func(bz []byte) types.PrivKey { - var bzArr = make([]byte, secp256k1.PrivKeySize) - copy(bzArr, bz) +func (s ecdsaAlgo) Generate() GenerateFn { + return s.gen +} + +func genSecp256k1(bz []byte) types.PrivKey { + var bzArr = make([]byte, secp256k1.PrivKeySize) + copy(bzArr, bz) + return &secp256k1.PrivKey{Key: bzArr} +} - return &secp256k1.PrivKey{Key: bzArr} +func genSecp256r1(bz []byte) types.PrivKey { + key, err := secp256r1.NewPrivKeyFromSecret(bz) + if err != nil { + panic(err) } + return key } diff --git a/crypto/hd/fundraiser_test.go b/crypto/hd/fundraiser_test.go index 4afbfc5a9ce..56d67854634 100644 --- a/crypto/hd/fundraiser_test.go +++ b/crypto/hd/fundraiser_test.go @@ -1,12 +1,14 @@ package hd_test import ( + "crypto/elliptic" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "testing" + "github.com/btcsuite/btcd/btcec" "github.com/stretchr/testify/require" bip39 "github.com/cosmos/go-bip39" @@ -26,6 +28,9 @@ type addrData struct { Addr string } +var secp256k1Curve = btcec.S256() +var secp256r1Curve = elliptic.P256() + func TestFullFundraiserPath(t *testing.T) { require.Equal(t, "m/44'/118'/0'/0/0", hd.NewFundraiserParams(0, 118, 0).String()) } @@ -63,7 +68,7 @@ func TestFundraiserCompatibility(t *testing.T) { t.Logf("ROUND: %d MNEMONIC: %s", i, d.Mnemonic) master, ch := hd.ComputeMastersFromSeed(seed) - priv, err := hd.DerivePrivateKeyForPath(master, ch, "m/44'/118'/0'/0/0") + priv, err := hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, "m/44'/118'/0'/0/0") require.NoError(t, err) privKey := &secp256k1.PrivKey{Key: priv} diff --git a/crypto/hd/hdpath.go b/crypto/hd/hdpath.go index 058ddcc4566..a97764298cb 100644 --- a/crypto/hd/hdpath.go +++ b/crypto/hd/hdpath.go @@ -1,6 +1,7 @@ package hd import ( + "crypto/elliptic" "crypto/hmac" "crypto/sha512" "encoding/binary" @@ -11,6 +12,7 @@ import ( "strings" "github.com/btcsuite/btcd/btcec" + bip39 "github.com/cosmos/go-bip39" ) // BIP44Params wraps BIP 44 params (5 level BIP 32 path). @@ -167,6 +169,15 @@ func (p BIP44Params) String() string { p.AddressIndex) } +func masterPrivKey(mnemonic string, bip39Passphrase string) (secret [32]byte, chainCode [32]byte, err error) { + var seed []byte + if seed, err = bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase); err != nil { + return + } + secret, chainCode = ComputeMastersFromSeed(seed) + return +} + // ComputeMastersFromSeed returns the master secret key's, and chain code. func ComputeMastersFromSeed(seed []byte) (secret [32]byte, chainCode [32]byte) { curveIdentifier := []byte("Bitcoin seed") @@ -175,9 +186,9 @@ func ComputeMastersFromSeed(seed []byte) (secret [32]byte, chainCode [32]byte) { return } -// DerivePrivateKeyForPath derives the private key by following the BIP 32/44 path from privKeyBytes, +// DeriveECDSAPrivKey derives the private key by following the BIP 32/44 path from privKeyBytes, // using the given chainCode. -func DerivePrivateKeyForPath(privKeyBytes, chainCode [32]byte, path string) ([]byte, error) { +func DeriveECDSAPrivKey(curve elliptic.Curve, privKeyBytes, chainCode [32]byte, path string) ([]byte, error) { // First step is to trim the right end path separator lest we panic. // See issue https://github.com/cosmos/cosmos-sdk/issues/8557 path = strings.TrimRightFunc(path, func(r rune) bool { return r == filepath.Separator }) @@ -210,7 +221,7 @@ func DerivePrivateKeyForPath(privKeyBytes, chainCode [32]byte, path string) ([]b return []byte{}, fmt.Errorf("invalid BIP 32 path %s: %w", path, err) } - data, chainCode = derivePrivateKey(data, chainCode, uint32(idx), harden) + data, chainCode = derivePrivateKey(curve, data, chainCode, uint32(idx), harden) } derivedKey := make([]byte, 32) @@ -228,39 +239,33 @@ func DerivePrivateKeyForPath(privKeyBytes, chainCode [32]byte, path string) ([]b // It returns the new private key and new chain code. // For more information on hardened keys see: // - https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki -func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, harden bool) ([32]byte, [32]byte) { +func derivePrivateKey(curve elliptic.Curve, privKeyBytes, chainCode [32]byte, index uint32, harden bool) ([32]byte, [32]byte) { var data []byte if harden { index |= 0x80000000 - data = append([]byte{byte(0)}, privKeyBytes[:]...) + data = append([]byte{0}, privKeyBytes[:]...) } else { + // we use btcec instead of tendermint // this can't return an error: - _, ecPub := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes[:]) + _, ecPub := btcec.PrivKeyFromBytes(curve, privKeyBytes[:]) pubkeyBytes := ecPub.SerializeCompressed() data = pubkeyBytes - - /* By using btcec, we can remove the dependency on tendermint/crypto/secp256k1 - pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey() - public := pubkey.(secp256k1.PubKeySecp256k1) - data = public[:] - */ } data = append(data, uint32ToBytes(index)...) data2, chainCode2 := i64(chainCode[:], data) - x := addScalars(privKeyBytes[:], data2[:]) - + x := addScalars(privKeyBytes[:], data2[:], curve.Params().N) return x, chainCode2 } // modular big endian addition -func addScalars(a []byte, b []byte) [32]byte { +func addScalars(a []byte, b []byte, order *big.Int) [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, order).Bytes() x2 := [32]byte{} copy(x2[32-len(x):], x) diff --git a/crypto/hd/hdpath_test.go b/crypto/hd/hdpath_test.go index 8b1c40692c8..e983c119c9b 100644 --- a/crypto/hd/hdpath_test.go +++ b/crypto/hd/hdpath_test.go @@ -173,19 +173,26 @@ func TestDeriveHDPathRange(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.path, func(t *testing.T) { + require := require.New(t) master, ch := hd.ComputeMastersFromSeed(seed) - _, err := hd.DerivePrivateKeyForPath(master, ch, tt.path) + _, err := hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, tt.path) + checkError(require, err, tt.wantErr) - if tt.wantErr == "" { - require.NoError(t, err, "unexpected error") - } else { - require.Error(t, err, "expected a report of an int overflow") - require.Contains(t, err.Error(), tt.wantErr) - } + _, err = hd.DeriveECDSAPrivKey(secp256r1Curve, master, ch, tt.path) + checkError(require, err, tt.wantErr) }) } } +func checkError(require *require.Assertions, err error, wantErr string) { + if wantErr == "" { + require.NoError(err, "unexpected error") + } else { + require.Error(err, "expected a report of an int overflow") + require.Contains(err.Error(), wantErr) + } +} + func ExampleStringifyPathParams() { path := hd.NewParams(44, 0, 0, false, 0) fmt.Println(path.String()) @@ -203,34 +210,34 @@ func ExampleSomeBIP32TestVecs() { fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") fmt.Println() // cosmos - priv, err := hd.DerivePrivateKeyForPath(master, ch, types.FullFundraiserPath) + priv, err := hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, types.FullFundraiserPath) if err != nil { fmt.Println("INVALID") } else { fmt.Println(hex.EncodeToString(priv[:])) } // bitcoin - priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") + priv, err = hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, "44'/0'/0'/0/0") if err != nil { fmt.Println("INVALID") } else { fmt.Println(hex.EncodeToString(priv[:])) } // ether - priv, err = hd.DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") + priv, err = hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, "44'/60'/0'/0/0") if err != nil { fmt.Println("INVALID") } else { fmt.Println(hex.EncodeToString(priv[:])) } // INVALID - priv, err = hd.DerivePrivateKeyForPath(master, ch, "X/0'/0'/0/0") + priv, err = hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, "X/0'/0'/0/0") if err != nil { fmt.Println("INVALID") } else { fmt.Println(hex.EncodeToString(priv[:])) } - priv, err = hd.DerivePrivateKeyForPath(master, ch, "-44/0'/0'/0/0") + priv, err = hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, "-44/0'/0'/0/0") if err != nil { fmt.Println("INVALID") } else { @@ -245,13 +252,13 @@ func ExampleSomeBIP32TestVecs() { "advice process birth april short trust crater change bacon monkey medal garment " + "gorilla ranch hour rival razor call lunar mention taste vacant woman sister") master, ch = hd.ComputeMastersFromSeed(seed) - priv, _ = hd.DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") + priv, _ = hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, "44'/1'/1'/0/4") fmt.Println(hex.EncodeToString(priv[:])) seed = mnemonicToSeed("idea naive region square margin day captain habit " + "gun second farm pact pulse someone armed") master, ch = hd.ComputeMastersFromSeed(seed) - priv, _ = hd.DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") + priv, _ = hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, "44'/0'/0'/0/420") fmt.Println(hex.EncodeToString(priv[:])) fmt.Println() @@ -261,7 +268,7 @@ func ExampleSomeBIP32TestVecs() { // bip32 path: m/0/7 seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") master, ch = hd.ComputeMastersFromSeed(seed) - priv, _ = hd.DerivePrivateKeyForPath(master, ch, "0/7") + priv, _ = hd.DeriveECDSAPrivKey(secp256k1Curve, master, ch, "0/7") fmt.Println(hex.EncodeToString(priv[:])) // Output: keys from fundraiser test-vector (cosmos, bitcoin, ether) @@ -300,7 +307,7 @@ func TestDerivePrivateKeyForPathDoNotCrash(t *testing.T) { for _, path := range paths { path := path t.Run(path, func(t *testing.T) { - hd.DerivePrivateKeyForPath([32]byte{}, [32]byte{}, path) + hd.DeriveECDSAPrivKey(secp256k1Curve, [32]byte{}, [32]byte{}, path) }) } } diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index 0634871a551..f3e23acab5a 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -203,7 +203,7 @@ type keystore struct { func newKeystore(kr keyring.Keyring, opts ...Option) keystore { // Default options for keybase options := Options{ - SupportedAlgos: SigningAlgoList{hd.Secp256k1}, + SupportedAlgos: SigningAlgoList{hd.Secp256k1, hd.Secp256r1}, SupportedAlgosLedger: SigningAlgoList{hd.Secp256k1}, } diff --git a/crypto/keyring/signing_algorithms.go b/crypto/keyring/signing_algorithms.go index 920dd7b5531..b84ddd2a7ba 100644 --- a/crypto/keyring/signing_algorithms.go +++ b/crypto/keyring/signing_algorithms.go @@ -21,7 +21,7 @@ func NewSigningAlgoFromString(str string, algoList SigningAlgoList) (SignatureAl return algo, nil } } - return nil, fmt.Errorf("provided algorithm %q is not supported", str) + return nil, fmt.Errorf("provided algorithm %q is not supported, supported algorithms: %v", str, algoList) } // SigningAlgoList is a slice of signature algorithms diff --git a/crypto/keys/internal/ecdsa/privkey.go b/crypto/keys/internal/ecdsa/privkey.go index 0930285b04a..83913db01b7 100644 --- a/crypto/keys/internal/ecdsa/privkey.go +++ b/crypto/keys/internal/ecdsa/privkey.go @@ -1,23 +1,54 @@ package ecdsa import ( + "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "fmt" + "io" "math/big" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // GenPrivKey generates a new secp256r1 private key. It uses operating system randomness. -func GenPrivKey(curve elliptic.Curve) (PrivKey, error) { - key, err := ecdsa.GenerateKey(curve, rand.Reader) +func GenPrivKey(c elliptic.Curve) (PrivKey, error) { + return privFromBuffer(c, rand.Reader) +} + +// NewPrivKey creates a private key derived from random bytes +// The bytes size must be at least the size of the Curve field. This function is +// deterministic. +func NewPrivKey(c elliptic.Curve, random []byte) (PrivKey, error) { + if s := c.Params().BitSize; len(random) < s { + return PrivKey{}, fmt.Errorf("wrong secret length, must be at least %v", s) + } + return privFromBuffer(c, bytes.NewBuffer(random)) +} + +// NewPrivKeyFromSecret creates a private key derived for the secret number +// represented in big-endian. The `secret` must be a curve field element. +// This function is deterministic. +func NewPrivKeyFromSecret(c elliptic.Curve, secret []byte) (PrivKey, error) { + var d = new(big.Int).SetBytes(secret) + if d.Cmp(c.Params().N) >= 1 { + return PrivKey{}, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "secret not in the curve base field") + } + sk := new(PrivKey) + return *sk, sk.Unmarshal(secret, c, (c.Params().Params().BitSize+7)/8) +} + +func privFromBuffer(c elliptic.Curve, buf io.Reader) (PrivKey, error) { + key, err := ecdsa.GenerateKey(c, buf) if err != nil { return PrivKey{}, err } return PrivKey{*key}, nil } +// PrivKey is type which partially iimplements cryptotypes.PrivKey type PrivKey struct { ecdsa.PrivateKey } @@ -56,7 +87,7 @@ func (sk *PrivKey) MarshalTo(dAtA []byte) (int, error) { return len(bz), nil } -// Unmarshal implements proto.Marshaler interface. +// Unmarshal implements proto.Marshaler interface. bz must have expectedSize bytes func (sk *PrivKey) Unmarshal(bz []byte, curve elliptic.Curve, expectedSize int) error { if len(bz) != expectedSize { return fmt.Errorf("wrong ECDSA SK bytes, expecting %d bytes", expectedSize) diff --git a/crypto/keys/internal/ecdsa/privkey_internal_test.go b/crypto/keys/internal/ecdsa/privkey_internal_test.go index 095e593656d..9512570ccb7 100644 --- a/crypto/keys/internal/ecdsa/privkey_internal_test.go +++ b/crypto/keys/internal/ecdsa/privkey_internal_test.go @@ -1,6 +1,7 @@ package ecdsa import ( + "crypto/rand" "testing" "github.com/tendermint/tendermint/crypto" @@ -14,6 +15,31 @@ func TestSKSuite(t *testing.T) { type SKSuite struct{ CommonSuite } +func (suite *SKSuite) TestNewPrivKey() { + require := suite.Require() + cparams := secp256r1.Params() + + secret := make([]byte, cparams.BitSize) + n, err := rand.Reader.Read(secret) + require.NoError(err) + require.Equal(cparams.BitSize, n) + + sk, err := NewPrivKey(secp256r1, secret) + require.NoError(err) + for i := 0; i < 10; i++ { + sk2, err := NewPrivKey(secp256r1, secret) + require.NoError(err) + require.True(sk.Equal(&sk2.PrivateKey), "NewPrivKey must be deterministic") + } + + // Test if NewPrivKeyFromSecret correctly creates a private key + + secret = suite.sk.D.Bytes() + sk, err = NewPrivKeyFromSecret(secp256r1, secret) + require.NoError(err) + require.True(sk.Equal(&suite.sk.PrivateKey), "NewPrivKeyFromSecret must correctly recreate a private key") +} + func (suite *SKSuite) TestString() { const prefix = "abc" suite.Require().Equal(prefix+"{-}", suite.sk.String(prefix)) diff --git a/crypto/keys/secp256r1/doc.go b/crypto/keys/secp256r1/doc.go index be938dc77f9..5e0a5399a3b 100644 --- a/crypto/keys/secp256r1/doc.go +++ b/crypto/keys/secp256r1/doc.go @@ -15,7 +15,8 @@ const ( fieldSize = 32 pubKeySize = fieldSize + 1 - name = "secp256r1" + // Name is a typename for ECDSA secp256r1 keys in Cosmos SDK + Name = "secp256r1" ) var secp256r1 elliptic.Curve diff --git a/crypto/keys/secp256r1/privkey.go b/crypto/keys/secp256r1/privkey.go index 8ca725420ff..be0d751b48f 100644 --- a/crypto/keys/secp256r1/privkey.go +++ b/crypto/keys/secp256r1/privkey.go @@ -11,6 +11,13 @@ func GenPrivKey() (*PrivKey, error) { return &PrivKey{&ecdsaSK{key}}, err } +// NewPrivKeyFromSecret creates a private key derived for the secret number +// represented in big-endian. The `secret` must be a valid ECDSA field element. +func NewPrivKeyFromSecret(secret []byte) (*PrivKey, error) { + key, err := ecdsa.NewPrivKeyFromSecret(secp256r1, secret) + return &PrivKey{&ecdsaSK{key}}, err +} + // PubKey implements SDK PrivKey interface. func (m *PrivKey) PubKey() cryptotypes.PubKey { return &PubKey{&ecdsaPK{m.Secret.PubKey()}} @@ -18,12 +25,12 @@ func (m *PrivKey) PubKey() cryptotypes.PubKey { // String implements SDK proto.Message interface. func (m *PrivKey) String() string { - return m.Secret.String(name) + return m.Secret.String(Name) } // Type returns key type name. Implements SDK PrivKey interface. func (m *PrivKey) Type() string { - return name + return Name } // Sign hashes and signs the message usign ECDSA. Implements sdk.PrivKey interface. diff --git a/crypto/keys/secp256r1/pubkey.go b/crypto/keys/secp256r1/pubkey.go index d75c0f5298b..087bfe674f3 100644 --- a/crypto/keys/secp256r1/pubkey.go +++ b/crypto/keys/secp256r1/pubkey.go @@ -10,7 +10,7 @@ import ( // String implements proto.Message interface. func (m *PubKey) String() string { - return m.Key.String(name) + return m.Key.String(Name) } // Bytes implements SDK PubKey interface. @@ -34,7 +34,7 @@ func (m *PubKey) Address() tmcrypto.Address { // Type returns key type name. Implements SDK PubKey interface. func (m *PubKey) Type() string { - return name + return Name } // VerifySignature implements SDK PubKey interface. diff --git a/crypto/keys/secp256r1/pubkey_internal_test.go b/crypto/keys/secp256r1/pubkey_internal_test.go index 84b587d0c63..330d47240e4 100644 --- a/crypto/keys/secp256r1/pubkey_internal_test.go +++ b/crypto/keys/secp256r1/pubkey_internal_test.go @@ -41,7 +41,7 @@ func (suite *PKSuite) TestString() { } func (suite *PKSuite) TestType() { - suite.Require().Equal(name, suite.pk.Type()) + suite.Require().Equal(Name, suite.pk.Type()) } func (suite *PKSuite) TestEquals() {