diff --git a/client/keyring/README.md b/client/keyring/README.md new file mode 100644 index 00000000..97872e75 --- /dev/null +++ b/client/keyring/README.md @@ -0,0 +1,103 @@ +# Injective Chain Keyring Helper + +Creates a new keyring from a variety of options. See `ConfigOpt` and related options. This keyring helper allows to initialize Cosmos SDK keyring used for signing transactions. + +It allows flexibly define a static configuration of keys, supports multiple pre-defined keys in the same keyring and allows to load keys from a file, derive from mnemonic or read plain private key bytes from a HEX string. Extremely useful for testing and local development, but also robust for production use cases. + +## Usage + +```go +NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmkeyring.Keyring, error) +``` + +**ConfigOpts:** + +These options are global on the keyring level. + +* `WithKeyringDir` option sets keyring path in the filesystem, useful when keyring backend is `file`. +* `WithKeyringAppName` option sets keyring application name (defaults to `injectived`) +* `WithKeyringBackend` sets the keyring backend. Expected values: `test`, `file`, `os`. +* `WithUseLedger` sets the option to use hardware wallet, if available on the system. + +These options allow to add keys to the keyring during initialization. + +* `WithKey` adds a single key to the keyring, without having alias name. +* `WithNamedKey` addes a single key to the keyring, with a name. +* `WithDefaultKey` sets a default key reference to use for signing (by name). + +**KeyConfigOpts:** + +These options are set per key. + +* `WithKeyFrom` sets the key name to use for signing. Must exist in the provided keyring. +* `WithKeyPassphrase` sets the passphrase for keyring files. The package will fallback to `os.Stdin` if this option was not provided, but passphrase is required. +* `WithPrivKeyHex` allows to specify a private key as plain-text hex. Insecure option, use for testing only. The package will create a virtual keyring holding that key, to meet all the interfaces. +* `WithMnemonic` allows to specify a mnemonic pharse as plain-text hex. Insecure option, use for testing only. The package will create a virtual keyring to derive the keys and meet all the interfaces. + +## Examples + +Initialize an in-memory keyring with a private key hex: + +```go +NewCosmosKeyring( + cdc, + WithKey( + WithPrivKeyHex("e6888cb164d52e4880e08a8a5dbe69cd62f67fde3d5906f2c5c951be553b2267"), + WithKeyFrom("sender"), + ), +) +``` + +Initialize an in-memory keyring with a mnemonic phrase: + +```go +NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic("real simple naive ....... love"), + WithKeyFrom("sender"), + ), +) +``` + +Real world use case of keyring initialization from CLI flags, with a single named key set as default: + +```go +NewCosmosKeyring( + cdc, + WithKeyringDir(*keyringDir), + WithKeyringAppName(*keyringAppName), + WithKeyringBackend(Backend(*keyringBackend)), + + WithNamedKey( + "dispatcher", + WithKeyFrom(*dispatcherKeyFrom), + WithKeyPassphrase(*dispatcherKeyPassphrase), + WithPrivKeyHex(*dispatcherKeyPrivateHex), + WithMnemonic(*dispatcherKeyMnemonic), + ), + + WithDefaultKey( + "dispatcher", + ), +) +``` + +## Testing + +```bash +go test -v -cover + +PASS +coverage: 83.1% of statements +``` + +## Generating a Test Fixture + +```bash +> cd testdata + +> injectived keys --keyring-dir `pwd` --keyring-backend file add test +``` + +Passphrase should be `test12345678` for this fixture to work. diff --git a/client/keyring/errors.go b/client/keyring/errors.go new file mode 100644 index 00000000..bd591844 --- /dev/null +++ b/client/keyring/errors.go @@ -0,0 +1,20 @@ +package keyring + +import "github.com/pkg/errors" + +var ( + ErrCosmosKeyringCreationFailed = errors.New("cosmos keyring creation failed") + ErrCosmosKeyringImportFailed = errors.New("cosmos keyring unable to import key") + ErrDeriveFailed = errors.New("key derivation failed") + ErrFailedToApplyConfigOption = errors.New("failed to apply config option") + ErrFailedToApplyKeyConfigOption = errors.New("failed to apply a key config option") + ErrFilepathIncorrect = errors.New("incorrect filepath") + ErrHexFormatError = errors.New("hex format error") + ErrIncompatibleOptionsProvided = errors.New("incompatible keyring options provided") + ErrInsufficientKeyDetails = errors.New("insufficient cosmos key details provided") + ErrKeyIncompatible = errors.New("provided key is incompatible with requested config") + ErrKeyRecordNotFound = errors.New("key record not found") + ErrPrivkeyConflict = errors.New("privkey conflict") + ErrUnexpectedAddress = errors.New("unexpected address") + ErrMultipleKeysWithDifferentSecurity = errors.New("key security is different: cannot mix keyring with privkeys") +) diff --git a/client/keyring/key_config.go b/client/keyring/key_config.go new file mode 100644 index 00000000..79b74ef2 --- /dev/null +++ b/client/keyring/key_config.go @@ -0,0 +1,69 @@ +package keyring + +import ( + bip39 "github.com/cosmos/go-bip39" + "github.com/pkg/errors" +) + +type cosmosKeyConfig struct { + Name string + KeyFrom string + KeyPassphrase string + PrivKeyHex string + Mnemonic string +} + +// KeyConfigOpt defines a known cosmos keyring key option. +type KeyConfigOpt func(c *cosmosKeyConfig) error + +// WithKeyFrom sets the key name to use for signing. Must exist in the provided keyring. +func WithKeyFrom(v string) KeyConfigOpt { + return func(c *cosmosKeyConfig) error { + if len(v) > 0 { + c.KeyFrom = v + } + + return nil + } +} + +// WithKeyPassphrase sets the passphrase for keyring files. Insecure option, use for testing only. +// The package will fallback to os.Stdin if this option was not provided, but pass is required. +func WithKeyPassphrase(v string) KeyConfigOpt { + return func(c *cosmosKeyConfig) error { + if len(v) > 0 { + c.KeyPassphrase = v + } + + return nil + } +} + +// WithPrivKeyHex allows to specify a private key as plaintext hex. Insecure option, use for testing only. +// The package will create a virtual keyring holding that key, to meet all the interfaces. +func WithPrivKeyHex(v string) KeyConfigOpt { + return func(c *cosmosKeyConfig) error { + if len(v) > 0 { + c.PrivKeyHex = v + } + + return nil + } +} + +// WithMnemonic allows to specify a mnemonic pharse as plaintext. Insecure option, use for testing only. +// The package will create a virtual keyring to derive the keys and meet all the interfaces. +func WithMnemonic(v string) KeyConfigOpt { + return func(c *cosmosKeyConfig) error { + if len(v) > 0 { + if !bip39.IsMnemonicValid(v) { + err := errors.New("provided mnemonic is not a valid BIP39 mnemonic") + return err + } + + c.Mnemonic = v + } + + return nil + } +} diff --git a/client/keyring/keyring.go b/client/keyring/keyring.go new file mode 100644 index 00000000..d9052b7f --- /dev/null +++ b/client/keyring/keyring.go @@ -0,0 +1,491 @@ +package keyring + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "io" + "os" + "path/filepath" + "strings" + + "github.com/InjectiveLabs/sdk-go/chain/crypto/hd" + "github.com/cosmos/cosmos-sdk/codec" + cosmcrypto "github.com/cosmos/cosmos-sdk/crypto" + cosmkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" +) + +var ( + defaultKeyringKeyName = "default" + emptyCosmosAddress = sdk.AccAddress{} +) + +// NewCosmosKeyring creates a new keyring from a variety of options. See ConfigOpt and related options. +func NewCosmosKeyring(cdc codec.Codec, opts ...ConfigOpt) (sdk.AccAddress, cosmkeyring.Keyring, error) { + config := &cosmosKeyringConfig{} + for optIdx, optFn := range opts { + if err := optFn(config); err != nil { + err = errors.Wrapf(ErrFailedToApplyConfigOption, "option #%d: %s", optIdx+1, err.Error()) + return emptyCosmosAddress, nil, err + } + } + + if len(config.Keys) == 0 { + return emptyCosmosAddress, nil, ErrInsufficientKeyDetails + } + + var kb cosmkeyring.Keyring + var realKB cosmkeyring.Keyring + var usingRealKeyring bool + var firstKey *sdk.AccAddress + + for keyIdx, keyConfig := range config.Keys { + switch { + case len(keyConfig.Mnemonic) > 0: + if usingRealKeyring { + return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity + } else if kb == nil { + kb = cosmkeyring.NewInMemory(cdc, hd.EthSecp256k1Option()) + } + + if config.UseLedger { + err := errors.Wrap(ErrIncompatibleOptionsProvided, "cannot combine ledger and mnemonic options") + return emptyCosmosAddress, nil, err + } + + addr, err := fromMnemonic(kb, keyConfig) + if err != nil { + return addr, kb, err + } + + if keyIdx == 0 { + firstKey = &addr + } + + case len(keyConfig.PrivKeyHex) > 0: + if usingRealKeyring { + return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity + } else if kb == nil { + kb = cosmkeyring.NewInMemory(cdc, hd.EthSecp256k1Option()) + } + + if config.UseLedger { + err := errors.Wrap(ErrIncompatibleOptionsProvided, "cannot combine ledger and privkey options") + return emptyCosmosAddress, nil, err + } + + addr, err := fromPrivkeyHex(kb, keyConfig) + if err != nil { + return addr, kb, err + } + + if keyIdx == 0 { + firstKey = &addr + } + + case len(keyConfig.KeyFrom) > 0: + if kb != nil { + return emptyCosmosAddress, nil, ErrMultipleKeysWithDifferentSecurity + } else { + usingRealKeyring = true + } + + var fromIsAddress bool + + addressFrom, err := sdk.AccAddressFromBech32(keyConfig.KeyFrom) + if err == nil { + fromIsAddress = true + } + + addr, kb, err := fromCosmosKeyring(cdc, config, keyConfig, addressFrom, fromIsAddress) + if err != nil { + return addr, kb, err + } + + realKB = kb + if keyIdx == 0 { + firstKey = &addr + } + + default: + err := errors.Wrapf(ErrInsufficientKeyDetails, "key %d details", keyIdx+1) + return emptyCosmosAddress, nil, err + } + } + + if realKB != nil { + if len(config.DefaultKey) > 0 { + defaultKeyAddr, err := findKeyInKeyring(realKB, config, config.DefaultKey) + if err != nil { + return emptyCosmosAddress, nil, err + } + + return defaultKeyAddr, realKB, nil + } + + return *firstKey, realKB, nil + } + + if len(config.DefaultKey) > 0 { + defaultKeyAddr, err := findKeyInKeyring(kb, config, config.DefaultKey) + if err != nil { + return emptyCosmosAddress, nil, err + } + + return defaultKeyAddr, kb, nil + } + + return *firstKey, kb, nil +} + +func fromPrivkeyHex( + kb cosmkeyring.Keyring, + keyConfig *cosmosKeyConfig, +) (sdk.AccAddress, error) { + pkBytes, err := hexToBytes(keyConfig.PrivKeyHex) + if err != nil { + err = errors.Wrapf(ErrHexFormatError, "failed to decode cosmos account privkey: %s", err.Error()) + return emptyCosmosAddress, err + } + + cosmosAccPk := hd.EthSecp256k1.Generate()(pkBytes) + addressFromPk := sdk.AccAddress(cosmosAccPk.PubKey().Address().Bytes()) + + keyName := keyConfig.Name + + // check that if cosmos 'From' specified separately, it must match the provided privkey + if len(keyConfig.KeyFrom) > 0 { + addressFrom, err := sdk.AccAddressFromBech32(keyConfig.KeyFrom) + if err == nil { + if !bytes.Equal(addressFrom.Bytes(), addressFromPk.Bytes()) { + err = errors.Wrapf( + ErrUnexpectedAddress, + "expected account address %s but got %s from the private key", + addressFrom.String(), addressFromPk.String(), + ) + + return emptyCosmosAddress, err + } + } else if len(keyName) == 0 { + // use it as a name then + keyName = keyConfig.KeyFrom + } else if keyName != keyConfig.KeyFrom { + err := errors.Errorf( + "key 'from' opt is a name, but doesn't match given key name: %s != %s", + keyConfig.KeyFrom, keyName, + ) + return emptyCosmosAddress, err + } + } + + if len(keyName) == 0 { + keyName = defaultKeyringKeyName + } + + // add a PK into a Keyring + err = addFromPrivKey(kb, keyName, cosmosAccPk) + if err != nil { + err = errors.WithStack(err) + } + + return addressFromPk, err +} + +func fromMnemonic( + kb cosmkeyring.Keyring, + keyConfig *cosmosKeyConfig, +) (sdk.AccAddress, error) { + cfg := sdk.GetConfig() + + pkBytes, err := hd.EthSecp256k1.Derive()( + keyConfig.Mnemonic, + cosmkeyring.DefaultBIP39Passphrase, + cfg.GetFullBIP44Path(), + ) + if err != nil { + err = errors.Wrapf(ErrDeriveFailed, "failed to derive secp256k1 private key: %s", err.Error()) + return emptyCosmosAddress, err + } + + cosmosAccPk := hd.EthSecp256k1.Generate()(pkBytes) + addressFromPk := sdk.AccAddress(cosmosAccPk.PubKey().Address().Bytes()) + + keyName := keyConfig.Name + + // check that if cosmos 'From' specified separately, it must match the derived privkey + if len(keyConfig.KeyFrom) > 0 { + addressFrom, err := sdk.AccAddressFromBech32(keyConfig.KeyFrom) + if err == nil { + if !bytes.Equal(addressFrom.Bytes(), addressFromPk.Bytes()) { + err = errors.Wrapf( + ErrUnexpectedAddress, + "expected account address %s but got %s from the mnemonic at /0", + addressFrom.String(), addressFromPk.String(), + ) + + return emptyCosmosAddress, err + } + } else if len(keyName) == 0 { + // use it as a name then + keyName = keyConfig.KeyFrom + } else if keyName != keyConfig.KeyFrom { + err := errors.Errorf( + "key 'from' opt is a name, but doesn't match given key name: %s != %s", + keyConfig.KeyFrom, keyName, + ) + return emptyCosmosAddress, err + } + } + + // check that if 'PrivKeyHex' specified separately, it must match the derived privkey too + if len(keyConfig.PrivKeyHex) > 0 { + if err := checkPrivkeyHexMatchesMnemonic(keyConfig.PrivKeyHex, pkBytes); err != nil { + return emptyCosmosAddress, err + } + } + + if len(keyName) == 0 { + keyName = defaultKeyringKeyName + } + + // add a PK into a Keyring + err = addFromPrivKey(kb, keyName, cosmosAccPk) + if err != nil { + err = errors.WithStack(err) + } + + return addressFromPk, err +} + +func checkPrivkeyHexMatchesMnemonic(pkHex string, mnemonicDerivedPkBytes []byte) error { + pkBytesFromHex, err := hexToBytes(pkHex) + if err != nil { + err = errors.Wrapf(ErrHexFormatError, "failed to decode cosmos account privkey: %s", err.Error()) + return err + } + + if !bytes.Equal(mnemonicDerivedPkBytes, pkBytesFromHex) { + err := errors.Wrap( + ErrPrivkeyConflict, + "both mnemonic and privkey hex options provided, but privkey doesn't match mnemonic", + ) + return err + } + + return nil +} + +func fromCosmosKeyring( + cdc codec.Codec, + config *cosmosKeyringConfig, + keyConfig *cosmosKeyConfig, + fromAddress sdk.AccAddress, + fromIsAddress bool, +) (sdk.AccAddress, cosmkeyring.Keyring, error) { + var passReader io.Reader = os.Stdin + if len(keyConfig.KeyPassphrase) > 0 { + passReader = newPassReader(keyConfig.KeyPassphrase) + } + + var err error + absoluteKeyringDir := config.KeyringDir + if !filepath.IsAbs(config.KeyringDir) { + absoluteKeyringDir, err = filepath.Abs(config.KeyringDir) + if err != nil { + err = errors.Wrapf(ErrFilepathIncorrect, "failed to get abs path for keyring dir: %s", err.Error()) + return emptyCosmosAddress, nil, err + } + } + + kb, err := cosmkeyring.New( + config.KeyringAppName, + string(config.KeyringBackend), + absoluteKeyringDir, + passReader, + cdc, + hd.EthSecp256k1Option(), + ) + if err != nil { + err = errors.Wrapf(ErrCosmosKeyringCreationFailed, "failed to init cosmos keyring: %s", err.Error()) + return emptyCosmosAddress, nil, err + } + + var keyRecord *cosmkeyring.Record + if fromIsAddress { + keyRecord, err = kb.KeyByAddress(fromAddress) + } else { + keyName := keyConfig.Name + if len(keyName) > 0 && keyConfig.KeyFrom != keyName { + err := errors.Errorf( + "key 'from' opt is a name, but doesn't match given key name: %s != %s", + keyConfig.KeyFrom, keyName, + ) + + return emptyCosmosAddress, nil, err + } + + keyRecord, err = kb.Key(keyConfig.KeyFrom) + } + + if err != nil { + err = errors.Wrapf( + ErrKeyRecordNotFound, "couldn't find an entry for the key '%s' in keybase: %s", + keyConfig.KeyFrom, err.Error()) + + return emptyCosmosAddress, nil, err + } + + if err := checkKeyRecord(config, keyRecord); err != nil { + return emptyCosmosAddress, nil, err + } + + addr, err := keyRecord.GetAddress() + if err != nil { + return emptyCosmosAddress, nil, err + } + + return addr, kb, nil +} + +func findKeyInKeyring(kb cosmkeyring.Keyring, config *cosmosKeyringConfig, fromSpec string) (sdk.AccAddress, error) { + var fromIsAddress bool + + addressFrom, err := sdk.AccAddressFromBech32(fromSpec) + if err == nil { + fromIsAddress = true + } + + var keyRecord *cosmkeyring.Record + if fromIsAddress { + keyRecord, err = kb.KeyByAddress(addressFrom) + } else { + keyRecord, err = kb.Key(fromSpec) + } + + if err != nil { + err = errors.Wrapf( + ErrKeyRecordNotFound, "couldn't find an entry for the key '%s' in keybase: %s", + fromSpec, err.Error()) + + return emptyCosmosAddress, err + } + + if err := checkKeyRecord(config, keyRecord); err != nil { + return emptyCosmosAddress, err + } + + addr, err := keyRecord.GetAddress() + if err != nil { + return emptyCosmosAddress, err + } + + return addr, nil +} + +func checkKeyRecord( + config *cosmosKeyringConfig, + keyRecord *cosmkeyring.Record, +) error { + switch keyType := keyRecord.GetType(); keyType { + case cosmkeyring.TypeLocal: + // kb has a key and it's totally usable + return nil + + case cosmkeyring.TypeLedger: + // the kb stores references to ledger keys, so we must explicitly + // check that. kb doesn't know how to scan HD keys - they must be added manually before + if config.UseLedger { + return nil + } + err := errors.Wrapf( + ErrKeyIncompatible, + "'%s' key is a ledger reference, enable ledger option", + keyRecord.Name, + ) + return err + + case cosmkeyring.TypeOffline: + err := errors.Wrapf( + ErrKeyIncompatible, + "'%s' key is an offline key, not supported yet", + keyRecord.Name, + ) + return err + + case cosmkeyring.TypeMulti: + err := errors.Wrapf( + ErrKeyIncompatible, + "'%s' key is an multisig key, not supported yet", + keyRecord.Name, + ) + return err + + default: + err := errors.Wrapf( + ErrKeyIncompatible, + "'%s' key has unsupported type: %s", + keyRecord.Name, keyType, + ) + return err + } +} + +func newPassReader(pass string) io.Reader { + return &passReader{ + pass: pass, + buf: new(bytes.Buffer), + } +} + +type passReader struct { + pass string + buf *bytes.Buffer +} + +var _ io.Reader = &passReader{} + +func (r *passReader) Read(p []byte) (n int, err error) { + n, err = r.buf.Read(p) + if err == io.EOF || n == 0 { + r.buf.WriteString(r.pass + "\n") + + n, err = r.buf.Read(p) + } + + return n, err +} + +// addFromPrivKey adds a PrivKey into temporary in-mem keyring. +// Allows to init Context when the key has been provided in plaintext and parsed. +func addFromPrivKey(kb cosmkeyring.Keyring, name string, privKey cryptotypes.PrivKey) error { + tmpPhrase := randPhrase(64) + armored := cosmcrypto.EncryptArmorPrivKey(privKey, tmpPhrase, privKey.Type()) + err := kb.ImportPrivKey(name, armored, tmpPhrase) + if err != nil { + err = errors.Wrapf(ErrCosmosKeyringImportFailed, "failed to import privkey: %s", err.Error()) + return err + } + + return nil +} + +func hexToBytes(str string) ([]byte, error) { + data, err := hex.DecodeString(strings.TrimPrefix(str, "0x")) + if err != nil { + return nil, err + } + + return data, nil +} + +func randPhrase(size int) string { + buf := make([]byte, size) + if _, err := rand.Read(buf); err != nil { + panic(err) + } + + return string(buf) +} diff --git a/client/keyring/keyring_config.go b/client/keyring/keyring_config.go new file mode 100644 index 00000000..42ac4cbc --- /dev/null +++ b/client/keyring/keyring_config.go @@ -0,0 +1,120 @@ +package keyring + +import ( + "github.com/pkg/errors" +) + +// ConfigOpt defines a known cosmos keyring option. +type ConfigOpt func(c *cosmosKeyringConfig) error + +type cosmosKeyringConfig struct { + KeyringDir string + KeyringAppName string + KeyringBackend Backend + UseLedger bool + + Keys []*cosmosKeyConfig + DefaultKey string +} + +// Backend defines a known keyring backend name. +type Backend string + +const ( + // BackendTest is a testing backend, no passphrases required. + BackendTest Backend = "test" + // BackendFile is a backend where keys are stored as encrypted files. + BackendFile Backend = "file" + // BackendOS is a backend where keys are stored in the OS key chain. Platform specific. + BackendOS Backend = "os" +) + +// WithKeyringDir option sets keyring path in the filesystem, useful when keyring backend is `file`. +func WithKeyringDir(v string) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + if len(v) > 0 { + c.KeyringDir = v + } + + return nil + } +} + +// WithKeyringAppName option sets keyring application name (used by Cosmos to separate keyrings). +func WithKeyringAppName(v string) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + if len(v) > 0 { + c.KeyringAppName = v + } + + return nil + } +} + +// WithKeyringBackend sets the keyring backend. Expected values: test, file, os. +func WithKeyringBackend(v Backend) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + if len(v) > 0 { + c.KeyringBackend = v + } + + return nil + } +} + +// WithUseLedger sets the option to use hardware wallet, if available on the system. +func WithUseLedger(b bool) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + c.UseLedger = b + + return nil + } +} + +// WithKey adds an unnamed key into the keyring, based on its individual options. +func WithKey(opts ...KeyConfigOpt) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + config := &cosmosKeyConfig{} + + for optIdx, optFn := range opts { + if err := optFn(config); err != nil { + err = errors.Wrapf(ErrFailedToApplyKeyConfigOption, "key option #%d: %s", optIdx+1, err.Error()) + return err + } + } + + c.Keys = append(c.Keys, config) + return nil + } +} + +// WithNamedKey adds a key into the keyring, based on its individual options, with a given name (alias). +func WithNamedKey(name string, opts ...KeyConfigOpt) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + config := &cosmosKeyConfig{ + Name: name, + } + + for optIdx, optFn := range opts { + if err := optFn(config); err != nil { + err = errors.Wrapf(ErrFailedToApplyKeyConfigOption, "key option #%d: %s", optIdx+1, err.Error()) + return err + } + } + + c.Keys = append(c.Keys, config) + return nil + } +} + +// WithDefaultKey specifies the default key (name or address) to be fetched during keyring init. +// This key must exist in specified keys. +func WithDefaultKey(v string) ConfigOpt { + return func(c *cosmosKeyringConfig) error { + if len(v) > 0 { + c.DefaultKey = v + } + + return nil + } +} diff --git a/client/keyring/keyring_errors_test.go b/client/keyring/keyring_errors_test.go new file mode 100644 index 00000000..8ff13cb0 --- /dev/null +++ b/client/keyring/keyring_errors_test.go @@ -0,0 +1,193 @@ +package keyring + +import ( + "os" + + "github.com/InjectiveLabs/sdk-go/chain/crypto/hd" + cosmkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" +) + +func (s *KeyringTestSuite) TestErrCosmosKeyringCreationFailed() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend("kowabunga"), + WithKey( + WithKeyFrom(testAccAddressBech), + ), + ) + + requireT.ErrorIs(err, ErrCosmosKeyringCreationFailed) +} + +func (s *KeyringTestSuite) TestErrFailedToApplyConfigOption() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(`???`), + ), + ) + + requireT.ErrorIs(err, ErrFailedToApplyConfigOption) +} + +func (s *KeyringTestSuite) TestErrHexFormatError() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex("nothex"), + ), + ) + + requireT.ErrorIs(err, ErrHexFormatError) + + _, _, err = NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(testMnemonic), + WithPrivKeyHex("nothex"), + ), + ) + + requireT.ErrorIs(err, ErrHexFormatError) +} + +func (s *KeyringTestSuite) TestErrIncompatibleOptionsProvided() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithUseLedger(true), + WithKey( + WithMnemonic(testMnemonic), + ), + ) + + requireT.ErrorIs(err, ErrIncompatibleOptionsProvided) + + _, _, err = NewCosmosKeyring( + s.cdc, + WithUseLedger(true), + WithKey( + WithPrivKeyHex(testPrivKeyHex), + ), + ) + + requireT.ErrorIs(err, ErrIncompatibleOptionsProvided) +} + +func (s *KeyringTestSuite) TestErrInsufficientKeyDetails() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring(s.cdc) + + requireT.ErrorIs(err, ErrInsufficientKeyDetails) +} + +func (s *KeyringTestSuite) TestErrKeyIncompatible() { + requireT := s.Require() + + addr, kb, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testPrivKeyHex), + ), + ) + requireT.NoError(err) + + testRecord, err := kb.KeyByAddress(addr) + requireT.NoError(err) + testRecordPubKey, err := testRecord.GetPubKey() + requireT.NoError(err) + + kbDir, err := os.MkdirTemp(os.TempDir(), "keyring-test-kbroot-*") + requireT.NoError(err) + s.T().Cleanup(func() { + _ = os.RemoveAll(kbDir) + }) + + testKeyring, err := cosmkeyring.New( + "keyring_test", + cosmkeyring.BackendTest, + kbDir, + nil, + s.cdc, + hd.EthSecp256k1Option(), + ) + requireT.NoError(err) + + _, err = testKeyring.SaveOfflineKey("test_pubkey", testRecordPubKey) + requireT.NoError(err) + + _, _, err = NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendTest), + WithKeyringDir(kbDir), + WithKeyringAppName("keyring_test"), + WithKey( + WithKeyFrom("test_pubkey"), + ), + ) + requireT.ErrorIs(err, ErrKeyIncompatible) + + // TODO: add test for unsupported multisig keys +} + +func (s *KeyringTestSuite) TestErrKeyRecordNotFound() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendFile), + WithKeyringDir("./testdata"), + WithKey( + WithKeyFrom("kowabunga"), + WithKeyPassphrase("test12345678"), + ), + ) + + requireT.ErrorIs(err, ErrKeyRecordNotFound) +} + +func (s *KeyringTestSuite) TestErrPrivkeyConflict() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testOtherPrivKeyHex), + WithMnemonic(testMnemonic), // different mnemonic + ), + ) + + requireT.ErrorIs(err, ErrPrivkeyConflict) +} + +func (s *KeyringTestSuite) TestErrUnexpectedAddress() { + requireT := s.Require() + + _, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testOtherPrivKeyHex), + WithKeyFrom(testAccAddressBech), // will not match privkey above + ), + ) + + requireT.ErrorIs(err, ErrUnexpectedAddress) + + _, _, err = NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(testMnemonic), + WithKeyFrom("inj1xypj9l9sjdaduaafhgx39ru70utnzfuklcpxz9"), // will not match mnemonic above + ), + ) + + requireT.ErrorIs(err, ErrUnexpectedAddress) +} diff --git a/client/keyring/keyring_test.go b/client/keyring/keyring_test.go new file mode 100644 index 00000000..990e1645 --- /dev/null +++ b/client/keyring/keyring_test.go @@ -0,0 +1,302 @@ +package keyring + +import ( + "encoding/hex" + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + cosmcrypto "github.com/cosmos/cosmos-sdk/crypto" + cosmkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + crypto_cdc "github.com/InjectiveLabs/sdk-go/chain/crypto/codec" + "github.com/InjectiveLabs/sdk-go/chain/crypto/hd" + ctypes "github.com/InjectiveLabs/sdk-go/chain/types" + "github.com/InjectiveLabs/sdk-go/client/chain" +) + +type KeyringTestSuite struct { + suite.Suite + + cdc codec.Codec +} + +func TestKeyringTestSuite(t *testing.T) { + suite.Run(t, new(KeyringTestSuite)) +} + +func getCryptoCodec() *codec.ProtoCodec { + registry := chain.NewInterfaceRegistry() + crypto_cdc.RegisterInterfaces(registry) + return codec.NewProtoCodec(registry) +} + +func (s *KeyringTestSuite) SetupTest() { + config := sdk.GetConfig() + ctypes.SetBech32Prefixes(config) + ctypes.SetBip44CoinType(config) + + s.cdc = getCryptoCodec() +} + +func (s *KeyringTestSuite) TestKeyFromPrivkey() { + requireT := s.Require() + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testPrivKeyHex), + WithKeyFrom(testAccAddressBech), // must match the privkey above + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + logPrivKey(s.T(), kb, accAddr) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.EqualValues(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +func (s *KeyringTestSuite) TestKeyFromMnemonic() { + requireT := s.Require() + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(testMnemonic), + WithPrivKeyHex(testPrivKeyHex), // must match mnemonic above + WithKeyFrom(testAccAddressBech), // must match mnemonic above + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + logPrivKey(s.T(), kb, accAddr) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.Equal(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +func (s *KeyringTestSuite) TestKeyringFile() { + requireT := s.Require() + + accAddr, _, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendFile), + WithKeyringDir("./testdata"), + WithKey( + WithKeyFrom("test"), + WithKeyPassphrase("test12345678"), + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendFile), + WithKeyringDir("./testdata"), + WithKey( + WithKeyFrom(testAccAddressBech), + WithKeyPassphrase("test12345678"), + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + requireT.Equal("test", record.Name) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + logPrivKey(s.T(), kb, accAddr) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.Equal(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +func (s *KeyringTestSuite) TestKeyringOsWithAppName() { + if testing.Short() { + s.T().Skip("skipping testing in short mode") + return + } + + requireT := require.New(s.T()) + + osKeyring, err := cosmkeyring.New( + "keyring_test", + cosmkeyring.BackendOS, + "", + nil, + s.cdc, + hd.EthSecp256k1Option(), + ) + requireT.NoError(err) + + var accRecord *cosmkeyring.Record + if accRecord, err = osKeyring.Key("test"); err != nil { + accRecord, err = osKeyring.NewAccount( + "test", + testMnemonic, + cosmkeyring.DefaultBIP39Passphrase, + sdk.GetConfig().GetFullBIP44Path(), + hd.EthSecp256k1, + ) + + requireT.NoError(err) + + accAddr, err := accRecord.GetAddress() + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + } + + s.T().Cleanup(func() { + // cleanup + addr, err := accRecord.GetAddress() + if err == nil { + _ = osKeyring.DeleteByAddress(addr) + } + }) + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithKeyringBackend(BackendOS), + WithKeyringAppName("keyring_test"), + WithKey( + WithKeyFrom("test"), + ), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + requireT.Equal("test", record.Name) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.Equal(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +func (s *KeyringTestSuite) TestUseFromAsName() { + requireT := s.Require() + + accAddr, _, err := NewCosmosKeyring( + s.cdc, + WithKey( + WithPrivKeyHex(testPrivKeyHex), + WithKeyFrom("kowabunga"), + ), + WithDefaultKey("kowabunga"), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + accAddr, _, err = NewCosmosKeyring( + s.cdc, + WithKey( + WithMnemonic(testMnemonic), + WithKeyFrom("kowabunga"), + ), + WithDefaultKey("kowabunga"), + ) + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) +} + +func (s *KeyringTestSuite) TestNamedKeys() { + requireT := s.Require() + + accAddr, kb, err := NewCosmosKeyring( + s.cdc, + WithNamedKey( + "bad", + WithPrivKeyHex(testOtherPrivKeyHex), + ), + + WithNamedKey( + "good", + WithPrivKeyHex(testPrivKeyHex), + ), + + WithDefaultKey("good"), + ) + + requireT.NoError(err) + requireT.Equal(testAccAddressBech, accAddr.String()) + + record, err := kb.KeyByAddress(accAddr) + requireT.NoError(err) + requireT.Equal(cosmkeyring.TypeLocal, record.GetType()) + requireT.Equal(expectedPubKeyType, record.PubKey.TypeUrl) + recordPubKey, err := record.GetPubKey() + requireT.NoError(err) + + logPrivKey(s.T(), kb, accAddr) + + res, pubkey, err := kb.SignByAddress(accAddr, []byte("test"), signing.SignMode_SIGN_MODE_DIRECT) + requireT.NoError(err) + requireT.EqualValues(recordPubKey, pubkey) + requireT.Equal(testSig, res) +} + +const expectedPubKeyType = "/injective.crypto.v1beta1.ethsecp256k1.PubKey" + +const testAccAddressBech = "inj1ycc302kea06htx5zw2kj4eyk3hgj63sz206fq0" + +//nolint:lll // mnemonic fixture +const testMnemonic = `real simple naive tissue alcohol bar short joy maze shoe reason item tray attitude panda century pulse skirt original autumn sea shop exhaust love` + +var testPrivKeyHex = "e6888cb164d52e4880e08a8a5dbe69cd62f67fde3d5906f2c5c951be553b2267" +var testOtherPrivKeyHex = "ef3bc8bc1e1bae12268e0192787673a4137af840bfcbd1aa4c535bbd95fe6837" + +var testSig = []byte{ + 0xf9, 0x04, 0x3e, 0x81, 0x83, 0xb2, 0x73, 0xf6, + 0xdd, 0xf7, 0xd6, 0x91, 0x6f, 0xb5, 0x63, 0xf4, + 0x8a, 0xa2, 0x4a, 0x51, 0x63, 0xe1, 0x04, 0x18, + 0xd2, 0xe6, 0xed, 0x9e, 0xda, 0x52, 0x2f, 0x0a, + 0x69, 0x74, 0x04, 0x73, 0x7b, 0x9a, 0xf1, 0xc8, + 0xdf, 0xe7, 0xf3, 0x4a, 0x48, 0xe6, 0x5f, 0xc0, + 0x69, 0x5e, 0x6e, 0x03, 0x9e, 0x6e, 0x5f, 0x31, + 0xa6, 0x40, 0x19, 0x1b, 0x76, 0x07, 0xd9, 0x65, + 0x00, +} + +func logPrivKey(t *testing.T, kb cosmkeyring.Keyring, accAddr sdk.AccAddress) { + armor, _ := kb.ExportPrivKeyArmorByAddress(accAddr, "") + privKey, _, _ := cosmcrypto.UnarmorDecryptPrivKey(armor, "") + t.Log("[PRIV]", hex.EncodeToString(privKey.Bytes())) +} diff --git a/client/keyring/testdata/keyring-file/263117aad9ebf5759a8272ad2ae4968dd12d4602.address b/client/keyring/testdata/keyring-file/263117aad9ebf5759a8272ad2ae4968dd12d4602.address new file mode 100644 index 00000000..4730fa37 --- /dev/null +++ b/client/keyring/testdata/keyring-file/263117aad9ebf5759a8272ad2ae4968dd12d4602.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wOS0wNyAxMzo0OTowNi42NDcwMjMgKzAyMDAgQ0VTVCBtPSs4MS42NDQ1NDE5MTgiLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJyY1pRdHMtbzJFaDFGZHhCIn0.-BfYEQoZEiTDwc9fsuLawiEIE_P8Q8KAKhbk3aU0b1-YQv8Brjsihg.bwXK-xKtobJd3mWC.c5rNN9FoqUpnrIjwqU3xPqcgCUgbCF8GgAUibcQfmyYk1MIvbM7aSx1y6ngO0UCRLCZdPhJgxxfBAbPZrbFe7DL4XKn5RfdbPO0a43pN0CHiHu3z86YlPwTS2ADQSta1Zup_ek2boS39GOAgXgC1kYIiox8b1aM_zOvx7M1ASdhnRqMoGu-kYth0FQoocbYRlTF9WMGR40iW-xfSFSYxvoORZDYvfgy8_hIjAYw7bfFkHQ.WWP2VXAeTBZ5L30NTqNbfg \ No newline at end of file diff --git a/client/keyring/testdata/keyring-file/310322fcb0937ade77a9ba0d128f9e7f17312796.address b/client/keyring/testdata/keyring-file/310322fcb0937ade77a9ba0d128f9e7f17312796.address new file mode 100644 index 00000000..7b5e5e2c --- /dev/null +++ b/client/keyring/testdata/keyring-file/310322fcb0937ade77a9ba0d128f9e7f17312796.address @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wOS0wNyAxMzo1MzozMi41MjczNjIgKzAyMDAgQ0VTVCBtPSswLjU0NDc3NTc1MSIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjgxOTIsInAycyI6IjhGTDlhUXNlSzVZV2RlblcifQ.cc0WF0wygByoLnNwnrAeFMqRbzFduFelXwQlKVbnYg7Y7sQoVCtVzQ.qC1rIh3zSt9Lfc-V.6MPAr7OMxRwq91SM3o8G43d-NtwTqzCmjoYXFxH2bvYmxA0i2L-EH6-_MzZaR7UBV_wJk130vYM0BIrgyWhWpNIHDf0xATBq6rMhVuhEUP4WLgiQQp_cAR0AJ1qZ2pcJyxCnWpDHSdg1D3vP734H6djM77guObVRmGrk5Xp2eRcC4EEEP1DsF53xHxR_ciH8mq1RO5G5GWmitVPzPrrZGYoD1XQudT300kk3mPGnEUP6uVs-JO4.ER5W3QEeONNz7lUZAAbLIg \ No newline at end of file diff --git a/client/keyring/testdata/keyring-file/keyhash b/client/keyring/testdata/keyring-file/keyhash new file mode 100644 index 00000000..5d41bad2 --- /dev/null +++ b/client/keyring/testdata/keyring-file/keyhash @@ -0,0 +1 @@ +$2a$10$dwTjlfhSfydxZcMd8dqWxOjZ06RgbCv3oCrmrkv8.M6jzKGCzx5r2 \ No newline at end of file diff --git a/client/keyring/testdata/keyring-file/test.info b/client/keyring/testdata/keyring-file/test.info new file mode 100644 index 00000000..5580cfdb --- /dev/null +++ b/client/keyring/testdata/keyring-file/test.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wOS0wNyAxMzo0OTowNi42MzkyNjIgKzAyMDAgQ0VTVCBtPSs4MS42MzY3ODE1NDMiLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJGMUxoTER3ajhPR2VBRVh0In0.0Sa9gloQcB_4t5RAkS1kgqlCgBu0NwZG_WCHpA7eU3B7bD2zZjKM-A.uLO-mtT3vfO1pJLx.q7FZhW_tKnFTL80UuJU9LPj8T0nFS5UOcy_j23G3UGUs-gstVs9cJEtSNZOCz14-EcsRdJtWm5T9nx0Aauh_48LySQ-LDBbbycH1BJjyEMDhxn8zf0En8uIpZWiWa6vgfeomJE7BY_tRVFnMtzXvFJ69Ky37wFjSKeBac0XaxRc2XsBYUIRJY3xqnni53nvjw55fmyHo5-gBV-OC3ZDX4LmeTQcKw71LCVfGA1oxuk7BQcsYHc2_v2Lxr5rDuoZJl1Do32r63ss2fee85-q0Htjw_unaswZa0KLfMyyMOyRyTPsw0NHik8YjqkGJt9EOfARgu_IFGYoxaTotBBnjbpxOg7dpX4467WnFjkUbXtVFWqEAvIOhRmMbobUqJIPz1Ai5t3jYN-PIKfElgXciXdXPVEVe0j7ABJsNZCFJ11FueRqWhgTw_1B0OWrXjyWQ9TM-Yba9h_v7Cw0AP4lhvk6nm95H_pwYXX_dURj_5w.DUxlLK9M5aJbnHeYmWoHZQ \ No newline at end of file diff --git a/client/keyring/testdata/keyring-file/test2.info b/client/keyring/testdata/keyring-file/test2.info new file mode 100644 index 00000000..a78d4339 --- /dev/null +++ b/client/keyring/testdata/keyring-file/test2.info @@ -0,0 +1 @@ +eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjcmVhdGVkIjoiMjAyNC0wOS0wNyAxMzo1MzozMi41MTY1MTcgKzAyMDAgQ0VTVCBtPSswLjUzMzkzMDc1MSIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjgxOTIsInAycyI6InFkWl9BbUtFUTg4UE8zb1EifQ.0XmW52t7Bn9bZKbjRLTLz0t8xPkgspFZNpzEgWgWaVUBugjlHqXm3w.mDeWYJuwq7t8UPy9.RDkxLQVylI0V1q1Ibihp73y5EnfqKYAwpQtdAKC9zcTOLVNIziYBjNBfgFKRbNE6q38AZo2_mN4GqH3o-9OwEWlj-qWe0H1EjXVuWqpteaT4EpYC7ZX9uMrk6yQyf_lfniGnL0f5j_hzMl_CVC6lGUgOf6nS1fOxZ-we00rcLNu-3W9ZZreTVwDQG-w2sCV95nreMTmkGd-z8BmgZsDDDB0YB0gnW_TDHVyn3zNi8S3SQxXEgKqXGi5KdsJTBQHkl6fx55LYb4o1sSvgiA8JcNtqtwvARNCq2tLS8ADGMMrrbIPEpmHS69Gd-6UEFpmP8vOeWKwKk52y9ozL___q9yMC-y71DkRpbOcM5zYEBZmStD2gucpYxlEJY5hEVN3szHQZz7MfW8MqiESlRi1-cRCanVDZOl3K6AbRix_F75yMxkWg3OEjwueecsKXFlqzz_Zv8MxMFJQWAYiEloXLZaNPS9GIUrWnALLO2BvySlE2lwf9.yzPjJMAGieybi_UccRHU2w \ No newline at end of file diff --git a/go.mod b/go.mod index 34e0b5b1..25c1e09d 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/cometbft/cometbft v0.38.9 github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/cosmos-sdk v0.50.7 + github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogoproto v1.5.0 github.com/cosmos/ibc-go/modules/capability v1.0.0 github.com/cosmos/ibc-go/v8 v8.2.0 @@ -85,7 +86,6 @@ require ( github.com/cometbft/cometbft-db v0.9.1 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.0.2 // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/iavl v1.1.2 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect @@ -123,6 +123,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -136,6 +137,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.5.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect @@ -150,12 +152,14 @@ require ( github.com/klauspost/compress v1.17.7 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.7 // indirect github.com/linxGnu/grocksdb v1.8.14 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/minio/highwayhash v1.0.2 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mtibben/percent v0.2.1 // indirect diff --git a/go.sum b/go.sum index a9b25d4b..20ae5357 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CosmWasm/wasmvm/v2 v2.0.0 h1:IqNCI2G0mvs7K6ej17/I28805rVqnu+Y1cWDqIdwb08= @@ -87,6 +89,8 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -97,6 +101,8 @@ github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I= +github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -170,6 +176,7 @@ github.com/bugsnag/panicwrap v1.3.4/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywR github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk= github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= @@ -224,6 +231,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/cometbft/cometbft-db v0.9.1 h1:MIhVX5ja5bXNHF8EYrThkG9F7r9kSfv8BX4LWaxWJ4M= github.com/cometbft/cometbft-db v0.9.1/go.mod h1:iliyWaoV0mRwBJoizElCwwRA9Tf7jZJOURcRZF9m60U= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -287,6 +296,10 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -728,6 +741,10 @@ github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdM github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -738,6 +755,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0= github.com/outcaste-io/ristretto v0.2.3/go.mod h1:W8HywhmtlopSB1jeMg3JtdIhf+DYkLAr0VN/s4+MHac= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= @@ -1081,6 +1100,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=