[#159] Add keyer to neofs-cli

Keyer prints information about private key, public key,
NEO3 Wallet, scripthash. It can generate new private key
or generate multisig address.

Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
Alex Vanin 2020-11-06 15:58:39 +03:00 committed by Alex Vanin
parent fcb35d82cf
commit ded45e1fbc
3 changed files with 337 additions and 0 deletions

View file

@ -2,19 +2,24 @@ package cmd
import (
"bytes"
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"time"
"github.com/nspcc-dev/neofs-api-go/pkg"
"github.com/nspcc-dev/neofs-api-go/pkg/token"
v2ACL "github.com/nspcc-dev/neofs-api-go/v2/acl"
"github.com/nspcc-dev/neofs-node/pkg/util/keyer"
"github.com/spf13/cobra"
)
var errKeyerSingleArgument = errors.New("pass only one argument at a time")
var (
utilCmd = &cobra.Command{
Use: "util",
@ -42,6 +47,12 @@ var (
Short: "convert representation of extended ACL table",
RunE: convertEACLTable,
}
keyerCmd = &cobra.Command{
Use: "keyer",
Short: "generate or print information about keys",
RunE: processKeyer,
}
)
func init() {
@ -49,6 +60,7 @@ func init() {
utilCmd.AddCommand(signCmd)
utilCmd.AddCommand(convertCmd)
utilCmd.AddCommand(keyerCmd)
signCmd.AddCommand(signBearerCmd)
signBearerCmd.Flags().String("from", "", "File with JSON or binary encoded bearer token to sign")
@ -63,6 +75,11 @@ func init() {
_ = convertEACLCmd.MarkFlagRequired("from")
convertEACLCmd.Flags().String("to", "", "File to dump extended ACL table (default: binary encoded)")
convertEACLCmd.Flags().Bool("json", false, "Dump extended ACL table in JSON encoding")
keyerCmd.Flags().BoolP("generate", "g", false, "generate new private key")
keyerCmd.Flags().Bool("hex", false, "print all values in hex encoding")
keyerCmd.Flags().BoolP("uncompressed", "u", false, "use uncompressed public key format")
keyerCmd.Flags().BoolP("multisig", "m", false, "calculate multisig address from public keys")
}
func signBearerToken(cmd *cobra.Command, _ []string) error {
@ -157,6 +174,48 @@ func convertEACLTable(cmd *cobra.Command, _ []string) error {
return nil
}
func processKeyer(cmd *cobra.Command, args []string) error {
var (
err error
result = new(keyer.Dashboard)
generate, _ = cmd.Flags().GetBool("generate")
useHex, _ = cmd.Flags().GetBool("hex")
uncompressed, _ = cmd.Flags().GetBool("uncompressed")
multisig, _ = cmd.Flags().GetBool("multisig")
)
if multisig {
err = result.ParseMultiSig(args)
} else {
if len(args) > 1 {
return errKeyerSingleArgument
}
var argument string
if len(args) > 0 {
argument = args[0]
}
switch {
case generate:
err = keyerGenerate(argument, result)
case fileExists(argument):
err = keyerParseFile(argument, result)
default:
err = result.ParseString(argument)
}
}
if err != nil {
return err
}
result.PrettyPrint(uncompressed, useHex)
return nil
}
func completeBearerToken(btok *token.BearerToken) error {
if v2 := btok.ToV2(); v2 != nil {
// set eACL table version, because it usually omitted
@ -191,3 +250,41 @@ func prettyPrintUnixTime(s string) string {
return timestamp.String()
}
func keyerGenerate(filename string, d *keyer.Dashboard) error {
key := make([]byte, keyer.NeoPrivateKeySize)
_, err := rand.Read(key)
if err != nil {
return fmt.Errorf("can't get random source: %w", err)
}
err = d.ParseBinary(key)
if err != nil {
return fmt.Errorf("can't parse key: %w", err)
}
if filename != "" {
return ioutil.WriteFile(filename, key, 0600)
}
return nil
}
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func keyerParseFile(filename string, d *keyer.Dashboard) error {
data, err := ioutil.ReadFile(filename)
if err != nil {
return fmt.Errorf("can't open %v file: %w", filename, err)
}
return d.ParseBinary(data)
}

122
pkg/util/keyer/dashboard.go Normal file
View file

@ -0,0 +1,122 @@
package keyer
import (
"crypto/elliptic"
"encoding/hex"
"fmt"
"os"
"text/tabwriter"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
)
type (
Dashboard struct {
privKey *keys.PrivateKey
pubKey *keys.PublicKey
scriptHash3 util.Uint160
multisigKeys keys.PublicKeys
}
)
func (d Dashboard) PrettyPrint(uncompressed, useHex bool) {
var (
data []byte
privKey, pubKey, wif, wallet3, sh3, shBE3, multiSigAddr string
)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
if d.privKey != nil {
privKey = d.privKey.String()
wif = d.privKey.WIF()
if useHex {
wif = base58ToHex(wif)
}
}
if d.pubKey != nil {
if uncompressed {
data = elliptic.Marshal(elliptic.P256(), d.pubKey.X, d.pubKey.Y)
} else {
data = d.pubKey.Bytes()
}
pubKey = hex.EncodeToString(data)
}
if !d.scriptHash3.Equals(util.Uint160{}) {
sh3 = d.scriptHash3.StringLE()
shBE3 = d.scriptHash3.StringBE()
wallet3 = address.Uint160ToString(d.scriptHash3)
if useHex {
wallet3 = base58ToHex(wallet3)
}
}
if len(d.multisigKeys) != 0 {
u160, err := scriptHashFromMultikey(d.multisigKeys)
if err != nil {
panic("can't create multisig redeem script")
}
multiSigAddr = address.Uint160ToString(u160)
}
if privKey != "" {
fmt.Fprintf(w, "PrivateKey\t%s\n", privKey)
}
if pubKey != "" {
fmt.Fprintf(w, "PublicKey\t%s\n", pubKey)
}
if wif != "" {
fmt.Fprintf(w, "WIF\t%s\n", wif)
}
if wallet3 != "" {
fmt.Fprintf(w, "Wallet3.0\t%s\n", wallet3)
}
if sh3 != "" {
fmt.Fprintf(w, "ScriptHash3.0\t%s\n", sh3)
}
if shBE3 != "" {
fmt.Fprintf(w, "ScriptHash3.0BE\t%s\n", shBE3)
}
if multiSigAddr != "" {
fmt.Fprintf(w, "MultiSigAddress\t%s\n", multiSigAddr)
}
w.Flush()
}
func base58ToHex(data string) string {
val, err := base58.Decode(data)
if err != nil {
panic("produced incorrect base58 value")
}
return hex.EncodeToString(val)
}
func scriptHashFromMultikey(k keys.PublicKeys) (util.Uint160, error) {
script, err := smartcontract.CreateDefaultMultiSigRedeemScript(k)
if err != nil {
return util.Uint160{}, fmt.Errorf("can't create multisig redeem script: %w", err)
}
return hash.Hash160(script), nil
}

118
pkg/util/keyer/parser.go Normal file
View file

@ -0,0 +1,118 @@
package keyer
import (
"crypto/elliptic"
"encoding/hex"
"errors"
"fmt"
"strings"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/util"
)
const (
NeoPrivateKeySize = 32
scriptHashSize = 20
addressSize = 25
publicKeyCompressedSize = 33
wifSize = 38
publicKeyUncompressedSize = 65
)
var errInputType = errors.New("unknown input type")
func (d *Dashboard) ParseString(data string) error {
// just in case remove 0x prefixes if there are some
data = strings.TrimPrefix(data, "0x")
data = strings.TrimPrefix(data, "0X")
var (
rawData []byte
err error
)
// data could be encoded in base58 or hex formats, try both
rawData, err = base58.Decode(data)
if err != nil {
rawData, err = hex.DecodeString(data)
if err != nil {
return fmt.Errorf("data is not hex or base58 encoded: %w", err)
}
}
return d.ParseBinary(rawData)
}
func (d *Dashboard) ParseBinary(data []byte) error {
var err error
switch len(data) {
case NeoPrivateKeySize:
d.privKey, err = keys.NewPrivateKeyFromBytes(data)
if err != nil {
return fmt.Errorf("can't parse private key: %w", err)
}
case wifSize:
d.privKey, err = keys.NewPrivateKeyFromWIF(base58.Encode(data))
if err != nil {
return fmt.Errorf("can't parse WIF: %w", err)
}
case publicKeyCompressedSize, publicKeyUncompressedSize:
d.pubKey, err = keys.NewPublicKeyFromBytes(data, elliptic.P256())
if err != nil {
return fmt.Errorf("can't parse public key: %w", err)
}
case addressSize:
d.scriptHash3, err = address.StringToUint160(base58.Encode(data))
if err != nil {
return fmt.Errorf("can't parse address: %w", err)
}
case scriptHashSize:
sc, err := util.Uint160DecodeBytesLE(data)
if err != nil {
return fmt.Errorf("can't parse script hash: %w", err)
}
d.scriptHash3 = sc
default:
return errInputType
}
d.fill()
return nil
}
func (d *Dashboard) ParseMultiSig(data []string) error {
d.multisigKeys = make(keys.PublicKeys, 0, len(data))
for i := range data {
data, err := hex.DecodeString(data[i])
if err != nil {
return fmt.Errorf("pass only hex encoded public keys: %w", err)
}
key, err := keys.NewPublicKeyFromBytes(data, elliptic.P256())
if err != nil {
return fmt.Errorf("pass only hex encoded public keys: %w", err)
}
d.multisigKeys = append(d.multisigKeys, key)
}
return nil
}
func (d *Dashboard) fill() {
if d.privKey != nil {
d.pubKey = d.privKey.PublicKey()
}
if d.pubKey != nil {
d.scriptHash3 = d.pubKey.GetScriptHash()
}
}