Merge PR #2090: Improve crypto/keys and add `keys mnemonic` and `keys new` commands
* crypto/keys/hd: use btcec to remove dep on tendermint * crypto/keys/bcrypt: improve comment about fork * crypto/keys/bip39 -> crypto/keys/bip39/fundraiser * crypto/keys/bip39: bring in fork of tyler-smith * crypto/keys/hd: update dep * crypto/keys: update deps * crypto/keys: move mintkey.go into new crypto/keys/mintkey * crypto/keys/hd: NewParamsFromPath * crypto/keys: keybase.Derive takes a bip39 passphrase too * crypto/keys/hd: BIP44Params.DerivationPath * gaiacli keys: add commands new and mnemonic * fix lints * minor fixes from review * update Gopkg.toml * add tendermint fork of golang.org/x/crypto * pin some transitive deps * crypto/keys/bcrypt: remove * remove in favour of fork of golang.org/x/crypto/bcrypt at github.com/tendermint/crypto/bcrypt * crypto/keys/bip39: remove completely * use fork cosmos/go-bip39 instead * Gopkg.toml: dont use master * Pull in changes from my PR * fixes from review * enforce min len for --unsafe-entropy * lint fix * feedback from review * fix dep
This commit is contained in:
parent
c961a684c9
commit
1ee8deed2b
|
@ -48,12 +48,19 @@
|
|||
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
|
||||
digest = "1:e8a3550c8786316675ff54ad6f09d265d129c9d986919af7f541afba50d87ce2"
|
||||
name = "github.com/cosmos/go-bip39"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "52158e4697b87de16ed390e1bdaf813e581008fa"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
|
||||
version = "v1.1.1"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b"
|
||||
|
@ -390,8 +397,7 @@
|
|||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5"
|
||||
digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = [
|
||||
"leveldb",
|
||||
|
@ -408,7 +414,7 @@
|
|||
"leveldb/util",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd"
|
||||
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f"
|
||||
|
@ -524,10 +530,10 @@
|
|||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a"
|
||||
digest = "1:aaff04fa01d9b824fde6799759cc597b3ac3671b9ad31924c28b6557d0ee5284"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
"blowfish",
|
||||
"chacha20poly1305",
|
||||
"curve25519",
|
||||
|
@ -544,7 +550,8 @@
|
|||
"salsa20/salsa",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "e3636079e1a4c1f337f212cc5cd2aca108f6c900"
|
||||
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||
source = "https://github.com/tendermint/crypto"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
|
||||
|
@ -563,15 +570,14 @@
|
|||
revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8bc8ecef1d63576cfab4d08b44a1f255dd67e5b019b7a44837d62380f266a91c"
|
||||
digest = "1:4bd75b1a219bc590b05c976bbebf47f4e993314ebb5c7cbf2efe05a09a184d54"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"cpu",
|
||||
"unix",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "e4b3c5e9061176387e7cea65e4dc5853801f3fb7"
|
||||
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||
|
@ -597,12 +603,11 @@
|
|||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:1e6b0176e8c5dd8ff551af65c76f8b73a99bcf4d812cedff1b91711b7df4804c"
|
||||
digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c"
|
||||
name = "google.golang.org/genproto"
|
||||
packages = ["googleapis/rpc/status"]
|
||||
pruneopts = "UT"
|
||||
revision = "c7e5094acea1ca1b899e2259d80a6b0f882f81f8"
|
||||
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
|
||||
|
@ -653,6 +658,7 @@
|
|||
"github.com/bartekn/go-bip39",
|
||||
"github.com/bgentry/speakeasy",
|
||||
"github.com/btcsuite/btcd/btcec",
|
||||
"github.com/cosmos/go-bip39",
|
||||
"github.com/golang/protobuf/proto",
|
||||
"github.com/gorilla/mux",
|
||||
"github.com/mattn/go-isatty",
|
||||
|
@ -699,7 +705,7 @@
|
|||
"github.com/tendermint/tendermint/types",
|
||||
"github.com/tendermint/tendermint/version",
|
||||
"github.com/zondax/ledger-goclient",
|
||||
"golang.org/x/crypto/blowfish",
|
||||
"golang.org/x/crypto/bcrypt",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
39
Gopkg.toml
39
Gopkg.toml
|
@ -59,22 +59,51 @@
|
|||
name = "github.com/tendermint/tendermint"
|
||||
version = "=0.25.0"
|
||||
|
||||
## deps without releases:
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/bartekn/go-bip39"
|
||||
revision = "a05967ea095d81c8fe4833776774cfaff8e5036c"
|
||||
name = "golang.org/x/crypto"
|
||||
source = "https://github.com/tendermint/crypto"
|
||||
revision = "3764759f34a542a3aef74d6b02e35be7ab893bba"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/cosmos/go-bip39"
|
||||
revision = "52158e4697b87de16ed390e1bdaf813e581008fa"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/zondax/ledger-goclient"
|
||||
version = "=v0.1.0"
|
||||
|
||||
## transitive deps, with releases:
|
||||
|
||||
[[override]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
version = "=v1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rakyll/statik"
|
||||
version = "=v0.1.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
version = "1.0.0"
|
||||
|
||||
## transitive deps, without releases:
|
||||
#
|
||||
|
||||
[[override]]
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445"
|
||||
|
||||
[[override]]
|
||||
name = "golang.org/x/sys"
|
||||
revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf"
|
||||
|
||||
[[override]]
|
||||
name = "google.golang.org/genproto"
|
||||
revision = "383e8b2c3b9e36c4076b235b32537292176bae20"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
version = "1.0.0"
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/bgentry/speakeasy"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -44,13 +44,8 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
|
|||
|
||||
// GetSeed will request a seed phrase from stdin and trims off
|
||||
// leading/trailing spaces
|
||||
func GetSeed(prompt string, buf *bufio.Reader) (seed string, err error) {
|
||||
if inputIsTty() {
|
||||
fmt.Println(prompt)
|
||||
}
|
||||
seed, err = readLineFromBuf(buf)
|
||||
seed = strings.TrimSpace(seed)
|
||||
return
|
||||
func GetSeed(prompt string, buf *bufio.Reader) (string, error) {
|
||||
return GetString(prompt, buf)
|
||||
}
|
||||
|
||||
// GetCheckPassword will prompt for a password twice to verify they
|
||||
|
@ -133,5 +128,6 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) {
|
|||
|
||||
// PrintPrefixed prints a string with > prefixed for use in prompts.
|
||||
func PrintPrefixed(msg string) {
|
||||
fmt.Printf("> %s\n", msg)
|
||||
msg = fmt.Sprintf("> %s\n", msg)
|
||||
fmt.Fprint(os.Stderr, msg)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
package keys
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
bip39 "github.com/bartekn/go-bip39"
|
||||
)
|
||||
|
||||
const (
|
||||
flagUserEntropy = "unsafe-entropy"
|
||||
|
||||
mnemonicEntropySize = 256
|
||||
)
|
||||
|
||||
func mnemonicKeyCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "mnemonic",
|
||||
Short: "Compute the bip39 mnemonic for some input entropy",
|
||||
Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy",
|
||||
RunE: runMnemonicCmd,
|
||||
}
|
||||
cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runMnemonicCmd(cmd *cobra.Command, args []string) error {
|
||||
flags := cmd.Flags()
|
||||
|
||||
userEntropy, _ := flags.GetBool(flagUserEntropy)
|
||||
|
||||
var entropySeed []byte
|
||||
|
||||
if userEntropy {
|
||||
// prompt the user to enter some entropy
|
||||
buf := client.BufferStdin()
|
||||
inputEntropy, err := client.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(inputEntropy) < 43 {
|
||||
return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy))
|
||||
}
|
||||
conf, err := client.GetConfirmation(
|
||||
fmt.Sprintf("> Input length: %d", len(inputEntropy)),
|
||||
buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !conf {
|
||||
return nil
|
||||
}
|
||||
|
||||
// hash input entropy to get entropy seed
|
||||
hashedEntropy := sha256.Sum256([]byte(inputEntropy))
|
||||
entropySeed = hashedEntropy[:]
|
||||
printStep()
|
||||
} else {
|
||||
// read entropy seed straight from crypto.Rand
|
||||
var err error
|
||||
entropySeed, err = bip39.NewEntropy(mnemonicEntropySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
mnemonic, err := bip39.NewMnemonic(entropySeed[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(mnemonic)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package keys
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/bartekn/go-bip39"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
)
|
||||
|
||||
const (
|
||||
flagNewDefault = "default"
|
||||
flagBIP44Path = "bip44-path"
|
||||
)
|
||||
|
||||
func newKeyCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "new",
|
||||
Short: "Interactive command to derive a new private key, encrypt it, and save to disk",
|
||||
Long: `Derive a new private key using an interactive command that will prompt you for each input.
|
||||
Optionally specify a bip39 mnemonic, a bip39 passphrase to further secure the mnemonic,
|
||||
and a bip32 HD path to derive a specific account. The key will be stored under the given name
|
||||
and encrypted with the given password. The only input that is required is the encryption password.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runNewCmd,
|
||||
}
|
||||
cmd.Flags().Bool(flagNewDefault, false, "Skip the prompts and just use the default values for everything")
|
||||
cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device")
|
||||
cmd.Flags().String(flagBIP44Path, "44'/118'/0'/0/0", "BIP44 path from which to derive a private key")
|
||||
return cmd
|
||||
}
|
||||
|
||||
/*
|
||||
input
|
||||
- bip39 mnemonic
|
||||
- bip39 passphrase
|
||||
- bip44 path
|
||||
- local encryption password
|
||||
output
|
||||
- armor encrypted private key (saved to file)
|
||||
*/
|
||||
// nolint: gocyclo
|
||||
func runNewCmd(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
kb, err := GetKeyBase()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := client.BufferStdin()
|
||||
_, err = kb.Get(name)
|
||||
if err == nil {
|
||||
// account exists, ask for user confirmation
|
||||
if response, err := client.GetConfirmation(
|
||||
fmt.Sprintf("> override the existing name %s", name), buf); err != nil || !response {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
useDefaults, _ := flags.GetBool(flagNewDefault)
|
||||
bipFlag := flags.Lookup(flagBIP44Path)
|
||||
|
||||
bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || useDefaults)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we're using ledger, only thing we need is the path.
|
||||
// generate key and we're done.
|
||||
if viper.GetBool(client.FlagUseLedger) {
|
||||
|
||||
algo := keys.Secp256k1 // SigningAlgo(viper.GetString(flagType))
|
||||
path := bip44Params.DerivationPath() // ccrypto.DerivationPath{44, 118, account, 0, index}
|
||||
info, err := kb.CreateLedger(name, path, algo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printCreate(info, "")
|
||||
return nil
|
||||
}
|
||||
|
||||
// get the mnemonic
|
||||
var mnemonic string
|
||||
if !useDefaults {
|
||||
mnemonic, err = client.GetString("> Enter your bip39 mnemonic, or hit enter to generate one.", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(mnemonic) == 0 {
|
||||
// read entropy seed straight from crypto.Rand and convert to mnemonic
|
||||
entropySeed, err := bip39.NewEntropy(mnemonicEntropySize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mnemonic, err = bip39.NewMnemonic(entropySeed[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// get bip39 passphrase
|
||||
var bip39Passphrase string
|
||||
if !useDefaults {
|
||||
printStep()
|
||||
printPrefixed("Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed")
|
||||
bip39Passphrase, err = client.GetString("> Most users should just hit enter to use the default, \"\"", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if they use one, make them re-enter it
|
||||
if len(bip39Passphrase) != 0 {
|
||||
p2, err := client.GetString("Repeat the passphrase:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bip39Passphrase != p2 {
|
||||
return errors.New("passphrases don't match")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the encryption password
|
||||
printStep()
|
||||
encryptPassword, err := client.GetCheckPassword(
|
||||
"> Enter a passphrase to encrypt your key to disk:",
|
||||
"> Repeat the passphrase:", buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = info
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) {
|
||||
buf := bufio.NewReader(os.Stdin)
|
||||
bip44Path := path
|
||||
|
||||
// if it wasnt set in the flag, give it a chance to overide interactively
|
||||
if !flagSet {
|
||||
printStep()
|
||||
|
||||
var err error
|
||||
bip44Path, err = client.GetString(fmt.Sprintf("> Enter your bip44 path. Default is %s\n", path), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(bip44Path) == 0 {
|
||||
bip44Path = path
|
||||
}
|
||||
}
|
||||
|
||||
bip44params, err := hd.NewParamsFromPath(bip44Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bip44params, nil
|
||||
}
|
||||
|
||||
func printPrefixed(msg string) {
|
||||
fmt.Printf("> %s\n", msg)
|
||||
}
|
||||
|
||||
func printStep() {
|
||||
printPrefixed("-------------------------------------")
|
||||
}
|
|
@ -19,6 +19,8 @@ func Commands() *cobra.Command {
|
|||
needs to sign with a private key.`,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
mnemonicKeyCommand(),
|
||||
newKeyCommand(),
|
||||
addKeyCommand(),
|
||||
listKeysCmd,
|
||||
showKeysCmd(),
|
||||
|
|
|
@ -10,20 +10,20 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
p2p "github.com/tendermint/tendermint/p2p"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
p2p "github.com/tendermint/tendermint/p2p"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
client "github.com/cosmos/cosmos-sdk/client"
|
||||
keys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||
tests "github.com/cosmos/cosmos-sdk/tests"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
version "github.com/cosmos/cosmos-sdk/version"
|
||||
|
@ -35,7 +35,7 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
cryptoKeys.BcryptSecurityParameter = 1
|
||||
mintkey.BcryptSecurityParameter = 1
|
||||
version.Version = os.Getenv("VERSION")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bcrypt
|
||||
|
||||
import "encoding/base64"
|
||||
|
||||
const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
var bcEncoding = base64.NewEncoding(alphabet)
|
||||
|
||||
func base64Encode(src []byte) []byte {
|
||||
n := bcEncoding.EncodedLen(len(src))
|
||||
dst := make([]byte, n)
|
||||
bcEncoding.Encode(dst, src)
|
||||
for dst[n-1] == '=' {
|
||||
n--
|
||||
}
|
||||
return dst[:n]
|
||||
}
|
||||
|
||||
func base64Decode(src []byte) ([]byte, error) {
|
||||
numOfEquals := 4 - (len(src) % 4)
|
||||
for i := 0; i < numOfEquals; i++ {
|
||||
src = append(src, '=')
|
||||
}
|
||||
|
||||
dst := make([]byte, bcEncoding.DecodedLen(len(src)))
|
||||
n, err := bcEncoding.Decode(dst, src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dst[:n], nil
|
||||
}
|
|
@ -1,297 +0,0 @@
|
|||
package bcrypt
|
||||
|
||||
// MODIFIED BY TENDERMINT TO EXPOSE NONCE
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
|
||||
// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
|
||||
|
||||
// The code is a port of Provos and Mazières's C implementation.
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/crypto/blowfish"
|
||||
)
|
||||
|
||||
const (
|
||||
// the minimum allowable cost as passed in to GenerateFromPassword
|
||||
MinCost int = 4
|
||||
// the maximum allowable cost as passed in to GenerateFromPassword
|
||||
MaxCost int = 31
|
||||
// the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword
|
||||
DefaultCost int = 10
|
||||
)
|
||||
|
||||
// The error returned from CompareHashAndPassword when a password and hash do
|
||||
// not match.
|
||||
var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password")
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash is too short to
|
||||
// be a bcrypt hash.
|
||||
var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password")
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash was created with
|
||||
// a bcrypt algorithm newer than this implementation.
|
||||
type HashVersionTooNewError byte
|
||||
|
||||
func (hv HashVersionTooNewError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion)
|
||||
}
|
||||
|
||||
// The error returned from CompareHashAndPassword when a hash starts with something other than '$'
|
||||
type InvalidHashPrefixError byte
|
||||
|
||||
// Format error
|
||||
func (ih InvalidHashPrefixError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih))
|
||||
}
|
||||
|
||||
// Invalid bcrypt cost
|
||||
type InvalidCostError int
|
||||
|
||||
func (ic InvalidCostError) Error() string {
|
||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) // nolint: unconvert
|
||||
}
|
||||
|
||||
const (
|
||||
majorVersion = '2'
|
||||
minorVersion = 'a'
|
||||
maxSaltSize = 16
|
||||
maxCryptedHashSize = 23
|
||||
encodedSaltSize = 22
|
||||
encodedHashSize = 31
|
||||
minHashSize = 59
|
||||
)
|
||||
|
||||
// magicCipherData is an IV for the 64 Blowfish encryption calls in
|
||||
// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes.
|
||||
var magicCipherData = []byte{
|
||||
0x4f, 0x72, 0x70, 0x68,
|
||||
0x65, 0x61, 0x6e, 0x42,
|
||||
0x65, 0x68, 0x6f, 0x6c,
|
||||
0x64, 0x65, 0x72, 0x53,
|
||||
0x63, 0x72, 0x79, 0x44,
|
||||
0x6f, 0x75, 0x62, 0x74,
|
||||
}
|
||||
|
||||
type hashed struct {
|
||||
hash []byte
|
||||
salt []byte
|
||||
cost int // allowed range is MinCost to MaxCost
|
||||
major byte
|
||||
minor byte
|
||||
}
|
||||
|
||||
// GenerateFromPassword returns the bcrypt hash of the password at the given
|
||||
// cost. If the cost given is less than MinCost, the cost will be set to
|
||||
// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package,
|
||||
// to compare the returned hashed password with its cleartext version.
|
||||
func GenerateFromPassword(salt []byte, password []byte, cost int) ([]byte, error) {
|
||||
if len(salt) != maxSaltSize {
|
||||
return nil, fmt.Errorf("salt len must be %v", maxSaltSize)
|
||||
}
|
||||
p, err := newFromPassword(salt, password, cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.Hash(), nil
|
||||
}
|
||||
|
||||
// CompareHashAndPassword compares a bcrypt hashed password with its possible
|
||||
// plaintext equivalent. Returns nil on success, or an error on failure.
|
||||
func CompareHashAndPassword(hashedPassword, password []byte) error {
|
||||
p, err := newFromHash(hashedPassword)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherHash, err := bcrypt(password, p.cost, p.salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor}
|
||||
if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrMismatchedHashAndPassword
|
||||
}
|
||||
|
||||
// Cost returns the hashing cost used to create the given hashed
|
||||
// password. When, in the future, the hashing cost of a password system needs
|
||||
// to be increased in order to adjust for greater computational power, this
|
||||
// function allows one to establish which passwords need to be updated.
|
||||
func Cost(hashedPassword []byte) (int, error) {
|
||||
p, err := newFromHash(hashedPassword)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.cost, nil
|
||||
}
|
||||
|
||||
func newFromPassword(salt []byte, password []byte, cost int) (*hashed, error) {
|
||||
if cost < MinCost {
|
||||
cost = DefaultCost
|
||||
}
|
||||
p := new(hashed)
|
||||
p.major = majorVersion
|
||||
p.minor = minorVersion
|
||||
|
||||
err := checkCost(cost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.cost = cost
|
||||
|
||||
p.salt = base64Encode(salt)
|
||||
hash, err := bcrypt(password, p.cost, p.salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.hash = hash
|
||||
return p, err
|
||||
}
|
||||
|
||||
func newFromHash(hashedSecret []byte) (*hashed, error) {
|
||||
if len(hashedSecret) < minHashSize {
|
||||
return nil, ErrHashTooShort
|
||||
}
|
||||
p := new(hashed)
|
||||
n, err := p.decodeVersion(hashedSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedSecret = hashedSecret[n:]
|
||||
n, err = p.decodeCost(hashedSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashedSecret = hashedSecret[n:]
|
||||
|
||||
// The "+2" is here because we'll have to append at most 2 '=' to the salt
|
||||
// when base64 decoding it in expensiveBlowfishSetup().
|
||||
p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2)
|
||||
copy(p.salt, hashedSecret[:encodedSaltSize])
|
||||
|
||||
hashedSecret = hashedSecret[encodedSaltSize:]
|
||||
p.hash = make([]byte, len(hashedSecret))
|
||||
copy(p.hash, hashedSecret)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) {
|
||||
cipherData := make([]byte, len(magicCipherData))
|
||||
copy(cipherData, magicCipherData)
|
||||
|
||||
c, err := expensiveBlowfishSetup(password, uint32(cost), salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < 24; i += 8 {
|
||||
for j := 0; j < 64; j++ {
|
||||
c.Encrypt(cipherData[i:i+8], cipherData[i:i+8])
|
||||
}
|
||||
}
|
||||
|
||||
// Bug compatibility with C bcrypt implementations. We only encode 23 of
|
||||
// the 24 bytes encrypted.
|
||||
hsh := base64Encode(cipherData[:maxCryptedHashSize])
|
||||
return hsh, nil
|
||||
}
|
||||
|
||||
func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) {
|
||||
|
||||
csalt, err := base64Decode(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Bug compatibility with C bcrypt implementations. They use the trailing
|
||||
// NULL in the key string during expansion.
|
||||
ckey := append(key, 0)
|
||||
|
||||
c, err := blowfish.NewSaltedCipher(ckey, csalt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var i, rounds uint64
|
||||
rounds = 1 << cost
|
||||
for i = 0; i < rounds; i++ {
|
||||
blowfish.ExpandKey(ckey, c)
|
||||
blowfish.ExpandKey(csalt, c)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (p *hashed) Hash() []byte {
|
||||
arr := make([]byte, 60)
|
||||
arr[0] = '$'
|
||||
arr[1] = p.major
|
||||
n := 2
|
||||
if p.minor != 0 {
|
||||
arr[2] = p.minor
|
||||
n = 3
|
||||
}
|
||||
arr[n] = '$'
|
||||
n++
|
||||
copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost)))
|
||||
n += 2
|
||||
arr[n] = '$'
|
||||
n++
|
||||
copy(arr[n:], p.salt)
|
||||
n += encodedSaltSize
|
||||
copy(arr[n:], p.hash)
|
||||
n += encodedHashSize
|
||||
return arr[:n]
|
||||
}
|
||||
|
||||
func (p *hashed) decodeVersion(sbytes []byte) (int, error) {
|
||||
if sbytes[0] != '$' {
|
||||
return -1, InvalidHashPrefixError(sbytes[0])
|
||||
}
|
||||
if sbytes[1] > majorVersion {
|
||||
return -1, HashVersionTooNewError(sbytes[1])
|
||||
}
|
||||
p.major = sbytes[1]
|
||||
n := 3
|
||||
if sbytes[2] != '$' {
|
||||
p.minor = sbytes[2]
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// sbytes should begin where decodeVersion left off.
|
||||
func (p *hashed) decodeCost(sbytes []byte) (int, error) {
|
||||
cost, err := strconv.Atoi(string(sbytes[0:2]))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
err = checkCost(cost)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
p.cost = cost
|
||||
return 3, nil
|
||||
}
|
||||
|
||||
func (p *hashed) String() string {
|
||||
return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor)
|
||||
}
|
||||
|
||||
func checkCost(cost int) error {
|
||||
if cost < MinCost || cost > MaxCost {
|
||||
return InvalidCostError(cost)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package bip39
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/bartekn/go-bip39"
|
||||
)
|
||||
|
||||
// ValidSentenceLen defines the mnemonic sentence lengths supported by this BIP 39 library.
|
||||
type ValidSentenceLen uint8
|
||||
|
||||
const (
|
||||
// FundRaiser is the sentence length used during the cosmos fundraiser (12 words).
|
||||
FundRaiser ValidSentenceLen = 12
|
||||
// Size of the checksum employed for the fundraiser
|
||||
FundRaiserChecksumSize = 4
|
||||
// FreshKey is the sentence length used for newly created keys (24 words).
|
||||
FreshKey ValidSentenceLen = 24
|
||||
// Size of the checksum employed for new keys
|
||||
FreshKeyChecksumSize = 8
|
||||
)
|
||||
|
||||
// NewMnemonic will return a string consisting of the mnemonic words for
|
||||
// the given sentence length.
|
||||
func NewMnemonic(len ValidSentenceLen) (words []string, err error) {
|
||||
// len = (entropySize + checksum) / 11
|
||||
var entropySize int
|
||||
switch len {
|
||||
case FundRaiser:
|
||||
// entropySize = 128
|
||||
entropySize = int(len)*11 - FundRaiserChecksumSize
|
||||
case FreshKey:
|
||||
// entropySize = 256
|
||||
entropySize = int(len)*11 - FreshKeyChecksumSize
|
||||
}
|
||||
var entropy []byte
|
||||
entropy, err = bip39.NewEntropy(entropySize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var mnemonic string
|
||||
mnemonic, err = bip39.NewMnemonic(entropy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
words = strings.Split(mnemonic, " ")
|
||||
return
|
||||
}
|
||||
|
||||
// MnemonicToSeed creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password).
|
||||
// This method does not validate the mnemonics checksum.
|
||||
func MnemonicToSeed(mne string) (seed []byte) {
|
||||
// we do not checksum here...
|
||||
seed = bip39.NewSeed(mne, "")
|
||||
return
|
||||
}
|
||||
|
||||
// MnemonicToSeedWithErrChecking returns the same seed as MnemonicToSeed.
|
||||
// It creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password).
|
||||
//
|
||||
// Different from MnemonicToSeed it validates the checksum.
|
||||
// For details on the checksum see the BIP 39 spec.
|
||||
func MnemonicToSeedWithErrChecking(mne string) (seed []byte, err error) {
|
||||
seed, err = bip39.NewSeedWithErrorChecking(mne, "")
|
||||
return
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package bip39
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWordCodec_NewMnemonic(t *testing.T) {
|
||||
_, err := NewMnemonic(FundRaiser)
|
||||
require.NoError(t, err, "unexpected error generating fundraiser mnemonic")
|
||||
|
||||
_, err = NewMnemonic(FreshKey)
|
||||
require.NoError(t, err, "unexpected error generating new 24-word mnemonic")
|
||||
}
|
|
@ -7,9 +7,10 @@ import (
|
|||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/bartekn/go-bip39"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
)
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
)
|
||||
|
||||
// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser.
|
||||
|
@ -55,6 +54,77 @@ func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32
|
|||
}
|
||||
}
|
||||
|
||||
// Parse the BIP44 path and unmarshal into the struct.
|
||||
// nolint: gocyclo
|
||||
func NewParamsFromPath(path string) (*BIP44Params, error) {
|
||||
spl := strings.Split(path, "/")
|
||||
if len(spl) != 5 {
|
||||
return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl))
|
||||
}
|
||||
|
||||
if spl[0] != "44'" {
|
||||
return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0])
|
||||
}
|
||||
|
||||
if !isHardened(spl[1]) || !isHardened(spl[2]) {
|
||||
return nil,
|
||||
fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2])
|
||||
}
|
||||
if isHardened(spl[3]) || isHardened(spl[4]) {
|
||||
return nil,
|
||||
fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4])
|
||||
}
|
||||
|
||||
purpose, err := hardenedInt(spl[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coinType, err := hardenedInt(spl[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
account, err := hardenedInt(spl[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
change, err := hardenedInt(spl[3])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !(change == 0 || change == 1) {
|
||||
return nil, fmt.Errorf("change field can only be 0 or 1")
|
||||
}
|
||||
|
||||
addressIdx, err := hardenedInt(spl[4])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BIP44Params{
|
||||
purpose: purpose,
|
||||
coinType: coinType,
|
||||
account: account,
|
||||
change: change > 0,
|
||||
addressIdx: addressIdx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func hardenedInt(field string) (uint32, error) {
|
||||
field = strings.TrimSuffix(field, "'")
|
||||
i, err := strconv.Atoi(field)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if i < 0 {
|
||||
return 0, fmt.Errorf("fields must not be negative. got %d", i)
|
||||
}
|
||||
return uint32(i), nil
|
||||
}
|
||||
|
||||
func isHardened(field string) bool {
|
||||
return strings.HasSuffix(field, "'")
|
||||
}
|
||||
|
||||
// NewFundraiserParams creates a BIP 44 parameter object from the params:
|
||||
// m / 44' / 118' / account' / 0 / address_index
|
||||
// The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser.
|
||||
|
@ -62,6 +132,21 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params {
|
|||
return NewParams(44, 118, account, false, addressIdx)
|
||||
}
|
||||
|
||||
// Return the BIP44 fields as an array.
|
||||
func (p BIP44Params) DerivationPath() []uint32 {
|
||||
change := uint32(0)
|
||||
if p.change {
|
||||
change = 1
|
||||
}
|
||||
return []uint32{
|
||||
p.purpose,
|
||||
p.coinType,
|
||||
p.account,
|
||||
change,
|
||||
p.addressIdx,
|
||||
}
|
||||
}
|
||||
|
||||
func (p BIP44Params) String() string {
|
||||
var changeStr string
|
||||
if p.change {
|
||||
|
@ -128,10 +213,15 @@ func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, h
|
|||
data = append([]byte{byte(0)}, privKeyBytes[:]...)
|
||||
} else {
|
||||
// this can't return an error:
|
||||
pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey()
|
||||
_, ecPub := btcec.PrivKeyFromBytes(btcec.S256(), 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)
|
||||
|
|
|
@ -3,9 +3,20 @@ package hd
|
|||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bip39"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
)
|
||||
|
||||
var defaultBIP39Passphrase = ""
|
||||
|
||||
// return bip39 seed with empty passphrase
|
||||
func mnemonicToSeed(mnemonic string) []byte {
|
||||
return bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
|
||||
}
|
||||
|
||||
//nolint
|
||||
func ExampleStringifyPathParams() {
|
||||
path := NewParams(44, 0, 0, false, 0)
|
||||
|
@ -13,10 +24,57 @@ func ExampleStringifyPathParams() {
|
|||
// Output: 44'/0'/0'/0/0
|
||||
}
|
||||
|
||||
func TestParamsFromPath(t *testing.T) {
|
||||
goodCases := []struct {
|
||||
params *BIP44Params
|
||||
path string
|
||||
}{
|
||||
{&BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"},
|
||||
{&BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"},
|
||||
{&BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"},
|
||||
{&BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"},
|
||||
{&BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"},
|
||||
{&BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"},
|
||||
{&BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"},
|
||||
}
|
||||
|
||||
for i, c := range goodCases {
|
||||
params, err := NewParamsFromPath(c.path)
|
||||
errStr := fmt.Sprintf("%d %v", i, c)
|
||||
assert.NoError(t, err, errStr)
|
||||
assert.EqualValues(t, c.params, params, errStr)
|
||||
assert.Equal(t, c.path, c.params.String())
|
||||
}
|
||||
|
||||
badCases := []struct {
|
||||
path string
|
||||
}{
|
||||
{"43'/0'/0'/0/0"}, // doesnt start with 44
|
||||
{"44'/1'/0'/0/0/5"}, // too many fields
|
||||
{"44'/0'/1'/0"}, // too few fields
|
||||
{"44'/0'/0'/2/0"}, // change field can only be 0/1
|
||||
{"44/0'/0'/0/0"}, // first field needs '
|
||||
{"44'/0/0'/0/0"}, // second field needs '
|
||||
{"44'/0'/0/0/0"}, // third field needs '
|
||||
{"44'/0'/0'/0'/0"}, // fourth field must not have '
|
||||
{"44'/0'/0'/0/0'"}, // fifth field must not have '
|
||||
{"44'/-1'/0'/0/0"}, // no negatives
|
||||
{"44'/0'/0'/-1/0"}, // no negatives
|
||||
}
|
||||
|
||||
for i, c := range badCases {
|
||||
params, err := NewParamsFromPath(c.path)
|
||||
errStr := fmt.Sprintf("%d %v", i, c)
|
||||
assert.Nil(t, params, errStr)
|
||||
assert.Error(t, err, errStr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//nolint
|
||||
func ExampleSomeBIP32TestVecs() {
|
||||
|
||||
seed := bip39.MnemonicToSeed("barrel original fuel morning among eternal " +
|
||||
seed := mnemonicToSeed("barrel original fuel morning among eternal " +
|
||||
"filter ball stove pluck matrix mechanic")
|
||||
master, ch := ComputeMastersFromSeed(seed)
|
||||
fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)")
|
||||
|
@ -35,14 +93,14 @@ func ExampleSomeBIP32TestVecs() {
|
|||
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html")
|
||||
fmt.Println()
|
||||
|
||||
seed = bip39.MnemonicToSeed(
|
||||
seed = mnemonicToSeed(
|
||||
"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 = ComputeMastersFromSeed(seed)
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4")
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
|
||||
seed = bip39.MnemonicToSeed("idea naive region square margin day captain habit " +
|
||||
seed = mnemonicToSeed("idea naive region square margin day captain habit " +
|
||||
"gun second farm pact pulse someone armed")
|
||||
master, ch = ComputeMastersFromSeed(seed)
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420")
|
||||
|
@ -53,7 +111,7 @@ func ExampleSomeBIP32TestVecs() {
|
|||
fmt.Println()
|
||||
|
||||
// bip32 path: m/0/7
|
||||
seed = bip39.MnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history")
|
||||
seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history")
|
||||
master, ch = ComputeMastersFromSeed(seed)
|
||||
priv, _ = DerivePrivateKeyForPath(master, ch, "0/7")
|
||||
fmt.Println(hex.EncodeToString(priv[:]))
|
||||
|
|
|
@ -6,11 +6,15 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bip39"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cosmos/go-bip39"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||
"github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
|
@ -46,6 +50,14 @@ const (
|
|||
infoSuffix = "info"
|
||||
)
|
||||
|
||||
const (
|
||||
// used for deriving seed from mnemonic
|
||||
defaultBIP39Passphrase = ""
|
||||
|
||||
// bits of entropy to draw when creating a mnemonic
|
||||
defaultEntropySize = 256
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnsupportedSigningAlgo is raised when the caller tries to use a
|
||||
// different signing scheme than secp256k1.
|
||||
|
@ -85,12 +97,17 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string
|
|||
}
|
||||
|
||||
// default number of words (24):
|
||||
mnemonicS, err := bip39.NewMnemonic(bip39.FreshKey)
|
||||
// this generates a mnemonic directly from the number of words by reading system entropy.
|
||||
entropy, err := bip39.NewEntropy(defaultEntropySize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mnemonic = strings.Join(mnemonicS, " ")
|
||||
seed := bip39.MnemonicToSeed(mnemonic)
|
||||
mnemonic, err = bip39.NewMnemonic(entropy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase)
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath)
|
||||
return
|
||||
}
|
||||
|
@ -102,7 +119,7 @@ func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err err
|
|||
err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words))
|
||||
return
|
||||
}
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -119,7 +136,7 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf
|
|||
err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words))
|
||||
return
|
||||
}
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -127,12 +144,12 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf
|
|||
return
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) {
|
||||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic)
|
||||
func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) {
|
||||
seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info, err = kb.persistDerivedKey(seed, passwd, name, params.String())
|
||||
info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String())
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -229,7 +246,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t
|
|||
err = fmt.Errorf("private key not available")
|
||||
return
|
||||
}
|
||||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -279,7 +296,7 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr
|
|||
err = fmt.Errorf("private key not available")
|
||||
return nil, err
|
||||
}
|
||||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -296,7 +313,7 @@ func (kb dbKeybase) Export(name string) (armor string, err error) {
|
|||
if bz == nil {
|
||||
return "", fmt.Errorf("no key to export with name %s", name)
|
||||
}
|
||||
return armorInfoBytes(bz), nil
|
||||
return mintkey.ArmorInfoBytes(bz), nil
|
||||
}
|
||||
|
||||
// ExportPubKey returns public keys in ASCII armored format.
|
||||
|
@ -311,7 +328,7 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
return armorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||
return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||
|
@ -319,7 +336,7 @@ func (kb dbKeybase) Import(name string, armor string) (err error) {
|
|||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
infoBytes, err := unarmorInfoBytes(armor)
|
||||
infoBytes, err := mintkey.UnarmorInfoBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -335,7 +352,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
|||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
pubBytes, err := unarmorPubKeyBytes(armor)
|
||||
pubBytes, err := mintkey.UnarmorPubKeyBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -360,7 +377,7 @@ func (kb dbKeybase) Delete(name, passphrase string) error {
|
|||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
_, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
_, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -394,7 +411,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
|
|||
switch info.(type) {
|
||||
case localInfo:
|
||||
linfo := info.(localInfo)
|
||||
key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||
key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -411,7 +428,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro
|
|||
|
||||
func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string) Info {
|
||||
// encrypt private key using passphrase
|
||||
privArmor := encryptArmorPrivKey(priv, passphrase)
|
||||
privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase)
|
||||
// make Info
|
||||
pub := priv.PubKey()
|
||||
info := newLocalInfo(name, pub, privArmor)
|
||||
|
|
|
@ -4,9 +4,12 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/hd"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
|
||||
|
@ -15,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
BcryptSecurityParameter = 1
|
||||
mintkey.BcryptSecurityParameter = 1
|
||||
}
|
||||
|
||||
// TestKeyManagement makes sure we can manipulate these keys well
|
||||
|
@ -342,7 +345,7 @@ func TestSeedPhrase(t *testing.T) {
|
|||
|
||||
// let us re-create it from the mnemonic-phrase
|
||||
params := *hd.NewFundraiserParams(0, 0)
|
||||
newInfo, err := cstore.Derive(n2, mnemonic, p2, params)
|
||||
newInfo, err := cstore.Derive(n2, mnemonic, defaultBIP39Passphrase, p2, params)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, n2, newInfo.GetName())
|
||||
require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address())
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
package keys
|
||||
package mintkey
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/bcrypt"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/armor"
|
||||
"github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
"github.com/tendermint/tendermint/crypto/xsalsa20symmetric"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -34,11 +35,16 @@ const (
|
|||
// TODO: Consider increasing default
|
||||
var BcryptSecurityParameter = 12
|
||||
|
||||
func armorInfoBytes(bz []byte) string {
|
||||
//-----------------------------------------------------------------
|
||||
// add armor
|
||||
|
||||
// Armor the InfoBytes
|
||||
func ArmorInfoBytes(bz []byte) string {
|
||||
return armorBytes(bz, blockTypeKeyInfo)
|
||||
}
|
||||
|
||||
func armorPubKeyBytes(bz []byte) string {
|
||||
// Armor the PubKeyBytes
|
||||
func ArmorPubKeyBytes(bz []byte) string {
|
||||
return armorBytes(bz, blockTypePubKey)
|
||||
}
|
||||
|
||||
|
@ -50,11 +56,16 @@ func armorBytes(bz []byte, blockType string) string {
|
|||
return armor.EncodeArmor(blockType, header, bz)
|
||||
}
|
||||
|
||||
func unarmorInfoBytes(armorStr string) (bz []byte, err error) {
|
||||
//-----------------------------------------------------------------
|
||||
// remove armor
|
||||
|
||||
// Unarmor the InfoBytes
|
||||
func UnarmorInfoBytes(armorStr string) (bz []byte, err error) {
|
||||
return unarmorBytes(armorStr, blockTypeKeyInfo)
|
||||
}
|
||||
|
||||
func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
|
||||
// Unarmor the PubKeyBytes
|
||||
func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
|
||||
return unarmorBytes(armorStr, blockTypePubKey)
|
||||
}
|
||||
|
||||
|
@ -74,7 +85,11 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
||||
//-----------------------------------------------------------------
|
||||
// encrypt/decrypt with armor
|
||||
|
||||
// Encrypt and armor the private key.
|
||||
func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
||||
saltBytes, encBytes := encryptPrivKey(privKey, passphrase)
|
||||
header := map[string]string{
|
||||
"kdf": "bcrypt",
|
||||
|
@ -84,7 +99,22 @@ func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string {
|
|||
return armorStr
|
||||
}
|
||||
|
||||
func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
|
||||
// encrypt the given privKey with the passphrase using a randomly
|
||||
// generated salt and the xsalsa20 cipher. returns the salt and the
|
||||
// encrypted priv key.
|
||||
func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
|
||||
saltBytes = crypto.CRandBytes(16)
|
||||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||
if err != nil {
|
||||
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||||
}
|
||||
key = crypto.Sha256(key) // get 32 bytes
|
||||
privKeyBytes := privKey.Bytes()
|
||||
return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
|
||||
}
|
||||
|
||||
// Unarmor and decrypt the private key.
|
||||
func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) {
|
||||
var privKey crypto.PrivKey
|
||||
blockType, header, encBytes, err := armor.DecodeArmor(armorStr)
|
||||
if err != nil {
|
||||
|
@ -107,17 +137,6 @@ func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey,
|
|||
return privKey, err
|
||||
}
|
||||
|
||||
func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) {
|
||||
saltBytes = crypto.CRandBytes(16)
|
||||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||
if err != nil {
|
||||
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error())
|
||||
}
|
||||
key = crypto.Sha256(key) // Get 32 bytes
|
||||
privKeyBytes := privKey.Bytes()
|
||||
return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key)
|
||||
}
|
||||
|
||||
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
|
||||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter)
|
||||
if err != nil {
|
|
@ -26,8 +26,12 @@ type Keybase interface {
|
|||
CreateKey(name, mnemonic, passwd string) (info Info, err error)
|
||||
// CreateFundraiserKey takes a mnemonic and derives, a password
|
||||
CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error)
|
||||
// Derive derives a key from the passed mnemonic using a BIP44 path.
|
||||
Derive(name, mnemonic, passwd string, params hd.BIP44Params) (Info, error)
|
||||
// Compute a BIP39 seed from th mnemonic and bip39Passwd.
|
||||
// Derive private key from the seed using the BIP44 params.
|
||||
// Encrypt the key to disk using encryptPasswd.
|
||||
// See https://github.com/cosmos/cosmos-sdk/issues/2095
|
||||
Derive(name, mnemonic, bip39Passwd,
|
||||
encryptPasswd string, params hd.BIP44Params) (Info, error)
|
||||
// Create, store, and return a new Ledger key reference
|
||||
CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error)
|
||||
|
||||
|
|
Loading…
Reference in New Issue