Browse Source

Merge pull request #15 from tendermint/develop

v0.2.0
pull/1782/head
Ethan Buchman 8 years ago
committed by GitHub
parent
commit
7dff40942a
61 changed files with 3342 additions and 317 deletions
  1. +1
    -0
      .gitignore
  2. +27
    -0
      CHANGELOG.md
  3. +29
    -2
      Makefile
  4. +6
    -0
      _gen.go
  5. +1
    -1
      armor.go
  6. +21
    -0
      circle.yml
  7. +117
    -0
      cmd/README.md
  8. +44
    -0
      cmd/get.go
  9. +27
    -0
      cmd/keys/main.go
  10. +36
    -0
      cmd/list.go
  11. +56
    -0
      cmd/new.go
  12. +58
    -0
      cmd/root.go
  13. +106
    -0
      cmd/serve.go
  14. +58
    -0
      cmd/update.go
  15. +78
    -0
      cmd/utils.go
  16. +9
    -0
      crypto.go
  17. +33
    -68
      embed_test.go
  18. +90
    -19
      encode_test.go
  19. +152
    -0
      glide.lock
  20. +40
    -0
      glide.yaml
  21. +18
    -17
      hd/address.go
  22. +22
    -0
      keys/Makefile
  23. +25
    -0
      keys/cryptostore/docs.go
  24. +49
    -0
      keys/cryptostore/enc_storage.go
  25. +54
    -0
      keys/cryptostore/encoder.go
  26. +59
    -0
      keys/cryptostore/encoder_test.go
  27. +44
    -0
      keys/cryptostore/generator.go
  28. +125
    -0
      keys/cryptostore/holder.go
  29. +243
    -0
      keys/cryptostore/holder_test.go
  30. +41
    -0
      keys/cryptostore/storage_test.go
  31. +2
    -0
      keys/keys.toml
  32. +13
    -0
      keys/server/README.md
  33. +58
    -0
      keys/server/helpers.go
  34. +127
    -0
      keys/server/keys.go
  35. +190
    -0
      keys/server/keys_test.go
  36. +28
    -0
      keys/server/types/keys.go
  37. +12
    -0
      keys/server/valid.go
  38. +10
    -0
      keys/storage.go
  39. +171
    -0
      keys/storage/filestorage/main.go
  40. +106
    -0
      keys/storage/filestorage/main_test.go
  41. +70
    -0
      keys/storage/memstorage/main.go
  42. +69
    -0
      keys/storage/memstorage/main_test.go
  43. +71
    -0
      keys/transactions.go
  44. +10
    -0
      keys/tx/docs.go
  45. +67
    -0
      keys/tx/multi.go
  46. +77
    -0
      keys/tx/multi_test.go
  47. +57
    -0
      keys/tx/one.go
  48. +73
    -0
      keys/tx/one_test.go
  49. +76
    -0
      keys/tx/reader.go
  50. +70
    -0
      keys/tx/reader_test.go
  51. +32
    -62
      priv_key.go
  52. +62
    -0
      privkeyinner_wrapper.go
  53. +30
    -61
      pub_key.go
  54. +1
    -1
      pub_key_test.go
  55. +62
    -0
      pubkeyinner_wrapper.go
  56. +1
    -1
      random.go
  57. +36
    -58
      signature.go
  58. +26
    -26
      signature_test.go
  59. +62
    -0
      signatureinner_wrapper.go
  60. +1
    -1
      symmetric.go
  61. +3
    -0
      version.go

+ 1
- 0
.gitignore View File

@ -1,2 +1,3 @@
*.swp *.swp
*.swo *.swo
vendor

+ 27
- 0
CHANGELOG.md View File

@ -0,0 +1,27 @@
# Changelog
## 0.2.0 (May 18, 2017)
BREAKING CHANGES:
- [hd] The following functions no longer take a `coin string` as argument: `ComputeAddress`, `AddrFromPubKeyBytes`, `ComputeAddressForPrivKey`, `ComputeWIF`, `WIFFromPrivKeyBytes`
- Changes to `PrivKey`, `PubKey`, and `Signature` (denoted `Xxx` below):
- interfaces are renamed `XxxInner`, and are not for use outside the package, though they must be exposed for sake of serialization.
- `Xxx` is now a struct that wraps the corresponding `XxxInner` interface
FEATURES:
- `github.com/tendermint/go-keys -> github.com/tendermint/go-crypto/keys` - command and lib for generating and managing encrypted keys
- [hd] New function `WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string`
- Changes to `PrivKey`, `PubKey`, and `Signature` (denoted `Xxx` below):
- Expose a new method `Unwrap() XxxInner` on the `Xxx` struct which returns the corresponding `XxxInner` interface
- Expose a new method `Wrap() Xxx` on the `XxxInner` interface which returns the corresponding `Xxx` struct
IMPROVEMENTS:
- Update to use new `tmlibs` repository
## 0.1.0 (April 14, 2017)
Initial release

+ 29
- 2
Makefile View File

@ -1,9 +1,36 @@
.PHONY: docs
.PHONEY: all docs test install get_vendor_deps ensure_tools codegen
GOTOOLS = \
github.com/Masterminds/glide
REPO:=github.com/tendermint/go-crypto REPO:=github.com/tendermint/go-crypto
docs: docs:
@go get github.com/davecheney/godoc2md @go get github.com/davecheney/godoc2md
godoc2md $(REPO) > README.md godoc2md $(REPO) > README.md
all: install test
install:
go install ./cmd/keys
test: test:
go test ./...
go test `glide novendor`
get_vendor_deps: ensure_tools
@rm -rf vendor/
@echo "--> Running glide install"
@glide install
ensure_tools:
go get $(GOTOOLS)
prepgen: install
go install ./vendor/github.com/btcsuite/btcutil/base58
go install ./vendor/github.com/stretchr/testify/assert
go install ./vendor/github.com/stretchr/testify/require
go install ./vendor/golang.org/x/crypto/bcrypt
codegen:
@echo "--> regenerating all interface wrappers"
@gen
@echo "Done!"

+ 6
- 0
_gen.go View File

@ -0,0 +1,6 @@
package main
import (
_ "github.com/tendermint/go-wire/gen"
_ "github.com/clipperhouse/stringer"
)

+ 1
- 1
armor.go View File

@ -4,7 +4,7 @@ import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
. "github.com/tendermint/go-common"
. "github.com/tendermint/tmlibs/common"
"golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/armor"
) )


+ 21
- 0
circle.yml View File

@ -0,0 +1,21 @@
machine:
environment:
GOPATH: /home/ubuntu/.go_workspace
PROJECT_PARENT_PATH: "$GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME"
PROJECT_PATH: $GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
GO15VENDOREXPERIMENT: 1
hosts:
circlehost: 127.0.0.1
localhost: 127.0.0.1
dependencies:
override:
- mkdir -p "$PROJECT_PARENT_PATH"
- ln -sf "$HOME/$CIRCLE_PROJECT_REPONAME/" "$PROJECT_PATH"
post:
- go version
test:
override:
- "go version"
- "cd $PROJECT_PATH && make get_vendor_deps && make test"

+ 117
- 0
cmd/README.md View File

@ -0,0 +1,117 @@
# Keys CLI
This is as much an example how to expose cobra/viper, as for a cli itself
(I think this code is overkill for what go-keys needs). But please look at
the commands, and give feedback and changes.
`RootCmd` calls some initialization functions (`cobra.OnInitialize` and `RootCmd.PersistentPreRunE`) which serve to connect environmental variables and cobra flags, as well as load the config file. It also validates the flags registered on root and creates the cryptomanager, which will be used by all subcommands.
## Help info
```
# keys help
Keys allows you to manage your local keystore for tendermint.
These keys may be in any format supported by go-crypto and can be
used by light-clients, full nodes, or any other application that
needs to sign with a private key.
Usage:
keys [command]
Available Commands:
get Get details of one key
list List all keys
new Create a new public/private key pair
serve Run the key manager as an http server
update Change the password for a private key
Flags:
--keydir string Directory to store private keys (subdir of root) (default "keys")
-o, --output string Output format (text|json) (default "text")
-r, --root string root directory for config and data (default "/Users/ethan/.tlc")
Use "keys [command] --help" for more information about a command.
```
## Getting the config file
The first step is to load in root, by checking the following in order:
* -r, --root command line flag
* TM_ROOT environmental variable
* default ($HOME/.tlc evaluated at runtime)
Once the `rootDir` is established, the script looks for a config file named `keys.{json,toml,yaml,hcl}` in that directory and parses it. These values will provide defaults for flags of the same name.
There is an example config file for testing out locally, which writes keys to `./.mykeys`. You can
## Getting/Setting variables
When we want to get the value of a user-defined variable (eg. `output`), we can call `viper.GetString("output")`, which will do the following checks, until it finds a match:
* Is `--output` command line flag present?
* Is `TM_OUTPUT` environmental variable set?
* Was a config file found and does it have an `output` variable?
* Is there a default set on the command line flag?
If no variable is set and there was no default, we get back "".
This setup allows us to have powerful command line flags, but use env variables or config files (local or 12-factor style) to avoid passing these arguments every time.
## Nesting structures
Sometimes we don't just need key-value pairs, but actually a multi-level config file, like
```
[mail]
from = "no-reply@example.com"
server = "mail.example.com"
port = 567
password = "XXXXXX"
```
This CLI is too simple to warant such a structure, but I think eg. tendermint could benefit from such an approach. Here are some pointers:
* [Accessing nested keys from config files](https://github.com/spf13/viper#accessing-nested-keys)
* [Overriding nested values with envvars](https://www.netlify.com/blog/2016/09/06/creating-a-microservice-boilerplate-in-go/#nested-config-values) - the mentioned outstanding PR is already merged into master!
* Overriding nested values with cli flags? (use `--log_config.level=info` ??)
I'd love to see an example of this fully worked out in a more complex CLI.
## Have your cake and eat it too
It's easy to render data different ways. Some better for viewing, some better for importing to other programs. You can just add some global (persistent) flags to control the output formatting, and everyone gets what they want.
```
# keys list -e hex
All keys:
betty d0789984492b1674e276b590d56b7ae077f81adc
john b77f4720b220d1411a649b6c7f1151eb6b1c226a
# keys list -e btc
All keys:
betty 3uTF4r29CbtnzsNHZoPSYsE4BDwH
john 3ZGp2Md35iw4XVtRvZDUaAEkCUZP
# keys list -e b64 -o json
[
{
"name": "betty",
"address": "0HiZhEkrFnTidrWQ1Wt64Hf4Gtw=",
"pubkey": {
"type": "secp256k1",
"data": "F83WvhT0KwttSoqQqd_0_r2ztUUaQix5EXdO8AZyREoV31Og780NW59HsqTAb2O4hZ-w-j0Z-4b2IjfdqqfhVQ=="
}
},
{
"name": "john",
"address": "t39HILIg0UEaZJtsfxFR62scImo=",
"pubkey": {
"type": "ed25519",
"data": "t1LFmbg_8UTwj-n1wkqmnTp6NfaOivokEhlYySlGYCY="
}
}
]
```

+ 44
- 0
cmd/get.go View File

@ -0,0 +1,44 @@
// Copyright © 2017 Ethan Frey
//
// 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 cmd
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// getCmd represents the get command
var getCmd = &cobra.Command{
Use: "get <name>",
Short: "Get details of one key",
Long: `Return public details of one local key.`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
info, err := GetKeyManager().Get(name)
if err == nil {
printInfo(info)
}
return err
},
}
func init() {
RootCmd.AddCommand(getCmd)
}

+ 27
- 0
cmd/keys/main.go View File

@ -0,0 +1,27 @@
// Copyright © 2017 Ethan Frey
//
// 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 main
import (
"os"
"github.com/tendermint/go-crypto/cmd"
"github.com/tendermint/tmlibs/cli"
)
func main() {
root := cli.PrepareMainCmd(cmd.RootCmd, "TM", os.ExpandEnv("$HOME/.tlc"))
root.Execute()
}

+ 36
- 0
cmd/list.go View File

@ -0,0 +1,36 @@
// Copyright © 2017 Ethan Frey
//
// 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 cmd
import "github.com/spf13/cobra"
// listCmd represents the list command
var listCmd = &cobra.Command{
Use: "list",
Short: "List all keys",
Long: `Return a list of all public keys stored by this key manager
along with their associated name and address.`,
RunE: func(cmd *cobra.Command, args []string) error {
infos, err := GetKeyManager().List()
if err == nil {
printInfos(infos)
}
return err
},
}
func init() {
RootCmd.AddCommand(listCmd)
}

+ 56
- 0
cmd/new.go View File

@ -0,0 +1,56 @@
// Copyright © 2017 Ethan Frey
//
// 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 cmd
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// newCmd represents the new command
var newCmd = &cobra.Command{
Use: "new <name>",
Short: "Create a new public/private key pair",
Long: `Add a public/private key pair to the key store.
The password muts be entered in the terminal and not
passed as a command line argument for security.`,
RunE: newPassword,
}
func init() {
RootCmd.AddCommand(newCmd)
newCmd.Flags().StringP("type", "t", "ed25519", "Type of key (ed25519|secp256k1)")
}
func newPassword(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
algo := viper.GetString("type")
pass, err := getCheckPassword("Enter a passphrase:", "Repeat the passphrase:")
if err != nil {
return err
}
info, err := GetKeyManager().Create(name, pass, algo)
if err == nil {
printInfo(info)
}
return err
}

+ 58
- 0
cmd/root.go View File

@ -0,0 +1,58 @@
// Copyright © 2017 Ethan Frey
//
// 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 cmd
import (
"path/filepath"
"github.com/spf13/cobra"
"github.com/spf13/viper"
keys "github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/filestorage"
"github.com/tendermint/tmlibs/cli"
)
const KeySubdir = "keys"
var (
manager keys.Manager
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "keys",
Short: "Key manager for tendermint clients",
Long: `Keys allows you to manage your local keystore for tendermint.
These keys may be in any format supported by go-crypto and can be
used by light-clients, full nodes, or any other application that
needs to sign with a private key.`,
}
// GetKeyManager initializes a key manager based on the configuration
func GetKeyManager() keys.Manager {
if manager == nil {
// store the keys directory
rootDir := viper.GetString(cli.HomeFlag)
keyDir := filepath.Join(rootDir, KeySubdir)
// and construct the key manager
manager = cryptostore.New(
cryptostore.SecretBox,
filestorage.New(keyDir),
)
}
return manager
}

+ 106
- 0
cmd/serve.go View File

@ -0,0 +1,106 @@
// Copyright © 2017 Ethan Frey
//
// 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 cmd
import (
"fmt"
"net"
"net/http"
"os"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto/keys/server"
)
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Run the key manager as an http server",
Long: `Launch an http server with a rest api to manage the
private keys much more in depth than the cli can perform.
In particular, this will allow you to sign transactions with
the private keys in the store.`,
RunE: serveHTTP,
}
func init() {
RootCmd.AddCommand(serveCmd)
serveCmd.Flags().IntP("port", "p", 8118, "TCP Port for listen for http server")
serveCmd.Flags().StringP("socket", "s", "", "UNIX socket for more secure http server")
serveCmd.Flags().StringP("type", "t", "ed25519", "Default key type (ed25519|secp256k1)")
}
func serveHTTP(cmd *cobra.Command, args []string) error {
var l net.Listener
var err error
socket := viper.GetString("socket")
if socket != "" {
l, err = createSocket(socket)
if err != nil {
return errors.Wrap(err, "Cannot create socket")
}
} else {
port := viper.GetInt("port")
l, err = net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return errors.Errorf("Cannot listen on port %d", port)
}
}
router := mux.NewRouter()
ks := server.New(GetKeyManager(), viper.GetString("type"))
ks.Register(router)
// only set cors for tcp listener
var h http.Handler
if socket == "" {
allowedHeaders := handlers.AllowedHeaders([]string{"Content-Type"})
h = handlers.CORS(allowedHeaders)(router)
} else {
h = router
}
err = http.Serve(l, h)
fmt.Printf("Server Killed: %+v\n", err)
return nil
}
// createSocket deletes existing socket if there, creates a new one,
// starts a server on the socket, and sets permissions to 0600
func createSocket(socket string) (net.Listener, error) {
err := os.Remove(socket)
if err != nil && !os.IsNotExist(err) {
// only fail if it does exist and cannot be deleted
return nil, err
}
l, err := net.Listen("unix", socket)
if err != nil {
return nil, err
}
mode := os.FileMode(0700) | os.ModeSocket
err = os.Chmod(socket, mode)
if err != nil {
l.Close()
return nil, err
}
return l, nil
}

