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:
Ethan Buchman 2018-10-17 13:37:58 -04:00 committed by Rigel
parent c961a684c9
commit 1ee8deed2b
18 changed files with 575 additions and 503 deletions

38
Gopkg.lock generated
View File

@ -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

View File

@ -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"

View File

@ -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)
}

78
client/keys/mnemonic.go Normal file
View File

@ -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
}

182
client/keys/new.go Normal file
View File

@ -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("-------------------------------------")
}

View File

@ -19,6 +19,8 @@ func Commands() *cobra.Command {
needs to sign with a private key.`,
}
cmd.AddCommand(
mnemonicKeyCommand(),
newKeyCommand(),
addKeyCommand(),
listKeysCmd,
showKeysCmd(),

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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"
)

View File

@ -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)

View File

@ -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[:]))

View File

@ -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)

View File

@ -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())

View File

@ -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 {

View File

@ -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)