Browse Source

Merge pull request #11 from tendermint/unstable

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

+ 1
- 0
.gitignore View File

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

+ 20
- 2
Makefile View File

@ -1,9 +1,27 @@
.PHONY: docs
.PHONEY: all docs test install get_vendor_deps ensure_tools
GOTOOLS = \
github.com/Masterminds/glide
REPO:=github.com/tendermint/go-crypto
docs:
@go get github.com/davecheney/godoc2md
godoc2md $(REPO) > README.md
all: install test
install:
go install ./cmd/keys
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)

+ 1
- 1
armor.go View File

@ -4,7 +4,7 @@ import (
"bytes"
"io/ioutil"
. "github.com/tendermint/go-common"
. "github.com/tendermint/tmlibs/common"
"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="
}
}
]
```

+ 120
- 0
cmd/common.go View File

@ -0,0 +1,120 @@
package cmd
import (
"fmt"
"os"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/go-data/base58"
data "github.com/tendermint/go-wire/data"
)
/*******
TODO
This file should move into go-common or the like as a basis for all cli tools.
It is here for experimentation of re-use between go-keys and light-client.
*********/
const (
RootFlag = "root"
OutputFlag = "output"
EncodingFlag = "encoding"
)
func PrepareMainCmd(cmd *cobra.Command, envPrefix, defautRoot string) func() {
cobra.OnInitialize(func() { initEnv(envPrefix) })
cmd.PersistentFlags().StringP(RootFlag, "r", defautRoot, "root directory for config and data")
cmd.PersistentFlags().StringP(EncodingFlag, "e", "hex", "Binary encoding (hex|b64|btc)")
cmd.PersistentFlags().StringP(OutputFlag, "o", "text", "Output format (text|json)")
cmd.PersistentPreRunE = multiE(bindFlags, setEncoding, validateOutput, cmd.PersistentPreRunE)
return func() { execute(cmd) }
}
// initEnv sets to use ENV variables if set.
func initEnv(prefix string) {
// env variables with TM prefix (eg. TM_ROOT)
viper.SetEnvPrefix(prefix)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
}
// execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func execute(cmd *cobra.Command) {
if err := cmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
type wrapE func(cmd *cobra.Command, args []string) error
func multiE(fs ...wrapE) wrapE {
return func(cmd *cobra.Command, args []string) error {
for _, f := range fs {
if f != nil {
if err := f(cmd, args); err != nil {
return err
}
}
}
return nil
}
}
func bindFlags(cmd *cobra.Command, args []string) error {
// cmd.Flags() includes flags from this command and all persistent flags from the parent
if err := viper.BindPFlags(cmd.Flags()); err != nil {
return err
}
// rootDir is command line flag, env variable, or default $HOME/.tlc
rootDir := viper.GetString("root")
viper.SetConfigName("config") // name of config file (without extension)
viper.AddConfigPath(rootDir) // search root directory
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
// stderr, so if we redirect output to json file, this doesn't appear
// fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
} else if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
// we ignore not found error, only parse error
// stderr, so if we redirect output to json file, this doesn't appear
fmt.Fprintf(os.Stderr, "%#v", err)
}
return nil
}
// setEncoding reads the encoding flag
func setEncoding(cmd *cobra.Command, args []string) error {
// validate and set encoding
enc := viper.GetString("encoding")
switch enc {
case "hex":
data.Encoder = data.HexEncoder
case "b64":
data.Encoder = data.B64Encoder
case "btc":
data.Encoder = base58.BTCEncoder
default:
return errors.Errorf("Unsupported encoding: %s", enc)
}
return nil
}
func validateOutput(cmd *cobra.Command, args []string) error {
// validate output format
output := viper.GetString(OutputFlag)
switch output {
case "text", "json":
default:
return errors.Errorf("Unsupported output format: %s", output)
}
return nil
}

+ 47
- 0
cmd/get.go View File

@ -0,0 +1,47 @@
// 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/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.`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 || len(args[0]) == 0 {
fmt.Println("You must provide a name for the key")
return
}
name := args[0]
info, err := GetKeyManager().Get(name)
if err != nil {
fmt.Println(err.Error())
return
}
printInfo(info)
},
}
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"
)
func main() {
cmd.PrepareMainCmd(cmd.RootCmd, "TM", os.ExpandEnv("$HOME/.tlc"))
cmd.RootCmd.Execute()
// exec()
}