+ 58
- 0
cmd/update.go View File

@ -0,0 +1,58 @@
// Copyright © 2017 Ethan Frey
//
// 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 cmd
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// updateCmd represents the update command
var updateCmd = &cobra.Command{
Use: "update <name>",
Short: "Change the password for a private key",
Long: `Change the password for a private key.`,
RunE: updatePassword,
}
func init() {
RootCmd.AddCommand(updateCmd)
}
func updatePassword(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
oldpass, err := getPassword("Enter the current passphrase:")
if err != nil {
return err
}
newpass, err := getCheckPassword("Enter the new passphrase:", "Repeat the new passphrase:")
if err != nil {
return err
}
err = GetKeyManager().Update(name, oldpass, newpass)
if err != nil {
return err
}
fmt.Println("Password successfully updated!")
return nil
}

+ 78
- 0
cmd/utils.go View File

@ -0,0 +1,78 @@
package cmd
import (
"fmt"
"github.com/bgentry/speakeasy"
"github.com/pkg/errors"
"github.com/spf13/viper"
keys "github.com/tendermint/go-crypto/keys"
data "github.com/tendermint/go-wire/data"
"github.com/tendermint/tmlibs/cli"
)
const PassLength = 10
func getPassword(prompt string) (string, error) {
pass, err := speakeasy.Ask(prompt)
if err != nil {
return "", err
}
if len(pass) < PassLength {
return "", errors.Errorf("Password must be at least %d characters", PassLength)
}
return pass, nil
}
func getCheckPassword(prompt, prompt2 string) (string, error) {
// TODO: own function???
pass, err := getPassword(prompt)
if err != nil {
return "", err
}
pass2, err := getPassword(prompt2)
if err != nil {
return "", err
}
if pass != pass2 {
return "", errors.New("Passphrases don't match")
}
return pass, nil
}
func printInfo(info keys.Info) {
switch viper.Get(cli.OutputFlag) {
case "text":
addr, err := data.ToText(info.Address)
if err != nil {
panic(err) // really shouldn't happen...
}
sep := "\t\t"
if len(info.Name) > 7 {
sep = "\t"
}
fmt.Printf("%s%s%s\n", info.Name, sep, addr)
case "json":
json, err := data.ToJSON(info)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}
func printInfos(infos keys.Infos) {
switch viper.Get(cli.OutputFlag) {
case "text":
fmt.Println("All keys:")
for _, i := range infos {
printInfo(i)
}
case "json":
json, err := data.ToJSON(infos)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}

+ 9
- 0
crypto.go View File

@ -0,0 +1,9 @@
package crypto
// Types of implementations
const (
TypeEd25519 = byte(0x01)
TypeSecp256k1 = byte(0x02)
NameEd25519 = "ed25519"
NameSecp256k1 = "secp256k1"
)

+ 33
- 68
embed_test.go View File

@ -6,53 +6,16 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
data "github.com/tendermint/go-data"
data "github.com/tendermint/go-wire/data"
) )
type Foo struct {
Name string
}
func (f Foo) Greet() string {
return "Foo: " + f.Name
}
type Bar struct {
Age int
}
func (b Bar) Greet() string {
return fmt.Sprintf("Bar #%d", b.Age)
type PubName struct {
PubNameInner
} }
type PubNameInner interface { type PubNameInner interface {
Greet() string
}
type privNameInner interface {
Greet() string
}
type Greeter interface {
Greet() string
}
var (
pubNameMapper, privNameMapper data.Mapper
)
// register both public key types with go-data (and thus go-wire)
func init() {
pubNameMapper = data.NewMapper(PubName{}).
RegisterImplementation(Foo{}, "foo", 1).
RegisterImplementation(Bar{}, "bar", 2)
privNameMapper = data.NewMapper(PrivName{}).
RegisterImplementation(Foo{}, "foo", 1).
RegisterImplementation(Bar{}, "bar", 2)
}
type PubName struct {
PubNameInner
AssertIsPubNameInner()
String() string
} }
func (p PubName) MarshalJSON() ([]byte, error) { func (p PubName) MarshalJSON() ([]byte, error) {
@ -67,59 +30,61 @@ func (p *PubName) UnmarshalJSON(data []byte) error {
return err return err
} }
type PrivName struct {
privNameInner
}
var pubNameMapper = data.NewMapper(PubName{}).
RegisterImplementation(PubNameFoo{}, "foo", 1).
RegisterImplementation(PubNameBar{}, "bar", 2)
func (f PubNameFoo) AssertIsPubNameInner() {}
func (f PubNameBar) AssertIsPubNameInner() {}
func (p PrivName) MarshalJSON() ([]byte, error) {
return privNameMapper.ToJSON(p.privNameInner)
//----------------------------------------
type PubNameFoo struct {
Name string
} }
func (p *PrivName) UnmarshalJSON(data []byte) error {
parsed, err := privNameMapper.FromJSON(data)
if err == nil && parsed != nil {
p.privNameInner = parsed.(privNameInner)
}
return err
func (f PubNameFoo) String() string { return "Foo: " + f.Name }
type PubNameBar struct {
Age int
} }
func (b PubNameBar) String() string { return fmt.Sprintf("Bar #%d", b.Age) }
//----------------------------------------
// TestEncodeDemo tries the various strategies to encode the objects // TestEncodeDemo tries the various strategies to encode the objects
func TestEncodeDemo(t *testing.T) { func TestEncodeDemo(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
// assert := assert.New(t)
// require := require.New(t)
cases := []struct { cases := []struct {
in, out Greeter
in, out PubNameInner
expected string expected string
}{ }{
{PubName{Foo{"pub-foo"}}, &PubName{}, "Foo: pub-foo"},
{PubName{Bar{7}}, &PubName{}, "Bar #7"},
// Note these fail - if you can figure a solution here, I'll buy you a beer :)
// {PrivName{Foo{"priv-foo"}}, &PrivName{}, "Foo: priv-foo"},
// {PrivName{Bar{9}}, &PrivName{}, "Bar #9"},
{PubName{PubNameFoo{"pub-foo"}}, &PubName{}, "Foo: pub-foo"},
{PubName{PubNameBar{7}}, &PubName{}, "Bar #7"},
} }
for i, tc := range cases { for i, tc := range cases {
// make sure it is proper to start
require.Equal(tc.expected, tc.in.Greet())
fmt.Println(tc.expected)
// now, try to encode as binary
// Make sure it is proper to start
require.Equal(tc.expected, tc.in.String())
// Try to encode as binary
b, err := data.ToWire(tc.in) b, err := data.ToWire(tc.in)
if assert.Nil(err, "%d: %#v", i, tc.in) { if assert.Nil(err, "%d: %#v", i, tc.in) {
err := data.FromWire(b, tc.out) err := data.FromWire(b, tc.out)
if assert.Nil(err) { if assert.Nil(err) {
assert.Equal(tc.expected, tc.out.Greet())
assert.Equal(tc.expected, tc.out.String())
} }
} }
// try to encode it as json
// Try to encode it as json
j, err := data.ToJSON(tc.in) j, err := data.ToJSON(tc.in)
if assert.Nil(err, "%d: %#v", i, tc.in) { if assert.Nil(err, "%d: %#v", i, tc.in) {
err := data.FromJSON(j, tc.out) err := data.FromJSON(j, tc.out)
if assert.Nil(err) { if assert.Nil(err) {
assert.Equal(tc.expected, tc.out.Greet())
assert.Equal(tc.expected, tc.out.String())
} }
} }
} }


+ 90
- 19
encode_test.go View File

@ -1,12 +1,14 @@
package crypto package crypto
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
data "github.com/tendermint/go-data"
wire "github.com/tendermint/go-wire"
data "github.com/tendermint/go-wire/data"
) )
type byter interface { type byter interface {
@ -14,13 +16,15 @@ type byter interface {
} }
// go to wire encoding and back // go to wire encoding and back
func checkWire(t *testing.T, in byter, reader interface{}, typ byte) {
func checkWire(t *testing.T, in byter, reader interface{}, typ byte, size int) {
// test to and from binary // test to and from binary
bin, err := data.ToWire(in) bin, err := data.ToWire(in)
require.Nil(t, err, "%+v", err) require.Nil(t, err, "%+v", err)
assert.Equal(t, typ, bin[0]) assert.Equal(t, typ, bin[0])
// make sure this is compatible with current (Bytes()) encoding // make sure this is compatible with current (Bytes()) encoding
assert.Equal(t, in.Bytes(), bin) assert.Equal(t, in.Bytes(), bin)
// make sure we have the expected length
assert.Equal(t, size, len(bin))
err = data.FromWire(bin, reader) err = data.FromWire(bin, reader)
require.Nil(t, err, "%+v", err) require.Nil(t, err, "%+v", err)
@ -48,41 +52,61 @@ func checkJSON(t *testing.T, in interface{}, reader interface{}, typ string) {
assert.True(t, strings.Contains(string(js), parts[1])) assert.True(t, strings.Contains(string(js), parts[1]))
} }
// make sure go-wire json can still figure this out...
func checkWireJSON(t *testing.T, in interface{}, reader interface{}, typ byte) {
// test to and from binary
var err error
js := wire.JSONBytes(in)
btyp := fmt.Sprintf("[%d,", typ)
assert.True(t, strings.HasPrefix(string(js), btyp), string(js), btyp)
wire.ReadJSON(reader, js, &err)
require.Nil(t, err, "%+v", err)
}
func TestKeyEncodings(t *testing.T) { func TestKeyEncodings(t *testing.T) {
cases := []struct { cases := []struct {
privKey PrivKeyS
privKey PrivKey
keyType byte keyType byte
keyName string keyName string
// 1 (type byte) + size of byte array
privSize, pubSize int
}{ }{
{ {
privKey: PrivKeyS{GenPrivKeyEd25519()},
keyType: TypeEd25519,
keyName: NameEd25519,
privKey: GenPrivKeyEd25519().Wrap(),
keyType: TypeEd25519,
keyName: NameEd25519,
privSize: 65,
pubSize: 33,
}, },
{ {
privKey: PrivKeyS{GenPrivKeySecp256k1()},
keyType: TypeSecp256k1,
keyName: NameSecp256k1,
privKey: GenPrivKeySecp256k1().Wrap(),
keyType: TypeSecp256k1,
keyName: NameSecp256k1,
privSize: 33,
pubSize: 34,
}, },
} }
for _, tc := range cases { for _, tc := range cases {
// check (de/en)codings of private key // check (de/en)codings of private key
priv2 := PrivKeyS{}
checkWire(t, tc.privKey, &priv2, tc.keyType)
var priv2, priv3, priv4 PrivKey
checkWire(t, tc.privKey, &priv2, tc.keyType, tc.privSize)
assert.EqualValues(t, tc.privKey, priv2) assert.EqualValues(t, tc.privKey, priv2)
priv3 := PrivKeyS{}
checkJSON(t, tc.privKey, &priv3, tc.keyName) checkJSON(t, tc.privKey, &priv3, tc.keyName)
assert.EqualValues(t, tc.privKey, priv3) assert.EqualValues(t, tc.privKey, priv3)
checkWireJSON(t, tc.privKey, &priv4, tc.keyType)
assert.EqualValues(t, tc.privKey, priv4)
// check (de/en)codings of public key // check (de/en)codings of public key
pubKey := PubKeyS{tc.privKey.PubKey()}
pub2 := PubKeyS{}
checkWire(t, pubKey, &pub2, tc.keyType)
pubKey := tc.privKey.PubKey()
var pub2, pub3, pub4 PubKey
checkWire(t, pubKey, &pub2, tc.keyType, tc.pubSize)
assert.EqualValues(t, pubKey, pub2) assert.EqualValues(t, pubKey, pub2)
pub3 := PubKeyS{}
checkJSON(t, pubKey, &pub3, tc.keyName) checkJSON(t, pubKey, &pub3, tc.keyName)
assert.EqualValues(t, pubKey, pub3) assert.EqualValues(t, pubKey, pub3)
checkWireJSON(t, pubKey, &pub4, tc.keyType)
assert.EqualValues(t, pubKey, pub4)
} }
} }
@ -95,18 +119,65 @@ func toFromJSON(t *testing.T, in interface{}, recvr interface{}) {
func TestNilEncodings(t *testing.T) { func TestNilEncodings(t *testing.T) {
// make sure sigs are okay with nil // make sure sigs are okay with nil
a, b := SignatureS{}, SignatureS{}
var a, b Signature
toFromJSON(t, a, &b) toFromJSON(t, a, &b)
assert.EqualValues(t, a, b) assert.EqualValues(t, a, b)
// make sure sigs are okay with nil // make sure sigs are okay with nil
c, d := PubKeyS{}, PubKeyS{}
var c, d PubKey
toFromJSON(t, c, &d) toFromJSON(t, c, &d)
assert.EqualValues(t, c, d) assert.EqualValues(t, c, d)
// make sure sigs are okay with nil // make sure sigs are okay with nil
e, f := PrivKeyS{}, PrivKeyS{}
var e, f PrivKey
toFromJSON(t, e, &f) toFromJSON(t, e, &f)
assert.EqualValues(t, e, f) assert.EqualValues(t, e, f)
} }
type SigMessage struct {
Key PubKey
Sig Signature
}
func (s SigMessage) Bytes() []byte {
return wire.BinaryBytes(s)
}
func TestEmbededWireEncodings(t *testing.T) {
assert := assert.New(t)
cases := []struct {
privKey PrivKey
keyType byte
keyName string
size int // pub + sig size
}{
{
privKey: GenPrivKeyEd25519().Wrap(),
keyType: TypeEd25519,
keyName: NameEd25519,
size: 2 + 32 + 64,
},
// {
// privKey: GenPrivKeySecp256k1().Wrap(),
// keyType: TypeSecp256k1,
// keyName: NameSecp256k1,
// size: 2 + 33 + 72, // ugh, either 72 or 73 depending....
// },
}
payload := randBytes(20)
for i, tc := range cases {
pubKey := tc.privKey.PubKey()
sig := tc.privKey.Sign(payload)
assert.True(pubKey.VerifyBytes(payload, sig), "%d", i)
msg := SigMessage{
Key: pubKey,
Sig: sig,
}
var msg2 SigMessage
checkWire(t, msg, &msg2, tc.keyType, tc.size)
}
}

+ 152
- 0
glide.lock View File

@ -0,0 +1,152 @@
hash: 3bcee9fbccf29d21217b24b6a83ec51e1514f37b2ae5d8718cf6c5df80f4fb2c
updated: 2017-05-15T09:40:53.073691731-04:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
- name: github.com/btcsuite/btcd
version: b8df516b4b267acf2de46be593a9d948d1d2c420
subpackages:
- btcec
- chaincfg
- chaincfg/chainhash
- wire
- name: github.com/btcsuite/btcutil
version: 66871daeb12123ece012a9628d2798d01195c4b3
subpackages:
- base58
- hdkeychain
- name: github.com/btcsuite/fastsha256
version: 637e656429416087660c84436a2a035d69d54e2e
- name: github.com/clipperhouse/typewriter
version: c1a48da378ebb7db1db9f35981b5cc24bf2e5b85
- name: github.com/fsnotify/fsnotify
version: 4da3e2cfbabc9f751898f250b49f2439785783a1
- name: github.com/go-kit/kit
version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8
subpackages:
- log
- log/level
- log/term
- name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-playground/locales
version: 1e5f1161c6416a5ff48840eb8724a394e48cc534
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: 3a5767ca75ece5f7f1440b1d16975247f8d8b221
- name: github.com/gorilla/mux
version: 392c28fe23e1c45ddba891b0320b3b5df220beea
- name: github.com/hashicorp/hcl
version: a4b07c25de5ff55ad3b8936cea69a79a3d95a855
subpackages:
- hcl/ast
- hcl/parser
- hcl/scanner
- hcl/strconv
- hcl/token
- json/parser
- json/scanner
- json/token
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/magiconair/properties
version: 51463bfca2576e06c62a8504b5c0f06d61312647
- name: github.com/mitchellh/mapstructure
version: cc8532a8e9a55ea36402aa21efdf403a60d34096
- name: github.com/pelletier/go-buffruneio
version: c37440a7cf42ac63b919c752ca73a85067e05992
- name: github.com/pelletier/go-toml
version: 13d49d4606eb801b8f01ae542b4afc4c6ee3d84a
- name: github.com/pkg/errors
version: ff09b135c25aae272398c51a07235b90a75aa4f0
- name: github.com/spf13/afero
version: 9be650865eab0c12963d8753212f4f9c66cdcf12
subpackages:
- mem
- name: github.com/spf13/cast
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
- name: github.com/spf13/cobra
version: db6b9a8b3f3f400c8ecb4a4d7d02245b8facad66
- name: github.com/spf13/jwalterweatherman
version: fa7ca7e836cf3a8bb4ebf799f472c12d7e903d66
- name: github.com/spf13/pflag
version: 80fe0fb4eba54167e2ccae1c6c950e72abf61b73
- name: github.com/spf13/viper
version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2
- name: github.com/tendermint/ed25519
version: 1f52c6f8b8a5c7908aff4497c186af344b428925
subpackages:
- edwards25519
- extra25519
- name: github.com/tendermint/go-wire
version: 97beaedf0f4dbc035309157c92be3b30cc6e5d74
subpackages:
- data
- data/base58
- name: github.com/tendermint/tmlibs
version: 8f5a175ff4c869fedde710615a11f5745ff69bf3
subpackages:
- cli
- common
- log
- name: golang.org/x/crypto
version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e
subpackages:
- bcrypt
- blowfish
- nacl/secretbox
- openpgp/armor
- openpgp/errors
- pbkdf2
- poly1305
- ripemd160
- salsa20/salsa
- name: golang.org/x/sys
version: 9ccfe848b9db8435a24c424abbc07a921adf1df5
subpackages:
- unix
- name: golang.org/x/text
version: 470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4
subpackages:
- transform
- unicode/norm
- name: golang.org/x/tools
version: 144c6642b5d832d6c44a53dad6ee61665dd432ce
subpackages:
- go/ast/astutil
- imports
- name: gopkg.in/go-playground/validator.v9
version: 6d8c18553ea1ac493d049edd6f102f52e618f085
- name: gopkg.in/yaml.v2
version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b
testImports:
- name: github.com/cmars/basen
version: fe3947df716ebfda9847eb1b9a48f9592e06478c
- name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
subpackages:
- spew
- name: github.com/mndrix/btcutil
version: d3a63a5752ecf3fbc06bd97365da752111c263df
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert
- require
- name: github.com/tyler-smith/go-bip32
version: eb790af526c30f23a7c8b00a48e342f9d0bd6386
- name: github.com/tyler-smith/go-bip39
version: 8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc

+ 40
- 0
glide.yaml View File

@ -0,0 +1,40 @@
package: github.com/tendermint/go-crypto
import:
- package: github.com/btcsuite/btcd
subpackages:
- btcec
- package: github.com/btcsuite/btcutil
subpackages:
- base58
- package: github.com/tendermint/ed25519
subpackages:
- extra25519
- package: github.com/tendermint/tmlibs
version: develop
- package: github.com/tendermint/go-wire
version: develop
subpackages:
- data
- data/base58
- package: golang.org/x/crypto
subpackages:
- blowfish
- 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
testImport:
- package: github.com/mndrix/btcutil
- package: github.com/stretchr/testify
version: ^1.1.4
subpackages:
- assert
- require
- package: github.com/tyler-smith/go-bip32
- package: github.com/tyler-smith/go-bip39

+ 18
- 17
hd/address.go View File

@ -22,21 +22,13 @@ import (
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
const (
// BIP32 chainpath prefix
CHAINPATH_PREFIX_DEPOSIT = 0
CHAINPATH_PREFIX_CHANGE = 1
CHAINPATH_PREFIX_SWEEP = 2
CHAINPATH_PREFIX_SWEEP_DRY = 102
)
func ComputeAddress(coin string, pubKeyHex string, chainHex string, path string, index int32) string {
func ComputeAddress(pubKeyHex string, chainHex string, path string, index int32) string {
pubKeyBytes := DerivePublicKeyForPath( pubKeyBytes := DerivePublicKeyForPath(
HexDecode(pubKeyHex), HexDecode(pubKeyHex),
HexDecode(chainHex), HexDecode(chainHex),
fmt.Sprintf("%v/%v", path, index), fmt.Sprintf("%v/%v", path, index),
) )
return AddrFromPubKeyBytes(coin, pubKeyBytes)
return AddrFromPubKeyBytes(pubKeyBytes)
} }
func ComputePrivateKey(mprivHex string, chainHex string, path string, index int32) string { func ComputePrivateKey(mprivHex string, chainHex string, path string, index int32) string {
@ -48,9 +40,9 @@ func ComputePrivateKey(mprivHex string, chainHex string, path string, index int3
return HexEncode(privKeyBytes) return HexEncode(privKeyBytes)
} }
func ComputeAddressForPrivKey(coin string, privKey string) string {
func ComputeAddressForPrivKey(privKey string) string {
pubKeyBytes := PubKeyBytesFromPrivKeyBytes(HexDecode(privKey), true) pubKeyBytes := PubKeyBytesFromPrivKeyBytes(HexDecode(privKey), true)
return AddrFromPubKeyBytes(coin, pubKeyBytes)
return AddrFromPubKeyBytes(pubKeyBytes)
} }
func SignMessage(privKey string, message string, compress bool) string { func SignMessage(privKey string, message string, compress bool) string {
@ -86,8 +78,8 @@ func ComputeMastersFromSeed(seed string) (string, string, string, string) {
return HexEncode(pubKeyBytes), HexEncode(secret), HexEncode(chain), HexEncode(secret) return HexEncode(pubKeyBytes), HexEncode(secret), HexEncode(chain), HexEncode(secret)
} }
func ComputeWIF(coin string, privKey string, compress bool) string {
return WIFFromPrivKeyBytes(coin, HexDecode(privKey), compress)
func ComputeWIF(privKey string, compress bool) string {
return WIFFromPrivKeyBytes(HexDecode(privKey), compress)
} }
func ComputeTxId(rawTxHex string) string { func ComputeTxId(rawTxHex string) string {
@ -100,7 +92,7 @@ func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) {
if pubKeyBytes == nil { if pubKeyBytes == nil {
pubKeyBytes = PubKeyBytesFromPrivKeyBytes(privKeyBytes, true) pubKeyBytes = PubKeyBytesFromPrivKeyBytes(privKeyBytes, true)
} }
addr := AddrFromPubKeyBytes("BTC", pubKeyBytes)
addr := AddrFromPubKeyBytes(pubKeyBytes)
log.Println("\nprikey:\t%v\npubKeyBytes:\t%v\naddr:\t%v\nchain:\t%v", log.Println("\nprikey:\t%v\npubKeyBytes:\t%v\naddr:\t%v\nchain:\t%v",
HexEncode(privKeyBytes), HexEncode(privKeyBytes),
HexEncode(pubKeyBytes), HexEncode(pubKeyBytes),
@ -225,7 +217,8 @@ func I64(key []byte, data []byte) ([]byte, []byte) {
return I[:32], I[32:] return I[:32], I[32:]
} }
func AddrFromPubKeyBytes(coin string, pubKeyBytes []byte) string {
// This returns a Bitcoin-like address.
func AddrFromPubKeyBytes(pubKeyBytes []byte) string {
prefix := byte(0x00) // TODO Make const or configurable prefix := byte(0x00) // TODO Make const or configurable
h160 := CalcHash160(pubKeyBytes) h160 := CalcHash160(pubKeyBytes)
h160 = append([]byte{prefix}, h160...) h160 = append([]byte{prefix}, h160...)
@ -234,7 +227,15 @@ func AddrFromPubKeyBytes(coin string, pubKeyBytes []byte) string {
return base58.Encode(b) return base58.Encode(b)
} }
func WIFFromPrivKeyBytes(coin string, privKeyBytes []byte, compress bool) string {
func AddrBytesFromPubKeyBytes(pubKeyBytes []byte) (addrBytes []byte, checksum []byte) {
prefix := byte(0x00) // TODO Make const or configurable
h160 := CalcHash160(pubKeyBytes)
_h160 := append([]byte{prefix}, h160...)
checksum = CalcHash256(_h160)[:4]
return h160, checksum
}
func WIFFromPrivKeyBytes(privKeyBytes []byte, compress bool) string {
prefix := byte(0x80) // TODO Make const or configurable prefix := byte(0x80) // TODO Make const or configurable
bytes := append([]byte{prefix}, privKeyBytes...) bytes := append([]byte{prefix}, privKeyBytes...)
if compress { if compress {


+ 22
- 0
keys/Makefile View File

@ -0,0 +1,22 @@
GOTOOLS = \
github.com/mitchellh/gox \
github.com/Masterminds/glide
.PHONEY: all test install get_vendor_deps ensure_tools
all: install test
test:
go test `glide novendor`
install:
go install ./cmd/keys
get_vendor_deps: ensure_tools
@rm -rf vendor/
@echo "--> Running glide install"
@glide install
ensure_tools:
go get $(GOTOOLS)

+ 25
- 0
keys/cryptostore/docs.go View File

@ -0,0 +1,25 @@
/*
package cryptostore maintains everything needed for doing public-key signing and
key management in software, based on the go-crypto library from tendermint.
It is flexible, and allows the user to provide a key generation algorithm
(currently Ed25519 or Secp256k1), an encoder to passphrase-encrypt our keys
when storing them (currently SecretBox from NaCl), and a method to persist
the keys (currently FileStorage like ssh, or MemStorage for tests).
It should be relatively simple to write your own implementation of these
interfaces to match your specific security requirements.
Note that the private keys are never exposed outside the package, and the
interface of Manager could be implemented by an HSM in the future for
enhanced security. It would require a completely different implementation
however.
This Manager aims to implement Signer and KeyManager interfaces, along
with some extensions to allow importing/exporting keys and updating the
passphrase.
Encoder and Generator implementations are currently in this package,
keys.Storage implementations exist as subpackages of
keys/storage
*/
package cryptostore

+ 49
- 0
keys/cryptostore/enc_storage.go View File

@ -0,0 +1,49 @@
package cryptostore
import (
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
// encryptedStorage needs passphrase to get private keys
type encryptedStorage struct {
coder Encoder
store keys.Storage
}
func (es encryptedStorage) Put(name, pass string, key crypto.PrivKey) error {
secret, err := es.coder.Encrypt(key, pass)
if err != nil {
return err
}
ki := info(name, key)
return es.store.Put(name, secret, ki)
}
func (es encryptedStorage) Get(name, pass string) (crypto.PrivKey, keys.Info, error) {
secret, info, err := es.store.Get(name)
if err != nil {
return crypto.PrivKey{}, info, err
}
key, err := es.coder.Decrypt(secret, pass)
return key, info, err
}
func (es encryptedStorage) List() (keys.Infos, error) {
return es.store.List()
}
func (es encryptedStorage) Delete(name string) error {
return es.store.Delete(name)
}
// info hardcodes the encoding of keys
func info(name string, key crypto.PrivKey) keys.Info {
pub := key.PubKey()
return keys.Info{
Name: name,
Address: pub.Address(),
PubKey: pub,
}
}

+ 54
- 0
keys/cryptostore/encoder.go View File

@ -0,0 +1,54 @@
package cryptostore
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
)
var (
// SecretBox uses the algorithm from NaCL to store secrets securely
SecretBox Encoder = secretbox{}
// Noop doesn't do any encryption, should only be used in test code
Noop Encoder = noop{}
)
// Encoder is used to encrypt any key with a passphrase for storage.
//
// 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))
}
type secretbox struct{}
func (e secretbox) Encrypt(key crypto.PrivKey, pass string) ([]byte, error) {
s := secret(pass)
cipher := crypto.EncryptSymmetric(key.Bytes(), s)
return cipher, nil
}
func (e secretbox) Decrypt(data []byte, pass string) (crypto.PrivKey, error) {
s := secret(pass)
private, err := crypto.DecryptSymmetric(data, s)
if err != nil {
return crypto.PrivKey{}, errors.Wrap(err, "Invalid Passphrase")
}
key, err := crypto.PrivKeyFromBytes(private)
return key, errors.Wrap(err, "Invalid Passphrase")
}
type noop struct{}
func (n noop) Encrypt(key crypto.PrivKey, pass string) ([]byte, error) {
return key.Bytes(), nil
}
func (n noop) Decrypt(data []byte, pass string) (crypto.PrivKey, error) {
return crypto.PrivKeyFromBytes(data)
}

+ 59
- 0
keys/cryptostore/encoder_test.go View File

@ -0,0 +1,59 @@
package cryptostore_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/go-crypto/keys/cryptostore"
)
func TestNoopEncoder(t *testing.T) {
assert, require := assert.New(t), require.New(t)
noop := cryptostore.Noop
key := cryptostore.GenEd25519.Generate()
key2 := cryptostore.GenSecp256k1.Generate()
b, err := noop.Encrypt(key, "encode")
require.Nil(err)
assert.NotEmpty(b)
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")
require.Nil(err)
require.NotNil(pk)
assert.Equal(key, pk)
pk2, err := noop.Decrypt(b2, "kggugougp")
require.Nil(err)
require.NotNil(pk2)
assert.Equal(key2, pk2)
}
func TestSecretBox(t *testing.T) {
assert, require := assert.New(t), require.New(t)
enc := cryptostore.SecretBox
key := cryptostore.GenEd25519.Generate()
pass := "some-special-secret"
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")
require.NotNil(err)
require.True(pk.Empty())
// but decoding with the same passphrase gets us our key
pk, err = enc.Decrypt(b, pass)
require.Nil(err)
assert.Equal(key, pk)
}

+ 44
- 0
keys/cryptostore/generator.go View File

@ -0,0 +1,44 @@
package cryptostore
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
)
var (
// GenEd25519 produces Ed25519 private keys
GenEd25519 Generator = GenFunc(genEd25519)
// GenSecp256k1 produces Secp256k1 private keys
GenSecp256k1 Generator = GenFunc(genSecp256)
)
// Generator determines the type of private key the keystore creates
type Generator interface {
Generate() crypto.PrivKey
}
// GenFunc is a helper to transform a function into a Generator
type GenFunc func() crypto.PrivKey
func (f GenFunc) Generate() crypto.PrivKey {
return f()
}
func genEd25519() crypto.PrivKey {
return crypto.GenPrivKeyEd25519().Wrap()
}
func genSecp256() crypto.PrivKey {
return crypto.GenPrivKeySecp256k1().Wrap()
}
func getGenerator(algo string) (Generator, error) {
switch algo {
case crypto.NameEd25519:
return GenEd25519, nil
case crypto.NameSecp256k1:
return GenSecp256k1, nil
default:
return nil, errors.Errorf("Cannot generate keys for algorithm: %s", algo)
}
}

+ 125
- 0
keys/cryptostore/holder.go View File

@ -0,0 +1,125 @@
package cryptostore
import keys "github.com/tendermint/go-crypto/keys"
// Manager combines encyption and storage implementation to provide
// a full-featured key manager
type Manager struct {
es encryptedStorage
}
func New(coder Encoder, store keys.Storage) Manager {
return Manager{
es: encryptedStorage{
coder: coder,
store: store,
},
}
}
// 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
}
// 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:
//
func (s Manager) Create(name, passphrase, algo string) (keys.Info, error) {
gen, err := getGenerator(algo)
if err != nil {
return keys.Info{}, err
}
key := gen.Generate()
err = s.es.Put(name, passphrase, key)
return info(name, key), err
}
// List loads the keys from the storage and enforces alphabetical order
func (s Manager) List() (keys.Infos, error) {
res, err := s.es.List()
res.Sort()
return res, err
}
// Get returns the public information about one key
func (s Manager) Get(name string) (keys.Info, error) {
_, info, err := s.es.store.Get(name)
return info, err
}
// Sign will modify the Signable in order to attach a valid signature with
// this public key
//
// If no key for this name, or the passphrase doesn't match, returns an error
func (s Manager) Sign(name, passphrase string, tx keys.Signable) error {
key, _, err := s.es.Get(name, passphrase)
if err != nil {
return err
}
sig := key.Sign(tx.SignBytes())
pubkey := key.PubKey()
return tx.Sign(pubkey, sig)
}
// Export decodes the private key with the current password, encodes
// it with a secure one-time password and generates a sequence that can be
// Imported by another Manager
//
// 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) {
key, _, err := s.es.Get(name, oldpass)
if err != nil {
return nil, err
}
res, err := s.es.coder.Encrypt(key, transferpass)
return res, 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
// new passphrase.
func (s Manager) Import(name, newpass, transferpass string, data []byte) error {
key, err := s.es.coder.Decrypt(data, transferpass)
if err != nil {
return err
}
return s.es.Put(name, newpass, key)
}
// Delete removes key forever, but we must present the
// proper passphrase before deleting it (for security)
func (s Manager) Delete(name, passphrase string) error {
// verify we have the proper password before deleting
_, _, err := s.es.Get(name, passphrase)
if err != nil {
return err
}
return s.es.Delete(name)
}
// Update changes the passphrase with which a already stored key is encoded.
//
// oldpass must be the current passphrase used for encoding, newpass will be
// the only valid passphrase from this time forward
func (s Manager) Update(name, oldpass, newpass string) error {
key, _, err := s.es.Get(name, oldpass)
if err != nil {
return err
}
// we must delete first, as Putting over an existing name returns an error
s.Delete(name, oldpass)
return s.es.Put(name, newpass, key)
}

+ 243
- 0
keys/cryptostore/holder_test.go View File

@ -0,0 +1,243 @@
package cryptostore_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/memstorage"
)
// TestKeyManagement makes sure we can manipulate these keys well
func TestKeyManagement(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// make the storage with reasonable defaults
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
)
algo := crypto.NameEd25519
n1, n2, n3 := "personal", "business", "other"
p1, p2 := "1234", "really-secure!@#$"
// Check empty state
l, err := cstore.List()
require.Nil(err)
assert.Empty(l)
// create some keys
_, err = cstore.Get(n1)
assert.NotNil(err)
i, err := cstore.Create(n1, p1, algo)
require.Equal(n1, i.Name)
require.Nil(err)
_, err = cstore.Create(n2, p2, algo)
require.Nil(err)
// we can get these keys
i2, err := cstore.Get(n2)
assert.Nil(err)
_, err = cstore.Get(n3)
assert.NotNil(err)
// list shows them in order
keys, err := cstore.List()
require.Nil(err)
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)
// 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()
require.Nil(err)
assert.Equal(1, len(keys))
_, err = cstore.Get(n1)
assert.NotNil(err)
// make sure that it only signs with the right password
// tx := mock.NewSig([]byte("mytransactiondata"))
// err = cstore.Sign(n2, p1, tx)
// assert.NotNil(err)
// err = cstore.Sign(n2, p2, tx)
// assert.Nil(err, "%+v", err)
// sigs, err := tx.Signers()
// assert.Nil(err, "%+v", err)
// if assert.Equal(1, len(sigs)) {
// assert.Equal(i2.PubKey, sigs[0])
// }
}
// 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 assertPassword(assert *assert.Assertions, cstore cryptostore.Manager, name, pass, badpass string) {
err := cstore.Update(name, badpass, pass)
assert.NotNil(err)
err = cstore.Update(name, pass, pass)
assert.Nil(err, "%+v", err)
}
// TestAdvancedKeyManagement verifies update, import, export functionality
func TestAdvancedKeyManagement(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// make the storage with reasonable defaults
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
)
algo := crypto.NameSecp256k1
n1, n2 := "old-name", "new name"
p1, p2, p3, pt := "1234", "foobar", "ding booms!", "really-secure!@#$"
// make sure key works with initial password
_, err := cstore.Create(n1, p1, algo)
require.Nil(err, "%+v", err)
assertPassword(assert, cstore, n1, p1, p2)
// update password requires the existing password
err = cstore.Update(n1, "jkkgkg", p2)
assert.NotNil(err)
assertPassword(assert, cstore, n1, p1, p2)
// then it changes the password when correct
err = cstore.Update(n1, p1, p2)
assert.Nil(err)
// p2 is now the proper one!
assertPassword(assert, cstore, n1, p2, p1)
// exporting requires the proper name and passphrase
_, err = cstore.Export(n2, p2, pt)
assert.NotNil(err)
_, err = cstore.Export(n1, p1, pt)
assert.NotNil(err)
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)
assert.NotNil(err)
// import cannot overwrite existing keys
err = cstore.Import(n1, p3, pt, exported)
assert.NotNil(err)
// we can now import under another name
err = cstore.Import(n2, p3, pt, exported)
require.Nil(err, "%+v", err)
// make sure both passwords are now properly set (not to the transfer pass)
assertPassword(assert, cstore, n1, p2, pt)
assertPassword(assert, cstore, n2, p3, pt)
}
// 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
// }

+ 41
- 0
keys/cryptostore/storage_test.go View File

@ -0,0 +1,41 @@
package cryptostore
import (
"testing"
"github.com/stretchr/testify/assert"
keys "github.com/tendermint/go-crypto/keys"
)
func TestSortKeys(t *testing.T) {
assert := assert.New(t)
gen := GenEd25519.Generate
assert.NotEqual(gen(), gen())
// alphabetical order is n3, n1, n2
n1, n2, n3 := "john", "mike", "alice"
infos := keys.Infos{
info(n1, gen()),
info(n2, gen()),
info(n3, gen()),
}
// make sure they are initialized unsorted
assert.Equal(n1, infos[0].Name)
assert.Equal(n2, infos[1].Name)
assert.Equal(n3, infos[2].Name)
// now they are sorted
infos.Sort()
assert.Equal(n3, infos[0].Name)
assert.Equal(n1, infos[1].Name)
assert.Equal(n2, infos[2].Name)
// make sure info put some real data there...
assert.NotEmpty(infos[0].PubKey)
assert.NotEmpty(infos[0].PubKey.Address())
assert.NotEmpty(infos[1].PubKey)
assert.NotEmpty(infos[1].PubKey.Address())
assert.NotEqual(infos[0].PubKey, infos[1].PubKey)
}

+ 2
- 0
keys/keys.toml View File

@ -0,0 +1,2 @@
output = "text"
keydir = ".mykeys"

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

@ -0,0 +1,13 @@
# 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

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

@ -0,0 +1,58 @@
/*
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)
}

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

@ -0,0 +1,127 @@
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, err := k.manager.Create(req.Name, req.Passphrase, req.Algo)
if err != nil {
writeError(w, err)
return
}
writeSuccess(w, &key)
}
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")
}

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

@ -0,0 +1,190 @@
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(key.Name, n1)
// 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.Name, n2)
// 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(k.Name, n1)
assert.NotNil(k.Address)
assert.Equal(k.Address, key.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(),
)
// 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) (*keys.Info, 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 := keys.Info{}
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
}

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

@ -0,0 +1,28 @@
package types
// 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
}

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

@ -0,0 +1,12 @@
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")
}

+ 10
- 0
keys/storage.go View File

@ -0,0 +1,10 @@
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
}

+ 171
- 0
keys/storage/filestorage/main.go View File

@ -0,0 +1,171 @@
/*
package filestorage provides a secure on-disk storage of private keys and
metadata. Security is enforced by file and directory permissions, much
like standard ssh key storage.
*/
package filestorage
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
const (
BlockType = "Tendermint Light Client"
PrivExt = "tlc"
PubExt = "pub"
keyPerm = os.FileMode(0600)
pubPerm = os.FileMode(0644)
dirPerm = os.FileMode(0700)
)
type FileStore struct {
keyDir string
}
// New creates an instance of file-based key storage with tight permissions
//
// dir should be an absolute path of a directory owner by this user. It will
// 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
}
// 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 {
pub, priv := s.nameToPaths(name)
// write public info
err := writeInfo(pub, info)
if err != nil {
return err
}
// write private info
return write(priv, name, 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) {
pub, priv := s.nameToPaths(name)
info, err := readInfo(pub)
if err != nil {
return nil, info, err
}
key, _, err := read(priv)
return key, info.Format(), err
}
// List parses the key directory for public info and returns a list of
// Info for all keys located in this directory.
func (s FileStore) List() (keys.Infos, error) {
dir, err := os.Open(s.keyDir)
if err != nil {
return nil, errors.Wrap(err, "List Keys")
}
names, err := dir.Readdirnames(0)
if err != nil {
return nil, errors.Wrap(err, "List Keys")
}
// filter names for .pub ending and load them one by one
// half the files is a good guess for pre-allocating the slice
infos := make([]keys.Info, 0, len(names)/2)
for _, name := range names {
if strings.HasSuffix(name, PubExt) {
p := path.Join(s.keyDir, name)
info, err := readInfo(p)
if err != nil {
return nil, err
}
infos = append(infos, info.Format())
}
}
return infos, nil
}
// Delete permanently removes the public and private info for the named key
// The calling function should provide some security checks first.
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")
}
d, err := ioutil.ReadAll(f)
if err != nil {
return nil, "", errors.Wrap(err, "Reading data")
}
block, headers, key, err := crypto.DecodeArmor(string(d))
if err != nil {
return nil, "", errors.Wrap(err, "Invalid Armor")
}
if block != BlockType {
return nil, "", errors.Errorf("Unknown key type: %s", block)
}
return key, headers["name"], nil
}
func write(path, name string, 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}
text := crypto.EncodeArmor(BlockType, headers, key)
_, err = f.WriteString(text)
return errors.Wrap(err, "Writing data")
}

+ 106
- 0
keys/storage/filestorage/main_test.go View File

@ -0,0 +1,106 @@
package filestorage
import (
"io/ioutil"
"os"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
func TestBasicCRUD(t *testing.T) {
assert, require := assert.New(t), require.New(t)
dir, err := ioutil.TempDir("", "filestorage-test")
assert.Nil(err)
defer os.RemoveAll(dir)
store := New(dir)
name := "bar"
key := []byte("secret-key-here")
pubkey := crypto.GenPrivKeyEd25519().PubKey()
info := keys.Info{
Name: name,
PubKey: pubkey.Wrap(),
}
// No data: Get and Delete return nothing
_, _, err = store.Get(name)
assert.NotNil(err)
err = store.Delete(name)
assert.NotNil(err)
// List returns empty list
l, err := store.List()
assert.Nil(err)
assert.Empty(l)
// Putting the key in the store must work
err = store.Put(name, key, info)
assert.Nil(err)
// But a second time is a failure
err = store.Put(name, key, info)
assert.NotNil(err)
// Now, we can get and list properly
k, i, err := store.Get(name)
require.Nil(err, "%+v", err)
assert.Equal(key, k)
assert.Equal(info.Name, i.Name)
assert.Equal(info.PubKey, i.PubKey)
assert.NotEmpty(i.Address)
l, err = store.List()
require.Nil(err, "%+v", err)
assert.Equal(1, len(l))
assert.Equal(i, l[0])
// querying a non-existent key fails
_, _, err = store.Get("badname")
assert.NotNil(err)
// We can only delete once
err = store.Delete(name)
assert.Nil(err)
err = store.Delete(name)
assert.NotNil(err)
// and then Get and List don't work
_, _, err = store.Get(name)
assert.NotNil(err)
// List returns empty list
l, err = store.List()
assert.Nil(err)
assert.Empty(l)
}
func TestDirectoryHandling(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// prepare a temp dir and make sure it is not there
newDir := path.Join(os.TempDir(), "file-test-dir")
_, err := os.Open(newDir)
assert.True(os.IsNotExist(err))
defer os.RemoveAll(newDir)
// now, check with two levels deep....
parentDir := path.Join(os.TempDir(), "missing-dir")
nestedDir := path.Join(parentDir, "lots", "of", "levels", "here")
_, err = os.Open(parentDir)
assert.True(os.IsNotExist(err))
defer os.RemoveAll(parentDir)
// create a new storage, and verify it creates the directory with good permissions
for _, dir := range []string{newDir, nestedDir, newDir} {
New(dir)
d, err := os.Open(dir)
require.Nil(err)
defer d.Close()
stat, err := d.Stat()
require.Nil(err)
assert.Equal(dirPerm, stat.Mode()&os.ModePerm)
}
}

+ 70
- 0
keys/storage/memstorage/main.go View File

@ -0,0 +1,70 @@
/*
package memstorage provides a simple in-memory key store designed for
use in test cases, particularly to isolate them from the filesystem,
concurrency, and cleanup issues.
*/
package memstorage
import (
"github.com/pkg/errors"
keys "github.com/tendermint/go-crypto/keys"
)
type data struct {
info keys.Info
key []byte
}
type MemStore map[string]data
// New creates an instance of file-based key storage with tight permissions
func New() MemStore {
return MemStore{}
}
// assertStorage just makes sure we implement the Storage interface
func (s MemStore) assertStorage() keys.Storage {
return s
}
// 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 {
if _, ok := s[name]; ok {
return errors.Errorf("Key named '%s' already exists", name)
}
s[name] = data{info, 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
d, ok := s[name]
if !ok {
err = errors.Errorf("Key named '%s' doesn't exist", name)
}
return d.key, d.info.Format(), err
}
// List returns the public info of all keys in the MemStore in unsorted order
func (s MemStore) List() (keys.Infos, error) {
res := make([]keys.Info, len(s))
i := 0
for _, d := range s {
res[i] = d.info.Format()
i++
}
return res, nil
}
// Delete removes the named key from the MemStore, raising an error if it
// wasn't present yet.
func (s MemStore) Delete(name string) error {
_, ok := s[name]
if !ok {
return errors.Errorf("Key named '%s' doesn't exist", name)
}
delete(s, name)
return nil
}

+ 69
- 0
keys/storage/memstorage/main_test.go View File

@ -0,0 +1,69 @@
package memstorage
import (
"testing"
"github.com/stretchr/testify/assert"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
)
func TestBasicCRUD(t *testing.T) {
assert := assert.New(t)
store := New()
name := "foo"
key := []byte("secret-key-here")
pubkey := crypto.GenPrivKeyEd25519().PubKey()
info := keys.Info{
Name: name,
PubKey: pubkey,
}
// No data: Get and Delete return nothing
_, _, err := store.Get(name)
assert.NotNil(err)
err = store.Delete(name)
assert.NotNil(err)
// List returns empty list
l, err := store.List()
assert.Nil(err)
assert.Empty(l)
// Putting the key in the store must work
err = store.Put(name, key, info)
assert.Nil(err)
// But a second time is a failure
err = store.Put(name, key, info)
assert.NotNil(err)
// Now, we can get and list properly
k, i, err := store.Get(name)
assert.Nil(err)
assert.Equal(key, k)
assert.Equal(info.Name, i.Name)
assert.Equal(info.PubKey, i.PubKey)
assert.NotEmpty(i.Address)
l, err = store.List()
assert.Nil(err)
assert.Equal(1, len(l))
assert.Equal(i, l[0])
// querying a non-existent key fails
_, _, err = store.Get("badname")
assert.NotNil(err)
// We can only delete once
err = store.Delete(name)
assert.Nil(err)
err = store.Delete(name)
assert.NotNil(err)
// and then Get and List don't work
_, _, err = store.Get(name)
assert.NotNil(err)
// List returns empty list
l, err = store.List()
assert.Nil(err)
assert.Empty(l)
}

+ 71
- 0
keys/transactions.go View File

@ -0,0 +1,71 @@
package keys
import (
"sort"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-wire/data"
)
// Info is the public information about a key
type Info struct {
Name string `json:"name"`
Address data.Bytes `json:"address"`
PubKey crypto.PubKey `json:"pubkey"`
}
func (i *Info) Format() Info {
if !i.PubKey.Empty() {
i.Address = i.PubKey.Address()
}
return *i
}
// Infos is a wrapper to allows alphabetical sorting of the keys
type Infos []Info
func (k Infos) Len() int { return len(k) }
func (k Infos) Less(i, j int) bool { return k[i].Name < k[j].Name }
func (k Infos) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k Infos) Sort() {
if k != nil {
sort.Sort(k)
}
}
// Signable represents any transaction we wish to send to tendermint core
// These methods allow us to sign arbitrary Tx with the KeyStore
type Signable interface {
// SignBytes is the immutable data, which needs to be signed
SignBytes() []byte
// 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
Sign(pubkey crypto.PubKey, sig crypto.Signature) error
// 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
Signers() ([]crypto.PubKey, error)
// TxBytes returns the transaction data as well as all signatures
// It should return an error if Sign was never called
TxBytes() ([]byte, error)
}
// Signer allows one to use a keystore to sign transactions
type Signer interface {
Sign(name, passphrase string, tx Signable) error
}
// Manager allows simple CRUD on a keystore, as an aid to signing
type Manager interface {
Signer
Create(name, passphrase, algo string) (Info, error)
List() (Infos, error)
Get(name string) (Info, error)
Update(name, oldpass, newpass string) error
Delete(name, passphrase string) error
}

+ 10
- 0
keys/tx/docs.go View File

@ -0,0 +1,10 @@
/*
package tx contains generic Signable implementations that can be used
by your application or tests to handle authentication needs.
It currently supports transaction data as opaque bytes and either single
or multiple private key signatures using straightforward algorithms.
It currently does not support N-of-M key share signing of other more
complex algorithms (although it would be great to add them)
*/
package tx

+ 67
- 0
keys/tx/multi.go View File

@ -0,0 +1,67 @@
package tx
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-wire/data"
)
// MultiSig lets us wrap arbitrary data with a go-crypto signature
//
// TODO: rethink how we want to integrate this with KeyStore so it makes
// more sense (particularly the verify method)
type MultiSig struct {
Data data.Bytes
Sigs []Signed
}
type Signed struct {
Sig crypto.Signature
Pubkey crypto.PubKey
}
var _ SigInner = &MultiSig{}
func NewMulti(data []byte) Sig {
return Sig{&MultiSig{Data: data}}
}
// SignBytes returns the original data passed into `NewSig`
func (s *MultiSig) 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 *MultiSig) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
if pubkey.Empty() || sig.Empty() {
return errors.New("Signature or Key missing")
}
// set the value once we are happy
x := Signed{sig, pubkey}
s.Sigs = append(s.Sigs, x)
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 *MultiSig) Signers() ([]crypto.PubKey, error) {
if len(s.Sigs) == 0 {
return nil, errors.New("Never signed")
}
keys := make([]crypto.PubKey, len(s.Sigs))
for i := range s.Sigs {
ms := s.Sigs[i]
if !ms.Pubkey.VerifyBytes(s.Data, ms.Sig) {
return nil, errors.Errorf("Signature %d doesn't match (key: %X)", i, ms.Pubkey.Bytes())
}
keys[i] = ms.Pubkey
}
return keys, nil
}

