Skip to content

Commit

Permalink
Keystore implementation for substrate chain (cosmos#922)
Browse files Browse the repository at this point in the history
* add substrate keystore implementation

* implement key provider for substrate chain

* clean up keystore code

* run go mod tidy moving rename test to _test
* remove verbs from error messages
* correct interface method comments in Keyring and Info types
* initialize log in NewProvider method

* move type definitions to method implementation files

* check if KeyDirectory is empty before overwriting it in the NewProvider method

* remove comments in ExportPrivKeyArmor

* bump substrate-rpc-client to latest master

* change network type to uint16

* update go.mod in ibctest
  • Loading branch information
oshorefueled committed Oct 20, 2022
1 parent 1bbccc9 commit 8c6d418
Show file tree
Hide file tree
Showing 8 changed files with 1,123 additions and 14 deletions.
32 changes: 18 additions & 14 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/cosmos/relayer/v2
go 1.18

require (
github.com/99designs/keyring v1.2.1
github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220820010439-71c9d526f2f5
github.com/avast/retry-go/v4 v4.1.0
github.com/cosmos/cosmos-sdk v0.46.1
github.com/cosmos/ibc-go/v5 v5.0.0
Expand All @@ -12,6 +14,7 @@ require (
github.com/jsternberg/zap-logfmt v1.2.0
github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b
github.com/ory/dockertest/v3 v3.9.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.2
github.com/spf13/cobra v1.5.0
github.com/spf13/viper v1.12.0
Expand All @@ -34,9 +37,9 @@ require (
cosmossdk.io/math v1.0.0-beta.3 // indirect
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/Workiva/go-datastructures v1.0.53 // indirect
Expand Down Expand Up @@ -71,7 +74,7 @@ require (
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/docker/cli v20.10.14+incompatible // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v20.10.17+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
Expand Down Expand Up @@ -107,16 +110,16 @@ require (
github.com/hashicorp/go-getter v1.6.1 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/go-version v1.4.0 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/klauspost/compress v1.15.1 // indirect
github.com/lib/pq v1.10.6 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
Expand All @@ -131,7 +134,7 @@ require (
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.3 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.2 // indirect
Expand All @@ -147,7 +150,7 @@ require (
github.com/rs/cors v1.8.2 // indirect
github.com/rs/zerolog v1.27.0 // indirect
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
Expand All @@ -160,24 +163,23 @@ require (
github.com/tendermint/tm-db v0.6.7 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/zondax/hid v0.9.1-0.20220302062450-5552068d2266 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/net v0.0.0-20220726230323-06994584191e // indirect
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sys v0.0.0-20220727055044-e65921a090b8 // indirect
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/api v0.81.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220725144611-272f38e5d71b // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03 // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
Expand All @@ -186,3 +188,5 @@ require (
)

replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1

replace github.com/ChainSafe/go-schnorrkel v1.0.0 => github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d
125 changes: 125 additions & 0 deletions relayer/chains/substrate/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package substrate

import (
"os"

"github.com/cosmos/go-bip39"
"github.com/cosmos/relayer/v2/relayer/chains/substrate/keystore"
"github.com/cosmos/relayer/v2/relayer/provider"
)

func (sp *SubstrateProvider) CreateKeystore(path string) error {
keybase, err := keystore.New(sp.PCfg.ChainID, sp.PCfg.KeyringBackend, sp.PCfg.KeyDirectory, sp.Input)
if err != nil {
return err
}
sp.Keybase = keybase
return nil
}

func (sp *SubstrateProvider) KeystoreCreated(path string) bool {
if _, err := os.Stat(path); err != nil || sp.Keybase == nil {
return false
}
return true
}

func (sp *SubstrateProvider) AddKey(name string, coinType uint32) (output *provider.KeyOutput, err error) {
ko, err := sp.KeyAddOrRestore(name, coinType)
if err != nil {
return nil, err
}

return ko, nil
}

func (sp *SubstrateProvider) RestoreKey(name, mnemonic string, coinType uint32) (address string, err error) {
ko, err := sp.KeyAddOrRestore(name, coinType, mnemonic)
if err != nil {
return "", err
}
return ko.Address, nil
}

func (sp *SubstrateProvider) ShowAddress(name string) (address string, err error) {

info, err := sp.Keybase.Key(name)
if err != nil {
return "", err
}
return info.GetAddress(), nil
}

func (sp *SubstrateProvider) ListAddresses() (map[string]string, error) {

out := map[string]string{}
info, err := sp.Keybase.List()
if err != nil {
return nil, err
}
for _, k := range info {
addr := k.GetAddress()
out[k.GetName()] = addr
}
return out, nil
}

func (sp *SubstrateProvider) DeleteKey(name string) error {

if err := sp.Keybase.Delete(name); err != nil {
return err
}

return nil
}

func (sp *SubstrateProvider) KeyExists(name string) bool {
if sp.Keybase == nil {
return false
}

k, err := sp.Keybase.Key(name)
if err != nil {
return false
}
return k.GetName() == name
}

func (sp *SubstrateProvider) ExportPrivKeyArmor(keyName string) (armor string, err error) {
return "", nil
}

func (sp *SubstrateProvider) KeyAddOrRestore(keyName string, coinType uint32, mnemonic ...string) (*provider.KeyOutput, error) {

var mnemonicStr string
var err error

if len(mnemonic) > 0 {
mnemonicStr = mnemonic[0]
} else {
mnemonicStr, err = CreateMnemonic()
if err != nil {
return nil, err
}
}

info, err := sp.Keybase.NewAccount(keyName, mnemonicStr, sp.PCfg.Network)
if err != nil {
return nil, err
}

return &provider.KeyOutput{Mnemonic: mnemonicStr, Address: info.GetAddress()}, nil
}

// CreateMnemonic creates a new mnemonic
func CreateMnemonic() (string, error) {
entropySeed, err := bip39.NewEntropy(256)
if err != nil {
return "", err
}
mnemonic, err := bip39.NewMnemonic(entropySeed)
if err != nil {
return "", err
}
return mnemonic, nil
}
36 changes: 36 additions & 0 deletions relayer/chains/substrate/key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package substrate_test

import (
"testing"

"github.com/cosmos/relayer/v2/relayer/chains/substrate/keystore"

"github.com/stretchr/testify/require"
)

// TestKeyRestore restores a test mnemonic
func TestKeyRestoreAndRetrieve(t *testing.T) {
keyName := "test_key"
mnemonic := "blind master acoustic speak victory lend kiss grab glad help demand hood roast zone lend sponsor level cheap truck kingdom apology token hover reunion"
expectedAddress := "5Hn67YZ75F3XrHiJAtiscJMGQ4zFNw9e45CNfLuxL6vEVYz8"

provider, err := getTestProvider()
require.Nil(t, err)

config := getSubstrateConfig(homePath, 42)
provider.Keybase, err = keystore.New(config.ChainName, config.KeyringBackend, config.KeyDirectory, nil)
require.Nil(t, err)

if provider.KeyExists(keyName) {
err = provider.DeleteKey(keyName) // Delete if test is being run again
require.Nil(t, err)
}

address, err := provider.RestoreKey(keyName, mnemonic, 0)
require.Nil(t, err)
require.Equal(t, expectedAddress, address, "Restored address: %s does not match expected: %s", address, expectedAddress)

retrievedAddress, err := provider.ShowAddress(keyName)
require.Nil(t, err)
require.Equal(t, expectedAddress, address, "Restored address: %s does not match expected: %s", retrievedAddress, expectedAddress)
}
14 changes: 14 additions & 0 deletions relayer/chains/substrate/keystore/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package keystore

const (
ErrTextKeyNotFound = "key not found"
ErrTextKeyWithAddressNotFound = "key with address not found"
ErrTextAddressExists = "account with address already exists in keyring"
ErrTextPubkeyExists = "public key already exists in keystore"
ErrTextUnknownKeyringBackend = "unknown keyring backend"
ErrTextFailedToRead = "failed to read"
ErrTextFailedToOpen = "failed to open"
ErrTextTooManyWrongPassphrases = "too many failed passphrase attempts"
ErrTextIncorrectPassphrase = "incorrect passphrase"
ErrTextPassphraseDoNotMatch = "passphrase do not match"
)
70 changes: 70 additions & 0 deletions relayer/chains/substrate/keystore/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package keystore

import (
"encoding/json"
"fmt"

"github.com/ComposableFi/go-substrate-rpc-client/v4/signature"
)

// Info is the publicly exposed information about a keypair
type Info interface {
// GetName returns the name of the underlying key
GetName() string
// GetAddress returns the address of the underlying key as a string
GetAddress() string
// GetPublicKey returns the public key as bytes
GetPublicKey() []byte
// GetKeyringPair returns the keyring pair
GetKeyringPair() signature.KeyringPair
}

// localInfo is the public information about a locally stored key
type localInfo struct {
Name string `json:"name"`
Keypair signature.KeyringPair `json:"keypair"`
}

func newLocalInfo(name string, kp signature.KeyringPair) Info {
return &localInfo{
Name: name,
Keypair: kp,
}
}

func (i localInfo) GetAddress() string {
return i.Keypair.Address
}

func (i localInfo) GetName() string {
return i.Name
}

func (i localInfo) GetPublicKey() []byte {
return i.Keypair.PublicKey
}

func (i localInfo) GetKeyringPair() signature.KeyringPair {
return i.Keypair
}

func marshalInfo(i Info) ([]byte, error) {
marshalled, err := json.Marshal(i)
if err != nil {
return nil, err
}
return marshalled, nil
}

func unmarshalInfo(bz []byte) (Info, error) {
localInfo := localInfo{}
if err := json.Unmarshal(bz, &localInfo); err != nil {
return nil, err
}

return localInfo, nil
}

func infoKey(name string) string { return fmt.Sprintf("%s.%s", name, infoSuffix) }

func infoKeyBz(name string) []byte { return []byte(infoKey(name)) }
Loading

0 comments on commit 8c6d418

Please sign in to comment.