Browse Source

Merge pull request #37 from tendermint/nano

Nano Support
pull/1782/head
Ethan Frey 7 years ago
committed by GitHub
parent
commit
0a5b1d979a
23 changed files with 1064 additions and 591 deletions
  1. +1
    -1
      Makefile
  2. +14
    -16
      glide.lock
  3. +1
    -0
      glide.yaml
  4. +3
    -2
      keys/cryptostore/encoder.go
  5. +10
    -6
      keys/cryptostore/encoder_test.go
  6. +48
    -15
      keys/cryptostore/generator.go
  7. +7
    -6
      keys/cryptostore/holder.go
  8. +181
    -103
      keys/cryptostore/holder_test.go
  9. +4
    -1
      keys/cryptostore/storage_test.go
  10. +0
    -13
      keys/server/README.md
  11. +0
    -59
      keys/server/helpers.go
  12. +0
    -128
      keys/server/keys.go
  13. +0
    -193
      keys/server/keys_test.go
  14. +0
    -35
      keys/server/types/keys.go
  15. +0
    -12
      keys/server/valid.go
  16. +51
    -0
      keys/transactions.go
  17. +294
    -0
      nano/keys.go
  18. +142
    -0
      nano/keys_test.go
  19. +63
    -0
      nano/sign.go
  20. +159
    -0
      nano/sign_test.go
  21. +15
    -1
      priv_key.go
  22. +65
    -0
      priv_key_test.go
  23. +6
    -0
      signature.go

+ 1
- 1
Makefile View File

@ -10,7 +10,7 @@ REPO:=github.com/tendermint/go-crypto
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/


+ 14
- 16
glide.lock View File

@ -1,5 +1,5 @@
hash: c0a2db1b80c6b1b8aab31c526ce43e22e49b23c893c78b8fdb8546aa2e7b7cc6
updated: 2017-09-22T10:21:34.220901552-04:00
hash: a2243bfd21937edf660778300855e7cb72185164641cb278dbf0c220e8a0f60a
updated: 2017-10-23T17:21:02.40831023+02:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
@ -11,16 +11,16 @@ imports:
- 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/ledger
version: 5e432577be582bd18a3b4a9cd75dae7a317ade36
- name: github.com/flynn/hid
version: ed06a31c6245d4552e8dbba7e32e5b010b875d65
- name: github.com/go-kit/kit
version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8
subpackages:
@ -44,7 +44,7 @@ imports:
- name: github.com/gorilla/mux
version: bcd8bc72b08df0f70df986b97f95590779502d31
- name: github.com/howeyc/crc16
version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f
version: 58da63c846043d0bea709c8d47039df06577d6d9
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/pkg/errors
@ -59,12 +59,12 @@ imports:
- edwards25519
- extra25519
- name: github.com/tendermint/go-wire
version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb
version: 55ae61f1fc83cfaa57ab7d54250d7a1a2be0b83c
subpackages:
- data
- data/base58
- name: github.com/tendermint/tmlibs
version: bffe6744ec277d60f707ab442e25513617842f8e
version: 8e5266a9ef2527e68a1571f932db8228a331b556
subpackages:
- common
- log
@ -81,16 +81,14 @@ imports:
- ripemd160
- salsa20/salsa
- name: gopkg.in/go-playground/validator.v9
version: d529ee1b0f30352444f507cc6cdac96bfd12decc
version: 6d8c18553ea1ac493d049edd6f102f52e618f085
testImports:
- name: github.com/cmars/basen
version: fe3947df716ebfda9847eb1b9a48f9592e06478c
- 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 +101,6 @@ testImports:
- assert
- require
- name: github.com/tyler-smith/go-bip32
version: 2c9cfd17756470a0b7c3e4b7954bae7d11035504
version: eb790af526c30f23a7c8b00a48e342f9d0bd6386
- name: github.com/tyler-smith/go-bip39
version: 8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc

+ 1
- 0
glide.yaml View File

@ -30,6 +30,7 @@ import:
- 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


+ 3
- 2
keys/cryptostore/encoder.go View File