+ 77
- 0
keys/tx/multi_test.go View File

@ -0,0 +1,77 @@
package tx
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/memstorage"
)
func TestMultiSig(t *testing.T) {
assert, require := assert.New(t), require.New(t)
algo := crypto.NameEd25519
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
)
n, p := "foo", "bar"
n2, p2 := "other", "thing"
acct, err := cstore.Create(n, p, algo)
require.Nil(err, "%+v", err)
acct2, err := cstore.Create(n2, p2, algo)
require.Nil(err, "%+v", err)
type signer struct {
key keys.Info
name, pass string
}
cases := []struct {
data string
signers []signer
}{
{"one", []signer{{acct, n, p}}},
{"two", []signer{{acct2, n2, p2}}},
{"both", []signer{{acct, n, p}, {acct2, n2, p2}}},
}
for _, tc := range cases {
tx := NewMulti([]byte(tc.data))
// unsigned version
_, err = tx.Signers()
assert.NotNil(err)
orig, err := tx.TxBytes()
require.Nil(err, "%+v", err)
data := tx.SignBytes()
assert.Equal(tc.data, string(data))
// sign it
for _, s := range tc.signers {
err = cstore.Sign(s.name, s.pass, tx)
require.Nil(err, "%+v", err)
}
// make sure it is proper now
sigs, err := tx.Signers()
require.Nil(err, "%+v", err)
if assert.Equal(len(tc.signers), len(sigs)) {
for i := range sigs {
// This must be refactored...
assert.Equal(tc.signers[i].key.PubKey, sigs[i])
}
}
// the tx bytes should change after this
after, err := tx.TxBytes()
require.Nil(err, "%+v", err)
assert.NotEqual(orig, after, "%X != %X", orig, after)
// sign bytes are the same
data = tx.SignBytes()
assert.Equal(tc.data, string(data))
}
}

