@ -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,292 +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 ( | |||
MinCost int = 4 // the minimum allowable cost as passed in to GenerateFromPassword | |||
MaxCost int = 31 // the maximum allowable cost as passed in to GenerateFromPassword | |||
DefaultCost int = 10 // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword | |||
) | |||
// 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 | |||
func (ih InvalidHashPrefixError) Error() string { | |||
return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) | |||
} | |||
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 += 1 | |||
copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) | |||
n += 2 | |||
arr[n] = '$' | |||
n += 1 | |||
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,62 +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 | |||
// FreshKey is the sentence length used for newly created keys (24 words). | |||
FreshKey ValidSentenceLen = 24 | |||
) | |||
// NewMnemonic will return a string consisting of the mnemonic words for | |||
// the given sentence length. | |||
func NewMnemonic(len ValidSentenceLen) (words []string, err error) { | |||
// len = (ENT + checksum) / 11 | |||
var ENT int | |||
switch len { | |||
case FundRaiser: | |||
ENT = 128 | |||
case FreshKey: | |||
ENT = 256 | |||
} | |||
var entropy []byte | |||
entropy, err = bip39.NewEntropy(ENT) | |||
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/assert" | |||
) | |||
func TestWordCodec_NewMnemonic(t *testing.T) { | |||
_, err := NewMnemonic(FundRaiser) | |||
assert.NoError(t, err, "unexpected error generating fundraiser mnemonic") | |||
_, err = NewMnemonic(FreshKey) | |||
assert.NoError(t, err, "unexpected error generating new 24-word mnemonic") | |||
} |
@ -1,83 +0,0 @@ | |||
package hd | |||
import ( | |||
"encoding/hex" | |||
"encoding/json" | |||
"fmt" | |||
"io/ioutil" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/tyler-smith/go-bip39" | |||
"github.com/tendermint/go-crypto" | |||
) | |||
type addrData struct { | |||
Mnemonic string | |||
Master string | |||
Seed string | |||
Priv string | |||
Pub string | |||
Addr string | |||
} | |||
func initFundraiserTestVectors(t *testing.T) []addrData { | |||
// NOTE: atom fundraiser address | |||
// var hdPath string = "m/44'/118'/0'/0/0" | |||
var hdToAddrTable []addrData | |||
b, err := ioutil.ReadFile("test.json") | |||
if err != nil { | |||
t.Fatalf("could not read fundraiser test vector file (test.json): %s", err) | |||
} | |||
err = json.Unmarshal(b, &hdToAddrTable) | |||
if err != nil { | |||
t.Fatalf("could not decode test vectors (test.json): %s", err) | |||
} | |||
return hdToAddrTable | |||
} | |||
func TestFundraiserCompatibility(t *testing.T) { | |||
hdToAddrTable := initFundraiserTestVectors(t) | |||
for i, d := range hdToAddrTable { | |||
privB, _ := hex.DecodeString(d.Priv) | |||
pubB, _ := hex.DecodeString(d.Pub) | |||
addrB, _ := hex.DecodeString(d.Addr) | |||
seedB, _ := hex.DecodeString(d.Seed) | |||
masterB, _ := hex.DecodeString(d.Master) | |||
seed := bip39.NewSeed(d.Mnemonic, "") | |||
t.Log("================================") | |||
t.Logf("ROUND: %d MNEMONIC: %s", i, d.Mnemonic) | |||
master, ch := ComputeMastersFromSeed(seed) | |||
priv, err := DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") | |||
assert.NoError(t, err) | |||
pub := crypto.PrivKeySecp256k1(priv).PubKey() | |||
t.Log("\tNODEJS GOLANG\n") | |||
t.Logf("SEED \t%X %X\n", seedB, seed) | |||
t.Logf("MSTR \t%X %X\n", masterB, master) | |||
t.Logf("PRIV \t%X %X\n", privB, priv) | |||
t.Logf("PUB \t%X %X\n", pubB, pub) | |||
assert.Equal(t, seedB, seed) | |||
assert.Equal(t, master[:], masterB, fmt.Sprintf("Expected masters to match for %d", i)) | |||
assert.Equal(t, priv[:], privB, "Expected priv keys to match") | |||
var pubBFixed [33]byte | |||
copy(pubBFixed[:], pubB) | |||
assert.Equal(t, pub, crypto.PubKeySecp256k1(pubBFixed), fmt.Sprintf("Expected pub keys to match for %d", i)) | |||
addr := pub.Address() | |||
t.Logf("ADDR \t%X %X\n", addrB, addr) | |||
assert.Equal(t, addr, crypto.Address(addrB), fmt.Sprintf("Expected addresses to match %d", i)) | |||
} | |||
} | |||
@ -1,168 +0,0 @@ | |||
// Package hd provides basic functionality Hierarchical Deterministic Wallets. | |||
// | |||
// The user must understand the overall concept of the BIP 32 and the BIP 44 specs: | |||
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki | |||
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki | |||
// | |||
// In combination with the bip39 package in go-crypto this package provides the functionality for deriving keys using a | |||
// BIP 44 HD path, or, more general, by passing a BIP 32 path. | |||
// | |||
// In particular, this package (together with bip39) provides all necessary functionality to derive keys from | |||
// mnemonics generated during the cosmos fundraiser. | |||
package hd | |||
import ( | |||
"crypto/hmac" | |||
"crypto/sha512" | |||
"encoding/binary" | |||
"errors" | |||
"fmt" | |||
"math/big" | |||
"strconv" | |||
"strings" | |||
"github.com/btcsuite/btcd/btcec" | |||
"github.com/tendermint/go-crypto" | |||
) | |||
// BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser. | |||
const ( | |||
BIP44Prefix = "44'/118'/" | |||
FullFundraiserPath = BIP44Prefix + "0'/0/0" | |||
) | |||
// BIP44Params wraps BIP 44 params (5 level BIP 32 path). | |||
// To receive a canonical string representation ala | |||
// m / purpose' / coin_type' / account' / change / address_index | |||
// call String() on a BIP44Params instance. | |||
type BIP44Params struct { | |||
purpose uint32 | |||
coinType uint32 | |||
account uint32 | |||
change bool | |||
addressIdx uint32 | |||
} | |||
// NewParams creates a BIP 44 parameter object from the params: | |||
// m / purpose' / coin_type' / account' / change / address_index | |||
func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32) *BIP44Params { | |||
return &BIP44Params{ | |||
purpose: purpose, | |||
coinType: coinType, | |||
account: account, | |||
change: change, | |||
addressIdx: addressIdx, | |||
} | |||
} | |||
// 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. | |||
func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params { | |||
return NewParams(44, 118, account, false, addressIdx) | |||
} | |||
func (p BIP44Params) String() string { | |||
var changeStr string | |||
if p.change { | |||
changeStr = "1" | |||
} else { | |||
changeStr = "0" | |||
} | |||
// m / purpose' / coin_type' / account' / change / address_index | |||
return fmt.Sprintf("%d'/%d'/%d'/%s/%d", | |||
p.purpose, | |||
p.coinType, | |||
p.account, | |||
changeStr, | |||
p.addressIdx) | |||
} | |||
// ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex. | |||
func ComputeMastersFromSeed(seed []byte) (secret [32]byte, chainCode [32]byte) { | |||
masterSecret := []byte("Bitcoin seed") | |||
secret, chainCode = i64(masterSecret, seed) | |||
return | |||
} | |||
// DerivePrivateKeyForPath derives the private key by following the BIP 32/44 path from privKeyBytes, | |||
// using the given chainCode. | |||
func DerivePrivateKeyForPath(privKeyBytes [32]byte, chainCode [32]byte, path string) ([32]byte, error) { | |||
data := privKeyBytes | |||
parts := strings.Split(path, "/") | |||
for _, part := range parts { | |||
// do we have an apostrophe? | |||
harden := part[len(part)-1:] == "'" | |||
// harden == private derivation, else public derivation: | |||
if harden { | |||
part = part[:len(part)-1] | |||
} | |||
idx, err := strconv.Atoi(part) | |||
if err != nil { | |||
return [32]byte{}, fmt.Errorf("invalid BIP 32 path: %s", err) | |||
} | |||
if idx < 0 { | |||
return [32]byte{}, errors.New("invalid BIP 32 path: index negative ot too large") | |||
} | |||
data, chainCode = derivePrivateKey(data, chainCode, uint32(idx), harden) | |||
} | |||
var derivedKey [32]byte | |||
n := copy(derivedKey[:], data[:]) | |||
if n != 32 || len(data) != 32 { | |||
return [32]byte{}, fmt.Errorf("expected a (secp256k1) key of length 32, got length: %v", len(data)) | |||
} | |||
return derivedKey, nil | |||
} | |||
// derivePrivateKey derives the private key with index and chainCode. | |||
// If harden is true, the derivation is 'hardened'. | |||
// It returns the new private key and new chain code. | |||
// For more information on hardened keys see: | |||
// - https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki | |||
func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, harden bool) ([32]byte, [32]byte) { | |||
var data []byte | |||
if harden { | |||
index = index | 0x80000000 | |||
data = append([]byte{byte(0)}, privKeyBytes[:]...) | |||
} else { | |||
// this can't return an error: | |||
pubkey := crypto.PrivKeySecp256k1(privKeyBytes).PubKey() | |||
public := pubkey.(crypto.PubKeySecp256k1) | |||
data = public[:] | |||
} | |||
data = append(data, uint32ToBytes(index)...) | |||
data2, chainCode2 := i64(chainCode[:], data) | |||
x := addScalars(privKeyBytes[:], data2[:]) | |||
return x, chainCode2 | |||
} | |||
// modular big endian addition | |||
func addScalars(a []byte, b []byte) [32]byte { | |||
aInt := new(big.Int).SetBytes(a) | |||
bInt := new(big.Int).SetBytes(b) | |||
sInt := new(big.Int).Add(aInt, bInt) | |||
x := sInt.Mod(sInt, btcec.S256().N).Bytes() | |||
x2 := [32]byte{} | |||
copy(x2[32-len(x):], x) | |||
return x2 | |||
} | |||
func uint32ToBytes(i uint32) []byte { | |||
b := [4]byte{} | |||
binary.BigEndian.PutUint32(b[:], i) | |||
return b[:] | |||
} | |||
// i64 returns the two halfs of the SHA512 HMAC of key and data. | |||
func i64(key []byte, data []byte) (IL [32]byte, IR [32]byte) { | |||
mac := hmac.New(sha512.New, key) | |||
// sha512 does not err | |||
_, _ = mac.Write(data) | |||
I := mac.Sum(nil) | |||
copy(IL[:], I[:32]) | |||
copy(IR[:], I[32:]) | |||
return | |||
} |
@ -1,73 +0,0 @@ | |||
package hd | |||
import ( | |||
"encoding/hex" | |||
"fmt" | |||
"github.com/tendermint/go-crypto/keys/bip39" | |||
) | |||
func ExampleStringifyPathParams() { | |||
path := NewParams(44, 0, 0, false, 0) | |||
fmt.Println(path.String()) | |||
// Output: 44'/0'/0'/0/0 | |||
} | |||
func ExampleSomeBIP32TestVecs() { | |||
seed := bip39.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)") | |||
fmt.Println() | |||
// cosmos | |||
priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath) | |||
fmt.Println(hex.EncodeToString(priv[:])) | |||
// bitcoin | |||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") | |||
fmt.Println(hex.EncodeToString(priv[:])) | |||
// ether | |||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") | |||
fmt.Println(hex.EncodeToString(priv[:])) | |||
fmt.Println() | |||
fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html") | |||
fmt.Println() | |||
seed = bip39.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 " + | |||
"gun second farm pact pulse someone armed") | |||
master, ch = ComputeMastersFromSeed(seed) | |||
priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") | |||
fmt.Println(hex.EncodeToString(priv[:])) | |||
fmt.Println() | |||
fmt.Println("BIP 32 example") | |||
fmt.Println() | |||
// bip32 path: m/0/7 | |||
seed = bip39.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[:])) | |||
// Output: keys from fundraiser test-vector (cosmos, bitcoin, ether) | |||
// | |||
// bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c | |||
// e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d | |||
// 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc | |||
// | |||
// keys generated via https://coinomi.com/recovery-phrase-tool.html | |||
// | |||
// a61f10c5fecf40c084c94fa54273b6f5d7989386be4a37669e6d6f7b0169c163 | |||
// 32c4599843de3ef161a629a461d12c60b009b676c35050be5f7ded3a3b23501f | |||
// | |||
// BIP 32 example | |||
// | |||
// c4c11d8c03625515905d7e89d25dfc66126fbc629ecca6db489a1a72fc4bda78 | |||
} |
@ -1,362 +0,0 @@ | |||
package keys | |||
import ( | |||
"bufio" | |||
"fmt" | |||
"os" | |||
"strings" | |||
"github.com/pkg/errors" | |||
"github.com/tendermint/go-crypto" | |||
"github.com/tendermint/go-crypto/keys/bip39" | |||
"github.com/tendermint/go-crypto/keys/hd" | |||
dbm "github.com/tendermint/tmlibs/db" | |||
) | |||
var _ Keybase = dbKeybase{} | |||
// Language is a language to create the BIP 39 mnemonic in. | |||
// Currently, only english is supported though. | |||
// Find a list of all supported languages in the BIP 39 spec (word lists). | |||
type Language int | |||
const ( | |||
// English is the default language to create a mnemonic. | |||
// It is the only supported language by this package. | |||
English Language = iota + 1 | |||
// Japanese is currently not supported. | |||
Japanese | |||
// Korean is currently not supported. | |||
Korean | |||
// Spanish is currently not supported. | |||
Spanish | |||
// ChineseSimplified is currently not supported. | |||
ChineseSimplified | |||
// ChineseTraditional is currently not supported. | |||
ChineseTraditional | |||
// French is currently not supported. | |||
French | |||
// Italian is currently not supported. | |||
Italian | |||
) | |||
var ( | |||
// ErrUnsupportedSigningAlgo is raised when the caller tries to use a different signing scheme than secp256k1. | |||
ErrUnsupportedSigningAlgo = errors.New("unsupported signing algo: only secp256k1 is supported") | |||
// ErrUnsupportedLanguage is raised when the caller tries to use a different language than english for creating | |||
// a mnemonic sentence. | |||
ErrUnsupportedLanguage = errors.New("unsupported language: only english is supported") | |||
) | |||
// dbKeybase combines encryption and storage implementation to provide | |||
// a full-featured key manager | |||
type dbKeybase struct { | |||
db dbm.DB | |||
} | |||
// New creates a new keybase instance using the passed DB for reading and writing keys. | |||
func New(db dbm.DB) Keybase { | |||
return dbKeybase{ | |||
db: db, | |||
} | |||
} | |||
// CreateMnemonic generates a new key and persists it to storage, encrypted | |||
// using the provided password. | |||
// It returns the generated mnemonic and the key Info. | |||
// It returns an error if it fails to | |||
// generate a key for the given algo type, or if another key is | |||
// already stored under the same name. | |||
func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, mnemonic string, err error) { | |||
if language != English { | |||
return nil, "", ErrUnsupportedLanguage | |||
} | |||
if algo != Secp256k1 { | |||
err = ErrUnsupportedSigningAlgo | |||
return | |||
} | |||
// default number of words (24): | |||
mnemonicS, err := bip39.NewMnemonic(bip39.FreshKey) | |||
if err != nil { | |||
return | |||
} | |||
mnemonic = strings.Join(mnemonicS, " ") | |||
seed := bip39.MnemonicToSeed(mnemonic) | |||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) | |||
return | |||
} | |||
// CreateFundraiserKey converts a mnemonic to a private key and persists it, | |||
// encrypted with the given password. | |||
// TODO(ismail) | |||
func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) { | |||
words := strings.Split(mnemonic, " ") | |||
if len(words) != 12 { | |||
err = fmt.Errorf("recovering only works with 12 word (fundraiser) mnemonics, got: %v words", len(words)) | |||
return | |||
} | |||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) | |||
if err != nil { | |||
return | |||
} | |||
info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) | |||
return | |||
} | |||
func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) { | |||
seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) | |||
if err != nil { | |||
return | |||
} | |||
info, err = kb.persistDerivedKey(seed, passwd, name, params.String()) | |||
return | |||
} | |||
// CreateLedger creates a new locally-stored reference to a Ledger keypair | |||
// It returns the created key info and an error if the Ledger could not be queried | |||
func (kb dbKeybase) CreateLedger(name string, path crypto.DerivationPath, algo SigningAlgo) (Info, error) { | |||
if algo != Secp256k1 { | |||
return nil, ErrUnsupportedSigningAlgo | |||
} | |||
priv, err := crypto.NewPrivKeyLedgerSecp256k1(path) | |||
if err != nil { | |||
return nil, err | |||
} | |||
pub := priv.PubKey() | |||
return kb.writeLedgerKey(pub, path, name), nil | |||
} | |||
// CreateOffline creates a new reference to an offline keypair | |||
// It returns the created key info | |||
func (kb dbKeybase) CreateOffline(name string, pub crypto.PubKey) (Info, error) { | |||
return kb.writeOfflineKey(pub, name), nil | |||
} | |||
func (kb *dbKeybase) persistDerivedKey(seed []byte, passwd, name, fullHdPath string) (info Info, err error) { | |||
// create master key and derive first key: | |||
masterPriv, ch := hd.ComputeMastersFromSeed(seed) | |||
derivedPriv, err := hd.DerivePrivateKeyForPath(masterPriv, ch, fullHdPath) | |||
if err != nil { | |||
return | |||
} | |||
// if we have a password, use it to encrypt the private key and store it | |||
// else store the public key only | |||
if passwd != "" { | |||
info = kb.writeLocalKey(crypto.PrivKeySecp256k1(derivedPriv), name, passwd) | |||
} else { | |||
pubk := crypto.PrivKeySecp256k1(derivedPriv).PubKey() | |||
info = kb.writeOfflineKey(pubk, name) | |||
} | |||
return | |||
} | |||
// List returns the keys from storage in alphabetical order. | |||
func (kb dbKeybase) List() ([]Info, error) { | |||
var res []Info | |||
iter := kb.db.Iterator(nil, nil) | |||
defer iter.Close() | |||
for ; iter.Valid(); iter.Next() { | |||
info, err := readInfo(iter.Value()) | |||
if err != nil { | |||
return nil, err | |||
} | |||
res = append(res, info) | |||
} | |||
return res, nil | |||
} | |||
// Get returns the public information about one key. | |||
func (kb dbKeybase) Get(name string) (Info, error) { | |||
bs := kb.db.Get(infoKey(name)) | |||
return readInfo(bs) | |||
} | |||
// Sign signs the msg with the named key. | |||
// It returns an error if the key doesn't exist or the decryption fails. | |||
func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signature, pub crypto.PubKey, err error) { | |||
info, err := kb.Get(name) | |||
if err != nil { | |||
return | |||
} | |||
var priv crypto.PrivKey | |||
switch info.(type) { | |||
case localInfo: | |||
linfo := info.(localInfo) | |||
if linfo.PrivKeyArmor == "" { | |||
err = fmt.Errorf("private key not available") | |||
return | |||
} | |||
priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
case ledgerInfo: | |||
linfo := info.(ledgerInfo) | |||
priv, err = crypto.NewPrivKeyLedgerSecp256k1(linfo.Path) | |||
if err != nil { | |||
return | |||
} | |||
case offlineInfo: | |||
linfo := info.(offlineInfo) | |||
fmt.Printf("Bytes to sign:\n%s", msg) | |||
buf := bufio.NewReader(os.Stdin) | |||
fmt.Printf("\nEnter Amino-encoded signature:\n") | |||
// Will block until user inputs the signature | |||
signed, err := buf.ReadString('\n') | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
cdc.MustUnmarshalBinary([]byte(signed), sig) | |||
return sig, linfo.GetPubKey(), nil | |||
} | |||
sig, err = priv.Sign(msg) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
pub = priv.PubKey() | |||
return sig, pub, nil | |||
} | |||
func (kb dbKeybase) Export(name string) (armor string, err error) { | |||
bz := kb.db.Get(infoKey(name)) | |||
if bz == nil { | |||
return "", fmt.Errorf("no key to export with name %s", name) | |||
} | |||
return armorInfoBytes(bz), nil | |||
} | |||
// ExportPubKey returns public keys in ASCII armored format. | |||
// Retrieve a Info object by its name and return the public key in | |||
// a portable format. | |||
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { | |||
bz := kb.db.Get(infoKey(name)) | |||
if bz == nil { | |||
return "", fmt.Errorf("no key to export with name %s", name) | |||
} | |||
info, err := readInfo(bz) | |||
if err != nil { | |||
return | |||
} | |||
return armorPubKeyBytes(info.GetPubKey().Bytes()), nil | |||
} | |||
func (kb dbKeybase) Import(name string, armor string) (err error) { | |||
bz := kb.db.Get(infoKey(name)) | |||
if len(bz) > 0 { | |||
return errors.New("Cannot overwrite data for name " + name) | |||
} | |||
infoBytes, err := unarmorInfoBytes(armor) | |||
if err != nil { | |||
return | |||
} | |||
kb.db.Set(infoKey(name), infoBytes) | |||
return nil | |||
} | |||
// ImportPubKey imports ASCII-armored public keys. | |||
// Store a new Info object holding a public key only, i.e. it will | |||
// not be possible to sign with it as it lacks the secret key. | |||
func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { | |||
bz := kb.db.Get(infoKey(name)) | |||
if len(bz) > 0 { | |||
return errors.New("Cannot overwrite data for name " + name) | |||
} | |||
pubBytes, err := unarmorPubKeyBytes(armor) | |||
if err != nil { | |||
return | |||
} | |||
pubKey, err := crypto.PubKeyFromBytes(pubBytes) | |||
if err != nil { | |||
return | |||
} | |||
kb.writeOfflineKey(pubKey, name) | |||
return | |||
} | |||
// Delete removes key forever, but we must present the | |||
// proper passphrase before deleting it (for security). | |||
// A passphrase of 'yes' is used to delete stored | |||
// references to offline and Ledger / HW wallet keys | |||
func (kb dbKeybase) Delete(name, passphrase string) error { | |||
// verify we have the proper password before deleting | |||
info, err := kb.Get(name) | |||
if err != nil { | |||
return err | |||
} | |||
switch info.(type) { | |||
case localInfo: | |||
linfo := info.(localInfo) | |||
_, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) | |||
if err != nil { | |||
return err | |||
} | |||
kb.db.DeleteSync(infoKey(name)) | |||
return nil | |||
case ledgerInfo: | |||
case offlineInfo: | |||
if passphrase != "yes" { | |||
return fmt.Errorf("enter exactly 'yes' to delete the key") | |||
} | |||
kb.db.DeleteSync(infoKey(name)) | |||
return nil | |||
} | |||
return nil | |||
} | |||
// Update changes the passphrase with which an already stored key is | |||
// encrypted. | |||
// | |||
// oldpass must be the current passphrase used for encryption, | |||
// newpass will be the only valid passphrase from this time forward. | |||
func (kb dbKeybase) Update(name, oldpass, newpass string) error { | |||
info, err := kb.Get(name) | |||
if err != nil { | |||
return err | |||
} | |||
switch info.(type) { | |||
case localInfo: | |||
linfo := info.(localInfo) | |||
key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) | |||
if err != nil { | |||
return err | |||
} | |||
kb.writeLocalKey(key, name, newpass) | |||
return nil | |||
default: | |||
return fmt.Errorf("locally stored key required") | |||
} | |||
} | |||
func (kb dbKeybase) writeLocalKey(priv crypto.PrivKey, name, passphrase string) Info { | |||
// encrypt private key using passphrase | |||
privArmor := encryptArmorPrivKey(priv, passphrase) | |||
// make Info | |||
pub := priv.PubKey() | |||
info := newLocalInfo(name, pub, privArmor) | |||
kb.writeInfo(info, name) | |||
return info | |||
} | |||
func (kb dbKeybase) writeLedgerKey(pub crypto.PubKey, path crypto.DerivationPath, name string) Info { | |||
info := newLedgerInfo(name, pub, path) | |||
kb.writeInfo(info, name) | |||
return info | |||
} | |||
func (kb dbKeybase) writeOfflineKey(pub crypto.PubKey, name string) Info { | |||
info := newOfflineInfo(name, pub) | |||
kb.writeInfo(info, name) | |||
return info | |||
} | |||
func (kb dbKeybase) writeInfo(info Info, name string) { | |||
// write the info by key | |||
kb.db.SetSync(infoKey(name), writeInfo(info)) | |||
} | |||
func infoKey(name string) []byte { | |||
return []byte(fmt.Sprintf("%s.info", name)) | |||
} |
@ -1,383 +0,0 @@ | |||
package keys_test | |||
import ( | |||
"fmt" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/go-crypto" | |||
"github.com/tendermint/go-crypto/keys" | |||
"github.com/tendermint/go-crypto/keys/hd" | |||
dbm "github.com/tendermint/tmlibs/db" | |||
) | |||
// TestKeyManagement makes sure we can manipulate these keys well | |||
func TestKeyManagement(t *testing.T) { | |||
// make the storage with reasonable defaults | |||
cstore := keys.New( | |||
dbm.NewMemDB(), | |||
) | |||
algo := keys.Secp256k1 | |||
n1, n2, n3 := "personal", "business", "other" | |||
p1, p2 := "1234", "really-secure!@#$" | |||
// Check empty state | |||
l, err := cstore.List() | |||
require.Nil(t, err) | |||
assert.Empty(t, l) | |||
_, _, err = cstore.CreateMnemonic(n1, keys.English, p1, keys.Ed25519) | |||
assert.Error(t, err, "ed25519 keys are currently not supported by keybase") | |||
// create some keys | |||
_, err = cstore.Get(n1) | |||
assert.Error(t, err) | |||
i, _, err := cstore.CreateMnemonic(n1, keys.English, p1, algo) | |||
require.NoError(t, err) | |||
require.Equal(t, n1, i.GetName()) | |||
_, _, err = cstore.CreateMnemonic(n2, keys.English, p2, algo) | |||
require.NoError(t, err) | |||
// we can get these keys | |||
i2, err := cstore.Get(n2) | |||
assert.NoError(t, err) | |||
_, err = cstore.Get(n3) | |||
assert.NotNil(t, err) | |||
// list shows them in order | |||
keyS, err := cstore.List() | |||
require.NoError(t, err) | |||
require.Equal(t, 2, len(keyS)) | |||
// note these are in alphabetical order | |||
assert.Equal(t, n2, keyS[0].GetName()) | |||
assert.Equal(t, n1, keyS[1].GetName()) | |||
assert.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey()) | |||
// deleting a key removes it | |||
err = cstore.Delete("bad name", "foo") | |||
require.NotNil(t, err) | |||
err = cstore.Delete(n1, p1) | |||
require.NoError(t, err) | |||
keyS, err = cstore.List() | |||
require.NoError(t, err) | |||
assert.Equal(t, 1, len(keyS)) | |||
_, err = cstore.Get(n1) | |||
assert.Error(t, err) | |||
// create an offline key | |||
o1 := "offline" | |||
priv1 := crypto.GenPrivKeyEd25519() | |||
pub1 := priv1.PubKey() | |||
i, err = cstore.CreateOffline(o1, pub1) | |||
require.Nil(t, err) | |||
require.Equal(t, pub1, i.GetPubKey()) | |||
require.Equal(t, o1, i.GetName()) | |||
keyS, err = cstore.List() | |||
require.NoError(t, err) | |||
require.Equal(t, 2, len(keyS)) | |||
// delete the offline key | |||
err = cstore.Delete(o1, "no") | |||
require.NotNil(t, err) | |||
err = cstore.Delete(o1, "yes") | |||
require.NoError(t, err) | |||
keyS, err = cstore.List() | |||
require.NoError(t, err) | |||
require.Equal(t, 1, len(keyS)) | |||
} | |||
// TestSignVerify does some detailed checks on how we sign and validate | |||
// signatures | |||
func TestSignVerify(t *testing.T) { | |||
cstore := keys.New( | |||
dbm.NewMemDB(), | |||
) | |||
algo := keys.Secp256k1 | |||
n1, n2, n3 := "some dude", "a dudette", "dude-ish" | |||
p1, p2, p3 := "1234", "foobar", "foobar" | |||
// create two users and get their info | |||
i1, _, err := cstore.CreateMnemonic(n1, keys.English, p1, algo) | |||
require.Nil(t, err) | |||
i2, _, err := cstore.CreateMnemonic(n2, keys.English, p2, algo) | |||
require.Nil(t, err) | |||
// Import a public key | |||
armor, err := cstore.ExportPubKey(n2) | |||
require.Nil(t, err) | |||
cstore.ImportPubKey(n3, armor) | |||
i3, err := cstore.Get(n3) | |||
require.NoError(t, err) | |||
require.Equal(t, i3.GetName(), n3) | |||
// let's try to sign some messages | |||
d1 := []byte("my first message") | |||
d2 := []byte("some other important info!") | |||
d3 := []byte("feels like I forgot something...") | |||
// try signing both data with both keys... | |||
s11, pub1, err := cstore.Sign(n1, p1, d1) | |||
require.Nil(t, err) | |||
require.Equal(t, i1.GetPubKey(), pub1) | |||
s12, pub1, err := cstore.Sign(n1, p1, d2) | |||
require.Nil(t, err) | |||
require.Equal(t, i1.GetPubKey(), pub1) | |||
s21, pub2, err := cstore.Sign(n2, p2, d1) | |||
require.Nil(t, err) | |||
require.Equal(t, i2.GetPubKey(), pub2) | |||
s22, pub2, err := cstore.Sign(n2, p2, d2) | |||
require.Nil(t, err) | |||
require.Equal(t, i2.GetPubKey(), pub2) | |||
// let's try to validate and make sure it only works when everything is proper | |||
cases := []struct { | |||
key crypto.PubKey | |||
data []byte | |||
sig crypto.Signature | |||
valid bool | |||
}{ | |||
// proper matches | |||
{i1.GetPubKey(), d1, s11, true}, | |||
// change data, pubkey, or signature leads to fail | |||
{i1.GetPubKey(), d2, s11, false}, | |||
{i2.GetPubKey(), d1, s11, false}, | |||
{i1.GetPubKey(), d1, s21, false}, | |||
// make sure other successes | |||
{i1.GetPubKey(), d2, s12, true}, | |||
{i2.GetPubKey(), d1, s21, true}, | |||
{i2.GetPubKey(), d2, s22, true}, | |||
} | |||
for i, tc := range cases { | |||
valid := tc.key.VerifyBytes(tc.data, tc.sig) | |||
assert.Equal(t, tc.valid, valid, "%d", i) | |||
} | |||
// Now try to sign data with a secret-less key | |||
_, _, err = cstore.Sign(n3, p3, d3) | |||
assert.NotNil(t, err) | |||
} | |||
func assertPassword(t *testing.T, cstore keys.Keybase, name, pass, badpass string) { | |||
err := cstore.Update(name, badpass, pass) | |||
assert.NotNil(t, err) | |||
err = cstore.Update(name, pass, pass) | |||
assert.Nil(t, err, "%+v", err) | |||
} | |||
// TestExportImport tests exporting and importing keys. | |||
func TestExportImport(t *testing.T) { | |||
// make the storage with reasonable defaults | |||
db := dbm.NewMemDB() | |||
cstore := keys.New( | |||
db, | |||
) | |||
info, _, err := cstore.CreateMnemonic("john", keys.English,"secretcpw", keys.Secp256k1) | |||
assert.NoError(t, err) | |||
assert.Equal(t, info.GetName(), "john") | |||
john, err := cstore.Get("john") | |||
assert.NoError(t, err) | |||
assert.Equal(t, info.GetName(), "john") | |||
johnAddr := info.GetPubKey().Address() | |||
armor, err := cstore.Export("john") | |||
assert.NoError(t, err) | |||
err = cstore.Import("john2", armor) | |||
assert.NoError(t, err) | |||
john2, err := cstore.Get("john2") | |||
assert.NoError(t, err) | |||
assert.Equal(t, john.GetPubKey().Address(), johnAddr) | |||
assert.Equal(t, john.GetName(), "john") | |||
assert.Equal(t, john, john2) | |||
} | |||
// | |||
func TestExportImportPubKey(t *testing.T) { | |||
// make the storage with reasonable defaults | |||
db := dbm.NewMemDB() | |||
cstore := keys.New( | |||
db, | |||
) | |||
// CreateMnemonic a private-public key pair and ensure consistency | |||
notPasswd := "n9y25ah7" | |||
info, _, err := cstore.CreateMnemonic("john", keys.English, notPasswd, keys.Secp256k1) | |||
assert.Nil(t, err) | |||
assert.NotEqual(t, info, "") | |||
assert.Equal(t, info.GetName(), "john") | |||
addr := info.GetPubKey().Address() | |||
john, err := cstore.Get("john") | |||
assert.NoError(t, err) | |||
assert.Equal(t, john.GetName(), "john") | |||
assert.Equal(t, john.GetPubKey().Address(), addr) | |||
// Export the public key only | |||
armor, err := cstore.ExportPubKey("john") | |||
assert.NoError(t, err) | |||
// Import it under a different name | |||
err = cstore.ImportPubKey("john-pubkey-only", armor) | |||
assert.NoError(t, err) | |||
// Ensure consistency | |||
john2, err := cstore.Get("john-pubkey-only") | |||
assert.NoError(t, err) | |||
// Compare the public keys | |||
assert.True(t, john.GetPubKey().Equals(john2.GetPubKey())) | |||
// Ensure the original key hasn't changed | |||
john, err = cstore.Get("john") | |||
assert.NoError(t, err) | |||
assert.Equal(t, john.GetPubKey().Address(), addr) | |||
assert.Equal(t, john.GetName(), "john") | |||
// Ensure keys cannot be overwritten | |||
err = cstore.ImportPubKey("john-pubkey-only", armor) | |||
assert.NotNil(t, err) | |||
} | |||
// TestAdvancedKeyManagement verifies update, import, export functionality | |||
func TestAdvancedKeyManagement(t *testing.T) { | |||
// make the storage with reasonable defaults | |||
cstore := keys.New( | |||
dbm.NewMemDB(), | |||
) | |||
algo := keys.Secp256k1 | |||
n1, n2 := "old-name", "new name" | |||
p1, p2 := "1234", "foobar" | |||
// make sure key works with initial password | |||
_, _, err := cstore.CreateMnemonic(n1, keys.English, p1, algo) | |||
require.Nil(t, err, "%+v", err) | |||
assertPassword(t, cstore, n1, p1, p2) | |||
// update password requires the existing password | |||
err = cstore.Update(n1, "jkkgkg", p2) | |||
assert.NotNil(t, err) | |||
assertPassword(t, cstore, n1, p1, p2) | |||
// then it changes the password when correct | |||
err = cstore.Update(n1, p1, p2) | |||
assert.NoError(t, err) | |||
// p2 is now the proper one! | |||
assertPassword(t, cstore, n1, p2, p1) | |||
// exporting requires the proper name and passphrase | |||
_, err = cstore.Export(n1 + ".notreal") | |||
assert.NotNil(t, err) | |||
_, err = cstore.Export(" " + n1) | |||
assert.NotNil(t, err) | |||
_, err = cstore.Export(n1 + " ") | |||
assert.NotNil(t, err) | |||
_, err = cstore.Export("") | |||
assert.NotNil(t, err) | |||
exported, err := cstore.Export(n1) | |||
require.Nil(t, err, "%+v", err) | |||
// import succeeds | |||
err = cstore.Import(n2, exported) | |||
assert.NoError(t, err) | |||
// second import fails | |||
err = cstore.Import(n2, exported) | |||
assert.NotNil(t, err) | |||
} | |||
// TestSeedPhrase verifies restoring from a seed phrase | |||
func TestSeedPhrase(t *testing.T) { | |||
// make the storage with reasonable defaults | |||
cstore := keys.New( | |||
dbm.NewMemDB(), | |||
) | |||
algo := keys.Secp256k1 | |||
n1, n2 := "lost-key", "found-again" | |||
p1, p2 := "1234", "foobar" | |||
// make sure key works with initial password | |||
info, mnemonic, err := cstore.CreateMnemonic(n1, keys.English, p1, algo) | |||
require.Nil(t, err, "%+v", err) | |||
assert.Equal(t, n1, info.GetName()) | |||
assert.NotEmpty(t, mnemonic) | |||
// now, let us delete this key | |||
err = cstore.Delete(n1, p1) | |||
require.Nil(t, err, "%+v", err) | |||
_, err = cstore.Get(n1) | |||
require.NotNil(t, err) | |||
// let us re-create it from the mnemonic-phrase | |||
params := *hd.NewFundraiserParams(0 ,0 ) | |||
newInfo, err := cstore.Derive(n2,mnemonic, p2, params) | |||
require.NoError(t, err) | |||
assert.Equal(t, n2, newInfo.GetName()) | |||
assert.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) | |||
assert.Equal(t, info.GetPubKey(), newInfo.GetPubKey()) | |||
} | |||
func ExampleNew() { | |||
// Select the encryption and storage for your cryptostore | |||
cstore := keys.New( | |||
dbm.NewMemDB(), | |||
) | |||
sec := keys.Secp256k1 | |||
// Add keys and see they return in alphabetical order | |||
bob, _, err := cstore.CreateMnemonic("Bob", keys.English, "friend", sec) | |||
if err != nil { | |||
// this should never happen | |||
fmt.Println(err) | |||
} else { | |||
// return info here just like in List | |||
fmt.Println(bob.GetName()) | |||
} | |||
cstore.CreateMnemonic("Alice", keys.English, "secret", sec) | |||
cstore.CreateMnemonic("Carl", keys.English, "mitm", sec) | |||
info, _ := cstore.List() | |||
for _, i := range info { | |||
fmt.Println(i.GetName()) | |||
} | |||
// We need to use passphrase to generate a signature | |||
tx := []byte("deadbeef") | |||
sig, pub, err := cstore.Sign("Bob", "friend", tx) | |||
if err != nil { | |||
fmt.Println("don't accept real passphrase") | |||
} | |||
// and we can validate the signature with publicly available info | |||
binfo, _ := cstore.Get("Bob") | |||
if !binfo.GetPubKey().Equals(bob.GetPubKey()) { | |||
fmt.Println("Get and Create return different keys") | |||
} | |||
if pub.Equals(binfo.GetPubKey()) { | |||
fmt.Println("signed by Bob") | |||
} | |||
if !pub.VerifyBytes(tx, sig) { | |||
fmt.Println("invalid signature") | |||
} | |||
// Output: | |||
// Bob | |||
// Alice | |||
// Bob | |||
// Carl | |||
// signed by Bob | |||
} |
@ -1,12 +0,0 @@ | |||
package keys | |||
// SigningAlgo defines an algorithm to derive key-pairs which can be used for cryptographic signing. | |||
type SigningAlgo string | |||
const ( | |||
// Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters. | |||
Secp256k1 = SigningAlgo("secp256k1") | |||
// Ed25519 represents the Ed25519 signature system. | |||
// It is currently not supported for end-user keys (wallets/ledgers). | |||
Ed25519 = SigningAlgo("ed25519") | |||
) |
@ -1,2 +0,0 @@ | |||
output = "text" | |||
keydir = ".mykeys" |
@ -1,115 +0,0 @@ | |||
package keys | |||
import ( | |||
"encoding/hex" | |||
"fmt" | |||
cmn "github.com/tendermint/tmlibs/common" | |||
"github.com/tendermint/go-crypto" | |||
"github.com/tendermint/go-crypto/keys/bcrypt" | |||
) | |||
const ( | |||
blockTypePrivKey = "TENDERMINT PRIVATE KEY" | |||
blockTypeKeyInfo = "TENDERMINT KEY INFO" | |||
blockTypePubKey = "TENDERMINT PUBLIC KEY" | |||
) | |||
func armorInfoBytes(bz []byte) string { | |||
return armorBytes(bz, blockTypeKeyInfo) | |||
} | |||
func armorPubKeyBytes(bz []byte) string { | |||
return armorBytes(bz, blockTypePubKey) | |||
} | |||
func armorBytes(bz []byte, blockType string) string { | |||
header := map[string]string{ | |||
"type": "Info", | |||
"version": "0.0.0", | |||
} | |||
return crypto.EncodeArmor(blockType, header, bz) | |||
} | |||
func unarmorInfoBytes(armorStr string) (bz []byte, err error) { | |||
return unarmorBytes(armorStr, blockTypeKeyInfo) | |||
} | |||
func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) { | |||
return unarmorBytes(armorStr, blockTypePubKey) | |||
} | |||
func unarmorBytes(armorStr, blockType string) (bz []byte, err error) { | |||
bType, header, bz, err := crypto.DecodeArmor(armorStr) | |||
if err != nil { | |||
return | |||
} | |||
if bType != blockType { | |||
err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType) | |||
return | |||
} | |||
if header["version"] != "0.0.0" { | |||
err = fmt.Errorf("Unrecognized version: %v", header["version"]) | |||
return | |||
} | |||
return | |||
} | |||
func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { | |||
saltBytes, encBytes := encryptPrivKey(privKey, passphrase) | |||
header := map[string]string{ | |||
"kdf": "bcrypt", | |||
"salt": fmt.Sprintf("%X", saltBytes), | |||
} | |||
armorStr := crypto.EncodeArmor(blockTypePrivKey, header, encBytes) | |||
return armorStr | |||
} | |||
func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) { | |||
var privKey crypto.PrivKey | |||
blockType, header, encBytes, err := crypto.DecodeArmor(armorStr) | |||
if err != nil { | |||
return privKey, err | |||
} | |||
if blockType != blockTypePrivKey { | |||
return privKey, fmt.Errorf("Unrecognized armor type: %v", blockType) | |||
} | |||
if header["kdf"] != "bcrypt" { | |||
return privKey, fmt.Errorf("Unrecognized KDF type: %v", header["KDF"]) | |||
} | |||
if header["salt"] == "" { | |||
return privKey, fmt.Errorf("Missing salt bytes") | |||
} | |||
saltBytes, err := hex.DecodeString(header["salt"]) | |||
if err != nil { | |||
return privKey, fmt.Errorf("Error decoding salt: %v", err.Error()) | |||
} | |||
privKey, err = decryptPrivKey(saltBytes, encBytes, passphrase) | |||
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), 12) // TODO parameterize. 12 is good today (2016) | |||
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, crypto.EncryptSymmetric(privKeyBytes, key) | |||
} | |||
func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { | |||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 12) // TODO parameterize. 12 is good today (2016) | |||
if err != nil { | |||
cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) | |||
} | |||
key = crypto.Sha256(key) // Get 32 bytes | |||
privKeyBytes, err := crypto.DecryptSymmetric(encBytes, key) | |||
if err != nil { | |||
return privKey, err | |||
} | |||
privKey, err = crypto.PrivKeyFromBytes(privKeyBytes) | |||
return privKey, err | |||
} |
@ -1,142 +0,0 @@ | |||
package keys | |||
import ( | |||
crypto "github.com/tendermint/go-crypto" | |||
"github.com/tendermint/go-crypto/keys/hd" | |||
) | |||
// Keybase exposes operations on a generic keystore | |||
type Keybase interface { | |||
// CRUD on the keystore | |||
List() ([]Info, error) | |||
Get(name string) (Info, error) | |||
Delete(name, passphrase string) error | |||
// Sign some bytes, looking up the private key to use | |||
Sign(name, passphrase string, msg []byte) (crypto.Signature, crypto.PubKey, error) | |||
// CreateMnemonic creates a new mnemonic, and derives a hierarchical deterministic | |||
// key from that. | |||
CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, 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) | |||
// Create, store, and return a new Ledger key reference | |||
CreateLedger(name string, path crypto.DerivationPath, algo SigningAlgo) (info Info, err error) | |||
// Create, store, and return a new offline key reference | |||
CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) | |||
// The following operations will *only* work on locally-stored keys | |||
Update(name, oldpass, newpass string) error | |||
Import(name string, armor string) (err error) | |||
ImportPubKey(name string, armor string) (err error) | |||
Export(name string) (armor string, err error) | |||
ExportPubKey(name string) (armor string, err error) | |||
} | |||
// Info is the publicly exposed information about a keypair | |||
type Info interface { | |||
// Human-readable type for key listing | |||
GetType() string | |||
// Name of the key | |||
GetName() string | |||
// Public key | |||
GetPubKey() crypto.PubKey | |||
} | |||
var _ Info = &localInfo{} | |||
var _ Info = &ledgerInfo{} | |||
var _ Info = &offlineInfo{} | |||
// localInfo is the public information about a locally stored key | |||
type localInfo struct { | |||
Name string `json:"name"` | |||
PubKey crypto.PubKey `json:"pubkey"` | |||
PrivKeyArmor string `json:"privkey.armor"` | |||
} | |||
func newLocalInfo(name string, pub crypto.PubKey, privArmor string) Info { | |||
return &localInfo{ | |||
Name: name, | |||
PubKey: pub, | |||
PrivKeyArmor: privArmor, | |||
} | |||
} | |||
func (i localInfo) GetType() string { | |||
return "local" | |||
} | |||
func (i localInfo) GetName() string { | |||
return i.Name | |||
} | |||
func (i localInfo) GetPubKey() crypto.PubKey { | |||
return i.PubKey | |||
} | |||
// ledgerInfo is the public information about a Ledger key | |||
type ledgerInfo struct { | |||
Name string `json:"name"` | |||
PubKey crypto.PubKey `json:"pubkey"` | |||
Path crypto.DerivationPath `json:"path"` | |||
} | |||
func newLedgerInfo(name string, pub crypto.PubKey, path crypto.DerivationPath) Info { | |||
return &ledgerInfo{ | |||
Name: name, | |||
PubKey: pub, | |||
Path: path, | |||
} | |||
} | |||
func (i ledgerInfo) GetType() string { | |||
return "ledger" | |||
} | |||
func (i ledgerInfo) GetName() string { | |||
return i.Name | |||
} | |||
func (i ledgerInfo) GetPubKey() crypto.PubKey { | |||
return i.PubKey | |||
} | |||
// offlineInfo is the public information about an offline key | |||
type offlineInfo struct { | |||
Name string `json:"name"` | |||
PubKey crypto.PubKey `json:"pubkey"` | |||
} | |||
func newOfflineInfo(name string, pub crypto.PubKey) Info { | |||
return &offlineInfo{ | |||
Name: name, | |||
PubKey: pub, | |||
} | |||
} | |||
func (i offlineInfo) GetType() string { | |||
return "offline" | |||
} | |||
func (i offlineInfo) GetName() string { | |||
return i.Name | |||
} | |||
func (i offlineInfo) GetPubKey() crypto.PubKey { | |||
return i.PubKey | |||
} | |||
// encoding info | |||
func writeInfo(i Info) []byte { | |||
return cdc.MustMarshalBinary(i) | |||
} | |||
// decoding info | |||
func readInfo(bz []byte) (info Info, err error) { | |||
err = cdc.UnmarshalBinary(bz, &info) | |||
return | |||
} |
@ -1,16 +0,0 @@ | |||
package keys | |||
import ( | |||
amino "github.com/tendermint/go-amino" | |||
crypto "github.com/tendermint/go-crypto" | |||
) | |||
var cdc = amino.NewCodec() | |||
func init() { | |||
crypto.RegisterAmino(cdc) | |||
cdc.RegisterInterface((*Info)(nil), nil) | |||
cdc.RegisterConcrete(localInfo{}, "crypto/keys/localInfo", nil) | |||
cdc.RegisterConcrete(ledgerInfo{}, "crypto/keys/ledgerInfo", nil) | |||
cdc.RegisterConcrete(offlineInfo{}, "crypto/keys/offlineInfo", nil) | |||
} |