@ -43,7 +43,8 @@ func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string)
privKeyBytes := encBytes
// NOTE: Some keys weren't encrypted with a passphrase and hence we have the conditional
if passphrase != "" {
key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 14) // TODO parameterize. 14 is good today (2016)
var key []byte
key, err = bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), 14) // TODO parameterize. 14 is good today (2016)
if err != nil {
return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase")
}
@ -55,7 +56,7 @@ func (e secretbox) Decrypt(saltBytes []byte, encBytes []byte, passphrase string)
}
privKey, err = crypto.PrivKeyFromBytes(privKeyBytes)
if err != nil {
return crypto.PrivKey{}, errors.Wrap(err, "Couldn't get privKey from bytes")
return crypto.PrivKey{}, errors.Wrap(err, "Private Key")
}
return privKey, nil
}


+ 10
- 6
keys/cryptostore/encoder_test.go View File

@ -15,8 +15,10 @@ 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")
require.Nil(err)
@ -43,7 +45,8 @@ 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"
s, b, err := enc.Encrypt(key, pass)
@ -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
@ -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(nil, b, "")
require.Nil(err, "%+v", err)
pk, rerr := enc.Decrypt(nil, b, "")
require.NoError(rerr)
assert.Equal(key, pk)
}

+ 48
- 15
keys/cryptostore/generator.go View File

@ -4,6 +4,7 @@ import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/nano"
)
var (
@ -11,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}
}
}

+ 7
- 6
keys/cryptostore/holder.go View File

@ -33,14 +33,15 @@ var _ keys.Manager = Manager{}
//
// 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
@ -74,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)


+ 181
- 103
keys/cryptostore/holder_test.go View File

@ -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
@ -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)
// // make the storage with reasonable defaults
// cstore := cryptostore.New(
// cryptostore.GenSecp256k1,
// cryptostore.SecretBox,
// memstorage.New(),
// )
// 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)
// err = cstore.Create(n2, p2)
// require.Nil(err)
// i2, err := cstore.Get(n2)
// require.Nil(err)
// // 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)
// // 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}
// // 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)
// }
// }
// }
// }
// }
func TestSignVerify(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// 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"
// create two users and get their info
i1, _, err := cstore.Create(n1, p1, algo)
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!")
// 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
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},
}
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, "", nil, key.Bytes())
err = cstore.Import(name, pass, "", nil, key.Bytes())
require.Nil(err, "%+v", err)
// make sure the address matches
@ -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(),
// )
// // 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)
// }
// // 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")
// }
// // 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")
// }
// // Output:
// // Alice
// // Bob
// // Carl
// // signed by Bob
// }
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
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 := 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")
if !binfo.PubKey.Equals(bob.PubKey) {
fmt.Println("Get and Create return different keys")
}
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
}

+ 4
- 1
keys/cryptostore/storage_test.go View File

@ -14,7 +14,10 @@ import (
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


+ 0
- 13
keys/server/README.md View File

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

+ 0
- 59
keys/server/helpers.go View File

@ -1,59 +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"
"github.com/tendermint/go-crypto/keys/server/types"
data "github.com/tendermint/go-wire/data"
"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)
}

+ 0
- 128
keys/server/keys.go View File

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

+ 0
- 193
keys/server/keys_test.go View File

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

+ 0
- 35
keys/server/types/keys.go View File

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

+ 0
- 12
keys/server/valid.go View File

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

+ 51
- 0
keys/transactions.go View File

@ -1,9 +1,11 @@
package keys
import (
"fmt"
"sort"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
data "github.com/tendermint/go-wire/data"
)
@ -72,3 +74,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
}

+ 294
- 0
nano/keys.go View 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
- 0
nano/keys_test.go View 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
- 0
nano/sign.go View 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
- 0
nano/sign_test.go View 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)
}
}

+ 15
- 1
priv_key.go View File

@ -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
- 0
priv_key_test.go View 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)
}
}
}

+ 6
- 0
signature.go View File

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


Loading…
Cancel
Save