+ 57
- 0
keys/tx/one.go View File

@ -0,0 +1,57 @@
package tx
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-wire/data"
)
// OneSig lets us wrap arbitrary data with a go-crypto signature
//
// TODO: rethink how we want to integrate this with KeyStore so it makes
// more sense (particularly the verify method)
type OneSig struct {
Data data.Bytes
Signed
}
var _ SigInner = &OneSig{}
func New(data []byte) Sig {
return WrapSig(&OneSig{Data: data})
}
// SignBytes returns the original data passed into `NewSig`
func (s *OneSig) 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 *OneSig) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
if pubkey.Empty() || sig.Empty() {
return errors.New("Signature or Key missing")
}
if !s.Sig.Empty() {
return errors.New("Transaction can only be signed once")
}
// set the value once we are happy
s.Signed = Signed{sig, pubkey}
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 *OneSig) Signers() ([]crypto.PubKey, error) {
if s.Pubkey.Empty() || s.Sig.Empty() {
return nil, errors.New("Never signed")
}
if !s.Pubkey.VerifyBytes(s.Data, s.Sig) {
return nil, errors.New("Signature doesn't match")
}
return []crypto.PubKey{s.Pubkey}, nil
}

+ 73
- 0
keys/tx/one_test.go View File

@ -0,0 +1,73 @@
package tx
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/memstorage"
)
func TestOneSig(t *testing.T) {
assert, require := assert.New(t), require.New(t)
algo := crypto.NameEd25519
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
)
n, p := "foo", "bar"
n2, p2 := "other", "thing"
acct, err := cstore.Create(n, p, algo)
require.Nil(err, "%+v", err)
acct2, err := cstore.Create(n2, p2, algo)
require.Nil(err, "%+v", err)
cases := []struct {
data string
key keys.Info
name, pass string
}{
{"first", acct, n, p},
{"kehfkhefy8y", acct, n, p},
{"second", acct2, n2, p2},
}
for _, tc := range cases {
tx := New([]byte(tc.data))
// unsigned version
_, err = tx.Signers()
assert.NotNil(err)
orig, err := tx.TxBytes()
require.Nil(err, "%+v", err)
data := tx.SignBytes()
assert.Equal(tc.data, string(data))
// sign it
err = cstore.Sign(tc.name, tc.pass, tx)
require.Nil(err, "%+v", err)
// but not twice
err = cstore.Sign(tc.name, tc.pass, tx)
require.NotNil(err)
// make sure it is proper now
sigs, err := tx.Signers()
require.Nil(err, "%+v", err)
if assert.Equal(1, len(sigs)) {
// This must be refactored...
assert.Equal(tc.key.PubKey, sigs[0])
}
// the tx bytes should change after this
after, err := tx.TxBytes()
require.Nil(err, "%+v", err)
assert.NotEqual(orig, after, "%X != %X", orig, after)
// sign bytes are the same
data = tx.SignBytes()
assert.Equal(tc.data, string(data))
}
}

