Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new cosmos keyring helper for injective client #235

Merged
merged 7 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ jobs:
with:
go-version-file: "go.mod"
check-latest: true
- name: Install pass helper
run: sudo apt-get update && sudo apt-get install -y pass
- name: Generate GPG key
run: "
echo \"%no-protection\nKey-Type: 1\nKey-Length: 4096\nSubkey-Type: 1\nSubkey-Length: 4096\nName-Comment: keyring_test\nExpire-Date: 0\" > genkey && gpg --gen-key --batch genkey"
- name: Setup OS keystore
run: pass init keyring_test
- name: Run test and calculate coverage
run: make coverage
- name: Upload coverage to Codecov
Expand Down
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ exclude: |
(?x)^(
chain/.*|
exchange/.*|
proto/.*
proto/.*|
client/keyring/testdata/.*
)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
101 changes: 101 additions & 0 deletions client/keyring/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 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.
maxim-inj marked this conversation as resolved.
Show resolved Hide resolved

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.
maxim-inj marked this conversation as resolved.
Show resolved Hide resolved

## 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.
maxim-inj marked this conversation as resolved.
Show resolved Hide resolved

* `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.
maxim-inj marked this conversation as resolved.
Show resolved Hide resolved

## 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.
20 changes: 20 additions & 0 deletions client/keyring/errors.go
Original file line number Diff line number Diff line change
@@ -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")
)
maxim-inj marked this conversation as resolved.
Show resolved Hide resolved
69 changes: 69 additions & 0 deletions client/keyring/key_config.go
Original file line number Diff line number Diff line change
@@ -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 v != "" {
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 v != "" {
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 v != "" {
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 v != "" {
if !bip39.IsMnemonicValid(v) {
err := errors.New("provided mnemonic is not a valid BIP39 mnemonic")
return err
}

c.Mnemonic = v
}

return nil
}
}
Loading
Loading