mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-15 16:11:21 +00:00
commit
d1f00be7a0
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.0 (October 27, 2017)
|
||||
|
||||
BREAKING CHANGES:
|
||||
|
||||
- `keys`: use bcrypt plus salt
|
||||
|
||||
FEATURES:
|
||||
|
||||
- add support for signing via Ledger Nano
|
||||
|
||||
IMPROVEMENTS:
|
||||
|
||||
- linting and comments
|
||||
|
||||
## 0.3.0 (September 22, 2017)
|
||||
|
||||
BREAKING CHANGES:
|
||||
|
42
Makefile
42
Makefile
@ -2,13 +2,15 @@
|
||||
|
||||
GOTOOLS = \
|
||||
github.com/Masterminds/glide \
|
||||
github.com/jteeuwen/go-bindata/go-bindata
|
||||
github.com/jteeuwen/go-bindata/go-bindata \
|
||||
github.com/alecthomas/gometalinter
|
||||
|
||||
REPO:=github.com/tendermint/go-crypto
|
||||
|
||||
all: get_vendor_deps test
|
||||
all: get_vendor_deps metalinter_test test
|
||||
|
||||
test:
|
||||
go test `glide novendor`
|
||||
go test -p 1 `glide novendor`
|
||||
|
||||
get_vendor_deps: ensure_tools
|
||||
@rm -rf vendor/
|
||||
@ -31,3 +33,37 @@ codegen:
|
||||
@echo "--> regenerating all interface wrappers"
|
||||
@gen
|
||||
@echo "Done!"
|
||||
|
||||
metalinter: ensure_tools
|
||||
@gometalinter --install
|
||||
gometalinter --vendor --deadline=600s --enable-all --disable=lll ./...
|
||||
|
||||
metalinter_test: ensure_tools
|
||||
@gometalinter --install
|
||||
gometalinter --vendor --deadline=600s --disable-all \
|
||||
--enable=deadcode \
|
||||
--enable=gas \
|
||||
--enable=goconst \
|
||||
--enable=gocyclo \
|
||||
--enable=gosimple \
|
||||
--enable=ineffassign \
|
||||
--enable=interfacer \
|
||||
--enable=maligned \
|
||||
--enable=megacheck \
|
||||
--enable=misspell \
|
||||
--enable=safesql \
|
||||
--enable=staticcheck \
|
||||
--enable=structcheck \
|
||||
--enable=unconvert \
|
||||
--enable=unused \
|
||||
--enable=vetshadow \
|
||||
--enable=vet \
|
||||
--enable=varcheck \
|
||||
./...
|
||||
|
||||
#--enable=dupl \
|
||||
#--enable=errcheck \
|
||||
#--enable=goimports \
|
||||
#--enable=golint \ <== comments on anything exported
|
||||
#--enable=gotype \
|
||||
#--enable=unparam \
|
||||
|
6
_gen.go
6
_gen.go
@ -1,6 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/tendermint/go-wire/gen"
|
||||
_ "github.com/clipperhouse/stringer"
|
||||
)
|
2
armor.go
2
armor.go
@ -22,7 +22,7 @@ func EncodeArmor(blockType string, headers map[string]string, data []byte) strin
|
||||
if err != nil {
|
||||
PanicSanity("Error encoding ascii armor: " + err.Error())
|
||||
}
|
||||
return string(buf.Bytes())
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func DecodeArmor(armorStr string) (blockType string, headers map[string]string, data []byte, err error) {
|
||||
|
@ -50,7 +50,7 @@ func (ih InvalidHashPrefixError) Error() string {
|
||||
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))
|
||||
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) // nolint: unconvert
|
||||
}
|
||||
|
||||
const (
|
||||
|
48
doc.go
Normal file
48
doc.go
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
go-crypto is a customized/convenience cryptography package
|
||||
for supporting Tendermint.
|
||||
|
||||
It wraps select functionality of equivalent functions in the
|
||||
Go standard library, for easy usage with our libraries.
|
||||
|
||||
Keys:
|
||||
|
||||
All key generation functions return an instance of the PrivKey interface
|
||||
which implements methods
|
||||
|
||||
AssertIsPrivKeyInner()
|
||||
Bytes() []byte
|
||||
Sign(msg []byte) Signature
|
||||
PubKey() PubKey
|
||||
Equals(PrivKey) bool
|
||||
Wrap() PrivKey
|
||||
|
||||
From the above method we can:
|
||||
a) Retrieve the public key if needed
|
||||
|
||||
pubKey := key.PubKey()
|
||||
|
||||
For example:
|
||||
privKey, err := crypto.GenPrivKeyEd25519()
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
pubKey := privKey.PubKey()
|
||||
...
|
||||
// And then you can use the private and public key
|
||||
doSomething(privKey, pubKey)
|
||||
|
||||
|
||||
We also provide hashing wrappers around algorithms:
|
||||
|
||||
Sha256
|
||||
sum := crypto.Sha256([]byte("This is Tendermint"))
|
||||
fmt.Printf("%x\n", sum)
|
||||
|
||||
Ripemd160
|
||||
sum := crypto.Ripemd160([]byte("This is consensus"))
|
||||
fmt.Printf("%x\n", sum)
|
||||
*/
|
||||
package crypto
|
||||
|
||||
// TODO: Add more docs in here
|
@ -73,8 +73,8 @@ func TestEncodeDemo(t *testing.T) {
|
||||
// Try to encode as binary
|
||||
b, err := data.ToWire(tc.in)
|
||||
if assert.Nil(err, "%d: %#v", i, tc.in) {
|
||||
err := data.FromWire(b, tc.out)
|
||||
if assert.Nil(err) {
|
||||
err2 := data.FromWire(b, tc.out)
|
||||
if assert.Nil(err2) {
|
||||
assert.Equal(tc.expected, tc.out.String())
|
||||
}
|
||||
}
|
||||
@ -82,8 +82,8 @@ func TestEncodeDemo(t *testing.T) {
|
||||
// Try to encode it as json
|
||||
j, err := data.ToJSON(tc.in)
|
||||
if assert.Nil(err, "%d: %#v", i, tc.in) {
|
||||
err := data.FromJSON(j, tc.out)
|
||||
if assert.Nil(err) {
|
||||
err2 := data.FromJSON(j, tc.out)
|
||||
if assert.Nil(err2) {
|
||||
assert.Equal(tc.expected, tc.out.String())
|
||||
}
|
||||
}
|
||||
|
35
example_test.go
Normal file
35
example_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2017 Tendermint. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func ExampleSha256() {
|
||||
sum := crypto.Sha256([]byte("This is Tendermint"))
|
||||
fmt.Printf("%x\n", sum)
|
||||
// Output:
|
||||
// f91afb642f3d1c87c17eb01aae5cb65c242dfdbe7cf1066cc260f4ce5d33b94e
|
||||
}
|
||||
|
||||
func ExampleRipemd160() {
|
||||
sum := crypto.Ripemd160([]byte("This is Tendermint"))
|
||||
fmt.Printf("%x\n", sum)
|
||||
// Output:
|
||||
// 051e22663e8f0fd2f2302f1210f954adff009005
|
||||
}
|
56
glide.lock
generated
56
glide.lock
generated
@ -1,28 +1,20 @@
|
||||
hash: c0a2db1b80c6b1b8aab31c526ce43e22e49b23c893c78b8fdb8546aa2e7b7cc6
|
||||
updated: 2017-09-22T10:21:34.220901552-04:00
|
||||
hash: 6e06a42eafe0aeff112cee86aef6b2cab0e2f62c2e6bfccfb629aa22f6b62773
|
||||
updated: 2017-10-27T18:45:18.350198941+02:00
|
||||
imports:
|
||||
- name: github.com/bgentry/speakeasy
|
||||
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
|
||||
- name: github.com/btcsuite/btcd
|
||||
version: b8df516b4b267acf2de46be593a9d948d1d2c420
|
||||
version: c7588cbf7690cd9f047a28efa2dcd8f2435a4e5e
|
||||
subpackages:
|
||||
- btcec
|
||||
- chaincfg
|
||||
- chaincfg/chainhash
|
||||
- wire
|
||||
- name: github.com/btcsuite/btcutil
|
||||
version: 86346b5a958c0cf94186b87855469ae991be501c
|
||||
version: 66871daeb12123ece012a9628d2798d01195c4b3
|
||||
subpackages:
|
||||
- base58
|
||||
- hdkeychain
|
||||
- name: github.com/btcsuite/fastsha256
|
||||
version: 637e656429416087660c84436a2a035d69d54e2e
|
||||
- name: github.com/btcsuite/golangcrypto
|
||||
version: 53f62d9b43e87a6c56975cf862af7edf33a8d0df
|
||||
subpackages:
|
||||
- ripemd160
|
||||
- name: github.com/ethanfrey/hid
|
||||
version: 660bb717bd4e7cbcdf0f7cd5cadf1cb2e4be452a
|
||||
- name: github.com/ethanfrey/ledger
|
||||
version: 23a7bb9d74bc83a862fcb4bddde24215b2295ad9
|
||||
- name: github.com/go-kit/kit
|
||||
version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8
|
||||
version: e2b298466b32c7cd5579a9b9b07e968fc9d9452c
|
||||
subpackages:
|
||||
- log
|
||||
- log/level
|
||||
@ -30,46 +22,36 @@ imports:
|
||||
- name: github.com/go-logfmt/logfmt
|
||||
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
|
||||
- name: github.com/go-playground/locales
|
||||
version: 1e5f1161c6416a5ff48840eb8724a394e48cc534
|
||||
version: e4cbcb5d0652150d40ad0646651076b6bd2be4f6
|
||||
subpackages:
|
||||
- currency
|
||||
- name: github.com/go-playground/universal-translator
|
||||
version: 71201497bace774495daed26a3874fd339e0b538
|
||||
- name: github.com/go-stack/stack
|
||||
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
|
||||
- name: github.com/gorilla/context
|
||||
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
|
||||
- name: github.com/gorilla/handlers
|
||||
version: a4043c62cc2329bacda331d33fc908ab11ef0ec3
|
||||
- name: github.com/gorilla/mux
|
||||
version: bcd8bc72b08df0f70df986b97f95590779502d31
|
||||
version: 817915b46b97fd7bb80e8ab6b69f01a53ac3eebf
|
||||
- name: github.com/howeyc/crc16
|
||||
version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f
|
||||
- name: github.com/kr/logfmt
|
||||
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
|
||||
- name: github.com/pkg/errors
|
||||
version: 645ef00459ed84a119197bfb8d8205042c6df63d
|
||||
- name: github.com/spf13/cobra
|
||||
version: 4cdb38c072b86bf795d2c81de50784d9fdd6eb77
|
||||
- name: github.com/spf13/viper
|
||||
version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2
|
||||
- name: github.com/tendermint/ed25519
|
||||
version: 1f52c6f8b8a5c7908aff4497c186af344b428925
|
||||
version: d8387025d2b9d158cf4efb07e7ebf814bcce2057
|
||||
subpackages:
|
||||
- edwards25519
|
||||
- extra25519
|
||||
- name: github.com/tendermint/go-wire
|
||||
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
|
||||
version: 8ee84b5b2581530168daf66fc89c548d27403c57
|
||||
subpackages:
|
||||
- data
|
||||
- data/base58
|
||||
- name: github.com/tendermint/tmlibs
|
||||
version: bffe6744ec277d60f707ab442e25513617842f8e
|
||||
version: 092eb701c7276907cdbed258750e22ce895b6735
|
||||
subpackages:
|
||||
- common
|
||||
- log
|
||||
- name: golang.org/x/crypto
|
||||
version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e
|
||||
version: edd5e9b0879d13ee6970a50153d85b8fec9f7686
|
||||
subpackages:
|
||||
- bcrypt
|
||||
- blowfish
|
||||
@ -81,16 +63,12 @@ imports:
|
||||
- ripemd160
|
||||
- salsa20/salsa
|
||||
- name: gopkg.in/go-playground/validator.v9
|
||||
version: d529ee1b0f30352444f507cc6cdac96bfd12decc
|
||||
version: 1304298bf10d085adec514b076772a79c9cadb6b
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/FactomProject/basen
|
||||
version: fe3947df716ebfda9847eb1b9a48f9592e06478c
|
||||
- name: github.com/FactomProject/btcutilecc
|
||||
version: d3a63a5752ecf3fbc06bd97365da752111c263df
|
||||
- name: github.com/mndrix/btcutil
|
||||
version: d3a63a5752ecf3fbc06bd97365da752111c263df
|
||||
- name: github.com/pmezard/go-difflib
|
||||
@ -103,6 +81,6 @@ testImports:
|
||||
- assert
|
||||
- require
|
||||
- name: github.com/tyler-smith/go-bip32
|
||||
version: 2c9cfd17756470a0b7c3e4b7954bae7d11035504
|
||||
version: eb790af526c30f23a7c8b00a48e342f9d0bd6386
|
||||
- name: github.com/tyler-smith/go-bip39
|
||||
version: 8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc
|
||||
|
@ -22,14 +22,9 @@ import:
|
||||
- nacl/secretbox
|
||||
- openpgp/armor
|
||||
- ripemd160
|
||||
- package: github.com/bgentry/speakeasy
|
||||
- package: github.com/gorilla/handlers
|
||||
- package: github.com/gorilla/mux
|
||||
- package: github.com/pkg/errors
|
||||
- package: github.com/spf13/cobra
|
||||
- package: github.com/spf13/viper
|
||||
- package: gopkg.in/go-playground/validator.v9
|
||||
- package: github.com/howeyc/crc16
|
||||
- package: github.com/ethanfrey/ledger
|
||||
testImport:
|
||||
- package: github.com/mndrix/btcutil
|
||||
- package: github.com/stretchr/testify
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"log"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -86,8 +85,7 @@ func ComputeTxId(rawTxHex string) string {
|
||||
return HexEncode(ReverseBytes(CalcHash256(HexDecode(rawTxHex))))
|
||||
}
|
||||
|
||||
// Private methods...
|
||||
|
||||
/*
|
||||
func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) {
|
||||
if pubKeyBytes == nil {
|
||||
pubKeyBytes = PubKeyBytesFromPrivKeyBytes(privKeyBytes, true)
|
||||
@ -99,6 +97,7 @@ func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) {
|
||||
addr,
|
||||
HexEncode(chain))
|
||||
}
|
||||
*/
|
||||
|
||||
func DerivePrivateKeyForPath(privKeyBytes []byte, chain []byte, path string) []byte {
|
||||
data := privKeyBytes
|
||||
@ -144,7 +143,7 @@ func DerivePublicKeyForPath(pubKeyBytes []byte, chain []byte, path string) []byt
|
||||
}
|
||||
|
||||
func DerivePrivateKey(privKeyBytes []byte, chain []byte, i uint32, prime bool) ([]byte, []byte) {
|
||||
data := []byte{}
|
||||
var data []byte
|
||||
if prime {
|
||||
i = i | 0x80000000
|
||||
data = append([]byte{byte(0)}, privKeyBytes...)
|
||||
@ -177,11 +176,11 @@ func addPoints(a []byte, b []byte) []byte {
|
||||
panic(err)
|
||||
}
|
||||
sumX, sumY := btcec.S256().Add(ap.X, ap.Y, bp.X, bp.Y)
|
||||
sum := (*btcec.PublicKey)(&btcec.PublicKey{
|
||||
sum := &btcec.PublicKey{
|
||||
Curve: btcec.S256(),
|
||||
X: sumX,
|
||||
Y: sumY,
|
||||
})
|
||||
}
|
||||
return sum.SerializeCompressed()
|
||||
}
|
||||
|
||||
@ -248,11 +247,11 @@ func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string {
|
||||
|
||||
func PubKeyBytesFromPrivKeyBytes(privKeyBytes []byte, compress bool) (pubKeyBytes []byte) {
|
||||
x, y := btcec.S256().ScalarBaseMult(privKeyBytes)
|
||||
pub := (*btcec.PublicKey)(&btcec.PublicKey{
|
||||
pub := &btcec.PublicKey{
|
||||
Curve: btcec.S256(),
|
||||
X: x,
|
||||
Y: y,
|
||||
})
|
||||
}
|
||||
|
||||
if compress {
|
||||
return pub.SerializeCompressed()
|
||||
|
@ -2,9 +2,9 @@ package hd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
//"crypto/hmac"
|
||||
//"crypto/sha512"
|
||||
//"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -15,10 +15,10 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tyler-smith/go-bip39"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcutil/hdkeychain"
|
||||
"github.com/mndrix/btcutil"
|
||||
"github.com/tyler-smith/go-bip32"
|
||||
//"github.com/btcsuite/btcd/chaincfg"
|
||||
//"github.com/btcsuite/btcutil/hdkeychain"
|
||||
//"github.com/mndrix/btcutil"
|
||||
//"github.com/tyler-smith/go-bip32"
|
||||
|
||||
"github.com/tendermint/go-crypto"
|
||||
)
|
||||
@ -33,7 +33,7 @@ type addrData struct {
|
||||
}
|
||||
|
||||
// NOTE: atom fundraiser address
|
||||
var hdPath string = "m/44'/118'/0'/0/0"
|
||||
// var hdPath string = "m/44'/118'/0'/0/0"
|
||||
var hdToAddrTable []addrData
|
||||
|
||||
func init() {
|
||||
@ -109,12 +109,14 @@ func TestReverseBytes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func ifExit(err error, n int) {
|
||||
if err != nil {
|
||||
fmt.Println(n, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func gocrypto(seed []byte) ([]byte, []byte, []byte) {
|
||||
|
||||
@ -131,6 +133,7 @@ func gocrypto(seed []byte) ([]byte, []byte, []byte) {
|
||||
return HexDecode(priv), privBytes, pubBytes
|
||||
}
|
||||
|
||||
/*
|
||||
func btcsuite(seed []byte) ([]byte, []byte, []byte) {
|
||||
fmt.Println("HD")
|
||||
masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
|
||||
@ -207,9 +210,9 @@ func tylerSmith(seed []byte) ([]byte, []byte, []byte) {
|
||||
pub := k.PublicKey().Key
|
||||
return masterKey.Key, priv, pub
|
||||
}
|
||||
*/
|
||||
|
||||
// Benchmarks
|
||||
|
||||
var revBytesCases = [][]byte{
|
||||
nil,
|
||||
[]byte(""),
|
||||
@ -238,5 +241,6 @@ func BenchmarkReverseBytes(b *testing.B) {
|
||||
// sink is necessary to ensure if the compiler tries
|
||||
// to smart, that it won't optimize away the benchmarks.
|
||||
if sink != nil {
|
||||
_ = sink
|
||||
}
|
||||
}
|
||||
|
@ -12,21 +12,21 @@ type encryptedStorage struct {
|
||||
}
|
||||
|
||||
func (es encryptedStorage) Put(name, pass string, key crypto.PrivKey) error {
|
||||
secret, err := es.coder.Encrypt(key, pass)
|
||||
saltBytes, encBytes, err := es.coder.Encrypt(key, pass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ki := info(name, key)
|
||||
return es.store.Put(name, secret, ki)
|
||||
return es.store.Put(name, saltBytes, encBytes, ki)
|
||||
}
|
||||
|
||||
func (es encryptedStorage) Get(name, pass string) (crypto.PrivKey, keys.Info, error) {
|
||||
secret, info, err := es.store.Get(name)
|
||||
saltBytes, encBytes, info, err := es.store.Get(name)
|
||||
if err != nil {
|
||||
return crypto.PrivKey{}, info, err
|
||||
}
|
||||
key, err := es.coder.Decrypt(secret, pass)
|
||||
key, err := es.coder.Decrypt(saltBytes, encBytes, pass)
|
||||
return key, info, err
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,23 @@ package cryptostore
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
// BcryptCost is as parameter to increase the resistance of the
|
||||
// encoded keys to brute force password guessing
|
||||
//
|
||||
// Jae: 14 is good today (2016)
|
||||
//
|
||||
// Ethan: loading the key (at each signing) takes a second on my desktop,
|
||||
// this is hard for laptops and deadly for mobile. You can raise it again,
|
||||
// but for now, I will make this usable
|
||||
//
|
||||
// TODO: review value
|
||||
BCryptCost = 12
|
||||
)
|
||||
|
||||
var (
|
||||
@ -16,45 +32,55 @@ var (
|
||||
//
|
||||
// This should use a well-designed symetric encryption algorithm
|
||||
type Encoder interface {
|
||||
Encrypt(key crypto.PrivKey, pass string) ([]byte, error)
|
||||
Decrypt(data []byte, pass string) (crypto.PrivKey, error)
|
||||
}
|
||||
|
||||
func secret(passphrase string) []byte {
|
||||
// TODO: Sha256(Bcrypt(passphrase))
|
||||
return crypto.Sha256([]byte(passphrase))
|
||||
Encrypt(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte, err error)
|
||||
Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error)
|
||||
}
|
||||
|
||||
type secretbox struct{}
|
||||
|
||||
func (e secretbox) Encrypt(key crypto.PrivKey, pass string) ([]byte, error) {
|
||||
if pass == "" {
|
||||
return key.Bytes(), nil
|
||||
func (e secretbox) Encrypt(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte, err error) {
|
||||
if passphrase == "" {
|
||||
return nil, privKey.Bytes(), nil
|
||||
}
|
||||
s := secret(pass)
|
||||
cipher := crypto.EncryptSymmetric(key.Bytes(), s)
|
||||
return cipher, nil
|
||||
|
||||
saltBytes = crypto.CRandBytes(16)
|
||||
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BCryptCost)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Couldn't generate bcrypt key from passphrase.")
|
||||
}
|
||||
key = crypto.Sha256(key) // Get 32 bytes
|
||||
privKeyBytes := privKey.Bytes()
|
||||
return saltBytes, crypto.EncryptSymmetric(privKeyBytes, key), nil
|
||||
}
|
||||
|
||||
func (e secretbox) Decrypt(data []byte, pass string) (key crypto.PrivKey, err error) {
|
||||
private := data
|
||||
if pass != "" {
|
||||
s := secret(pass)
|
||||
private, err = crypto.DecryptSymmetric(data, s)
|
||||
func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
|
||||
privKeyBytes := encBytes
|
||||
// NOTE: Some keys weren't encrypted with a passphrase and hence we have the conditional
|
||||
if passphrase != "" {
|
||||
var key []byte
|
||||
key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BCryptCost)
|
||||
if err != nil {
|
||||
return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase")
|
||||
}
|
||||
key = crypto.Sha256(key) // Get 32 bytes
|
||||
privKeyBytes, err = crypto.DecryptSymmetric(encBytes, key)
|
||||
if err != nil {
|
||||
return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase")
|
||||
}
|
||||
}
|
||||
key, err = crypto.PrivKeyFromBytes(private)
|
||||
return key, errors.Wrap(err, "Invalid Passphrase")
|
||||
privKey, err = crypto.PrivKeyFromBytes(privKeyBytes)
|
||||
if err != nil {
|
||||
return crypto.PrivKey{}, errors.Wrap(err, "Private Key")
|
||||
}
|
||||
return privKey, nil
|
||||
}
|
||||
|
||||
type noop struct{}
|
||||
|
||||
func (n noop) Encrypt(key crypto.PrivKey, pass string) ([]byte, error) {
|
||||
return key.Bytes(), nil
|
||||
func (n noop) Encrypt(key crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte, err error) {
|
||||
return []byte{}, key.Bytes(), nil
|
||||
}
|
||||
|
||||
func (n noop) Decrypt(data []byte, pass string) (crypto.PrivKey, error) {
|
||||
return crypto.PrivKeyFromBytes(data)
|
||||
func (n noop) Decrypt(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) {
|
||||
return crypto.PrivKeyFromBytes(encBytes)
|
||||
}
|
||||
|
@ -15,25 +15,27 @@ func TestNoopEncoder(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
noop := cryptostore.Noop
|
||||
|
||||
key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
|
||||
key2 := cryptostore.GenSecp256k1.Generate(cmn.RandBytes(16))
|
||||
key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
|
||||
require.NoError(err)
|
||||
key2, err := cryptostore.GenSecp256k1.Generate(cmn.RandBytes(16))
|
||||
require.NoError(err)
|
||||
|
||||
b, err := noop.Encrypt(key, "encode")
|
||||
_, b, err := noop.Encrypt(key, "encode")
|
||||
require.Nil(err)
|
||||
assert.NotEmpty(b)
|
||||
|
||||
b2, err := noop.Encrypt(key2, "encode")
|
||||
_, b2, err := noop.Encrypt(key2, "encode")
|
||||
require.Nil(err)
|
||||
assert.NotEmpty(b2)
|
||||
assert.NotEqual(b, b2)
|
||||
|
||||
// note the decode with a different password works - not secure!
|
||||
pk, err := noop.Decrypt(b, "decode")
|
||||
pk, err := noop.Decrypt(nil, b, "decode")
|
||||
require.Nil(err)
|
||||
require.NotNil(pk)
|
||||
assert.Equal(key, pk)
|
||||
|
||||
pk2, err := noop.Decrypt(b2, "kggugougp")
|
||||
pk2, err := noop.Decrypt(nil, b2, "kggugougp")
|
||||
require.Nil(err)
|
||||
require.NotNil(pk2)
|
||||
assert.Equal(key2, pk2)
|
||||
@ -43,20 +45,21 @@ func TestSecretBox(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
enc := cryptostore.SecretBox
|
||||
|
||||
key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
|
||||
key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
|
||||
require.NoError(err)
|
||||
pass := "some-special-secret"
|
||||
|
||||
b, err := enc.Encrypt(key, pass)
|
||||
s, b, err := enc.Encrypt(key, pass)
|
||||
require.Nil(err)
|
||||
assert.NotEmpty(b)
|
||||
|
||||
// decoding with a different pass is an error
|
||||
pk, err := enc.Decrypt(b, "decode")
|
||||
pk, err := enc.Decrypt(s, b, "decode")
|
||||
require.NotNil(err)
|
||||
require.True(pk.Empty())
|
||||
|
||||
// but decoding with the same passphrase gets us our key
|
||||
pk, err = enc.Decrypt(b, pass)
|
||||
pk, err = enc.Decrypt(s, b, pass)
|
||||
require.Nil(err)
|
||||
assert.Equal(key, pk)
|
||||
}
|
||||
@ -65,7 +68,8 @@ func TestSecretBoxNoPass(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
enc := cryptostore.SecretBox
|
||||
|
||||
key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
|
||||
key, rerr := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
|
||||
require.NoError(rerr)
|
||||
|
||||
cases := []struct {
|
||||
encode string
|
||||
@ -80,11 +84,11 @@ func TestSecretBoxNoPass(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
b, err := enc.Encrypt(key, tc.encode)
|
||||
s, b, err := enc.Encrypt(key, tc.encode)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
assert.NotEmpty(b, "%d", i)
|
||||
|
||||
pk, err := enc.Decrypt(b, tc.decode)
|
||||
pk, err := enc.Decrypt(s, b, tc.decode)
|
||||
if tc.valid {
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
assert.Equal(key, pk, "%d", i)
|
||||
@ -95,7 +99,7 @@ func TestSecretBoxNoPass(t *testing.T) {
|
||||
|
||||
// now let's make sure raw bytes also work...
|
||||
b := key.Bytes()
|
||||
pk, err := enc.Decrypt(b, "")
|
||||
require.Nil(err, "%+v", err)
|
||||
pk, rerr := enc.Decrypt(nil, b, "")
|
||||
require.NoError(rerr)
|
||||
assert.Equal(key, pk)
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ package cryptostore
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
"github.com/tendermint/go-crypto/nano"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -10,46 +12,78 @@ var (
|
||||
GenEd25519 Generator = GenFunc(genEd25519)
|
||||
// GenSecp256k1 produces Secp256k1 private keys
|
||||
GenSecp256k1 Generator = GenFunc(genSecp256)
|
||||
// GenLedgerEd25519 used Ed25519 keys stored on nano ledger s with cosmos app
|
||||
GenLedgerEd25519 Generator = GenFunc(genLedgerEd25519)
|
||||
)
|
||||
|
||||
// Generator determines the type of private key the keystore creates
|
||||
type Generator interface {
|
||||
Generate(secret []byte) crypto.PrivKey
|
||||
Generate(secret []byte) (crypto.PrivKey, error)
|
||||
}
|
||||
|
||||
// GenFunc is a helper to transform a function into a Generator
|
||||
type GenFunc func(secret []byte) crypto.PrivKey
|
||||
type GenFunc func(secret []byte) (crypto.PrivKey, error)
|
||||
|
||||
func (f GenFunc) Generate(secret []byte) crypto.PrivKey {
|
||||
func (f GenFunc) Generate(secret []byte) (crypto.PrivKey, error) {
|
||||
return f(secret)
|
||||
}
|
||||
|
||||
func genEd25519(secret []byte) crypto.PrivKey {
|
||||
return crypto.GenPrivKeyEd25519FromSecret(secret).Wrap()
|
||||
func genEd25519(secret []byte) (crypto.PrivKey, error) {
|
||||
key := crypto.GenPrivKeyEd25519FromSecret(secret).Wrap()
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func genSecp256(secret []byte) crypto.PrivKey {
|
||||
return crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap()
|
||||
func genSecp256(secret []byte) (crypto.PrivKey, error) {
|
||||
key := crypto.GenPrivKeySecp256k1FromSecret(secret).Wrap()
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func getGenerator(algo string) (Generator, error) {
|
||||
// secret is completely ignored for the ledger...
|
||||
// just for interface compatibility
|
||||
func genLedgerEd25519(secret []byte) (crypto.PrivKey, error) {
|
||||
return nano.NewPrivKeyLedgerEd25519Ed25519()
|
||||
}
|
||||
|
||||
type genInvalidByte struct {
|
||||
typ byte
|
||||
}
|
||||
|
||||
func (g genInvalidByte) Generate(secret []byte) (crypto.PrivKey, error) {
|
||||
err := errors.Errorf("Cannot generate keys for algorithm: %X", g.typ)
|
||||
return crypto.PrivKey{}, err
|
||||
}
|
||||
|
||||
type genInvalidAlgo struct {
|
||||
algo string
|
||||
}
|
||||
|
||||
func (g genInvalidAlgo) Generate(secret []byte) (crypto.PrivKey, error) {
|
||||
err := errors.Errorf("Cannot generate keys for algorithm: %s", g.algo)
|
||||
return crypto.PrivKey{}, err
|
||||
}
|
||||
|
||||
func getGenerator(algo string) Generator {
|
||||
switch algo {
|
||||
case crypto.NameEd25519:
|
||||
return GenEd25519, nil
|
||||
return GenEd25519
|
||||
case crypto.NameSecp256k1:
|
||||
return GenSecp256k1, nil
|
||||
return GenSecp256k1
|
||||
case nano.NameLedgerEd25519:
|
||||
return GenLedgerEd25519
|
||||
default:
|
||||
return nil, errors.Errorf("Cannot generate keys for algorithm: %s", algo)
|
||||
return genInvalidAlgo{algo}
|
||||
}
|
||||
}
|
||||
|
||||
func getGeneratorByType(typ byte) (Generator, error) {
|
||||
func getGeneratorByType(typ byte) Generator {
|
||||
switch typ {
|
||||
case crypto.TypeEd25519:
|
||||
return GenEd25519, nil
|
||||
return GenEd25519
|
||||
case crypto.TypeSecp256k1:
|
||||
return GenSecp256k1, nil
|
||||
return GenSecp256k1
|
||||
case nano.TypeLedgerEd25519:
|
||||
return GenLedgerEd25519
|
||||
default:
|
||||
return nil, errors.Errorf("Cannot generate keys for algorithm: %X", typ)
|
||||
return genInvalidByte{typ}
|
||||
}
|
||||
}
|
||||
|
@ -24,29 +24,24 @@ func New(coder Encoder, store keys.Storage, codec keys.Codec) Manager {
|
||||
}
|
||||
}
|
||||
|
||||
// exists just to make sure we fulfill the Signer interface
|
||||
func (s Manager) assertSigner() keys.Signer {
|
||||
return s
|
||||
}
|
||||
|
||||
// exists just to make sure we fulfill the Manager interface
|
||||
func (s Manager) assertKeyManager() keys.Manager {
|
||||
return s
|
||||
}
|
||||
// assert Manager satisfies keys.Signer and keys.Manager interfaces
|
||||
var _ keys.Signer = Manager{}
|
||||
var _ keys.Manager = Manager{}
|
||||
|
||||
// Create adds a new key to the storage engine, returning error if
|
||||
// another key already stored under this name
|
||||
//
|
||||
// algo must be a supported go-crypto algorithm: ed25519, secp256k1
|
||||
func (s Manager) Create(name, passphrase, algo string) (keys.Info, string, error) {
|
||||
gen, err := getGenerator(algo)
|
||||
// 128-bits are the all the randomness we can make use of
|
||||
secret := crypto.CRandBytes(16)
|
||||
gen := getGenerator(algo)
|
||||
|
||||
key, err := gen.Generate(secret)
|
||||
if err != nil {
|
||||
return keys.Info{}, "", err
|
||||
}
|
||||
|
||||
// 128-bits are the all the randomness we can make use of
|
||||
secret := crypto.CRandBytes(16)
|
||||
key := gen.Generate(secret)
|
||||
err = s.es.Put(name, passphrase, key)
|
||||
if err != nil {
|
||||
return keys.Info{}, "", err
|
||||
@ -80,11 +75,11 @@ func (s Manager) Recover(name, passphrase, seedphrase string) (keys.Info, error)
|
||||
l := len(secret)
|
||||
secret, typ := secret[:l-1], secret[l-1]
|
||||
|
||||
gen, err := getGeneratorByType(typ)
|
||||
gen := getGeneratorByType(typ)
|
||||
key, err := gen.Generate(secret)
|
||||
if err != nil {
|
||||
return keys.Info{}, err
|
||||
}
|
||||
key := gen.Generate(secret)
|
||||
|
||||
// d00d, it worked! create the bugger....
|
||||
err = s.es.Put(name, passphrase, key)
|
||||
@ -100,7 +95,7 @@ func (s Manager) List() (keys.Infos, error) {
|
||||
|
||||
// Get returns the public information about one key
|
||||
func (s Manager) Get(name string) (keys.Info, error) {
|
||||
_, info, err := s.es.store.Get(name)
|
||||
_, _, info, err := s.es.store.Get(name)
|
||||
return info, err
|
||||
}
|
||||
|
||||
@ -124,21 +119,23 @@ func (s Manager) Sign(name, passphrase string, tx keys.Signable) error {
|
||||
//
|
||||
// This is designed to copy from one device to another, or provide backups
|
||||
// during version updates.
|
||||
func (s Manager) Export(name, oldpass, transferpass string) ([]byte, error) {
|
||||
// TODO: How to handle Export with salt?
|
||||
func (s Manager) Export(name, oldpass, transferpass string) (salt, data []byte, err error) {
|
||||
key, _, err := s.es.Get(name, oldpass)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
res, err := s.es.coder.Encrypt(key, transferpass)
|
||||
return res, err
|
||||
salt, data, err = s.es.coder.Encrypt(key, transferpass)
|
||||
return salt, data, err
|
||||
}
|
||||
|
||||
// Import accepts bytes generated by Export along with the same transferpass
|
||||
// If they are valid, it stores the password under the given name with the
|
||||
// If they are valid, it stores the key under the given name with the
|
||||
// new passphrase.
|
||||
func (s Manager) Import(name, newpass, transferpass string, data []byte) error {
|
||||
key, err := s.es.coder.Decrypt(data, transferpass)
|
||||
// TODO: How to handle Import with salt?
|
||||
func (s Manager) Import(name, newpass, transferpass string, salt, data []byte) error {
|
||||
key, err := s.es.coder.Decrypt(salt, data, transferpass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package cryptostore_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -12,6 +15,7 @@ import (
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-crypto/keys/cryptostore"
|
||||
"github.com/tendermint/go-crypto/keys/storage/memstorage"
|
||||
"github.com/tendermint/go-crypto/nano"
|
||||
)
|
||||
|
||||
// TestKeyManagement makes sure we can manipulate these keys well
|
||||
@ -50,22 +54,22 @@ func TestKeyManagement(t *testing.T) {
|
||||
assert.NotNil(err)
|
||||
|
||||
// list shows them in order
|
||||
keys, err := cstore.List()
|
||||
keyS, err := cstore.List()
|
||||
require.Nil(err)
|
||||
require.Equal(2, len(keys))
|
||||
require.Equal(2, len(keyS))
|
||||
// note these are in alphabetical order
|
||||
assert.Equal(n2, keys[0].Name)
|
||||
assert.Equal(n1, keys[1].Name)
|
||||
assert.Equal(i2.PubKey, keys[0].PubKey)
|
||||
assert.Equal(n2, keyS[0].Name)
|
||||
assert.Equal(n1, keyS[1].Name)
|
||||
assert.Equal(i2.PubKey, keyS[0].PubKey)
|
||||
|
||||
// deleting a key removes it
|
||||
err = cstore.Delete("bad name", "foo")
|
||||
require.NotNil(err)
|
||||
err = cstore.Delete(n1, p1)
|
||||
require.Nil(err)
|
||||
keys, err = cstore.List()
|
||||
keyS, err = cstore.List()
|
||||
require.Nil(err)
|
||||
assert.Equal(1, len(keys))
|
||||
assert.Equal(1, len(keyS))
|
||||
_, err = cstore.Get(n1)
|
||||
assert.NotNil(err)
|
||||
|
||||
@ -84,65 +88,123 @@ func TestKeyManagement(t *testing.T) {
|
||||
|
||||
// TestSignVerify does some detailed checks on how we sign and validate
|
||||
// signatures
|
||||
// func TestSignVerify(t *testing.T) {
|
||||
// assert, require := assert.New(t), require.New(t)
|
||||
func TestSignVerify(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
// // make the storage with reasonable defaults
|
||||
// cstore := cryptostore.New(
|
||||
// cryptostore.GenSecp256k1,
|
||||
// cryptostore.SecretBox,
|
||||
// memstorage.New(),
|
||||
// )
|
||||
// make the storage with reasonable defaults
|
||||
cstore := cryptostore.New(
|
||||
cryptostore.SecretBox,
|
||||
memstorage.New(),
|
||||
keys.MustLoadCodec("english"),
|
||||
)
|
||||
algo := crypto.NameSecp256k1
|
||||
|
||||
// n1, n2 := "some dude", "a dudette"
|
||||
// p1, p2 := "1234", "foobar"
|
||||
n1, n2 := "some dude", "a dudette"
|
||||
p1, p2 := "1234", "foobar"
|
||||
|
||||
// // create two users and get their info
|
||||
// err := cstore.Create(n1, p1)
|
||||
// require.Nil(err)
|
||||
// i1, err := cstore.Get(n1)
|
||||
// require.Nil(err)
|
||||
// create two users and get their info
|
||||
i1, _, err := cstore.Create(n1, p1, algo)
|
||||
require.Nil(err)
|
||||
|
||||
// err = cstore.Create(n2, p2)
|
||||
// require.Nil(err)
|
||||
// i2, err := cstore.Get(n2)
|
||||
// require.Nil(err)
|
||||
i2, _, err := cstore.Create(n2, p2, algo)
|
||||
require.Nil(err)
|
||||
|
||||
// // let's try to sign some messages
|
||||
// d1 := []byte("my first message")
|
||||
// d2 := []byte("some other important info!")
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("my first message")
|
||||
d2 := []byte("some other important info!")
|
||||
|
||||
// // try signing both data with both keys...
|
||||
// s11, err := cstore.Signature(n1, p1, d1)
|
||||
// require.Nil(err)
|
||||
// s12, err := cstore.Signature(n1, p1, d2)
|
||||
// require.Nil(err)
|
||||
// s21, err := cstore.Signature(n2, p2, d1)
|
||||
// require.Nil(err)
|
||||
// s22, err := cstore.Signature(n2, p2, d2)
|
||||
// require.Nil(err)
|
||||
// try signing both data with both keys...
|
||||
s11 := keys.NewMockSignable(d1)
|
||||
err = cstore.Sign(n1, p1, s11)
|
||||
require.Nil(err)
|
||||
s12 := keys.NewMockSignable(d2)
|
||||
err = cstore.Sign(n1, p1, s12)
|
||||
require.Nil(err)
|
||||
s21 := keys.NewMockSignable(d1)
|
||||
err = cstore.Sign(n2, p2, s21)
|
||||
require.Nil(err)
|
||||
s22 := keys.NewMockSignable(d2)
|
||||
err = cstore.Sign(n2, p2, s22)
|
||||
require.Nil(err)
|
||||
|
||||
// // let's try to validate and make sure it only works when everything is proper
|
||||
// keys := [][]byte{i1.PubKey, i2.PubKey}
|
||||
// data := [][]byte{d1, d2}
|
||||
// sigs := [][]byte{s11, s12, s21, s22}
|
||||
// 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.PubKey, d1, s11.Signature, true},
|
||||
// change data, pubkey, or signature leads to fail
|
||||
{i1.PubKey, d2, s11.Signature, false},
|
||||
{i2.PubKey, d1, s11.Signature, false},
|
||||
{i1.PubKey, d1, s21.Signature, false},
|
||||
// make sure other successes
|
||||
{i1.PubKey, d2, s12.Signature, true},
|
||||
{i2.PubKey, d1, s21.Signature, true},
|
||||
{i2.PubKey, d2, s22.Signature, true},
|
||||
}
|
||||
|
||||
// // loop over keys and data
|
||||
// for k := 0; k < 2; k++ {
|
||||
// for d := 0; d < 2; d++ {
|
||||
// // make sure only the proper sig works
|
||||
// good := 2*k + d
|
||||
// for s := 0; s < 4; s++ {
|
||||
// err = cstore.Verify(data[d], sigs[s], keys[k])
|
||||
// if s == good {
|
||||
// assert.Nil(err, "%+v", err)
|
||||
// } else {
|
||||
// assert.NotNil(err)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
for i, tc := range cases {
|
||||
valid := tc.key.VerifyBytes(tc.data, tc.sig)
|
||||
assert.Equal(tc.valid, valid, "%d", i)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSignWithLedger makes sure we have ledger compatibility with
|
||||
// the crypto store.
|
||||
//
|
||||
// This test will only succeed with a ledger attached to the computer
|
||||
// and the cosmos app open
|
||||
func TestSignWithLedger(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
if os.Getenv("WITH_LEDGER") == "" {
|
||||
t.Skip("Set WITH_LEDGER to run code on real ledger")
|
||||
}
|
||||
|
||||
// make the storage with reasonable defaults
|
||||
cstore := cryptostore.New(
|
||||
cryptostore.SecretBox,
|
||||
memstorage.New(),
|
||||
keys.MustLoadCodec("english"),
|
||||
)
|
||||
n := "nano-s"
|
||||
p := "hard2hack"
|
||||
|
||||
// create a nano user
|
||||
c, _, err := cstore.Create(n, p, nano.NameLedgerEd25519)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(c.Name, n)
|
||||
_, ok := c.PubKey.Unwrap().(nano.PubKeyLedgerEd25519)
|
||||
require.True(ok)
|
||||
|
||||
// make sure we can get it back
|
||||
info, err := cstore.Get(n)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(info.Name, n)
|
||||
key := info.PubKey
|
||||
require.False(key.Empty())
|
||||
require.True(key.Equals(c.PubKey))
|
||||
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("welcome to cosmos")
|
||||
d2 := []byte("please turn on the app")
|
||||
|
||||
// try signing both data with the ledger...
|
||||
s1 := keys.NewMockSignable(d1)
|
||||
err = cstore.Sign(n, p, s1)
|
||||
require.Nil(err)
|
||||
s2 := keys.NewMockSignable(d2)
|
||||
err = cstore.Sign(n, p, s2)
|
||||
require.Nil(err)
|
||||
|
||||
// now, let's check those signatures work
|
||||
assert.True(key.VerifyBytes(d1, s1.Signature))
|
||||
assert.True(key.VerifyBytes(d2, s2.Signature))
|
||||
// and mismatched signatures don't
|
||||
assert.False(key.VerifyBytes(d1, s2.Signature))
|
||||
}
|
||||
|
||||
func assertPassword(assert *assert.Assertions, cstore cryptostore.Manager, name, pass, badpass string) {
|
||||
err := cstore.Update(name, badpass, pass)
|
||||
@ -162,13 +224,15 @@ func TestImportUnencrypted(t *testing.T) {
|
||||
keys.MustLoadCodec("english"),
|
||||
)
|
||||
|
||||
key := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
|
||||
key, err := cryptostore.GenEd25519.Generate(cmn.RandBytes(16))
|
||||
require.NoError(err)
|
||||
|
||||
addr := key.PubKey().Address()
|
||||
name := "john"
|
||||
pass := "top-secret"
|
||||
|
||||
// import raw bytes
|
||||
err := cstore.Import(name, pass, "", key.Bytes())
|
||||
err = cstore.Import(name, pass, "", nil, key.Bytes())
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// make sure the address matches
|
||||
@ -209,15 +273,15 @@ func TestAdvancedKeyManagement(t *testing.T) {
|
||||
assertPassword(assert, cstore, n1, p2, p1)
|
||||
|
||||
// exporting requires the proper name and passphrase
|
||||
_, err = cstore.Export(n2, p2, pt)
|
||||
_, _, err = cstore.Export(n2, p2, pt)
|
||||
assert.NotNil(err)
|
||||
_, err = cstore.Export(n1, p1, pt)
|
||||
_, _, err = cstore.Export(n1, p1, pt)
|
||||
assert.NotNil(err)
|
||||
exported, err := cstore.Export(n1, p2, pt)
|
||||
salt, exported, err := cstore.Export(n1, p2, pt)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// import fails on bad transfer pass
|
||||
err = cstore.Import(n2, p3, p2, exported)
|
||||
err = cstore.Import(n2, p3, p2, salt, exported)
|
||||
assert.NotNil(err)
|
||||
}
|
||||
|
||||
@ -256,45 +320,59 @@ func TestSeedPhrase(t *testing.T) {
|
||||
assert.Equal(info.PubKey, newInfo.PubKey)
|
||||
}
|
||||
|
||||
// func ExampleStore() {
|
||||
// // Select the encryption and storage for your cryptostore
|
||||
// cstore := cryptostore.New(
|
||||
// cryptostore.GenEd25519,
|
||||
// cryptostore.SecretBox,
|
||||
// // Note: use filestorage.New(dir) for real data
|
||||
// memstorage.New(),
|
||||
// )
|
||||
func ExampleNew() {
|
||||
// Select the encryption and storage for your cryptostore
|
||||
cstore := cryptostore.New(
|
||||
cryptostore.SecretBox,
|
||||
// Note: use filestorage.New(dir) for real data
|
||||
memstorage.New(),
|
||||
keys.MustLoadCodec("english"),
|
||||
)
|
||||
ed := crypto.NameEd25519
|
||||
sec := crypto.NameSecp256k1
|
||||
|
||||
// // Add keys and see they return in alphabetical order
|
||||
// cstore.Create("Bob", "friend")
|
||||
// cstore.Create("Alice", "secret")
|
||||
// cstore.Create("Carl", "mitm")
|
||||
// info, _ := cstore.List()
|
||||
// for _, i := range info {
|
||||
// fmt.Println(i.Name)
|
||||
// }
|
||||
// Add keys and see they return in alphabetical order
|
||||
bob, _, err := cstore.Create("Bob", "friend", ed)
|
||||
if err != nil {
|
||||
// this should never happen
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
// return info here just like in List
|
||||
fmt.Println(bob.Name)
|
||||
}
|
||||
cstore.Create("Alice", "secret", sec)
|
||||
cstore.Create("Carl", "mitm", ed)
|
||||
info, _ := cstore.List()
|
||||
for _, i := range info {
|
||||
fmt.Println(i.Name)
|
||||
}
|
||||
|
||||
// // We need to use passphrase to generate a signature
|
||||
// tx := mock.NewSig([]byte("deadbeef"))
|
||||
// err := cstore.Sign("Bob", "friend", tx)
|
||||
// if err != nil {
|
||||
// fmt.Println("don't accept real passphrase")
|
||||
// }
|
||||
// We need to use passphrase to generate a signature
|
||||
tx := keys.NewMockSignable([]byte("deadbeef"))
|
||||
err = cstore.Sign("Bob", "friend", tx)
|
||||
if err != nil {
|
||||
fmt.Println("don't accept real passphrase")
|
||||
}
|
||||
|
||||
// // and we can validate the signature with publically available info
|
||||
// binfo, _ := cstore.Get("Bob")
|
||||
// sigs, err := tx.Signers()
|
||||
// if err != nil {
|
||||
// fmt.Println("badly signed")
|
||||
// } else if bytes.Equal(sigs[0].Bytes(), binfo.PubKey.Bytes()) {
|
||||
// fmt.Println("signed by Bob")
|
||||
// } else {
|
||||
// fmt.Println("signed by someone else")
|
||||
// }
|
||||
// and we can validate the signature with publically available info
|
||||
binfo, _ := cstore.Get("Bob")
|
||||
if !binfo.PubKey.Equals(bob.PubKey) {
|
||||
fmt.Println("Get and Create return different keys")
|
||||
}
|
||||
|
||||
// // Output:
|
||||
// // Alice
|
||||
// // Bob
|
||||
// // Carl
|
||||
// // signed by Bob
|
||||
// }
|
||||
sigs, err := tx.Signers()
|
||||
if err != nil {
|
||||
fmt.Println("badly signed")
|
||||
} else if bytes.Equal(sigs[0].Bytes(), binfo.PubKey.Bytes()) {
|
||||
fmt.Println("signed by Bob")
|
||||
} else {
|
||||
fmt.Println("signed by someone else")
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Bob
|
||||
// Alice
|
||||
// Bob
|
||||
// Carl
|
||||
// signed by Bob
|
||||
}
|
||||
|
@ -5,16 +5,19 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
)
|
||||
|
||||
func TestSortKeys(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
gen := func() crypto.PrivKey { return GenEd25519.Generate(cmn.RandBytes(16)) }
|
||||
gen := func() crypto.PrivKey {
|
||||
key, _ := GenEd25519.Generate(cmn.RandBytes(16))
|
||||
return key
|
||||
}
|
||||
assert.NotEqual(gen(), gen())
|
||||
|
||||
// alphabetical order is n3, n1, n2
|
||||
|
@ -1,13 +0,0 @@
|
||||
# Proxy Server
|
||||
|
||||
This package provides all the functionality for a local http server, providing access to key management functionality (creating, listing, updating, and deleting keys). This is a nice building block for larger apps, and the HTTP handlers here can be embedded in a larger server that does nice things like signing transactions and posting them to a tendermint chain (which requires domain-knowledge of the transactions types and out of scope of this generic app).
|
||||
|
||||
## Key Management
|
||||
|
||||
We expose a number of methods for safely managing your keychain. If you are embedding this in a larger server, you will typically want to mount all these paths under `/keys`.
|
||||
|
||||
* `POST /` - provide a name and passphrase and create a brand new key
|
||||
* `GET /` - get a list of all available key names, along with their public key and address
|
||||
* `GET /{name}` - get public key and address for this named key
|
||||
* `PUT /{name}` - update the passphrase for the given key. requires you to correctly provide the current passphrase, as well as a new one.
|
||||
* `DELETE /{name}` - permanently delete this private key. requires you to correctly provide the current passphrase
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
package server provides http handlers to construct a server server
|
||||
for key management, transaction signing, and query validation.
|
||||
|
||||
Please read the README and godoc to see how to
|
||||
configure the server for your application.
|
||||
*/
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
data "github.com/tendermint/go-wire/data"
|
||||
"github.com/tendermint/go-crypto/keys/server/types"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func readRequest(r *http.Request, o interface{}) error {
|
||||
defer r.Body.Close()
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Read Request")
|
||||
}
|
||||
err = json.Unmarshal(data, o)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Parse")
|
||||
}
|
||||
return validate(o)
|
||||
}
|
||||
|
||||
// most errors are bad input, so 406... do better....
|
||||
func writeError(w http.ResponseWriter, err error) {
|
||||
// fmt.Printf("Error: %+v\n", err)
|
||||
res := types.ErrorResponse{
|
||||
Code: 406,
|
||||
Error: err.Error(),
|
||||
}
|
||||
writeCode(w, &res, 406)
|
||||
}
|
||||
|
||||
func writeCode(w http.ResponseWriter, o interface{}, code int) {
|
||||
// two space indent to make it easier to read
|
||||
data, err := data.ToJSON(o)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
w.Write(data)
|
||||
}
|
||||
}
|
||||
|
||||
func writeSuccess(w http.ResponseWriter, o interface{}) {
|
||||
writeCode(w, o, 200)
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-crypto/keys/server/types"
|
||||
)
|
||||
|
||||
type Keys struct {
|
||||
manager keys.Manager
|
||||
algo string
|
||||
}
|
||||
|
||||
func New(manager keys.Manager, algo string) Keys {
|
||||
return Keys{
|
||||
manager: manager,
|
||||
algo: algo,
|
||||
}
|
||||
}
|
||||
|
||||
func (k Keys) GenerateKey(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.CreateKeyRequest{
|
||||
Algo: k.algo, // default key type from cli
|
||||
}
|
||||
err := readRequest(r, &req)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, seed, err := k.manager.Create(req.Name, req.Passphrase, req.Algo)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
res := types.CreateKeyResponse{key, seed}
|
||||
writeSuccess(w, &res)
|
||||
}
|
||||
|
||||
func (k Keys) GetKey(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
key, err := k.manager.Get(name)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, &key)
|
||||
}
|
||||
|
||||
func (k Keys) ListKeys(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
keys, err := k.manager.List()
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, keys)
|
||||
}
|
||||
|
||||
func (k Keys) UpdateKey(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.UpdateKeyRequest{}
|
||||
err := readRequest(r, &req)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
if name != req.Name {
|
||||
writeError(w, errors.New("path and json key names don't match"))
|
||||
return
|
||||
}
|
||||
|
||||
err = k.manager.Update(req.Name, req.OldPass, req.NewPass)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := k.manager.Get(req.Name)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
writeSuccess(w, &key)
|
||||
}
|
||||
|
||||
func (k Keys) DeleteKey(w http.ResponseWriter, r *http.Request) {
|
||||
req := types.DeleteKeyRequest{}
|
||||
err := readRequest(r, &req)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
name := vars["name"]
|
||||
if name != req.Name {
|
||||
writeError(w, errors.New("path and json key names don't match"))
|
||||
return
|
||||
}
|
||||
|
||||
err = k.manager.Delete(req.Name, req.Passphrase)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// not really an error, but something generic
|
||||
resp := types.ErrorResponse{
|
||||
Success: true,
|
||||
}
|
||||
writeSuccess(w, &resp)
|
||||
}
|
||||
|
||||
func (k Keys) Register(r *mux.Router) {
|
||||
r.HandleFunc("/", k.GenerateKey).Methods("POST")
|
||||
r.HandleFunc("/", k.ListKeys).Methods("GET")
|
||||
r.HandleFunc("/{name}", k.GetKey).Methods("GET")
|
||||
r.HandleFunc("/{name}", k.UpdateKey).Methods("POST", "PUT")
|
||||
r.HandleFunc("/{name}", k.DeleteKey).Methods("DELETE")
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
package server_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
"github.com/tendermint/go-crypto/keys/cryptostore"
|
||||
"github.com/tendermint/go-crypto/keys/server"
|
||||
"github.com/tendermint/go-crypto/keys/server/types"
|
||||
"github.com/tendermint/go-crypto/keys/storage/memstorage"
|
||||
)
|
||||
|
||||
func TestKeyServer(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
r := setupServer()
|
||||
|
||||
// let's abstract this out a bit....
|
||||
keys, code, err := listKeys(r)
|
||||
require.Nil(err)
|
||||
require.Equal(http.StatusOK, code)
|
||||
assert.Equal(0, len(keys))
|
||||
|
||||
algo := "ed25519"
|
||||
n1, n2 := "personal", "business"
|
||||
p0, p1, p2 := "1234", "over10chars...", "really-secure!@#$"
|
||||
|
||||
// this fails for validation
|
||||
_, code, err = createKey(r, n1, p0, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.NotEqual(http.StatusOK, code)
|
||||
|
||||
// new password better
|
||||
key, code, err := createKey(r, n1, p1, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(http.StatusOK, code)
|
||||
require.Equal(n1, key.Key.Name)
|
||||
require.NotEmpty(n1, key.Seed)
|
||||
|
||||
// the other one works
|
||||
key2, code, err := createKey(r, n2, p2, algo)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(http.StatusOK, code)
|
||||
require.Equal(key2.Key.Name, n2)
|
||||
require.NotEmpty(n2, key.Seed)
|
||||
|
||||
// let's abstract this out a bit....
|
||||
keys, code, err = listKeys(r)
|
||||
require.Nil(err)
|
||||
require.Equal(http.StatusOK, code)
|
||||
if assert.Equal(2, len(keys)) {
|
||||
// in alphabetical order
|
||||
assert.Equal(keys[0].Name, n2)
|
||||
assert.Equal(keys[1].Name, n1)
|
||||
}
|
||||
|
||||
// get works
|
||||
k, code, err := getKey(r, n1)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(http.StatusOK, code)
|
||||
assert.Equal(n1, k.Name)
|
||||
assert.NotNil(k.Address)
|
||||
assert.Equal(key.Key.Address, k.Address)
|
||||
|
||||
// delete with proper key
|
||||
_, code, err = deleteKey(r, n1, p1)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(http.StatusOK, code)
|
||||
|
||||
// after delete, get and list different
|
||||
_, code, err = getKey(r, n1)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.NotEqual(http.StatusOK, code)
|
||||
keys, code, err = listKeys(r)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(http.StatusOK, code)
|
||||
if assert.Equal(1, len(keys)) {
|
||||
assert.Equal(keys[0].Name, n2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func setupServer() http.Handler {
|
||||
// make the storage with reasonable defaults
|
||||
cstore := cryptostore.New(
|
||||
cryptostore.SecretBox,
|
||||
memstorage.New(),
|
||||
keys.MustLoadCodec("english"),
|
||||
)
|
||||
|
||||
// build your http server
|
||||
ks := server.New(cstore, "ed25519")
|
||||
r := mux.NewRouter()
|
||||
sk := r.PathPrefix("/keys").Subrouter()
|
||||
ks.Register(sk)
|
||||
return r
|
||||
}
|
||||
|
||||
// return data, status code, and error
|
||||
func listKeys(h http.Handler) (keys.Infos, int, error) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/keys/", nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
h.ServeHTTP(rr, req)
|
||||
if http.StatusOK != rr.Code {
|
||||
return nil, rr.Code, nil
|
||||
}
|
||||
|
||||
data := keys.Infos{}
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &data)
|
||||
return data, rr.Code, err
|
||||
}
|
||||
|
||||
func getKey(h http.Handler, name string) (*keys.Info, int, error) {
|
||||
rr := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/keys/"+name, nil)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
h.ServeHTTP(rr, req)
|
||||
if http.StatusOK != rr.Code {
|
||||
return nil, rr.Code, nil
|
||||
}
|
||||
|
||||
data := keys.Info{}
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &data)
|
||||
return &data, rr.Code, err
|
||||
}
|
||||
|
||||
func createKey(h http.Handler, name, passphrase, algo string) (*types.CreateKeyResponse, int, error) {
|
||||
rr := httptest.NewRecorder()
|
||||
post := types.CreateKeyRequest{
|
||||
Name: name,
|
||||
Passphrase: passphrase,
|
||||
Algo: algo,
|
||||
}
|
||||
var b bytes.Buffer
|
||||
err := json.NewEncoder(&b).Encode(&post)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "/keys/", &b)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
h.ServeHTTP(rr, req)
|
||||
if http.StatusOK != rr.Code {
|
||||
return nil, rr.Code, nil
|
||||
}
|
||||
|
||||
data := new(types.CreateKeyResponse)
|
||||
err = json.Unmarshal(rr.Body.Bytes(), data)
|
||||
return data, rr.Code, err
|
||||
}
|
||||
|
||||
func deleteKey(h http.Handler, name, passphrase string) (*types.ErrorResponse, int, error) {
|
||||
rr := httptest.NewRecorder()
|
||||
post := types.DeleteKeyRequest{
|
||||
Name: name,
|
||||
Passphrase: passphrase,
|
||||
}
|
||||
var b bytes.Buffer
|
||||
err := json.NewEncoder(&b).Encode(&post)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", "/keys/"+name, &b)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
h.ServeHTTP(rr, req)
|
||||
if http.StatusOK != rr.Code {
|
||||
return nil, rr.Code, nil
|
||||
}
|
||||
|
||||
data := types.ErrorResponse{}
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &data)
|
||||
return &data, rr.Code, err
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package types
|
||||
|
||||
import "github.com/tendermint/go-crypto/keys"
|
||||
|
||||
// CreateKeyRequest is sent to create a new key
|
||||
type CreateKeyRequest struct {
|
||||
Name string `json:"name" validate:"required,min=4,printascii"`
|
||||
Passphrase string `json:"passphrase" validate:"required,min=10"`
|
||||
Algo string `json:"algo"`
|
||||
}
|
||||
|
||||
// DeleteKeyRequest to destroy a key permanently (careful!)
|
||||
type DeleteKeyRequest struct {
|
||||
Name string `json:"name" validate:"required,min=4,printascii"`
|
||||
Passphrase string `json:"passphrase" validate:"required,min=10"`
|
||||
}
|
||||
|
||||
// UpdateKeyRequest is sent to update the passphrase for an existing key
|
||||
type UpdateKeyRequest struct {
|
||||
Name string `json:"name" validate:"required,min=4,printascii"`
|
||||
OldPass string `json:"passphrase" validate:"required,min=10"`
|
||||
NewPass string `json:"new_passphrase" validate:"required,min=10"`
|
||||
}
|
||||
|
||||
// ErrorResponse is returned for 4xx and 5xx errors
|
||||
type ErrorResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error"` // error message if Success is false
|
||||
Code int `json:"code"` // error code if Success is false
|
||||
}
|
||||
|
||||
type CreateKeyResponse struct {
|
||||
Key keys.Info `json:"key"`
|
||||
Seed string `json:"seed_phrase"`
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
)
|
||||
|
||||
var v = validator.New()
|
||||
|
||||
func validate(req interface{}) error {
|
||||
return errors.Wrap(v.Struct(req), "Validate")
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package keys
|
||||
|
||||
// Storage has many implementation, based on security and sharing requirements
|
||||
// like disk-backed, mem-backed, vault, db, etc.
|
||||
type Storage interface {
|
||||
Put(name string, key []byte, info Info) error
|
||||
Get(name string) ([]byte, Info, error)
|
||||
List() (Infos, error)
|
||||
Delete(name string) error
|
||||
}
|
@ -6,6 +6,7 @@ like standard ssh key storage.
|
||||
package filestorage
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -13,19 +14,26 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
)
|
||||
|
||||
const (
|
||||
// BlockType is the type of block.
|
||||
BlockType = "Tendermint Light Client"
|
||||
PrivExt = "tlc"
|
||||
PubExt = "pub"
|
||||
keyPerm = os.FileMode(0600)
|
||||
pubPerm = os.FileMode(0644)
|
||||
dirPerm = os.FileMode(0700)
|
||||
|
||||
// PrivExt is the extension for private keys.
|
||||
PrivExt = "tlc"
|
||||
// PubExt is the extensions for public keys.
|
||||
PubExt = "pub"
|
||||
|
||||
keyPerm = os.FileMode(0600)
|
||||
// pubPerm = os.FileMode(0644)
|
||||
dirPerm = os.FileMode(0700)
|
||||
)
|
||||
|
||||
// FileStore is a file-based key storage with tight permissions.
|
||||
type FileStore struct {
|
||||
keyDir string
|
||||
}
|
||||
@ -36,20 +44,20 @@ type FileStore struct {
|
||||
// be created if it doesn't exist already.
|
||||
func New(dir string) FileStore {
|
||||
err := os.MkdirAll(dir, dirPerm)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return FileStore{dir}
|
||||
}
|
||||
|
||||
// assertStorage just makes sure we implement the proper Storage interface
|
||||
func (s FileStore) assertStorage() keys.Storage {
|
||||
return s
|
||||
}
|
||||
// assert FileStore satisfies keys.Storage
|
||||
var _ keys.Storage = FileStore{}
|
||||
|
||||
// Put creates two files, one with the public info as json, the other
|
||||
// with the (encoded) private key as gpg ascii-armor style
|
||||
func (s FileStore) Put(name string, key []byte, info keys.Info) error {
|
||||
func (s FileStore) Put(name string, salt, key []byte, info keys.Info) error {
|
||||
pub, priv := s.nameToPaths(name)
|
||||
|
||||
// write public info
|
||||
@ -59,22 +67,22 @@ func (s FileStore) Put(name string, key []byte, info keys.Info) error {
|
||||
}
|
||||
|
||||
// write private info
|
||||
return write(priv, name, key)
|
||||
return write(priv, name, salt, key)
|
||||
}
|
||||
|
||||
// Get loads the info and (encoded) private key from the directory
|
||||
// It uses `name` to generate the filename, and returns an error if the
|
||||
// files don't exist or are in the incorrect format
|
||||
func (s FileStore) Get(name string) ([]byte, keys.Info, error) {
|
||||
func (s FileStore) Get(name string) (salt []byte, key []byte, info keys.Info, err error) {
|
||||
pub, priv := s.nameToPaths(name)
|
||||
|
||||
info, err := readInfo(pub)
|
||||
info, err = readInfo(pub)
|
||||
if err != nil {
|
||||
return nil, info, err
|
||||
return nil, nil, info, err
|
||||
}
|
||||
|
||||
key, _, err := read(priv)
|
||||
return key, info.Format(), err
|
||||
salt, key, _, err = read(priv)
|
||||
return salt, key, info.Format(), err
|
||||
}
|
||||
|
||||
// List parses the key directory for public info and returns a list of
|
||||
@ -84,6 +92,8 @@ func (s FileStore) List() (keys.Infos, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "List Keys")
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
names, err := dir.Readdirnames(0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "List Keys")
|
||||
@ -111,61 +121,117 @@ func (s FileStore) List() (keys.Infos, error) {
|
||||
func (s FileStore) Delete(name string) error {
|
||||
pub, priv := s.nameToPaths(name)
|
||||
err := os.Remove(priv)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Deleting Private Key")
|
||||
}
|
||||
|
||||
err = os.Remove(pub)
|
||||
|
||||
return errors.Wrap(err, "Deleting Public Key")
|
||||
}
|
||||
|
||||
func (s FileStore) nameToPaths(name string) (pub, priv string) {
|
||||
privName := fmt.Sprintf("%s.%s", name, PrivExt)
|
||||
pubName := fmt.Sprintf("%s.%s", name, PubExt)
|
||||
|
||||
return path.Join(s.keyDir, pubName), path.Join(s.keyDir, privName)
|
||||
}
|
||||
|
||||
func writeInfo(path string, info keys.Info) error {
|
||||
return write(path, info.Name, info.PubKey.Bytes())
|
||||
}
|
||||
|
||||
func readInfo(path string) (info keys.Info, err error) {
|
||||
var data []byte
|
||||
data, info.Name, err = read(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pk, err := crypto.PubKeyFromBytes(data)
|
||||
info.PubKey = pk
|
||||
return
|
||||
}
|
||||
|
||||
func read(path string) ([]byte, string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "Reading data")
|
||||
return info, errors.Wrap(err, "Reading data")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
d, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "Reading data")
|
||||
return info, errors.Wrap(err, "Reading data")
|
||||
}
|
||||
|
||||
block, headers, key, err := crypto.DecodeArmor(string(d))
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "Invalid Armor")
|
||||
return info, errors.Wrap(err, "Invalid Armor")
|
||||
}
|
||||
|
||||
if block != BlockType {
|
||||
return nil, "", errors.Errorf("Unknown key type: %s", block)
|
||||
return info, errors.Errorf("Unknown key type: %s", block)
|
||||
}
|
||||
return key, headers["name"], nil
|
||||
|
||||
pk, _ := crypto.PubKeyFromBytes(key)
|
||||
info.Name = headers["name"]
|
||||
info.PubKey = pk
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func write(path, name string, key []byte) error {
|
||||
func read(path string) (salt, key []byte, name string, err error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, nil, "", errors.Wrap(err, "Reading data")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
d, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, nil, "", errors.Wrap(err, "Reading data")
|
||||
}
|
||||
|
||||
block, headers, key, err := crypto.DecodeArmor(string(d))
|
||||
if err != nil {
|
||||
return nil, nil, "", errors.Wrap(err, "Invalid Armor")
|
||||
}
|
||||
|
||||
if block != BlockType {
|
||||
return nil, nil, "", errors.Errorf("Unknown key type: %s", block)
|
||||
}
|
||||
|
||||
if headers["kdf"] != "bcrypt" {
|
||||
return nil, nil, "", errors.Errorf("Unrecognized KDF type: %v", headers["kdf"])
|
||||
}
|
||||
|
||||
if headers["salt"] == "" {
|
||||
return nil, nil, "", errors.Errorf("Missing salt bytes")
|
||||
}
|
||||
|
||||
salt, err = hex.DecodeString(headers["salt"])
|
||||
if err != nil {
|
||||
return nil, nil, "", errors.Errorf("Error decoding salt: %v", err.Error())
|
||||
}
|
||||
|
||||
return salt, key, headers["name"], nil
|
||||
}
|
||||
|
||||
func writeInfo(path string, info keys.Info) error {
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Writing data")
|
||||
}
|
||||
defer f.Close()
|
||||
headers := map[string]string{"name": name}
|
||||
text := crypto.EncodeArmor(BlockType, headers, key)
|
||||
|
||||
headers := map[string]string{"name": info.Name}
|
||||
text := crypto.EncodeArmor(BlockType, headers, info.PubKey.Bytes())
|
||||
_, err = f.WriteString(text)
|
||||
|
||||
return errors.Wrap(err, "Writing data")
|
||||
}
|
||||
|
||||
func write(path, name string, salt, key []byte) error {
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, keyPerm)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Writing data")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
headers := map[string]string{
|
||||
"name": name,
|
||||
"kdf": "bcrypt",
|
||||
"salt": fmt.Sprintf("%X", salt),
|
||||
}
|
||||
|
||||
text := crypto.EncodeArmor(BlockType, headers, key)
|
||||
_, err = f.WriteString(text)
|
||||
|
||||
return errors.Wrap(err, "Writing data")
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ func TestBasicCRUD(t *testing.T) {
|
||||
|
||||
name := "bar"
|
||||
key := []byte("secret-key-here")
|
||||
salt := []byte("salt-here")
|
||||
pubkey := crypto.GenPrivKeyEd25519().PubKey()
|
||||
info := keys.Info{
|
||||
Name: name,
|
||||
@ -29,7 +30,7 @@ func TestBasicCRUD(t *testing.T) {
|
||||
}
|
||||
|
||||
// No data: Get and Delete return nothing
|
||||
_, _, err = store.Get(name)
|
||||
_, _, _, err = store.Get(name)
|
||||
assert.NotNil(err)
|
||||
err = store.Delete(name)
|
||||
assert.NotNil(err)
|
||||
@ -39,14 +40,14 @@ func TestBasicCRUD(t *testing.T) {
|
||||
assert.Empty(l)
|
||||
|
||||
// Putting the key in the store must work
|
||||
err = store.Put(name, key, info)
|
||||
err = store.Put(name, salt, key, info)
|
||||
assert.Nil(err)
|
||||
// But a second time is a failure
|
||||
err = store.Put(name, key, info)
|
||||
err = store.Put(name, salt, key, info)
|
||||
assert.NotNil(err)
|
||||
|
||||
// Now, we can get and list properly
|
||||
k, i, err := store.Get(name)
|
||||
_, k, i, err := store.Get(name)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(key, k)
|
||||
assert.Equal(info.Name, i.Name)
|
||||
@ -58,7 +59,7 @@ func TestBasicCRUD(t *testing.T) {
|
||||
assert.Equal(i, l[0])
|
||||
|
||||
// querying a non-existent key fails
|
||||
_, _, err = store.Get("badname")
|
||||
_, _, _, err = store.Get("badname")
|
||||
assert.NotNil(err)
|
||||
|
||||
// We can only delete once
|
||||
@ -68,7 +69,7 @@ func TestBasicCRUD(t *testing.T) {
|
||||
assert.NotNil(err)
|
||||
|
||||
// and then Get and List don't work
|
||||
_, _, err = store.Get(name)
|
||||
_, _, _, err = store.Get(name)
|
||||
assert.NotNil(err)
|
||||
// List returns empty list
|
||||
l, err = store.List()
|
||||
|
@ -7,11 +7,13 @@ package memstorage
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
)
|
||||
|
||||
type data struct {
|
||||
info keys.Info
|
||||
salt []byte
|
||||
key []byte
|
||||
}
|
||||
|
||||
@ -22,29 +24,27 @@ func New() MemStore {
|
||||
return MemStore{}
|
||||
}
|
||||
|
||||
// assertStorage just makes sure we implement the Storage interface
|
||||
func (s MemStore) assertStorage() keys.Storage {
|
||||
return s
|
||||
}
|
||||
// assert MemStore satisfies keys.Storage
|
||||
var _ keys.Storage = MemStore{}
|
||||
|
||||
// Put adds the given key, returns an error if it another key
|
||||
// is already stored under this name
|
||||
func (s MemStore) Put(name string, key []byte, info keys.Info) error {
|
||||
func (s MemStore) Put(name string, salt, key []byte, info keys.Info) error {
|
||||
if _, ok := s[name]; ok {
|
||||
return errors.Errorf("Key named '%s' already exists", name)
|
||||
}
|
||||
s[name] = data{info, key}
|
||||
s[name] = data{info, salt, key}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the key stored under the name, or returns an error if not present
|
||||
func (s MemStore) Get(name string) ([]byte, keys.Info, error) {
|
||||
var err error
|
||||
func (s MemStore) Get(name string) (salt, key []byte, info keys.Info, err error) {
|
||||
d, ok := s[name]
|
||||
if !ok {
|
||||
err = errors.Errorf("Key named '%s' doesn't exist", name)
|
||||
}
|
||||
return d.key, d.info.Format(), err
|
||||
|
||||
return d.salt, d.key, d.info.Format(), err
|
||||
}
|
||||
|
||||
// List returns the public info of all keys in the MemStore in unsorted order
|
||||
|
@ -14,6 +14,7 @@ func TestBasicCRUD(t *testing.T) {
|
||||
|
||||
name := "foo"
|
||||
key := []byte("secret-key-here")
|
||||
salt := []byte("salt-here")
|
||||
pubkey := crypto.GenPrivKeyEd25519().PubKey()
|
||||
info := keys.Info{
|
||||
Name: name,
|
||||
@ -21,7 +22,7 @@ func TestBasicCRUD(t *testing.T) {
|
||||
}
|
||||
|
||||
// No data: Get and Delete return nothing
|
||||
_, _, err := store.Get(name)
|
||||
_, _, _, err := store.Get(name)
|
||||
assert.NotNil(err)
|
||||
err = store.Delete(name)
|
||||
assert.NotNil(err)
|
||||
@ -31,14 +32,14 @@ func TestBasicCRUD(t *testing.T) {
|
||||
assert.Empty(l)
|
||||
|
||||
// Putting the key in the store must work
|
||||
err = store.Put(name, key, info)
|
||||
err = store.Put(name, salt, key, info)
|
||||
assert.Nil(err)
|
||||
// But a second time is a failure
|
||||
err = store.Put(name, key, info)
|
||||
err = store.Put(name, salt, key, info)
|
||||
assert.NotNil(err)
|
||||
|
||||
// Now, we can get and list properly
|
||||
k, i, err := store.Get(name)
|
||||
_, k, i, err := store.Get(name)
|
||||
assert.Nil(err)
|
||||
assert.Equal(key, k)
|
||||
assert.Equal(info.Name, i.Name)
|
||||
@ -50,7 +51,7 @@ func TestBasicCRUD(t *testing.T) {
|
||||
assert.Equal(i, l[0])
|
||||
|
||||
// querying a non-existent key fails
|
||||
_, _, err = store.Get("badname")
|
||||
_, _, _, err = store.Get("badname")
|
||||
assert.NotNil(err)
|
||||
|
||||
// We can only delete once
|
||||
@ -60,7 +61,7 @@ func TestBasicCRUD(t *testing.T) {
|
||||
assert.NotNil(err)
|
||||
|
||||
// and then Get and List don't work
|
||||
_, _, err = store.Get(name)
|
||||
_, _, _, err = store.Get(name)
|
||||
assert.NotNil(err)
|
||||
// List returns empty list
|
||||
l, err = store.List()
|
||||
|
@ -1,12 +1,23 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
data "github.com/tendermint/go-wire/data"
|
||||
)
|
||||
|
||||
// Storage has many implementation, based on security and sharing requirements
|
||||
// like disk-backed, mem-backed, vault, db, etc.
|
||||
type Storage interface {
|
||||
Put(name string, salt []byte, key []byte, info Info) error
|
||||
Get(name string) (salt []byte, key []byte, info Info, err error)
|
||||
List() (Infos, error)
|
||||
Delete(name string) error
|
||||
}
|
||||
|
||||
// Info is the public information about a key
|
||||
type Info struct {
|
||||
Name string `json:"name"`
|
||||
@ -72,3 +83,52 @@ type Manager interface {
|
||||
Update(name, oldpass, newpass string) error
|
||||
Delete(name, passphrase string) error
|
||||
}
|
||||
|
||||
/**** MockSignable allows us to view data ***/
|
||||
|
||||
// MockSignable lets us wrap arbitrary data with a go-crypto signature
|
||||
type MockSignable struct {
|
||||
Data []byte
|
||||
PubKey crypto.PubKey
|
||||
Signature crypto.Signature
|
||||
}
|
||||
|
||||
var _ Signable = &MockSignable{}
|
||||
|
||||
// NewMockSignable sets the data to sign
|
||||
func NewMockSignable(data []byte) *MockSignable {
|
||||
return &MockSignable{Data: data}
|
||||
}
|
||||
|
||||
// TxBytes returns the full data with signatures
|
||||
func (s *MockSignable) TxBytes() ([]byte, error) {
|
||||
return wire.BinaryBytes(s), nil
|
||||
}
|
||||
|
||||
// SignBytes returns the original data passed into `NewSig`
|
||||
func (s *MockSignable) SignBytes() []byte {
|
||||
return s.Data
|
||||
}
|
||||
|
||||
// Sign will add a signature and pubkey.
|
||||
//
|
||||
// Depending on the Signable, one may be able to call this multiple times for multisig
|
||||
// Returns error if called with invalid data or too many times
|
||||
func (s *MockSignable) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
|
||||
s.PubKey = pubkey
|
||||
s.Signature = sig
|
||||
return nil
|
||||
}
|
||||
|
||||
// Signers will return the public key(s) that signed if the signature
|
||||
// is valid, or an error if there is any issue with the signature,
|
||||
// including if there are no signatures
|
||||
func (s *MockSignable) Signers() ([]crypto.PubKey, error) {
|
||||
if s.PubKey.Empty() {
|
||||
return nil, fmt.Errorf("no signers")
|
||||
}
|
||||
if !s.PubKey.VerifyBytes(s.SignBytes(), s.Signature) {
|
||||
return nil, fmt.Errorf("invalid signature")
|
||||
}
|
||||
return []crypto.PubKey{s.PubKey}, nil
|
||||
}
|
@ -119,8 +119,8 @@ func TestCheckInvalidLists(t *testing.T) {
|
||||
w, err := codec.BytesToWords(data)
|
||||
if tc.valid {
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
b, err := codec.WordsToBytes(w)
|
||||
assert.Nil(err, "%d: %+v", i, err)
|
||||
b, err1 := codec.WordsToBytes(w)
|
||||
assert.Nil(err1, "%d: %+v", i, err1)
|
||||
assert.Equal(data, b)
|
||||
} else {
|
||||
assert.NotNil(err, "%d", i)
|
||||
|
@ -204,9 +204,9 @@ func AssetNames() []string {
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"keys/wordlist/chinese_simplified.txt": keysWordlistChinese_simplifiedTxt,
|
||||
"keys/wordlist/english.txt": keysWordlistEnglishTxt,
|
||||
"keys/wordlist/japanese.txt": keysWordlistJapaneseTxt,
|
||||
"keys/wordlist/spanish.txt": keysWordlistSpanishTxt,
|
||||
"keys/wordlist/english.txt": keysWordlistEnglishTxt,
|
||||
"keys/wordlist/japanese.txt": keysWordlistJapaneseTxt,
|
||||
"keys/wordlist/spanish.txt": keysWordlistSpanishTxt,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
@ -248,13 +248,14 @@ type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"keys": &bintree{nil, map[string]*bintree{
|
||||
"wordlist": &bintree{nil, map[string]*bintree{
|
||||
"chinese_simplified.txt": &bintree{keysWordlistChinese_simplifiedTxt, map[string]*bintree{}},
|
||||
"english.txt": &bintree{keysWordlistEnglishTxt, map[string]*bintree{}},
|
||||
"japanese.txt": &bintree{keysWordlistJapaneseTxt, map[string]*bintree{}},
|
||||
"spanish.txt": &bintree{keysWordlistSpanishTxt, map[string]*bintree{}},
|
||||
"english.txt": &bintree{keysWordlistEnglishTxt, map[string]*bintree{}},
|
||||
"japanese.txt": &bintree{keysWordlistJapaneseTxt, map[string]*bintree{}},
|
||||
"spanish.txt": &bintree{keysWordlistSpanishTxt, map[string]*bintree{}},
|
||||
}},
|
||||
}},
|
||||
}}
|
||||
@ -305,4 +306,3 @@ func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
|
294
nano/keys.go
Normal file
294
nano/keys.go
Normal file
@ -0,0 +1,294 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
ledger "github.com/ethanfrey/ledger"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
wire "github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
//nolint
|
||||
const (
|
||||
NameLedgerEd25519 = "ledger-ed25519"
|
||||
TypeLedgerEd25519 = 0x10
|
||||
|
||||
// Timeout is the number of seconds to wait for a response from the ledger
|
||||
// if eg. waiting for user confirmation on button push
|
||||
Timeout = 20
|
||||
)
|
||||
|
||||
var device *ledger.Ledger
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func signLedger(device *ledger.Ledger, msg []byte) (pk crypto.PubKey, sig crypto.Signature, err error) {
|
||||
var resp []byte
|
||||
|
||||
packets := generateSignRequests(msg)
|
||||
for _, pack := range packets {
|
||||
resp, err = device.Exchange(pack, Timeout)
|
||||
if err != nil {
|
||||
return pk, sig, err
|
||||
}
|
||||
}
|
||||
|
||||
// the last call is the result we want and needs to be parsed
|
||||
key, bsig, err := parseDigest(resp)
|
||||
if err != nil {
|
||||
return pk, sig, err
|
||||
}
|
||||
|
||||
var b [32]byte
|
||||
copy(b[:], key)
|
||||
return PubKeyLedgerEd25519FromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil
|
||||
}
|
||||
|
||||
// PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano
|
||||
// we cache the PubKey from the first call to use it later
|
||||
type PrivKeyLedgerEd25519 struct {
|
||||
// PubKey should be private, but we want to encode it via go-wire
|
||||
// so we can view the address later, even without having the ledger
|
||||
// attached
|
||||
CachedPubKey crypto.PubKey
|
||||
}
|
||||
|
||||
// NewPrivKeyLedgerEd25519Ed25519 will generate a new key and store the
|
||||
// public key for later use.
|
||||
func NewPrivKeyLedgerEd25519Ed25519() (crypto.PrivKey, error) {
|
||||
var pk PrivKeyLedgerEd25519
|
||||
// getPubKey will cache the pubkey for later use,
|
||||
// this allows us to return an error early if the ledger
|
||||
// is not plugged in
|
||||
_, err := pk.getPubKey()
|
||||
return pk.Wrap(), err
|
||||
}
|
||||
|
||||
// ValidateKey allows us to verify the sanity of a key
|
||||
// after loading it from disk
|
||||
func (pk *PrivKeyLedgerEd25519) ValidateKey() error {
|
||||
// getPubKey will return an error if the ledger is not
|
||||
// properly set up...
|
||||
pub, err := pk.forceGetPubKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// verify this matches cached address
|
||||
if !pub.Equals(pk.CachedPubKey) {
|
||||
return errors.New("ledger doesn't match cached key")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertIsPrivKeyInner fulfils PrivKey Interface
|
||||
func (pk *PrivKeyLedgerEd25519) AssertIsPrivKeyInner() {}
|
||||
|
||||
// Bytes fulfils pk Interface - stores the cached pubkey so we can verify
|
||||
// the same key when we reconnect to a ledger
|
||||
func (pk *PrivKeyLedgerEd25519) Bytes() []byte {
|
||||
return wire.BinaryBytes(pk.Wrap())
|
||||
}
|
||||
|
||||
// Sign calls the ledger and stores the pk for future use
|
||||
//
|
||||
// XXX/TODO: panics if there is an error communicating with the ledger.
|
||||
//
|
||||
// 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 *PrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature {
|
||||
// oh, I wish there was better error handling
|
||||
dev, err := getLedger()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pub, sig, err := signLedger(dev, msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// if we have no pubkey yet, store it for future queries
|
||||
if pk.CachedPubKey.Empty() {
|
||||
pk.CachedPubKey = pub
|
||||
} else if !pk.CachedPubKey.Equals(pub) {
|
||||
panic("signed with a different key than stored")
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
// PubKey returns the stored PubKey
|
||||
// TODO: query the ledger if not there, once it is not volatile
|
||||
func (pk *PrivKeyLedgerEd25519) PubKey() crypto.PubKey {
|
||||
key, err := pk.getPubKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// getPubKey reads the pubkey from cache or from 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 *PrivKeyLedgerEd25519) getPubKey() (key crypto.PubKey, err error) {
|
||||
// if we have no pubkey, set it
|
||||
if pk.CachedPubKey.Empty() {
|
||||
pk.CachedPubKey, err = pk.forceGetPubKey()
|
||||
}
|
||||
return pk.CachedPubKey, err
|
||||
}
|
||||
|
||||
// forceGetPubKey is like getPubKey but ignores any cached key
|
||||
// and ensures we get it from the ledger itself.
|
||||
func (pk *PrivKeyLedgerEd25519) forceGetPubKey() (key crypto.PubKey, err error) {
|
||||
dev, err := getLedger()
|
||||
if err != nil {
|
||||
return key, errors.New("Can't connect to ledger device")
|
||||
}
|
||||
key, _, err = signLedger(dev, []byte{0})
|
||||
if err != nil {
|
||||
return key, errors.New("Please open cosmos app on the ledger")
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
|
||||
// Equals fulfils PrivKey Interface - makes sure both keys refer to the
|
||||
// same
|
||||
func (pk *PrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool {
|
||||
if ledger, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok {
|
||||
return pk.CachedPubKey.Equals(ledger.CachedPubKey)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MockPrivKeyLedgerEd25519 behaves as the ledger, but stores a pre-packaged call-response
|
||||
// for use in test cases
|
||||
type MockPrivKeyLedgerEd25519 struct {
|
||||
Msg []byte
|
||||
Pub [KeyLength]byte
|
||||
Sig [SigLength]byte
|
||||
}
|
||||
|
||||
// NewMockKey returns
|
||||
func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedgerEd25519) {
|
||||
var err error
|
||||
pk.Msg, err = hex.DecodeString(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bpk, err := hex.DecodeString(pubkey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bsig, err := hex.DecodeString(sig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
copy(pk.Pub[:], bpk)
|
||||
copy(pk.Sig[:], bsig)
|
||||
return pk
|
||||
}
|
||||
|
||||
var _ crypto.PrivKeyInner = MockPrivKeyLedgerEd25519{}
|
||||
|
||||
// AssertIsPrivKeyInner fulfils PrivKey Interface
|
||||
func (pk MockPrivKeyLedgerEd25519) AssertIsPrivKeyInner() {}
|
||||
|
||||
// Bytes fulfils PrivKey Interface - not supported
|
||||
func (pk MockPrivKeyLedgerEd25519) Bytes() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign returns a real SignatureLedger, if the msg matches what we expect
|
||||
func (pk MockPrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature {
|
||||
if !bytes.Equal(pk.Msg, msg) {
|
||||
panic("Mock key is for different msg")
|
||||
}
|
||||
return crypto.SignatureEd25519(pk.Sig).Wrap()
|
||||
}
|
||||
|
||||
// PubKey returns a real PubKeyLedgerEd25519, that will verify this signature
|
||||
func (pk MockPrivKeyLedgerEd25519) PubKey() crypto.PubKey {
|
||||
return PubKeyLedgerEd25519FromBytes(pk.Pub)
|
||||
}
|
||||
|
||||
// Equals compares that two Mocks have the same data
|
||||
func (pk MockPrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool {
|
||||
if mock, ok := other.Unwrap().(MockPrivKeyLedgerEd25519); ok {
|
||||
return bytes.Equal(mock.Pub[:], pk.Pub[:]) &&
|
||||
bytes.Equal(mock.Sig[:], pk.Sig[:]) &&
|
||||
bytes.Equal(mock.Msg, pk.Msg)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// pubkey
|
||||
|
||||
// PubKeyLedgerEd25519 works like a normal Ed25519 except a hash before the verify bytes
|
||||
type PubKeyLedgerEd25519 struct {
|
||||
crypto.PubKeyEd25519
|
||||
}
|
||||
|
||||
// PubKeyLedgerEd25519FromBytes creates a PubKey from the raw bytes
|
||||
func PubKeyLedgerEd25519FromBytes(key [32]byte) crypto.PubKey {
|
||||
return PubKeyLedgerEd25519{crypto.PubKeyEd25519(key)}.Wrap()
|
||||
}
|
||||
|
||||
// Bytes fulfils pk Interface - no data, just type info
|
||||
func (pk PubKeyLedgerEd25519) Bytes() []byte {
|
||||
return wire.BinaryBytes(pk.Wrap())
|
||||
}
|
||||
|
||||
// VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand
|
||||
func (pk PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig crypto.Signature) bool {
|
||||
hmsg := hashMsg(msg)
|
||||
return pk.PubKeyEd25519.VerifyBytes(hmsg, sig)
|
||||
}
|
||||
|
||||
// Equals implements PubKey interface
|
||||
func (pk PubKeyLedgerEd25519) Equals(other crypto.PubKey) bool {
|
||||
if ledger, ok := other.Unwrap().(PubKeyLedgerEd25519); ok {
|
||||
return pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519.Wrap())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/*** registration with go-data ***/
|
||||
|
||||
func init() {
|
||||
crypto.PrivKeyMapper.
|
||||
RegisterImplementation(&PrivKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519).
|
||||
RegisterImplementation(MockPrivKeyLedgerEd25519{}, "mock-ledger", 0x11)
|
||||
|
||||
crypto.PubKeyMapper.
|
||||
RegisterImplementation(PubKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519)
|
||||
}
|
||||
|
||||
// Wrap fulfils interface for PrivKey struct
|
||||
func (pk *PrivKeyLedgerEd25519) Wrap() crypto.PrivKey {
|
||||
return crypto.PrivKey{PrivKeyInner: pk}
|
||||
}
|
||||
|
||||
// Wrap fulfils interface for PrivKey struct
|
||||
func (pk MockPrivKeyLedgerEd25519) Wrap() crypto.PrivKey {
|
||||
return crypto.PrivKey{PrivKeyInner: pk}
|
||||
}
|
||||
|
||||
// Wrap fulfils interface for PubKey struct
|
||||
func (pk PubKeyLedgerEd25519) Wrap() crypto.PubKey {
|
||||
return crypto.PubKey{PubKeyInner: pk}
|
||||
}
|
142
nano/keys_test.go
Normal file
142
nano/keys_test.go
Normal file
@ -0,0 +1,142 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func TestLedgerKeys(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
cases := []struct {
|
||||
msg, pubkey, sig string
|
||||
valid bool
|
||||
}{
|
||||
0: {
|
||||
msg: "F00D",
|
||||
pubkey: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93",
|
||||
sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400",
|
||||
valid: true,
|
||||
},
|
||||
1: {
|
||||
msg: "DEADBEEF",
|
||||
pubkey: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C",
|
||||
sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00",
|
||||
valid: true,
|
||||
},
|
||||
2: {
|
||||
msg: "1234567890AA",
|
||||
pubkey: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA",
|
||||
sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B",
|
||||
valid: true,
|
||||
},
|
||||
3: {
|
||||
msg: "1234432112344321",
|
||||
pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: true,
|
||||
},
|
||||
4: {
|
||||
msg: "12344321123443",
|
||||
pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
5: {
|
||||
msg: "1234432112344321",
|
||||
pubkey: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
6: {
|
||||
msg: "1234432112344321",
|
||||
pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
bmsg, err := hex.DecodeString(tc.msg)
|
||||
require.NoError(err, "%d", i)
|
||||
|
||||
priv := NewMockKey(tc.msg, tc.pubkey, tc.sig)
|
||||
pub := priv.PubKey()
|
||||
sig := priv.Sign(bmsg)
|
||||
|
||||
valid := pub.VerifyBytes(bmsg, sig)
|
||||
assert.Equal(tc.valid, valid, "%d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealLedger(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
if os.Getenv("WITH_LEDGER") == "" {
|
||||
t.Skip("Set WITH_LEDGER to run code on real ledger")
|
||||
}
|
||||
msg := []byte("kuhehfeohg")
|
||||
|
||||
priv, err := NewPrivKeyLedgerEd25519Ed25519()
|
||||
require.Nil(err, "%+v", err)
|
||||
pub := priv.PubKey()
|
||||
sig := priv.Sign(msg)
|
||||
|
||||
valid := pub.VerifyBytes(msg, sig)
|
||||
assert.True(valid)
|
||||
|
||||
// now, let's serialize the key and make sure it still works
|
||||
bs := priv.Bytes()
|
||||
priv2, err := crypto.PrivKeyFromBytes(bs)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// make sure we get the same pubkey when we load from disk
|
||||
pub2 := priv2.PubKey()
|
||||
require.Equal(pub, pub2)
|
||||
|
||||
// signing with the loaded key should match the original pubkey
|
||||
sig = priv2.Sign(msg)
|
||||
valid = pub.VerifyBytes(msg, sig)
|
||||
assert.True(valid)
|
||||
|
||||
// make sure pubkeys serialize properly as well
|
||||
bs = pub.Bytes()
|
||||
bpub, err := crypto.PubKeyFromBytes(bs)
|
||||
require.NoError(err)
|
||||
assert.Equal(pub, bpub)
|
||||
}
|
||||
|
||||
// TestRealLedgerErrorHandling calls. These tests assume
|
||||
// the ledger is not plugged in....
|
||||
func TestRealLedgerErrorHandling(t *testing.T) {
|
||||
require := require.New(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)
|
||||
_, err := NewPrivKeyLedgerEd25519Ed25519()
|
||||
require.Error(err)
|
||||
|
||||
led := PrivKeyLedgerEd25519{} // empty
|
||||
// or with some pub key
|
||||
ed := crypto.GenPrivKeyEd25519()
|
||||
led2 := PrivKeyLedgerEd25519{CachedPubKey: ed.PubKey()}
|
||||
|
||||
// loading these should return errors
|
||||
bs := led.Bytes()
|
||||
_, err = crypto.PrivKeyFromBytes(bs)
|
||||
require.Error(err)
|
||||
|
||||
bs = led2.Bytes()
|
||||
_, err = crypto.PrivKeyFromBytes(bs)
|
||||
require.Error(err)
|
||||
}
|
63
nano/sign.go
Normal file
63
nano/sign.go
Normal file
@ -0,0 +1,63 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
App = 0x80
|
||||
Init = 0x00
|
||||
Update = 0x01
|
||||
Digest = 0x02
|
||||
MaxChunk = 253
|
||||
KeyLength = 32
|
||||
SigLength = 64
|
||||
)
|
||||
|
||||
var separator = []byte{0, 0xCA, 0xFE, 0}
|
||||
|
||||
func generateSignRequests(payload []byte) [][]byte {
|
||||
// nice one-shot
|
||||
digest := []byte{App, Digest}
|
||||
if len(payload) < MaxChunk {
|
||||
return [][]byte{append(digest, payload...)}
|
||||
}
|
||||
|
||||
// large payload is multi-chunk
|
||||
result := [][]byte{{App, Init}}
|
||||
update := []byte{App, Update}
|
||||
for len(payload) > MaxChunk {
|
||||
msg := append(update, payload[:MaxChunk]...)
|
||||
payload = payload[MaxChunk:]
|
||||
result = append(result, msg)
|
||||
}
|
||||
result = append(result, append(update, payload...))
|
||||
result = append(result, digest)
|
||||
return result
|
||||
}
|
||||
|
||||
func parseDigest(resp []byte) (key, sig []byte, err error) {
|
||||
if resp[0] != App || resp[1] != Digest {
|
||||
return nil, nil, errors.New("Invalid header")
|
||||
}
|
||||
resp = resp[2:]
|
||||
if len(resp) != KeyLength+SigLength+len(separator) {
|
||||
return nil, nil, errors.Errorf("Incorrect length: %d", len(resp))
|
||||
}
|
||||
|
||||
key, resp = resp[:KeyLength], resp[KeyLength:]
|
||||
if !bytes.Equal(separator, resp[:len(separator)]) {
|
||||
return nil, nil, errors.New("Cannot find 0xCAFE")
|
||||
}
|
||||
|
||||
sig = resp[len(separator):]
|
||||
return key, sig, nil
|
||||
}
|
||||
|
||||
func hashMsg(data []byte) []byte {
|
||||
res := sha512.Sum512(data)
|
||||
return res[:]
|
||||
}
|
159
nano/sign_test.go
Normal file
159
nano/sign_test.go
Normal file
@ -0,0 +1,159 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func parseEdKey(data []byte) (key crypto.PubKey, err error) {
|
||||
ed := crypto.PubKeyEd25519{}
|
||||
if len(data) < len(ed) {
|
||||
return key, errors.Errorf("Key length too short: %d", len(data))
|
||||
}
|
||||
copy(ed[:], data)
|
||||
return ed.Wrap(), nil
|
||||
}
|
||||
|
||||
func parseSig(data []byte) (key crypto.Signature, err error) {
|
||||
ed := crypto.SignatureEd25519{}
|
||||
if len(data) < len(ed) {
|
||||
return key, errors.Errorf("Sig length too short: %d", len(data))
|
||||
}
|
||||
copy(ed[:], data)
|
||||
return ed.Wrap(), nil
|
||||
}
|
||||
|
||||
func TestParseDigest(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
cases := []struct {
|
||||
output string
|
||||
key string
|
||||
sig string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
output: "80028E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C9300CAFE00787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400",
|
||||
key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93",
|
||||
sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
output: "800235467890876543525437890796574535467890",
|
||||
key: "",
|
||||
sig: "",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
msg, err := hex.DecodeString(tc.output)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
|
||||
lKey, lSig, err := parseDigest(msg)
|
||||
if !tc.valid {
|
||||
assert.NotNil(err, "%d", i)
|
||||
} else if assert.Nil(err, "%d: %+v", i, err) {
|
||||
key, err := hex.DecodeString(tc.key)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
sig, err := hex.DecodeString(tc.sig)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
|
||||
assert.Equal(key, lKey, "%d", i)
|
||||
assert.Equal(sig, lSig, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type cryptoCase struct {
|
||||
msg string
|
||||
key string
|
||||
sig string
|
||||
valid bool
|
||||
}
|
||||
|
||||
func toBytes(c cryptoCase) (msg, key, sig []byte, err error) {
|
||||
msg, err = hex.DecodeString(c.msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
key, err = hex.DecodeString(c.key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sig, err = hex.DecodeString(c.sig)
|
||||
return
|
||||
}
|
||||
|
||||
func TestCryptoConvert(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
cases := []cryptoCase{
|
||||
0: {
|
||||
msg: "F00D",
|
||||
key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93",
|
||||
sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400",
|
||||
valid: true,
|
||||
},
|
||||
1: {
|
||||
msg: "DEADBEEF",
|
||||
key: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C",
|
||||
sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00",
|
||||
valid: true,
|
||||
},
|
||||
2: {
|
||||
msg: "1234567890AA",
|
||||
key: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA",
|
||||
sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B",
|
||||
valid: true,
|
||||
},
|
||||
3: {
|
||||
msg: "1234432112344321",
|
||||
key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: true,
|
||||
},
|
||||
4: {
|
||||
msg: "12344321123443",
|
||||
key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
5: {
|
||||
msg: "1234432112344321",
|
||||
key: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
6: {
|
||||
msg: "1234432112344321",
|
||||
key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
msg, key, sig, err := toBytes(tc)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
|
||||
pk, err := parseEdKey(key)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
psig, err := parseSig(sig)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
|
||||
// it is not the signature of the message itself
|
||||
valid := pk.VerifyBytes(msg, psig)
|
||||
assert.False(valid, "%d", i)
|
||||
|
||||
// but rather of the hash of the msg
|
||||
hmsg := hashMsg(msg)
|
||||
valid = pk.VerifyBytes(hmsg, psig)
|
||||
assert.Equal(tc.valid, valid, "%d", i)
|
||||
}
|
||||
}
|
16
priv_key.go
16
priv_key.go
@ -13,13 +13,27 @@ import (
|
||||
|
||||
func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) {
|
||||
err = wire.ReadBinaryBytes(privKeyBytes, &privKey)
|
||||
if err == nil {
|
||||
// add support for a ValidateKey method on PrivKeys
|
||||
// to make sure they load correctly
|
||||
val, ok := privKey.Unwrap().(validatable)
|
||||
if ok {
|
||||
err = val.ValidateKey()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// validatable is an optional interface for keys that want to
|
||||
// check integrity
|
||||
type validatable interface {
|
||||
ValidateKey() error
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
// DO NOT USE THIS INTERFACE.
|
||||
// You probably want to use PubKey
|
||||
// You probably want to use PrivKey
|
||||
// +gen wrapper:"PrivKey,Impl[PrivKeyEd25519,PrivKeySecp256k1],ed25519,secp256k1"
|
||||
type PrivKeyInner interface {
|
||||
AssertIsPrivKeyInner()
|
||||
|
65
priv_key_test.go
Normal file
65
priv_key_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
)
|
||||
|
||||
type BadKey struct {
|
||||
PrivKeyEd25519
|
||||
}
|
||||
|
||||
// Wrap fulfils interface for PrivKey struct
|
||||
func (pk BadKey) Wrap() PrivKey {
|
||||
return PrivKey{pk}
|
||||
}
|
||||
|
||||
func (pk BadKey) Bytes() []byte {
|
||||
return wire.BinaryBytes(pk.Wrap())
|
||||
}
|
||||
|
||||
func (pk BadKey) ValidateKey() error {
|
||||
return fmt.Errorf("fuggly key")
|
||||
}
|
||||
|
||||
func init() {
|
||||
PrivKeyMapper.
|
||||
RegisterImplementation(BadKey{}, "bad", 0x66)
|
||||
}
|
||||
|
||||
func TestReadPrivKey(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
// garbage in, garbage out
|
||||
garbage := []byte("hjgewugfbiewgofwgewr")
|
||||
_, err := PrivKeyFromBytes(garbage)
|
||||
require.Error(err)
|
||||
|
||||
edKey := GenPrivKeyEd25519()
|
||||
badKey := BadKey{edKey}
|
||||
|
||||
cases := []struct {
|
||||
key PrivKey
|
||||
valid bool
|
||||
}{
|
||||
{edKey.Wrap(), true},
|
||||
{badKey.Wrap(), false},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
data := tc.key.Bytes()
|
||||
key, err := PrivKeyFromBytes(data)
|
||||
if tc.valid {
|
||||
assert.NoError(err, "%d", i)
|
||||
assert.Equal(tc.key, key, "%d", i)
|
||||
} else {
|
||||
assert.Error(err, "%d: %#v", i, key)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -63,6 +63,12 @@ func (sig *SignatureEd25519) UnmarshalJSON(enc []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func SignatureEd25519FromBytes(data []byte) Signature {
|
||||
var sig SignatureEd25519
|
||||
copy(sig[:], data)
|
||||
return sig.Wrap()
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
var _ SignatureInner = SignatureSecp256k1{}
|
||||
|
@ -1,3 +1,3 @@
|
||||
package crypto
|
||||
|
||||
const Version = "0.3.0"
|
||||
const Version = "0.4.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user