+ 76
- 0
keys/tx/reader.go View File

@ -0,0 +1,76 @@
package tx
import (
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
data "github.com/tendermint/go-wire/data"
)
const (
typeOneSig = byte(0x01)
typeMultiSig = byte(0x02)
nameOneSig = "sig"
nameMultiSig = "multi"
)
var _ keys.Signable = Sig{}
var TxMapper data.Mapper
func init() {
TxMapper = data.NewMapper(Sig{}).
RegisterImplementation(&OneSig{}, nameOneSig, typeOneSig).
RegisterImplementation(&MultiSig{}, nameMultiSig, typeMultiSig)
}
/*
DO NOT USE this interface.
It is public by necessity but should never be used directly
outside of this package.
Only use Sig, never SigInner
*/
type SigInner interface {
SignBytes() []byte
Sign(pubkey crypto.PubKey, sig crypto.Signature) error
Signers() ([]crypto.PubKey, error)
}
// Sig is what is exported, and handles serialization
type Sig struct {
SigInner
}
// TxBytes
func (s Sig) TxBytes() ([]byte, error) {
return data.ToWire(s)
}
// WrapSig goes from concrete implementation to "interface" struct
func WrapSig(pk SigInner) Sig {
if wrap, ok := pk.(Sig); ok {
pk = wrap.Unwrap()
}
return Sig{pk}
}
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
func (p Sig) Unwrap() SigInner {
pk := p.SigInner
for wrap, ok := pk.(Sig); ok; wrap, ok = pk.(Sig) {
pk = wrap.SigInner
}
return pk
}
func (p Sig) MarshalJSON() ([]byte, error) {
return TxMapper.ToJSON(p.Unwrap())
}
func (p *Sig) UnmarshalJSON(data []byte) (err error) {
parsed, err := TxMapper.FromJSON(data)
if err == nil && parsed != nil {
p.SigInner = parsed.(SigInner)
}
return
}

