@ -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) | |||||
} |
@ -1,19 +0,0 @@ | |||||
package crypto | |||||
import ( | |||||
ledger "github.com/zondax/ledger-goclient" | |||||
) | |||||
var device *ledger.Ledger | |||||
// Ledger derivation path | |||||
type DerivationPath = []uint32 | |||||
// getLedger gets a copy of the device, and caches it | |||||
func getLedger() (*ledger.Ledger, error) { | |||||
var err error | |||||
if device == nil { | |||||
device, err = ledger.FindLedger() | |||||
} | |||||
return device, err | |||||
} |
@ -1,124 +0,0 @@ | |||||
package crypto | |||||
import ( | |||||
"fmt" | |||||
secp256k1 "github.com/btcsuite/btcd/btcec" | |||||
ledger "github.com/zondax/ledger-goclient" | |||||
) | |||||
func pubkeyLedgerSecp256k1(device *ledger.Ledger, path DerivationPath) (pub PubKey, err error) { | |||||
key, err := device.GetPublicKeySECP256K1(path) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("error fetching public key: %v", err) | |||||
} | |||||
var p PubKeySecp256k1 | |||||
// Reserialize in the 33-byte compressed format | |||||
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256()) | |||||
copy(p[:], cmp.SerializeCompressed()) | |||||
pub = p | |||||
return | |||||
} | |||||
func signLedgerSecp256k1(device *ledger.Ledger, path DerivationPath, msg []byte) (sig Signature, err error) { | |||||
bsig, err := device.SignSECP256K1(path, msg) | |||||
if err != nil { | |||||
return sig, err | |||||
} | |||||
sig = SignatureSecp256k1FromBytes(bsig) | |||||
return | |||||
} | |||||
// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano | |||||
// we cache the PubKey from the first call to use it later | |||||
type PrivKeyLedgerSecp256k1 struct { | |||||
// PubKey should be private, but we want to encode it via go-amino | |||||
// so we can view the address later, even without having the ledger | |||||
// attached | |||||
CachedPubKey PubKey | |||||
Path DerivationPath | |||||
} | |||||
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the | |||||
// public key for later use. | |||||
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (PrivKey, error) { | |||||
var pk PrivKeyLedgerSecp256k1 | |||||
pk.Path = path | |||||
// cache the pubkey for later use | |||||
pubKey, err := pk.getPubKey() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
pk.CachedPubKey = pubKey | |||||
return &pk, err | |||||
} | |||||
// ValidateKey allows us to verify the sanity of a key | |||||
// after loading it from disk | |||||
func (pk PrivKeyLedgerSecp256k1) ValidateKey() error { | |||||
// getPubKey will return an error if the ledger is not | |||||
pub, err := pk.getPubKey() | |||||
if err != nil { | |||||
return err | |||||
} | |||||
// verify this matches cached address | |||||
if !pub.Equals(pk.CachedPubKey) { | |||||
return fmt.Errorf("cached key does not match retrieved key") | |||||
} | |||||
return nil | |||||
} | |||||
// AssertIsPrivKeyInner fulfils PrivKey Interface | |||||
func (pk *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {} | |||||
// Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify | |||||
// the same key when we reconnect to a ledger | |||||
func (pk PrivKeyLedgerSecp256k1) Bytes() []byte { | |||||
return cdc.MustMarshalBinaryBare(pk) | |||||
} | |||||
// Sign calls the ledger and stores the PubKey for future use | |||||
// | |||||
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, | |||||
// returning an error, so this should only trigger if the privkey is held | |||||
// in memory for a while before use. | |||||
func (pk PrivKeyLedgerSecp256k1) Sign(msg []byte) (Signature, error) { | |||||
dev, err := getLedger() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
sig, err := signLedgerSecp256k1(dev, pk.Path, msg) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
return sig, nil | |||||
} | |||||
// PubKey returns the stored PubKey | |||||
func (pk PrivKeyLedgerSecp256k1) PubKey() PubKey { | |||||
return pk.CachedPubKey | |||||
} | |||||
// getPubKey reads the pubkey the ledger itself | |||||
// since this involves IO, it may return an error, which is not exposed | |||||
// in the PubKey interface, so this function allows better error handling | |||||
func (pk PrivKeyLedgerSecp256k1) getPubKey() (key PubKey, err error) { | |||||
dev, err := getLedger() | |||||
if err != nil { | |||||
return key, fmt.Errorf("cannot connect to Ledger device - error: %v", err) | |||||
} | |||||
key, err = pubkeyLedgerSecp256k1(dev, pk.Path) | |||||
if err != nil { | |||||
return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err) | |||||
} | |||||
return key, err | |||||
} | |||||
// Equals fulfils PrivKey Interface - makes sure both keys refer to the | |||||
// same | |||||
func (pk PrivKeyLedgerSecp256k1) Equals(other PrivKey) bool { | |||||
if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok { | |||||
return pk.CachedPubKey.Equals(ledger.CachedPubKey) | |||||
} | |||||
return false | |||||
} |
@ -1,63 +0,0 @@ | |||||
package crypto | |||||
import ( | |||||
"os" | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/stretchr/testify/require" | |||||
) | |||||
func TestRealLedgerSecp256k1(t *testing.T) { | |||||
if os.Getenv("WITH_LEDGER") == "" { | |||||
t.Skip("Set WITH_LEDGER to run code on real ledger") | |||||
} | |||||
msg := []byte("kuhehfeohg") | |||||
path := DerivationPath{44, 60, 0, 0, 0} | |||||
priv, err := NewPrivKeyLedgerSecp256k1(path) | |||||
require.Nil(t, err, "%+v", err) | |||||
pub := priv.PubKey() | |||||
sig, err := priv.Sign(msg) | |||||
require.Nil(t, err) | |||||
valid := pub.VerifyBytes(msg, sig) | |||||
assert.True(t, valid) | |||||
// now, let's serialize the key and make sure it still works | |||||
bs := priv.Bytes() | |||||
priv2, err := PrivKeyFromBytes(bs) | |||||
require.Nil(t, err, "%+v", err) | |||||
// make sure we get the same pubkey when we load from disk | |||||
pub2 := priv2.PubKey() | |||||
require.Equal(t, pub, pub2) | |||||
// signing with the loaded key should match the original pubkey | |||||
sig, err = priv2.Sign(msg) | |||||
require.Nil(t, err) | |||||
valid = pub.VerifyBytes(msg, sig) | |||||
assert.True(t, valid) | |||||
// make sure pubkeys serialize properly as well | |||||
bs = pub.Bytes() | |||||
bpub, err := PubKeyFromBytes(bs) | |||||
require.NoError(t, err) | |||||
assert.Equal(t, pub, bpub) | |||||
} | |||||
// TestRealLedgerErrorHandling calls. These tests assume | |||||
// the ledger is not plugged in.... | |||||
func TestRealLedgerErrorHandling(t *testing.T) { | |||||
if os.Getenv("WITH_LEDGER") != "" { | |||||
t.Skip("Skipping on WITH_LEDGER as it tests unplugged cases") | |||||
} | |||||
// first, try to generate a key, must return an error | |||||
// (no panic) | |||||
path := DerivationPath{44, 60, 0, 0, 0} | |||||
_, err := NewPrivKeyLedgerSecp256k1(path) | |||||
require.Error(t, err) | |||||
} |