+ 42
- 0
cmd/list.go View File

@ -0,0 +1,42 @@
// 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/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.`,
Run: func(cmd *cobra.Command, args []string) {
infos, err := GetKeyManager().List()
if err != nil {
fmt.Println(err.Error())
return
}
printInfos(infos)
},
}
func init() {
RootCmd.AddCommand(listCmd)
}

+ 60
- 0
cmd/new.go View File

@ -0,0 +1,60 @@
// 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/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.`,
Run: newPassword,
}
func init() {
RootCmd.AddCommand(newCmd)
newCmd.Flags().StringP("type", "t", "ed25519", "Type of key (ed25519|secp256k1)")
}
func newPassword(cmd *cobra.Command, args []string) {
if len(args) != 1 || len(args[0]) == 0 {
fmt.Println("You must provide a name for the key")
return
}
name := args[0]
algo := viper.GetString("type")
pass, err := getCheckPassword("Enter a passphrase:", "Repeat the passphrase:")
if err != nil {
fmt.Println(err.Error())
return
}
info, err := GetKeyManager().Create(name, pass, algo)
if err != nil {
fmt.Println(err.Error())
return
}
printInfo(info)
}

+ 57
- 0
cmd/root.go View File

@ -0,0 +1,57 @@
// 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"
)
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("root")
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
}

+ 59
- 0
cmd/update.go View File

@ -0,0 +1,59 @@
// 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/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.`,
Run: updatePassword,
}
func init() {
RootCmd.AddCommand(updateCmd)
}
func updatePassword(cmd *cobra.Command, args []string) {
if len(args) != 1 || len(args[0]) == 0 {
fmt.Println("You must provide a name for the key")
return
}
name := args[0]
oldpass, err := getPassword("Enter the current passphrase:")
if err != nil {
fmt.Println(err.Error())
return
}
newpass, err := getCheckPassword("Enter the new passphrase:", "Repeat the new passphrase:")
if err != nil {
fmt.Println(err.Error())
return
}
err = GetKeyManager().Update(name, oldpass, newpass)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("Password successfully updated!")
}
}

+ 77
- 0
cmd/utils.go View File