+ 70
- 0
keys/tx/reader_test.go View File

@ -0,0 +1,70 @@
package tx
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-wire/data"
"github.com/tendermint/go-crypto/keys/cryptostore"
"github.com/tendermint/go-crypto/keys/storage/memstorage"
)
func TestReader(t *testing.T) {
assert, require := assert.New(t), require.New(t)
algo := crypto.NameEd25519
cstore := cryptostore.New(
cryptostore.SecretBox,
memstorage.New(),
)
type sigs struct{ name, pass string }
u := sigs{"alice", "1234"}
u2 := sigs{"bob", "foobar"}
_, err := cstore.Create(u.name, u.pass, algo)
require.Nil(err, "%+v", err)
_, err = cstore.Create(u2.name, u2.pass, algo)
require.Nil(err, "%+v", err)
cases := []struct {
tx Sig
sigs []sigs
}{
{New([]byte("first")), nil},
{New([]byte("second")), []sigs{u}},
{New([]byte("other")), []sigs{u2}},
{NewMulti([]byte("m-first")), nil},
{NewMulti([]byte("m-second")), []sigs{u}},
{NewMulti([]byte("m-other")), []sigs{u, u2}},
}
for _, tc := range cases {
tx := tc.tx
// make sure json serialization and loading works w/o sigs
var pre Sig
pjs, err := data.ToJSON(tx)
require.Nil(err, "%+v", err)
err = data.FromJSON(pjs, &pre)
require.Nil(err, "%+v", err)
assert.Equal(tx, pre)
for _, s := range tc.sigs {
err = cstore.Sign(s.name, s.pass, tx)
require.Nil(err, "%+v", err)
}
var post Sig
sjs, err := data.ToJSON(tx)
require.Nil(err, "%+v", err)
err = data.FromJSON(sjs, &post)
require.Nil(err, "%+v\n%s", err, string(sjs))
assert.Equal(tx, post)
if len(tc.sigs) > 0 {
assert.NotEqual(pjs, sjs, "%s\n ------ %s", string(pjs), string(sjs))
}
}
}

+ 32
- 62
priv_key.go View File

