mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 06:42:16 +00:00
Merge PR #142: Delete keys package & Ledger integration; update tests
This commit is contained in:
commit
edb36d38b2
67
Gopkg.lock
generated
67
Gopkg.lock
generated
@ -1,18 +1,6 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/bartekn/go-bip39"
|
||||
packages = ["."]
|
||||
revision = "a05967ea095d81c8fe4833776774cfaff8e5036c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/brejski/hid"
|
||||
packages = ["."]
|
||||
revision = "06112dcfcc50a7e0e4fd06e17f9791e788fdaafc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
@ -63,18 +51,6 @@
|
||||
revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jmhodges/levigo"
|
||||
packages = ["."]
|
||||
revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/kr/logfmt"
|
||||
@ -102,25 +78,6 @@
|
||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
||||
version = "v1.2.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = [
|
||||
"leveldb",
|
||||
"leveldb/cache",
|
||||
"leveldb/comparer",
|
||||
"leveldb/errors",
|
||||
"leveldb/filter",
|
||||
"leveldb/iterator",
|
||||
"leveldb/journal",
|
||||
"leveldb/memdb",
|
||||
"leveldb/opt",
|
||||
"leveldb/storage",
|
||||
"leveldb/table",
|
||||
"leveldb/util"
|
||||
]
|
||||
revision = "e2150783cd35f5b607daca48afd8c57ec54cc995"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/tendermint/ed25519"
|
||||
@ -134,31 +91,19 @@
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/go-amino"
|
||||
packages = ["."]
|
||||
revision = "1715b7b78c65d6adcc5937315be4710234cefe09"
|
||||
version = "0.10.0-rc2"
|
||||
revision = "2106ca61d91029c931fd54968c2bb02dc96b1412"
|
||||
version = "0.10.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
packages = [
|
||||
"common",
|
||||
"db",
|
||||
"log",
|
||||
"test"
|
||||
]
|
||||
revision = "692f1d86a6e2c0efa698fd1e4541b68c74ffaf38"
|
||||
version = "v0.8.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/tyler-smith/go-bip39"
|
||||
packages = ["."]
|
||||
revision = "8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/zondax/ledger-goclient"
|
||||
packages = ["."]
|
||||
revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
@ -168,25 +113,25 @@
|
||||
"chacha20poly1305",
|
||||
"hkdf",
|
||||
"internal/chacha20",
|
||||
"internal/subtle",
|
||||
"nacl/secretbox",
|
||||
"openpgp/armor",
|
||||
"openpgp/errors",
|
||||
"pbkdf2",
|
||||
"poly1305",
|
||||
"ripemd160",
|
||||
"salsa20/salsa"
|
||||
]
|
||||
revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
|
||||
revision = "7f39a6fea4fe9364fb61e1def6a268a51b4f3a06"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["cpu"]
|
||||
revision = "a9e25c09b96b8870693763211309e213c6ef299d"
|
||||
revision = "ad87a3a340fa7f3bed189293fbfa7a9b7e021ae1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "684746f2b651a722ee32eae056b437baf7a2213d68ddbfcefe04a1c3df27c93b"
|
||||
inputs-digest = "027b22b86396a971d5d5c1d298947f531f39743975d65a22e98601140aa1b1a1"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
12
Gopkg.toml
12
Gopkg.toml
@ -28,10 +28,6 @@
|
||||
name = "github.com/btcsuite/btcutil"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/pkg/errors"
|
||||
version = "0.8.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "1.2.1"
|
||||
@ -48,14 +44,6 @@
|
||||
name = "github.com/tendermint/tmlibs"
|
||||
version = "0.8.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/tyler-smith/go-bip39"
|
||||
branch = "master"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/zondax/ledger-goclient"
|
||||
revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
2
Makefile
2
Makefile
@ -51,7 +51,7 @@ get_vendor_deps:
|
||||
### Testing
|
||||
|
||||
test:
|
||||
go test -p 1 $(shell go list ./... | grep -v vendor)
|
||||
CGO_ENABLED=0 go test -p 1 $(shell go list ./... | grep -v vendor)
|
||||
|
||||
########################################
|
||||
### Formatting, linting, and vetting
|
||||
|
2
amino.go
2
amino.go
@ -28,8 +28,6 @@ func RegisterAmino(cdc *amino.Codec) {
|
||||
"tendermint/PrivKeyEd25519", nil)
|
||||
cdc.RegisterConcrete(PrivKeySecp256k1{},
|
||||
"tendermint/PrivKeySecp256k1", nil)
|
||||
cdc.RegisterConcrete(PrivKeyLedgerSecp256k1{},
|
||||
"tendermint/PrivKeyLedgerSecp256k1", nil)
|
||||
|
||||
cdc.RegisterInterface((*Signature)(nil), nil)
|
||||
cdc.RegisterConcrete(SignatureEd25519{},
|
||||
|
@ -50,7 +50,6 @@ func ExamplePrintRegisteredTypes() {
|
||||
//| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | |
|
||||
//| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | |
|
||||
//| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | |
|
||||
//| PrivKeyLedgerSecp256k1 | tendermint/PrivKeyLedgerSecp256k1 | 0x10CAB393 | variable | |
|
||||
//| SignatureEd25519 | tendermint/SignatureEd25519 | 0x2031EA53 | 0x40 | |
|
||||
//| SignatureSecp256k1 | tendermint/SignatureSecp256k1 | 0x7FC4A495 | variable | |
|
||||
}
|
||||
|
@ -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
|
||||
}
|
File diff suppressed because one or more lines are too long
362
keys/keybase.go
362
keys/keybase.go
@ -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
|
||||
}
|
12
keys/keys.go
12
keys/keys.go
@ -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"
|
115
keys/mintkey.go
115
keys/mintkey.go
@ -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
|
||||
}
|
142
keys/types.go
142
keys/types.go
@ -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
|
||||
}
|
16
keys/wire.go
16
keys/wire.go
@ -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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user