@ -0,0 +1,77 @@
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"
)
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(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(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/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 {
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) {
@ -67,59 +30,61 @@ func (p *PubName) UnmarshalJSON(data []byte) error {
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
func TestEncodeDemo(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// assert := assert.New(t)
// require := require.New(t)
cases := []struct {
in, out Greeter
in, out PubNameInner
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 {
// 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)
if assert.Nil(err, "%d: %#v", i, tc.in) {
err := data.FromWire(b, tc.out)
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)
if assert.Nil(err, "%d: %#v", i, tc.in) {
err := data.FromJSON(j, tc.out)
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
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
data "github.com/tendermint/go-data"
data "github.com/tendermint/go-wire/data"
wire "github.com/tendermint/go-wire"
)
type byter interface {
@ -14,13 +16,15 @@ type byter interface {
}
// 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
bin, err := data.ToWire(in)
require.Nil(t, err, "%+v", err)
assert.Equal(t, typ, bin[0])
// make sure this is compatible with current (Bytes()) encoding
assert.Equal(t, in.Bytes(), bin)
// make sure we have the expected length
assert.Equal(t, size, len(bin))
err = data.FromWire(bin, reader)
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]))
}
// 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))
wire.ReadJSON(reader, js, &err)
require.Nil(t, err, "%+v", err)
}
func TestKeyEncodings(t *testing.T) {
cases := []struct {
privKey PrivKeyS
privKey PrivKey
keyType byte
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 {
// 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)
priv3 := PrivKeyS{}
checkJSON(t, tc.privKey, &priv3, tc.keyName)
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
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)
pub3 := PubKeyS{}
checkJSON(t, pubKey, &pub3, tc.keyName)
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) {
// make sure sigs are okay with nil
a, b := SignatureS{}, SignatureS{}
var a, b Signature
toFromJSON(t, a, &b)
assert.EqualValues(t, a, b)
// make sure sigs are okay with nil
c, d := PubKeyS{}, PubKeyS{}
var c, d PubKey
toFromJSON(t, c, &d)
assert.EqualValues(t, c, d)
// make sure sigs are okay with nil
e, f := PrivKeyS{}, PrivKeyS{}
var e, f PrivKey
toFromJSON(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)
}
}

+ 148
- 0
glide.lock View File

@ -0,0 +1,148 @@
hash: 8a63c035134ec024df64d8cc43a732712e48e4cfc5de30d45c1b692b3e9a75b8
updated: 2017-04-19T17:06:49.640329917+02:00
imports:
- name: github.com/bgentry/speakeasy
version: 675b82c74c0ed12283ee81ba8a534c8982c07b85
- name: github.com/btcsuite/btcd
version: 583684b21bfbde9b5fc4403916fd7c807feb0289
subpackages:
- btcec
- chaincfg
- chaincfg/chainhash
- wire
- name: github.com/btcsuite/btcutil
version: a5ecb5d9547afe8d1672073dbdc348203de744a0
subpackages:
- base58
- hdkeychain
- name: github.com/btcsuite/golangcrypto
version: 53f62d9b43e87a6c56975cf862af7edf33a8d0df
subpackages:
- ripemd160
- name: github.com/fsnotify/fsnotify
version: 4da3e2cfbabc9f751898f250b49f2439785783a1
- name: github.com/go-playground/locales
version: 084b0226cf88d891a2bdeccac01d592af13a8f7b
subpackages:
- currency
- name: github.com/go-playground/universal-translator
version: b32fa301c9fe55953584134cb6853a13c87ec0a1
- 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: 630949a3c5fa3c613328e1b8256052cbc2327c9b
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/magiconair/properties
version: 51463bfca2576e06c62a8504b5c0f06d61312647
- name: github.com/mattn/go-colorable
version: a392f450ea64cee2b268dfaacdc2502b50a22b18
- name: github.com/mattn/go-isatty
version: 57fdcb988a5c543893cc61bce354a6e24ab70022
- name: github.com/mitchellh/mapstructure
version: 53818660ed4955e899c0bcafa97299a388bd7c8e
- name: github.com/pelletier/go-buffruneio
version: c37440a7cf42ac63b919c752ca73a85067e05992
- name: github.com/pelletier/go-toml
version: 13d49d4606eb801b8f01ae542b4afc4c6ee3d84a
- name: github.com/pkg/errors
version: bfd5150e4e41705ded2129ec33379de1cb90b513
- name: github.com/spf13/afero
version: 9be650865eab0c12963d8753212f4f9c66cdcf12
subpackages:
- mem
- name: github.com/spf13/cast
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
- name: github.com/spf13/cobra
version: fcd0c5a1df88f5d6784cb4feead962c3f3d0b66c
- name: github.com/spf13/jwalterweatherman
version: fa7ca7e836cf3a8bb4ebf799f472c12d7e903d66
- name: github.com/spf13/pflag
version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7
- name: github.com/spf13/viper
version: 5d46e70da8c0b6f812e0b170b7a985753b5c63cb
- name: github.com/tendermint/ed25519
version: 1f52c6f8b8a5c7908aff4497c186af344b428925
subpackages:
- edwards25519
- extra25519
- name: github.com/tendermint/go-data
version: e7fcc6d081ec8518912fcdc103188275f83a3ee5
subpackages:
- base58
- name: github.com/tendermint/go-wire
version: 9127836cbb6dd99e020cb840a0cedcedc4671468
subpackages:
- data
- data/base58
- name: github.com/tendermint/log15
version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6
subpackages:
- term
- name: github.com/tendermint/tmlibs
version: 2f8551d3b614dd0c0c6c114c42ab25901cc41a52
subpackages:
- common
- logger
- name: golang.org/x/crypto
version: 728b753d0135da6801d45a38e6f43ff55779c5c2
subpackages:
- bcrypt
- blowfish
- nacl/secretbox
- openpgp/armor
- openpgp/errors
- pbkdf2
- poly1305
- ripemd160
- salsa20/salsa
- name: golang.org/x/sys
version: 99f16d856c9836c42d24e7ab64ea72916925fa97
subpackages:
- unix
- name: golang.org/x/text
version: f4b4367115ec2de254587813edaa901bc1c723a8
subpackages:
- transform
- unicode/norm
- name: gopkg.in/go-playground/validator.v9
version: 4bd19358521c53f09639f21e2a9d6883d6890f24
- 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: unstable
- package: github.com/tendermint/go-wire
version: unstable
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"
)
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(
HexDecode(pubKeyHex),
HexDecode(chainHex),
fmt.Sprintf("%v/%v", path, index),
)
return AddrFromPubKeyBytes(coin, pubKeyBytes)
return AddrFromPubKeyBytes(pubKeyBytes)
}
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)
}
func ComputeAddressForPrivKey(coin string, privKey string) string {
func ComputeAddressForPrivKey(privKey string) string {
pubKeyBytes := PubKeyBytesFromPrivKeyBytes(HexDecode(privKey), true)
return AddrFromPubKeyBytes(coin, pubKeyBytes)
return AddrFromPubKeyBytes(pubKeyBytes)
}
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)
}
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 {
@ -100,7 +92,7 @@ func printKeyInfo(privKeyBytes []byte, pubKeyBytes []byte, chain []byte) {
if pubKeyBytes == nil {
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",
HexEncode(privKeyBytes),
HexEncode(pubKeyBytes),
@ -225,7 +217,8 @@ func I64(key []byte, data []byte) ([]byte, []byte) {
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
h160 := CalcHash160(pubKeyBytes)
h160 = append([]byte{prefix}, h160...)
@ -234,7 +227,15 @@ func AddrFromPubKeyBytes(coin string, pubKeyBytes []byte) string {
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
bytes := append([]byte{prefix}, privKeyBytes...)
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)
}

+ 70
- 0
keys/transactions.go View File

@ -0,0 +1,70 @@
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 {
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))
}
}
}