@ -6,91 +6,57 @@ import (
secp256k1 "github.com/btcsuite/btcd/btcec" secp256k1 "github.com/btcsuite/btcd/btcec"
"github.com/tendermint/ed25519" "github.com/tendermint/ed25519"
"github.com/tendermint/ed25519/extra25519" "github.com/tendermint/ed25519/extra25519"
. "github.com/tendermint/go-common"
data "github.com/tendermint/go-data"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
data "github.com/tendermint/go-wire/data"
. "github.com/tendermint/tmlibs/common"
) )
// PrivKey is part of PrivAccount and state.PrivValidator.
type PrivKey interface {
Bytes() []byte
Sign(msg []byte) Signature
PubKey() PubKey
Equals(PrivKey) bool
}
// Types of implementations
const (
TypeEd25519 = byte(0x01)
TypeSecp256k1 = byte(0x02)
NameEd25519 = "ed25519"
NameSecp256k1 = "secp256k1"
)
var privKeyMapper data.Mapper
// register both private key types with go-data (and thus go-wire)
func init() {
privKeyMapper = data.NewMapper(PrivKeyS{}).
RegisterImplementation(PrivKeyEd25519{}, NameEd25519, TypeEd25519).
RegisterImplementation(PrivKeySecp256k1{}, NameSecp256k1, TypeSecp256k1)
}
// PrivKeyS add json serialization to PrivKey
type PrivKeyS struct {
PrivKey
}
func WrapPrivKey(pk PrivKey) PrivKeyS {
for ppk, ok := pk.(PrivKeyS); ok; ppk, ok = pk.(PrivKeyS) {
pk = ppk.PrivKey
}
return PrivKeyS{pk}
}
func (p PrivKeyS) MarshalJSON() ([]byte, error) {
return privKeyMapper.ToJSON(p.PrivKey)
}
func (p *PrivKeyS) UnmarshalJSON(data []byte) (err error) {
parsed, err := privKeyMapper.FromJSON(data)
if err == nil && parsed != nil {
p.PrivKey = parsed.(PrivKey)
}
func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) {
err = wire.ReadBinaryBytes(privKeyBytes, &privKey)
return return
} }
func (p PrivKeyS) Empty() bool {
return p.PrivKey == nil
}
//----------------------------------------
func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) {
err = wire.ReadBinaryBytes(privKeyBytes, &privKey)
return
// DO NOT USE THIS INTERFACE.
// You probably want to use PubKey
// +gen wrapper:"PrivKey,Impl[PrivKeyEd25519,PrivKeySecp256k1],ed25519,secp256k1"
type PrivKeyInner interface {
AssertIsPrivKeyInner()
Bytes() []byte
Sign(msg []byte) Signature
PubKey() PubKey
Equals(PrivKey) bool
Wrap() PrivKey
} }
//------------------------------------- //-------------------------------------
var _ PrivKeyInner = PrivKeyEd25519{}
// Implements PrivKey // Implements PrivKey
type PrivKeyEd25519 [64]byte type PrivKeyEd25519 [64]byte
func (privKey PrivKeyEd25519) AssertIsPrivKeyInner() {}
func (privKey PrivKeyEd25519) Bytes() []byte { func (privKey PrivKeyEd25519) Bytes() []byte {
return wire.BinaryBytes(struct{ PrivKey }{privKey})
return wire.BinaryBytes(PrivKey{privKey})
} }
func (privKey PrivKeyEd25519) Sign(msg []byte) Signature { func (privKey PrivKeyEd25519) Sign(msg []byte) Signature {
privKeyBytes := [64]byte(privKey) privKeyBytes := [64]byte(privKey)
signatureBytes := ed25519.Sign(&privKeyBytes, msg) signatureBytes := ed25519.Sign(&privKeyBytes, msg)
return SignatureEd25519(*signatureBytes)
return SignatureEd25519(*signatureBytes).Wrap()
} }
func (privKey PrivKeyEd25519) PubKey() PubKey { func (privKey PrivKeyEd25519) PubKey() PubKey {
privKeyBytes := [64]byte(privKey) privKeyBytes := [64]byte(privKey)
return PubKeyEd25519(*ed25519.MakePublicKey(&privKeyBytes))
pubBytes := *ed25519.MakePublicKey(&privKeyBytes)
return PubKeyEd25519(pubBytes).Wrap()
} }
func (privKey PrivKeyEd25519) Equals(other PrivKey) bool { func (privKey PrivKeyEd25519) Equals(other PrivKey) bool {
if otherEd, ok := other.(PrivKeyEd25519); ok {
if otherEd, ok := other.Unwrap().(PrivKeyEd25519); ok {
return bytes.Equal(privKey[:], otherEd[:]) return bytes.Equal(privKey[:], otherEd[:])
} else { } else {
return false return false
@ -149,11 +115,15 @@ func GenPrivKeyEd25519FromSecret(secret []byte) PrivKeyEd25519 {
//------------------------------------- //-------------------------------------
var _ PrivKeyInner = PrivKeySecp256k1{}
// Implements PrivKey // Implements PrivKey
type PrivKeySecp256k1 [32]byte type PrivKeySecp256k1 [32]byte
func (privKey PrivKeySecp256k1) AssertIsPrivKeyInner() {}
func (privKey PrivKeySecp256k1) Bytes() []byte { func (privKey PrivKeySecp256k1) Bytes() []byte {
return wire.BinaryBytes(struct{ PrivKey }{privKey})
return wire.BinaryBytes(PrivKey{privKey})
} }
func (privKey PrivKeySecp256k1) Sign(msg []byte) Signature { func (privKey PrivKeySecp256k1) Sign(msg []byte) Signature {
@ -162,18 +132,18 @@ func (privKey PrivKeySecp256k1) Sign(msg []byte) Signature {
if err != nil { if err != nil {
PanicSanity(err) PanicSanity(err)
} }
return SignatureSecp256k1(sig__.Serialize())
return SignatureSecp256k1(sig__.Serialize()).Wrap()
} }
func (privKey PrivKeySecp256k1) PubKey() PubKey { func (privKey PrivKeySecp256k1) PubKey() PubKey {
_, pub__ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:]) _, pub__ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:])
var pub PubKeySecp256k1 var pub PubKeySecp256k1
copy(pub[:], pub__.SerializeCompressed()) copy(pub[:], pub__.SerializeCompressed())
return pub
return pub.Wrap()
} }
func (privKey PrivKeySecp256k1) Equals(other PrivKey) bool { func (privKey PrivKeySecp256k1) Equals(other PrivKey) bool {
if otherSecp, ok := other.(PrivKeySecp256k1); ok {
if otherSecp, ok := other.Unwrap().(PrivKeySecp256k1); ok {
return bytes.Equal(privKey[:], otherSecp[:]) return bytes.Equal(privKey[:], otherSecp[:])
} else { } else {
return false return false


+ 62
- 0
privkeyinner_wrapper.go View File

@ -0,0 +1,62 @@
// Generated by: main
// TypeWriter: wrapper
// Directive: +gen on PrivKeyInner
package crypto
import (
"github.com/tendermint/go-wire/data"
)
// Auto-generated adapters for happily unmarshaling interfaces
// Apache License 2.0
// Copyright (c) 2017 Ethan Frey (ethan.frey@tendermint.com)
type PrivKey struct {
PrivKeyInner "json:\"unwrap\""
}
var PrivKeyMapper = data.NewMapper(PrivKey{})
func (h PrivKey) MarshalJSON() ([]byte, error) {
return PrivKeyMapper.ToJSON(h.PrivKeyInner)
}
func (h *PrivKey) UnmarshalJSON(data []byte) (err error) {
parsed, err := PrivKeyMapper.FromJSON(data)
if err == nil && parsed != nil {
h.PrivKeyInner = parsed.(PrivKeyInner)
}
return err
}
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
func (h PrivKey) Unwrap() PrivKeyInner {
hi := h.PrivKeyInner
for wrap, ok := hi.(PrivKey); ok; wrap, ok = hi.(PrivKey) {
hi = wrap.PrivKeyInner
}
return hi
}
func (h PrivKey) Empty() bool {
return h.PrivKeyInner == nil
}
/*** below are bindings for each implementation ***/
func init() {
PrivKeyMapper.RegisterImplementation(PrivKeyEd25519{}, "ed25519", 0x1)
}
func (hi PrivKeyEd25519) Wrap() PrivKey {
return PrivKey{hi}
}
func init() {
PrivKeyMapper.RegisterImplementation(PrivKeySecp256k1{}, "secp256k1", 0x2)
}
func (hi PrivKeySecp256k1) Wrap() PrivKey {
return PrivKey{hi}
}

+ 30
- 61
pub_key.go View File

@ -7,68 +7,41 @@ import (
secp256k1 "github.com/btcsuite/btcd/btcec" secp256k1 "github.com/btcsuite/btcd/btcec"
"github.com/tendermint/ed25519" "github.com/tendermint/ed25519"
"github.com/tendermint/ed25519/extra25519" "github.com/tendermint/ed25519/extra25519"
. "github.com/tendermint/go-common"
data "github.com/tendermint/go-data"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
data "github.com/tendermint/go-wire/data"
. "github.com/tendermint/tmlibs/common"
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
// PubKey is part of Account and Validator.
type PubKey interface {
func PubKeyFromBytes(pubKeyBytes []byte) (pubKey PubKey, err error) {
err = wire.ReadBinaryBytes(pubKeyBytes, &pubKey)
return
}
//----------------------------------------
// DO NOT USE THIS INTERFACE.
// You probably want to use PubKey
// +gen wrapper:"PubKey,Impl[PubKeyEd25519,PubKeySecp256k1],ed25519,secp256k1"
type PubKeyInner interface {
AssertIsPubKeyInner()
Address() []byte Address() []byte
Bytes() []byte Bytes() []byte
KeyString() string KeyString() string
VerifyBytes(msg []byte, sig Signature) bool VerifyBytes(msg []byte, sig Signature) bool
Equals(PubKey) bool Equals(PubKey) bool
}
var pubKeyMapper data.Mapper
// register both public key types with go-data (and thus go-wire)
func init() {
pubKeyMapper = data.NewMapper(PubKeyS{}).
RegisterImplementation(PubKeyEd25519{}, NameEd25519, TypeEd25519).
RegisterImplementation(PubKeySecp256k1{}, NameSecp256k1, TypeSecp256k1)
}
// PubKeyS add json serialization to PubKey
type PubKeyS struct {
PubKey
}
func WrapPubKey(pk PubKey) PubKeyS {
for ppk, ok := pk.(PubKeyS); ok; ppk, ok = pk.(PubKeyS) {
pk = ppk.PubKey
}
return PubKeyS{pk}
}
func (p PubKeyS) MarshalJSON() ([]byte, error) {
return pubKeyMapper.ToJSON(p.PubKey)
}
func (p *PubKeyS) UnmarshalJSON(data []byte) (err error) {
parsed, err := pubKeyMapper.FromJSON(data)
if err == nil && parsed != nil {
p.PubKey = parsed.(PubKey)
}
return
}
func (p PubKeyS) Empty() bool {
return p.PubKey == nil
}
func PubKeyFromBytes(pubKeyBytes []byte) (pubKey PubKey, err error) {
err = wire.ReadBinaryBytes(pubKeyBytes, &pubKey)
return
Wrap() PubKey
} }
//------------------------------------- //-------------------------------------
// Implements PubKey
var _ PubKeyInner = PubKeyEd25519{}
// Implements PubKeyInner
type PubKeyEd25519 [32]byte type PubKeyEd25519 [32]byte
func (pubKey PubKeyEd25519) AssertIsPubKeyInner() {}
func (pubKey PubKeyEd25519) Address() []byte { func (pubKey PubKeyEd25519) Address() []byte {
w, n, err := new(bytes.Buffer), new(int), new(error) w, n, err := new(bytes.Buffer), new(int), new(error)
wire.WriteBinary(pubKey[:], w, n, err) wire.WriteBinary(pubKey[:], w, n, err)
@ -83,16 +56,12 @@ func (pubKey PubKeyEd25519) Address() []byte {
} }
func (pubKey PubKeyEd25519) Bytes() []byte { func (pubKey PubKeyEd25519) Bytes() []byte {
return wire.BinaryBytes(struct{ PubKey }{pubKey})
return wire.BinaryBytes(PubKey{pubKey})
} }
func (pubKey PubKeyEd25519) VerifyBytes(msg []byte, sig_ Signature) bool { func (pubKey PubKeyEd25519) VerifyBytes(msg []byte, sig_ Signature) bool {
// unwrap if needed
if wrap, ok := sig_.(SignatureS); ok {
sig_ = wrap.Signature
}
// make sure we use the same algorithm to sign // make sure we use the same algorithm to sign
sig, ok := sig_.(SignatureEd25519)
sig, ok := sig_.Unwrap().(SignatureEd25519)
if !ok { if !ok {
return false return false
} }
@ -134,7 +103,7 @@ func (pubKey PubKeyEd25519) KeyString() string {
} }
func (pubKey PubKeyEd25519) Equals(other PubKey) bool { func (pubKey PubKeyEd25519) Equals(other PubKey) bool {
if otherEd, ok := other.(PubKeyEd25519); ok {
if otherEd, ok := other.Unwrap().(PubKeyEd25519); ok {
return bytes.Equal(pubKey[:], otherEd[:]) return bytes.Equal(pubKey[:], otherEd[:])
} else { } else {
return false return false
@ -143,11 +112,15 @@ func (pubKey PubKeyEd25519) Equals(other PubKey) bool {
//------------------------------------- //-------------------------------------
var _ PubKeyInner = PubKeySecp256k1{}
// Implements PubKey. // Implements PubKey.
// Compressed pubkey (just the x-cord), // Compressed pubkey (just the x-cord),
// prefixed with 0x02 or 0x03, depending on the y-cord. // prefixed with 0x02 or 0x03, depending on the y-cord.
type PubKeySecp256k1 [33]byte type PubKeySecp256k1 [33]byte
func (pubKey PubKeySecp256k1) AssertIsPubKeyInner() {}
// Implements Bitcoin style addresses: RIPEMD160(SHA256(pubkey)) // Implements Bitcoin style addresses: RIPEMD160(SHA256(pubkey))
func (pubKey PubKeySecp256k1) Address() []byte { func (pubKey PubKeySecp256k1) Address() []byte {
hasherSHA256 := sha256.New() hasherSHA256 := sha256.New()
@ -160,16 +133,12 @@ func (pubKey PubKeySecp256k1) Address() []byte {
} }
func (pubKey PubKeySecp256k1) Bytes() []byte { func (pubKey PubKeySecp256k1) Bytes() []byte {
return wire.BinaryBytes(struct{ PubKey }{pubKey})
return wire.BinaryBytes(PubKey{pubKey})
} }
func (pubKey PubKeySecp256k1) VerifyBytes(msg []byte, sig_ Signature) bool { func (pubKey PubKeySecp256k1) VerifyBytes(msg []byte, sig_ Signature) bool {
// unwrap if needed
if wrap, ok := sig_.(SignatureS); ok {
sig_ = wrap.Signature
}
// and assert same algorithm to sign and verify // and assert same algorithm to sign and verify
sig, ok := sig_.(SignatureSecp256k1)
sig, ok := sig_.Unwrap().(SignatureSecp256k1)
if !ok { if !ok {
return false return false
} }
@ -207,7 +176,7 @@ func (pubKey PubKeySecp256k1) KeyString() string {
} }
func (pubKey PubKeySecp256k1) Equals(other PubKey) bool { func (pubKey PubKeySecp256k1) Equals(other PubKey) bool {
if otherSecp, ok := other.(PubKeySecp256k1); ok {
if otherSecp, ok := other.Unwrap().(PubKeySecp256k1); ok {
return bytes.Equal(pubKey[:], otherSecp[:]) return bytes.Equal(pubKey[:], otherSecp[:])
} else { } else {
return false return false


+ 1
- 1
pub_key_test.go View File

@ -31,7 +31,7 @@ func TestPubKeySecp256k1Address(t *testing.T) {
var priv PrivKeySecp256k1 var priv PrivKeySecp256k1
copy(priv[:], privB) copy(priv[:], privB)
pubT := priv.PubKey().(PubKeySecp256k1)
pubT := priv.PubKey().Unwrap().(PubKeySecp256k1)
pub := pubT[:] pub := pubT[:]
addr := priv.PubKey().Address() addr := priv.PubKey().Address()


+ 62
- 0
pubkeyinner_wrapper.go View File

@ -0,0 +1,62 @@
// Generated by: main
// TypeWriter: wrapper
// Directive: +gen on PubKeyInner
package crypto
import (
"github.com/tendermint/go-wire/data"
)
// Auto-generated adapters for happily unmarshaling interfaces
// Apache License 2.0
// Copyright (c) 2017 Ethan Frey (ethan.frey@tendermint.com)
type PubKey struct {
PubKeyInner "json:\"unwrap\""
}
var PubKeyMapper = data.NewMapper(PubKey{})
func (h PubKey) MarshalJSON() ([]byte, error) {
return PubKeyMapper.ToJSON(h.PubKeyInner)
}
func (h *PubKey) UnmarshalJSON(data []byte) (err error) {
parsed, err := PubKeyMapper.FromJSON(data)
if err == nil && parsed != nil {
h.PubKeyInner = parsed.(PubKeyInner)
}
return err
}
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
func (h PubKey) Unwrap() PubKeyInner {
hi := h.PubKeyInner
for wrap, ok := hi.(PubKey); ok; wrap, ok = hi.(PubKey) {
hi = wrap.PubKeyInner
}
return hi
}
func (h PubKey) Empty() bool {
return h.PubKeyInner == nil
}
/*** below are bindings for each implementation ***/
func init() {
PubKeyMapper.RegisterImplementation(PubKeyEd25519{}, "ed25519", 0x1)
}
func (hi PubKeyEd25519) Wrap() PubKey {
return PubKey{hi}
}
func init() {
PubKeyMapper.RegisterImplementation(PubKeySecp256k1{}, "secp256k1", 0x2)
}
func (hi PubKeySecp256k1) Wrap() PubKey {
return PubKey{hi}
}

+ 1
- 1
random.go View File

@ -8,7 +8,7 @@ import (
"io" "io"
"sync" "sync"
. "github.com/tendermint/go-common"
. "github.com/tendermint/tmlibs/common"
) )
var gRandInfo *randInfo var gRandInfo *randInfo


+ 36
- 58
signature.go View File

@ -4,68 +4,41 @@ import (
"bytes" "bytes"
"fmt" "fmt"
. "github.com/tendermint/go-common"
data "github.com/tendermint/go-data"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
data "github.com/tendermint/go-wire/data"
. "github.com/tendermint/tmlibs/common"
) )
// Signature is a part of Txs and consensus Votes.
type Signature interface {
Bytes() []byte
IsZero() bool
String() string
Equals(Signature) bool
}
var sigMapper data.Mapper
// register both public key types with go-data (and thus go-wire)
func init() {
sigMapper = data.NewMapper(SignatureS{}).
RegisterImplementation(SignatureEd25519{}, NameEd25519, TypeEd25519).
RegisterImplementation(SignatureSecp256k1{}, NameSecp256k1, TypeSecp256k1)
}
// SignatureS add json serialization to Signature
type SignatureS struct {
Signature
}
func WrapSignature(sig Signature) SignatureS {
for ssig, ok := sig.(SignatureS); ok; ssig, ok = sig.(SignatureS) {
sig = ssig.Signature
}
return SignatureS{sig}
}
func (p SignatureS) MarshalJSON() ([]byte, error) {
return sigMapper.ToJSON(p.Signature)
}
func (p *SignatureS) UnmarshalJSON(data []byte) (err error) {
parsed, err := sigMapper.FromJSON(data)
if err == nil && parsed != nil {
p.Signature = parsed.(Signature)
}
func SignatureFromBytes(sigBytes []byte) (sig Signature, err error) {
err = wire.ReadBinaryBytes(sigBytes, &sig)
return return
} }
func (p SignatureS) Empty() bool {
return p.Signature == nil
}
//----------------------------------------
func SignatureFromBytes(sigBytes []byte) (sig Signature, err error) {
err = wire.ReadBinaryBytes(sigBytes, &sig)
return
// DO NOT USE THIS INTERFACE.
// You probably want to use Signature.
// +gen wrapper:"Signature,Impl[SignatureEd25519,SignatureSecp256k1],ed25519,secp256k1"
type SignatureInner interface {
AssertIsSignatureInner()
Bytes() []byte
IsZero() bool
String() string
Equals(Signature) bool
Wrap() Signature
} }
//------------------------------------- //-------------------------------------
var _ SignatureInner = SignatureEd25519{}
// Implements Signature // Implements Signature
type SignatureEd25519 [64]byte type SignatureEd25519 [64]byte
func (sig SignatureEd25519) AssertIsSignatureInner() {}
func (sig SignatureEd25519) Bytes() []byte { func (sig SignatureEd25519) Bytes() []byte {
return wire.BinaryBytes(struct{ Signature }{sig})
return wire.BinaryBytes(Signature{sig})
} }
func (sig SignatureEd25519) IsZero() bool { return len(sig) == 0 } func (sig SignatureEd25519) IsZero() bool { return len(sig) == 0 }
@ -73,31 +46,35 @@ func (sig SignatureEd25519) IsZero() bool { return len(sig) == 0 }
func (sig SignatureEd25519) String() string { return fmt.Sprintf("/%X.../", Fingerprint(sig[:])) } func (sig SignatureEd25519) String() string { return fmt.Sprintf("/%X.../", Fingerprint(sig[:])) }
func (sig SignatureEd25519) Equals(other Signature) bool { func (sig SignatureEd25519) Equals(other Signature) bool {
if otherEd, ok := other.(SignatureEd25519); ok {
if otherEd, ok := other.Unwrap().(SignatureEd25519); ok {
return bytes.Equal(sig[:], otherEd[:]) return bytes.Equal(sig[:], otherEd[:])
} else { } else {
return false return false
} }
} }
func (p SignatureEd25519) MarshalJSON() ([]byte, error) {
return data.Encoder.Marshal(p[:])
func (sig SignatureEd25519) MarshalJSON() ([]byte, error) {
return data.Encoder.Marshal(sig[:])
} }
func (p *SignatureEd25519) UnmarshalJSON(enc []byte) error {
func (sig *SignatureEd25519) UnmarshalJSON(enc []byte) error {
var ref []byte var ref []byte
err := data.Encoder.Unmarshal(&ref, enc) err := data.Encoder.Unmarshal(&ref, enc)
copy(p[:], ref)
copy(sig[:], ref)
return err return err
} }
//------------------------------------- //-------------------------------------
var _ SignatureInner = SignatureSecp256k1{}
// Implements Signature // Implements Signature
type SignatureSecp256k1 []byte type SignatureSecp256k1 []byte
func (sig SignatureSecp256k1) AssertIsSignatureInner() {}
func (sig SignatureSecp256k1) Bytes() []byte { func (sig SignatureSecp256k1) Bytes() []byte {
return wire.BinaryBytes(struct{ Signature }{sig})
return wire.BinaryBytes(Signature{sig})
} }
func (sig SignatureSecp256k1) IsZero() bool { return len(sig) == 0 } func (sig SignatureSecp256k1) IsZero() bool { return len(sig) == 0 }
@ -105,16 +82,17 @@ func (sig SignatureSecp256k1) IsZero() bool { return len(sig) == 0 }
func (sig SignatureSecp256k1) String() string { return fmt.Sprintf("/%X.../", Fingerprint(sig[:])) } func (sig SignatureSecp256k1) String() string { return fmt.Sprintf("/%X.../", Fingerprint(sig[:])) }
func (sig SignatureSecp256k1) Equals(other Signature) bool { func (sig SignatureSecp256k1) Equals(other Signature) bool {
if otherEd, ok := other.(SignatureSecp256k1); ok {
if otherEd, ok := other.Unwrap().(SignatureSecp256k1); ok {
return bytes.Equal(sig[:], otherEd[:]) return bytes.Equal(sig[:], otherEd[:])
} else { } else {
return false return false
} }
} }
func (p SignatureSecp256k1) MarshalJSON() ([]byte, error) {
return data.Encoder.Marshal(p)
func (sig SignatureSecp256k1) MarshalJSON() ([]byte, error) {
return data.Encoder.Marshal(sig)
} }
func (p *SignatureSecp256k1) UnmarshalJSON(enc []byte) error {
return data.Encoder.Unmarshal((*[]byte)(p), enc)
func (sig *SignatureSecp256k1) UnmarshalJSON(enc []byte) error {
return data.Encoder.Unmarshal((*[]byte)(sig), enc)
} }

+ 26
- 26
signature_test.go View File

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/tendermint/ed25519" "github.com/tendermint/ed25519"
data "github.com/tendermint/go-data"
data "github.com/tendermint/go-wire/data"
) )
func TestSignAndValidateEd25519(t *testing.T) { func TestSignAndValidateEd25519(t *testing.T) {
@ -22,9 +22,9 @@ func TestSignAndValidateEd25519(t *testing.T) {
assert.True(t, pubKey.VerifyBytes(msg, sig)) assert.True(t, pubKey.VerifyBytes(msg, sig))
// Mutate the signature, just one bit. // Mutate the signature, just one bit.
sigEd := sig.(SignatureEd25519)
sigEd[0] ^= byte(0x01)
sig = Signature(sigEd)
sigEd := sig.Unwrap().(SignatureEd25519)
sigEd[7] ^= byte(0x01)
sig = sigEd.Wrap()
assert.False(t, pubKey.VerifyBytes(msg, sig)) assert.False(t, pubKey.VerifyBytes(msg, sig))
} }
@ -39,28 +39,28 @@ func TestSignAndValidateSecp256k1(t *testing.T) {
assert.True(t, pubKey.VerifyBytes(msg, sig)) assert.True(t, pubKey.VerifyBytes(msg, sig))
// Mutate the signature, just one bit. // Mutate the signature, just one bit.
sigEd := sig.(SignatureSecp256k1)
sigEd[0] ^= byte(0x01)
sig = Signature(sigEd)
sigEd := sig.Unwrap().(SignatureSecp256k1)
sigEd[3] ^= byte(0x01)
sig = sigEd.Wrap()
assert.False(t, pubKey.VerifyBytes(msg, sig)) assert.False(t, pubKey.VerifyBytes(msg, sig))
} }
func TestSignatureEncodings(t *testing.T) { func TestSignatureEncodings(t *testing.T) {
cases := []struct { cases := []struct {
privKey PrivKeyS
privKey PrivKey
sigSize int sigSize int
sigType byte sigType byte
sigName string sigName string
}{ }{
{ {
privKey: PrivKeyS{GenPrivKeyEd25519()},
privKey: GenPrivKeyEd25519().Wrap(),
sigSize: ed25519.SignatureSize, sigSize: ed25519.SignatureSize,
sigType: TypeEd25519, sigType: TypeEd25519,
sigName: NameEd25519, sigName: NameEd25519,
}, },
{ {
privKey: PrivKeyS{GenPrivKeySecp256k1()},
privKey: GenPrivKeySecp256k1().Wrap(),
sigSize: 0, // unknown sigSize: 0, // unknown
sigType: TypeSecp256k1, sigType: TypeSecp256k1,
sigName: NameSecp256k1, sigName: NameSecp256k1,
@ -69,10 +69,10 @@ func TestSignatureEncodings(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
// note we embed them from the beginning.... // note we embed them from the beginning....
pubKey := PubKeyS{tc.privKey.PubKey()}
pubKey := tc.privKey.PubKey()
msg := CRandBytes(128) msg := CRandBytes(128)
sig := SignatureS{tc.privKey.Sign(msg)}
sig := tc.privKey.Sign(msg)
// store as wire // store as wire
bin, err := data.ToWire(sig) bin, err := data.ToWire(sig)
@ -83,7 +83,7 @@ func TestSignatureEncodings(t *testing.T) {
assert.Equal(t, tc.sigType, bin[0]) assert.Equal(t, tc.sigType, bin[0])
// and back // and back
sig2 := SignatureS{}
sig2 := Signature{}
err = data.FromWire(bin, &sig2) err = data.FromWire(bin, &sig2)
require.Nil(t, err, "%+v", err) require.Nil(t, err, "%+v", err)
assert.EqualValues(t, sig, sig2) assert.EqualValues(t, sig, sig2)
@ -95,7 +95,7 @@ func TestSignatureEncodings(t *testing.T) {
assert.True(t, strings.Contains(string(js), tc.sigName)) assert.True(t, strings.Contains(string(js), tc.sigName))
// and back // and back
sig3 := SignatureS{}
sig3 := Signature{}
err = data.FromJSON(js, &sig3) err = data.FromJSON(js, &sig3)
require.Nil(t, err, "%+v", err) require.Nil(t, err, "%+v", err)
assert.EqualValues(t, sig, sig3) assert.EqualValues(t, sig, sig3)
@ -118,25 +118,25 @@ func TestWrapping(t *testing.T) {
sig := priv.Sign(msg) sig := priv.Sign(msg)
// do some wrapping // do some wrapping
pubs := []PubKeyS{
WrapPubKey(nil),
WrapPubKey(pub),
WrapPubKey(WrapPubKey(WrapPubKey(WrapPubKey(pub)))),
WrapPubKey(PubKeyS{PubKeyS{PubKeyS{pub}}}),
pubs := []PubKey{
PubKey{nil},
pub.Wrap(),
pub.Wrap().Wrap().Wrap(),
PubKey{PubKey{PubKey{pub}}}.Wrap(),
} }
for _, p := range pubs { for _, p := range pubs {
_, ok := p.PubKey.(PubKeyS)
_, ok := p.PubKeyInner.(PubKey)
assert.False(ok) assert.False(ok)
} }
sigs := []SignatureS{
WrapSignature(nil),
WrapSignature(sig),
WrapSignature(WrapSignature(WrapSignature(WrapSignature(sig)))),
WrapSignature(SignatureS{SignatureS{SignatureS{sig}}}),
sigs := []Signature{
Signature{nil},
sig.Wrap(),
sig.Wrap().Wrap().Wrap(),
Signature{Signature{Signature{sig}}}.Wrap(),
} }
for _, s := range sigs { for _, s := range sigs {
_, ok := s.Signature.(SignatureS)
_, ok := s.SignatureInner.(Signature)
assert.False(ok) assert.False(ok)
} }


+ 62
- 0
signatureinner_wrapper.go View File

@ -0,0 +1,62 @@
// Generated by: main
// TypeWriter: wrapper
// Directive: +gen on SignatureInner
package crypto
import (
"github.com/tendermint/go-wire/data"
)
// Auto-generated adapters for happily unmarshaling interfaces
// Apache License 2.0
// Copyright (c) 2017 Ethan Frey (ethan.frey@tendermint.com)
type Signature struct {
SignatureInner "json:\"unwrap\""
}
var SignatureMapper = data.NewMapper(Signature{})
func (h Signature) MarshalJSON() ([]byte, error) {
return SignatureMapper.ToJSON(h.SignatureInner)
}
func (h *Signature) UnmarshalJSON(data []byte) (err error) {
parsed, err := SignatureMapper.FromJSON(data)
if err == nil && parsed != nil {
h.SignatureInner = parsed.(SignatureInner)
}
return err
}
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
func (h Signature) Unwrap() SignatureInner {
hi := h.SignatureInner
for wrap, ok := hi.(Signature); ok; wrap, ok = hi.(Signature) {
hi = wrap.SignatureInner
}
return hi
}
func (h Signature) Empty() bool {
return h.SignatureInner == nil
}
/*** below are bindings for each implementation ***/
func init() {
SignatureMapper.RegisterImplementation(SignatureEd25519{}, "ed25519", 0x1)
}
func (hi SignatureEd25519) Wrap() Signature {
return Signature{hi}
}
func init() {
SignatureMapper.RegisterImplementation(SignatureSecp256k1{}, "secp256k1", 0x2)
}
func (hi SignatureSecp256k1) Wrap() Signature {
return Signature{hi}
}

+ 1
- 1
symmetric.go View File

@ -3,7 +3,7 @@ package crypto
import ( import (
"errors" "errors"
. "github.com/tendermint/go-common"
. "github.com/tendermint/tmlibs/common"
"golang.org/x/crypto/nacl/secretbox" "golang.org/x/crypto/nacl/secretbox"
) )


+ 3
- 0
version.go View File

@ -0,0 +1,3 @@
package crypto
const Version = "0.2.0"

Loading…
Cancel
Save