+ 59
- 49
priv_key.go View File

@ -6,91 +6,89 @@ import (
secp256k1 "github.com/btcsuite/btcd/btcec"
"github.com/tendermint/ed25519"
"github.com/tendermint/ed25519/extra25519"
. "github.com/tendermint/go-common"
data "github.com/tendermint/go-data"
. "github.com/tendermint/tmlibs/common"
data "github.com/tendermint/go-wire/data"
"github.com/tendermint/go-wire"
)
// PrivKey is part of PrivAccount and state.PrivValidator.
type PrivKey interface {
Bytes() []byte
Sign(msg []byte) Signature
PubKey() PubKey
Equals(PrivKey) bool
func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) {
err = wire.ReadBinaryBytes(privKeyBytes, &privKey)
return
}
// 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
type PrivKey struct {
PrivKeyInner `json:"unwrap"`
}
func WrapPrivKey(pk PrivKey) PrivKeyS {
for ppk, ok := pk.(PrivKeyS); ok; ppk, ok = pk.(PrivKeyS) {
pk = ppk.PrivKey
}
return PrivKeyS{pk}
// DO NOT USE THIS INTERFACE.
// You probably want to use PubKey
type PrivKeyInner interface {
AssertIsPrivKeyInner()
Bytes() []byte
Sign(msg []byte) Signature
PubKey() PubKey
Equals(PrivKey) bool
Wrap() PrivKey
}
func (p PrivKeyS) MarshalJSON() ([]byte, error) {
return privKeyMapper.ToJSON(p.PrivKey)
func (p PrivKey) MarshalJSON() ([]byte, error) {
return privKeyMapper.ToJSON(p.PrivKeyInner)
}
func (p *PrivKeyS) UnmarshalJSON(data []byte) (err error) {
func (p *PrivKey) UnmarshalJSON(data []byte) (err error) {
parsed, err := privKeyMapper.FromJSON(data)
if err == nil && parsed != nil {
p.PrivKey = parsed.(PrivKey)
p.PrivKeyInner = parsed.(PrivKeyInner)
}
return
}
func (p PrivKeyS) Empty() bool {
return p.PrivKey == nil
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
func (p PrivKey) Unwrap() PrivKeyInner {
pk := p.PrivKeyInner
for wrap, ok := pk.(PrivKey); ok; wrap, ok = pk.(PrivKey) {
pk = wrap.PrivKeyInner
}
return pk
}
func PrivKeyFromBytes(privKeyBytes []byte) (privKey PrivKey, err error) {
err = wire.ReadBinaryBytes(privKeyBytes, &privKey)
return
func (p PrivKey) Empty() bool {
return p.PrivKeyInner == nil
}
var privKeyMapper = data.NewMapper(PrivKey{}).
RegisterImplementation(PrivKeyEd25519{}, NameEd25519, TypeEd25519).
RegisterImplementation(PrivKeySecp256k1{}, NameSecp256k1, TypeSecp256k1)
//-------------------------------------
var _ PrivKeyInner = PrivKeyEd25519{}
// Implements PrivKey
type PrivKeyEd25519 [64]byte
func (privKey PrivKeyEd25519) AssertIsPrivKeyInner() {}
func (privKey PrivKeyEd25519) Bytes() []byte {
return wire.BinaryBytes(struct{ PrivKey }{privKey})
return wire.BinaryBytes(PrivKey{privKey})
}
func (privKey PrivKeyEd25519) Sign(msg []byte) Signature {
privKeyBytes := [64]byte(privKey)
signatureBytes := ed25519.Sign(&privKeyBytes, msg)
return SignatureEd25519(*signatureBytes)
return SignatureEd25519(*signatureBytes).Wrap()
}
func (privKey PrivKeyEd25519) PubKey() PubKey {
privKeyBytes := [64]byte(privKey)
return PubKeyEd25519(*ed25519.MakePublicKey(&privKeyBytes))
pubBytes := *ed25519.MakePublicKey(&privKeyBytes)
return PubKeyEd25519(pubBytes).Wrap()
}
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[:])
} else {
return false
@ -130,6 +128,10 @@ func (privKey PrivKeyEd25519) Generate(index int) PrivKeyEd25519 {
return PrivKeyEd25519(newKey)
}
func (privKey PrivKeyEd25519) Wrap() PrivKey {
return PrivKey{privKey}
}
func GenPrivKeyEd25519() PrivKeyEd25519 {
privKeyBytes := new([64]byte)
copy(privKeyBytes[:32], CRandBytes(32))
@ -149,11 +151,15 @@ func GenPrivKeyEd25519FromSecret(secret []byte) PrivKeyEd25519 {
//-------------------------------------
var _ PrivKeyInner = PrivKeySecp256k1{}
// Implements PrivKey
type PrivKeySecp256k1 [32]byte
func (privKey PrivKeySecp256k1) AssertIsPrivKeyInner() {}
func (privKey PrivKeySecp256k1) Bytes() []byte {
return wire.BinaryBytes(struct{ PrivKey }{privKey})
return wire.BinaryBytes(PrivKey{privKey})
}
func (privKey PrivKeySecp256k1) Sign(msg []byte) Signature {
@ -162,18 +168,18 @@ func (privKey PrivKeySecp256k1) Sign(msg []byte) Signature {
if err != nil {
PanicSanity(err)
}
return SignatureSecp256k1(sig__.Serialize())
return SignatureSecp256k1(sig__.Serialize()).Wrap()
}
func (privKey PrivKeySecp256k1) PubKey() PubKey {
_, pub__ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:])
var pub PubKeySecp256k1
copy(pub[:], pub__.SerializeCompressed())
return pub
return pub.Wrap()
}
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[:])
} else {
return false
@ -195,6 +201,10 @@ func (privKey PrivKeySecp256k1) String() string {
return Fmt("PrivKeySecp256k1{*****}")
}
func (privKey PrivKeySecp256k1) Wrap() PrivKey {
return PrivKey{privKey}
}
/*
// Deterministically generates new priv-key bytes from key.
func (key PrivKeySecp256k1) Generate(index int) PrivKeySecp256k1 {


+ 58
- 49
pub_key.go View File

@ -7,68 +7,73 @@ import (
secp256k1 "github.com/btcsuite/btcd/btcec"
"github.com/tendermint/ed25519"
"github.com/tendermint/ed25519/extra25519"
. "github.com/tendermint/go-common"
data "github.com/tendermint/go-data"
. "github.com/tendermint/tmlibs/common"
data "github.com/tendermint/go-wire/data"
"github.com/tendermint/go-wire"
"golang.org/x/crypto/ripemd160"
)
// PubKey is part of Account and Validator.
type PubKey interface {
Address() []byte
Bytes() []byte
KeyString() string
VerifyBytes(msg []byte, sig Signature) bool
Equals(PubKey) bool
func PubKeyFromBytes(pubKeyBytes []byte) (pubKey PubKey, err error) {
err = wire.ReadBinaryBytes(pubKeyBytes, &pubKey)
return
}
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)
type PubKey struct {
PubKeyInner `json:"unwrap"`
}
// 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}
// DO NOT USE THIS INTERFACE.
// You probably want to use PubKey
type PubKeyInner interface {
AssertIsPubKeyInner()
Address() []byte
Bytes() []byte
KeyString() string
VerifyBytes(msg []byte, sig Signature) bool
Equals(PubKey) bool
Wrap() PubKey
}
func (p PubKeyS) MarshalJSON() ([]byte, error) {
return pubKeyMapper.ToJSON(p.PubKey)
func (pk PubKey) MarshalJSON() ([]byte, error) {
return pubKeyMapper.ToJSON(pk.PubKeyInner)
}
func (p *PubKeyS) UnmarshalJSON(data []byte) (err error) {
func (pk *PubKey) UnmarshalJSON(data []byte) (err error) {
parsed, err := pubKeyMapper.FromJSON(data)
if err == nil && parsed != nil {
p.PubKey = parsed.(PubKey)
pk.PubKeyInner = parsed.(PubKeyInner)
}
return
}
func (p PubKeyS) Empty() bool {
return p.PubKey == nil
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
func (pk PubKey) Unwrap() PubKeyInner {
pkI := pk.PubKeyInner
for wrap, ok := pkI.(PubKey); ok; wrap, ok = pkI.(PubKey) {
pkI = wrap.PubKeyInner
}
return pkI
}
func PubKeyFromBytes(pubKeyBytes []byte) (pubKey PubKey, err error) {
err = wire.ReadBinaryBytes(pubKeyBytes, &pubKey)
return
func (p PubKey) Empty() bool {
return p.PubKeyInner == nil
}
var pubKeyMapper = data.NewMapper(PubKey{}).
RegisterImplementation(PubKeyEd25519{}, NameEd25519, TypeEd25519).
RegisterImplementation(PubKeySecp256k1{}, NameSecp256k1, TypeSecp256k1)
//-------------------------------------
// Implements PubKey
var _ PubKeyInner = PubKeyEd25519{}
// Implements PubKeyInner
type PubKeyEd25519 [32]byte
func (pubKey PubKeyEd25519) AssertIsPubKeyInner() {}
func (pubKey PubKeyEd25519) Address() []byte {
w, n, err := new(bytes.Buffer), new(int), new(error)
wire.WriteBinary(pubKey[:], w, n, err)
@ -83,16 +88,12 @@ func (pubKey PubKeyEd25519) Address() []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 {
// unwrap if needed
if wrap, ok := sig_.(SignatureS); ok {
sig_ = wrap.Signature
}
// make sure we use the same algorithm to sign
sig, ok := sig_.(SignatureEd25519)
sig, ok := sig_.Unwrap().(SignatureEd25519)
if !ok {
return false
}
@ -134,20 +135,28 @@ func (pubKey PubKeyEd25519) KeyString() string {
}
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[:])
} else {
return false
}
}
func (pubKey PubKeyEd25519) Wrap() PubKey {
return PubKey{pubKey}
}
//-------------------------------------
var _ PubKeyInner = PubKeySecp256k1{}
// Implements PubKey.
// Compressed pubkey (just the x-cord),
// prefixed with 0x02 or 0x03, depending on the y-cord.
type PubKeySecp256k1 [33]byte
func (pubKey PubKeySecp256k1) AssertIsPubKeyInner() {}
// Implements Bitcoin style addresses: RIPEMD160(SHA256(pubkey))
func (pubKey PubKeySecp256k1) Address() []byte {
hasherSHA256 := sha256.New()
@ -160,16 +169,12 @@ func (pubKey PubKeySecp256k1) Address() []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 {
// unwrap if needed
if wrap, ok := sig_.(SignatureS); ok {
sig_ = wrap.Signature
}
// and assert same algorithm to sign and verify
sig, ok := sig_.(SignatureSecp256k1)
sig, ok := sig_.Unwrap().(SignatureSecp256k1)
if !ok {
return false
}
@ -207,9 +212,13 @@ func (pubKey PubKeySecp256k1) KeyString() string {
}
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[:])
} else {
return false
}
}
func (pubKey PubKeySecp256k1) Wrap() PubKey {
return PubKey{pubKey}
}

+ 1
- 1
pub_key_test.go View File

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


+ 1
- 1
random.go View File

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


+ 62
- 45
signature.go View File

@ -4,68 +4,73 @@ import (
"bytes"
"fmt"
. "github.com/tendermint/go-common"
data "github.com/tendermint/go-data"
. "github.com/tendermint/tmlibs/common"
data "github.com/tendermint/go-wire/data"
"github.com/tendermint/go-wire"
)
// Signature is a part of Txs and consensus Votes.
type Signature interface {
Bytes() []byte
IsZero() bool
String() string
Equals(Signature) bool
func SignatureFromBytes(sigBytes []byte) (sig Signature, err error) {
err = wire.ReadBinaryBytes(sigBytes, &sig)
return
}
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)
type Signature struct {
SignatureInner `json:"unwrap"`
}
// 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}
// DO NOT USE THIS INTERFACE.
// You probably want to use Signature.
type SignatureInner interface {
AssertIsSignatureInner()
Bytes() []byte
IsZero() bool
String() string
Equals(Signature) bool
Wrap() Signature
}
func (p SignatureS) MarshalJSON() ([]byte, error) {
return sigMapper.ToJSON(p.Signature)
func (sig Signature) MarshalJSON() ([]byte, error) {
return sigMapper.ToJSON(sig.SignatureInner)
}
func (p *SignatureS) UnmarshalJSON(data []byte) (err error) {
func (sig *Signature) UnmarshalJSON(data []byte) (err error) {
parsed, err := sigMapper.FromJSON(data)
if err == nil && parsed != nil {
p.Signature = parsed.(Signature)
sig.SignatureInner = parsed.(SignatureInner)
}
return
}
func (p SignatureS) Empty() bool {
return p.Signature == nil
// Unwrap recovers the concrete interface safely (regardless of levels of embeds)
func (sig Signature) Unwrap() SignatureInner {
pk := sig.SignatureInner
for wrap, ok := pk.(Signature); ok; wrap, ok = pk.(Signature) {
pk = wrap.SignatureInner
}
return pk
}
func SignatureFromBytes(sigBytes []byte) (sig Signature, err error) {
err = wire.ReadBinaryBytes(sigBytes, &sig)
return
func (sig Signature) Empty() bool {
return sig.SignatureInner == nil
}
var sigMapper = data.NewMapper(Signature{}).
RegisterImplementation(SignatureEd25519{}, NameEd25519, TypeEd25519).
RegisterImplementation(SignatureSecp256k1{}, NameSecp256k1, TypeSecp256k1)
//-------------------------------------
var _ SignatureInner = SignatureEd25519{}
// Implements Signature
type SignatureEd25519 [64]byte
func (sig SignatureEd25519) AssertIsSignatureInner() {}
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 }
@ -73,31 +78,39 @@ func (sig SignatureEd25519) IsZero() bool { return len(sig) == 0 }
func (sig SignatureEd25519) String() string { return fmt.Sprintf("/%X.../", Fingerprint(sig[:])) }
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[:])
} else {
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
err := data.Encoder.Unmarshal(&ref, enc)
copy(p[:], ref)
copy(sig[:], ref)
return err
}
func (sig SignatureEd25519) Wrap() Signature {
return Signature{sig}
}
//-------------------------------------
var _ SignatureInner = SignatureSecp256k1{}
// Implements Signature
type SignatureSecp256k1 []byte
func (sig SignatureSecp256k1) AssertIsSignatureInner() {}
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 }
@ -105,16 +118,20 @@ func (sig SignatureSecp256k1) IsZero() bool { return len(sig) == 0 }
func (sig SignatureSecp256k1) String() string { return fmt.Sprintf("/%X.../", Fingerprint(sig[:])) }
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[:])
} else {
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 (sig *SignatureSecp256k1) UnmarshalJSON(enc []byte) error {
return data.Encoder.Unmarshal((*[]byte)(sig), enc)
}
func (p *SignatureSecp256k1) UnmarshalJSON(enc []byte) error {
return data.Encoder.Unmarshal((*[]byte)(p), enc)
func (sig SignatureSecp256k1) Wrap() Signature {
return Signature{sig}
}

+ 26
- 26
signature_test.go View File

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


+ 1
- 1
symmetric.go View File

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


Loading…
Cancel
Save