Repository: cosmos/relayer
Pull request #1120 (2023-03-09 17:56:49)

Add sr25519 support
Sei chain requires sr25519 support.

- Adds the sr25519 proto codec and the algorithm implementation.
- Adds `signature-algorithm` to cosmos chain provider config and chain registry json
- Adds `--signature-algorithm` flag to add and restore key commands


Patch:
diff --git a/cmd/keys.go b/cmd/keys.go
index 9148946..aa915d0 100644
--- a/cmd/keys.go
+++ b/cmd/keys.go
@@ -22,6 +22,7 @@ import (
 	"io"
 	"strings"
 
+	"github.com/cosmos/cosmos-sdk/crypto/hd"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/cosmos/relayer/v2/relayer/chains/cosmos"
 	"github.com/spf13/cobra"
@@ -30,6 +31,7 @@ import (
 
 const (
 	flagCoinType           = "coin-type"
+	flagAlgo               = "signing-algorithm"
 	defaultCoinType uint32 = sdk.CoinType
 )
 
@@ -75,7 +77,9 @@ $ %s k a cosmoshub testkey`, appName, appName, appName)),
 				return errKeyExists(keyName)
 			}
 
-			coinType, err := cmd.Flags().GetInt32(flagCoinType)
+			cmdFlags := cmd.Flags()
+
+			coinType, err := cmdFlags.GetInt32(flagCoinType)
 			if err != nil {
 				return err
 			}
@@ -88,7 +92,20 @@ $ %s k a cosmoshub testkey`, appName, appName, appName)),
 				}
 			}
 
-			ko, err := chain.ChainProvider.AddKey(keyName, uint32(coinType))
+			algo, err := cmdFlags.GetString(flagAlgo)
+			if err != nil {
+				return err
+			}
+
+			if algo == "" {
+				if ccp, ok := chain.ChainProvider.(*cosmos.CosmosProvider); ok {
+					algo = ccp.PCfg.SigningAlgorithm
+				} else {
+					algo = string(hd.Secp256k1Type)
+				}
+			}
+
+			ko, err := chain.ChainProvider.AddKey(keyName, uint32(coinType), algo)
 			if err != nil {
 				return fmt.Errorf("failed to add key: %w", err)
 			}
@@ -103,6 +120,7 @@ $ %s k a cosmoshub testkey`, appName, appName, appName)),
 		},
 	}
 	cmd.Flags().Int32(flagCoinType, -1, "coin type number for HD derivation")
+	cmd.Flags().String(flagAlgo, "", "signing algorithm for key (secp256k1, sr25519)")
 
 	return cmd
 }
@@ -129,7 +147,9 @@ $ %s k r cosmoshub faucet-key "[mnemonic-words]"`, appName, appName)),
 				return errKeyExists(keyName)
 			}
 
-			coinType, err := cmd.Flags().GetInt32(flagCoinType)
+			cmdFlags := cmd.Flags()
+
+			coinType, err := cmdFlags.GetInt32(flagCoinType)
 			if err != nil {
 				return err
 			}
@@ -142,7 +162,20 @@ $ %s k r cosmoshub faucet-key "[mnemonic-words]"`, appName, appName)),
 				}
 			}
 
-			address, err := chain.ChainProvider.RestoreKey(keyName, args[2], uint32(coinType))
+			algo, err := cmdFlags.GetString(flagAlgo)
+			if err != nil {
+				return err
+			}
+
+			if algo == "" {
+				if ccp, ok := chain.ChainProvider.(*cosmos.CosmosProvider); ok {
+					algo = ccp.PCfg.SigningAlgorithm
+				} else {
+					algo = string(hd.Secp256k1Type)
+				}
+			}
+
+			address, err := chain.ChainProvider.RestoreKey(keyName, args[2], uint32(coinType), algo)
 			if err != nil {
 				return err
 			}
@@ -152,6 +185,7 @@ $ %s k r cosmoshub faucet-key "[mnemonic-words]"`, appName, appName)),
 		},
 	}
 	cmd.Flags().Int32(flagCoinType, -1, "coin type number for HD derivation")
+	cmd.Flags().String(flagAlgo, "", "signing algorithm for key (secp256k1, sr25519)")
 
 	return cmd
 }
diff --git a/cregistry/chain_info.go b/cregistry/chain_info.go
index 978142f..509b132 100644
--- a/cregistry/chain_info.go
+++ b/cregistry/chain_info.go
@@ -55,8 +55,9 @@ type ChainInfo struct {
 	Genesis      struct {
 		GenesisURL string `json:"genesis_url"`
 	} `json:"genesis"`
-	Slip44   *int `json:"slip44"`
-	Codebase struct {
+	Slip44           *int   `json:"slip44"`
+	SigningAlgorithm string `json:"signing-algorithm"`
+	Codebase         struct {
 		GitRepo            string   `json:"git_repo"`
 		RecommendedVersion string   `json:"recommended_version"`
 		CompatibleVersions []string `json:"compatible_versions"`
@@ -251,18 +252,19 @@ func (c ChainInfo) GetChainConfig(ctx context.Context) (*cosmos.CosmosProviderCo
 	}
 
 	return &cosmos.CosmosProviderConfig{
-		Key:            "default",
-		ChainID:        c.ChainID,
-		RPCAddr:        rpc,
-		AccountPrefix:  c.Bech32Prefix,
-		KeyringBackend: "test",
-		GasAdjustment:  1.2,
-		GasPrices:      gasPrices,
-		KeyDirectory:   home,
-		Debug:          debug,
-		Timeout:        "20s",
-		OutputFormat:   "json",
-		SignModeStr:    "direct",
-		Slip44:         c.Slip44,
+		Key:              "default",
+		ChainID:          c.ChainID,
+		RPCAddr:          rpc,
+		AccountPrefix:    c.Bech32Prefix,
+		KeyringBackend:   "test",
+		GasAdjustment:    1.2,
+		GasPrices:        gasPrices,
+		KeyDirectory:     home,
+		Debug:            debug,
+		Timeout:          "20s",
+		OutputFormat:     "json",
+		SignModeStr:      "direct",
+		Slip44:           c.Slip44,
+		SigningAlgorithm: c.SigningAlgorithm,
 	}, nil
 }
diff --git a/go.mod b/go.mod
index a452df9..ab96146 100644
--- a/go.mod
+++ b/go.mod
@@ -35,7 +35,6 @@ require (
 	golang.org/x/term v0.6.0
 	golang.org/x/text v0.8.0
 	google.golang.org/grpc v1.53.0
-	google.golang.org/protobuf v1.29.1
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.1
 )
@@ -176,6 +175,7 @@ require (
 	google.golang.org/api v0.110.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 // indirect
+	google.golang.org/protobuf v1.29.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	nhooyr.io/websocket v1.8.6 // indirect
 	pgregory.net/rapid v0.5.5 // indirect
diff --git a/proto/cosmos/crypto/sr25519/keys.proto b/proto/cosmos/crypto/sr25519/keys.proto
new file mode 100644
index 0000000..eed4813
--- /dev/null
+++ b/proto/cosmos/crypto/sr25519/keys.proto
@@ -0,0 +1,20 @@
+syntax = "proto3";
+
+// buf:lint:ignore PACKAGE_VERSION_SUFFIX
+package cosmos.crypto.sr25519;
+
+import "gogoproto/gogo.proto";
+
+// Originally github.com/cosmos/cosmos-sdk/crypto/keys/sr25519
+option go_package = "github.com/cosmos/relayer/v2/relayer/chains/cosmos/keys/sr25519";
+
+option (gogoproto.messagename_all)      = true;
+option (gogoproto.goproto_stringer_all) = false;
+option (gogoproto.goproto_getters_all)  = false;
+
+// PubKey defines a sr25519 ECDSA public key.
+message PubKey {
+    option (gogoproto.goproto_stringer) = false;
+
+    bytes key = 1 [(gogoproto.casttype) = "github.com/cometbft/cometbft/crypto/sr25519.PubKey"];
+}
diff --git a/relayer/chain.go b/relayer/chain.go
index 3dafdba..a23229b 100644
--- a/relayer/chain.go
+++ b/relayer/chain.go
@@ -8,6 +8,7 @@ import (
 	"time"
 
 	"github.com/avast/retry-go/v4"
+	"github.com/cosmos/cosmos-sdk/crypto/hd"
 	clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types"
 	"github.com/cosmos/relayer/v2/relayer/provider"
 	"go.uber.org/zap"
@@ -20,6 +21,7 @@ var (
 	RtyErr    = retry.LastErrorOnly(true)
 
 	defaultCoinType uint32 = 118
+	defaultAlgo     string = string(hd.Secp256k1Type)
 )
 
 // Chain represents the necessary data for connecting to and identifying a chain and its counterparties
@@ -146,7 +148,7 @@ func (c *Chain) CreateTestKey() error {
 	if c.ChainProvider.KeyExists(c.ChainProvider.Key()) {
 		return fmt.Errorf("key {%s} exists for chain {%s}", c.ChainProvider.Key(), c.ChainID())
 	}
-	_, err := c.ChainProvider.AddKey(c.ChainProvider.Key(), defaultCoinType)
+	_, err := c.ChainProvider.AddKey(c.ChainProvider.Key(), defaultCoinType, defaultAlgo)
 	return err
 }
 
diff --git a/relayer/chains/cosmos/keys.go b/relayer/chains/cosmos/keys.go
index 272c96b..0ccdd09 100644
--- a/relayer/chains/cosmos/keys.go
+++ b/relayer/chains/cosmos/keys.go
@@ -9,6 +9,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/crypto/keyring"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/cosmos/go-bip39"
+	"github.com/cosmos/relayer/v2/relayer/chains/cosmos/keys/sr25519"
 	"github.com/cosmos/relayer/v2/relayer/codecs/ethermint"
 	"github.com/cosmos/relayer/v2/relayer/codecs/injective"
 	"github.com/cosmos/relayer/v2/relayer/provider"
@@ -19,12 +20,14 @@ const ethereumCoinType = uint32(60)
 var (
 	// SupportedAlgorithms defines the list of signing algorithms used on Evmos:
 	//  - secp256k1     (Cosmos)
-	//  - eth_secp256k1 (Ethereum)
-	SupportedAlgorithms = keyring.SigningAlgoList{hd.Secp256k1, ethermint.EthSecp256k1, injective.EthSecp256k1}
+	//  - sr25519		(Cosmos)
+	//  - eth_secp256k1 (Ethereum, Injective)
+	SupportedAlgorithms = keyring.SigningAlgoList{hd.Secp256k1, sr25519.Sr25519, ethermint.EthSecp256k1, injective.EthSecp256k1}
 	// SupportedAlgorithmsLedger defines the list of signing algorithms used on Evmos for the Ledger device:
 	//  - secp256k1     (Cosmos)
-	//  - eth_secp256k1 (Ethereum)
-	SupportedAlgorithmsLedger = keyring.SigningAlgoList{hd.Secp256k1, ethermint.EthSecp256k1, injective.EthSecp256k1}
+	//  - sr25519		(Cosmos)
+	//  - eth_secp256k1 (Ethereum, Injective)
+	SupportedAlgorithmsLedger = keyring.SigningAlgoList{hd.Secp256k1, sr25519.Sr25519, ethermint.EthSecp256k1, injective.EthSecp256k1}
 )
 
 // KeyringAlgoOptions defines a function keys options for the ethereum Secp256k1 curve.
@@ -58,8 +61,8 @@ func (cc *CosmosProvider) KeystoreCreated(path string) bool {
 
 // AddKey generates a new mnemonic which is then converted to a private key and BIP-39 HD Path and persists it to the keystore.
 // It fails if there is an existing key with the same address.
-func (cc *CosmosProvider) AddKey(name string, coinType uint32) (output *provider.KeyOutput, err error) {
-	ko, err := cc.KeyAddOrRestore(name, coinType)
+func (cc *CosmosProvider) AddKey(name string, coinType uint32, signingAlgorithm string) (output *provider.KeyOutput, err error) {
+	ko, err := cc.KeyAddOrRestore(name, coinType, signingAlgorithm)
 	if err != nil {
 		return nil, err
 	}
@@ -68,8 +71,8 @@ func (cc *CosmosProvider) AddKey(name string, coinType uint32) (output *provider
 
 // RestoreKey converts a mnemonic to a private key and BIP-39 HD Path and persists it to the keystore.
 // It fails if there is an existing key with the same address.
-func (cc *CosmosProvider) RestoreKey(name, mnemonic string, coinType uint32) (address string, err error) {
-	ko, err := cc.KeyAddOrRestore(name, coinType, mnemonic)
+func (cc *CosmosProvider) RestoreKey(name, mnemonic string, coinType uint32, signingAlgorithm string) (address string, err error) {
+	ko, err := cc.KeyAddOrRestore(name, coinType, signingAlgorithm, mnemonic)
 	if err != nil {
 		return "", err
 	}
@@ -78,10 +81,17 @@ func (cc *CosmosProvider) RestoreKey(name, mnemonic string, coinType uint32) (ad
 
 // KeyAddOrRestore either generates a new mnemonic or uses the specified mnemonic and converts it to a private key
 // and BIP-39 HD Path which is then persisted to the keystore. It fails if there is an existing key with the same address.
-func (cc *CosmosProvider) KeyAddOrRestore(keyName string, coinType uint32, mnemonic ...string) (*provider.KeyOutput, error) {
+func (cc *CosmosProvider) KeyAddOrRestore(keyName string, coinType uint32, signingAlgorithm string, mnemonic ...string) (*provider.KeyOutput, error) {
 	var mnemonicStr string
 	var err error
-	algo := keyring.SignatureAlgo(hd.Secp256k1)
+
+	var algo keyring.SignatureAlgo
+	switch signingAlgorithm {
+	case string(hd.Sr25519Type):
+		algo = sr25519.Sr25519
+	default:
+		algo = hd.Secp256k1
+	}
 
 	if len(mnemonic) > 0 {
 		mnemonicStr = mnemonic[0]
diff --git a/relayer/chains/cosmos/keys/sr25519/algo.go b/relayer/chains/cosmos/keys/sr25519/algo.go
new file mode 100644
index 0000000..d0c0eca
--- /dev/null
+++ b/relayer/chains/cosmos/keys/sr25519/algo.go
@@ -0,0 +1,46 @@
+package sr25519
+
+import (
+	bip39 "github.com/cosmos/go-bip39"
+
+	tmsr25519 "github.com/cometbft/cometbft/crypto/sr25519"
+	"github.com/cosmos/cosmos-sdk/crypto/hd"
+	"github.com/cosmos/cosmos-sdk/crypto/types"
+)
+
+var Sr25519 = sr25519Algo{}
+
+type sr25519Algo struct {
+}
+
+func (s sr25519Algo) Name() hd.PubKeyType {
+	return hd.Sr25519Type
+}
+
+// Derive derives and returns the sr25519 private key for the given seed and HD path.
+func (s sr25519Algo) Derive() hd.DeriveFn {
+	return func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) {
+		seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase)
+		if err != nil {
+			return nil, err
+		}
+
+		masterPriv, ch := hd.ComputeMastersFromSeed(seed)
+		if len(hdPath) == 0 {
+			return masterPriv[:], nil
+		}
+		derivedKey, err := hd.DerivePrivateKeyForPath(masterPriv, ch, hdPath)
+
+		return derivedKey, err
+	}
+}
+
+// Generate generates a sr25519 private key from the given bytes.
+func (s sr25519Algo) Generate() hd.GenerateFn {
+	return func(bz []byte) types.PrivKey {
+		var bzArr = make([]byte, 32)
+		copy(bzArr, bz)
+
+		return &PrivKey{PrivKey: tmsr25519.GenPrivKeyFromSecret(bzArr)}
+	}
+}
diff --git a/relayer/chains/cosmos/keys/sr25519/keys.pb.go b/relayer/chains/cosmos/keys/sr25519/keys.pb.go
new file mode 100644
index 0000000..49a3599
--- /dev/null
+++ b/relayer/chains/cosmos/keys/sr25519/keys.pb.go
@@ -0,0 +1,320 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: cosmos/crypto/sr25519/keys.proto
+
+// buf:lint:ignore PACKAGE_VERSION_SUFFIX
+
+package sr25519
+
+import (
+	fmt "fmt"
+	github_com_cometbft_cometbft_crypto_sr25519 "github.com/cometbft/cometbft/crypto/sr25519"
+	proto "github.com/cosmos/gogoproto/proto"
+	_ "github.com/gogo/protobuf/gogoproto"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+// PubKey defines a sr25519 ECDSA public key.
+type PubKey struct {
+	Key github_com_cometbft_cometbft_crypto_sr25519.PubKey `protobuf:"bytes,1,opt,name=key,proto3,casttype=github.com/cometbft/cometbft/crypto/sr25519.PubKey" json:"key,omitempty"`
+}
+
+func (m *PubKey) Reset()      { *m = PubKey{} }
+func (*PubKey) ProtoMessage() {}
+func (*PubKey) Descriptor() ([]byte, []int) {
+	return fileDescriptor_daddabaf35039fbd, []int{0}
+}
+func (m *PubKey) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *PubKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_PubKey.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *PubKey) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PubKey.Merge(m, src)
+}
+func (m *PubKey) XXX_Size() int {
+	return m.Size()
+}
+func (m *PubKey) XXX_DiscardUnknown() {
+	xxx_messageInfo_PubKey.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PubKey proto.InternalMessageInfo
+
+func (*PubKey) XXX_MessageName() string {
+	return "cosmos.crypto.sr25519.PubKey"
+}
+func init() {
+	proto.RegisterType((*PubKey)(nil), "cosmos.crypto.sr25519.PubKey")
+}
+
+func init() { proto.RegisterFile("cosmos/crypto/sr25519/keys.proto", fileDescriptor_daddabaf35039fbd) }
+
+var fileDescriptor_daddabaf35039fbd = []byte{
+	// 222 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0xce, 0x2f, 0xce,
+	0xcd, 0x2f, 0xd6, 0x4f, 0x2e, 0xaa, 0x2c, 0x28, 0xc9, 0xd7, 0x2f, 0x2e, 0x32, 0x32, 0x35, 0x35,
+	0xb4, 0xd4, 0xcf, 0x4e, 0xad, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x85, 0xa8,
+	0xd0, 0x83, 0xa8, 0xd0, 0x83, 0xaa, 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xab, 0xd0, 0x07,
+	0xb1, 0x20, 0x8a, 0x95, 0x22, 0xb8, 0xd8, 0x02, 0x4a, 0x93, 0xbc, 0x53, 0x2b, 0x85, 0x3c, 0xb8,
+	0x98, 0xb3, 0x53, 0x2b, 0x25, 0x18, 0x15, 0x18, 0x35, 0x78, 0x9c, 0xcc, 0x7e, 0xdd, 0x93, 0x37,
+	0x4a, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0xcf, 0x4d, 0x2d,
+	0x49, 0x4a, 0x2b, 0x41, 0x62, 0xa0, 0xd8, 0xaf, 0x07, 0x31, 0x24, 0x08, 0x64, 0x84, 0x15, 0xcb,
+	0x8c, 0x05, 0xf2, 0x0c, 0x4e, 0xa9, 0x27, 0x1e, 0xca, 0x31, 0xdc, 0x78, 0x28, 0xc7, 0x70, 0xe2,
+	0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70,
+	0xe2, 0xb1, 0x1c, 0xe3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0xd9, 0xa3, 0x58,
+	0x02, 0xf6, 0x59, 0x51, 0x6a, 0x4e, 0x62, 0x65, 0x6a, 0x91, 0x7e, 0x99, 0x11, 0x9c, 0x99, 0x9c,
+	0x91, 0x98, 0x99, 0x57, 0x0c, 0x53, 0x00, 0xf2, 0x2b, 0xcc, 0xe2, 0x24, 0x36, 0xb0, 0x3f, 0x8c,
+	0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x24, 0xfd, 0x40, 0x0e, 0x18, 0x01, 0x00, 0x00,
+}
+
+func (m *PubKey) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *PubKey) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *PubKey) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Key) > 0 {
+		i -= len(m.Key)
+		copy(dAtA[i:], m.Key)
+		i = encodeVarintKeys(dAtA, i, uint64(len(m.Key)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintKeys(dAtA []byte, offset int, v uint64) int {
+	offset -= sovKeys(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *PubKey) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Key)
+	if l > 0 {
+		n += 1 + l + sovKeys(uint64(l))
+	}
+	return n
+}
+
+func sovKeys(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozKeys(x uint64) (n int) {
+	return sovKeys(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *PubKey) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowKeys
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: PubKey: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: PubKey: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowKeys
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthKeys
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthKeys
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...)
+			if m.Key == nil {
+				m.Key = []byte{}
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipKeys(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthKeys
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipKeys(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowKeys
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowKeys
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowKeys
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthKeys
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupKeys
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthKeys
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthKeys        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowKeys          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupKeys = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/relayer/chains/cosmos/keys/sr25519/privkey.go b/relayer/chains/cosmos/keys/sr25519/privkey.go
new file mode 100644
index 0000000..b7b6efa
--- /dev/null
+++ b/relayer/chains/cosmos/keys/sr25519/privkey.go
@@ -0,0 +1,47 @@
+package sr25519
+
+import (
+	tmsr25519 "github.com/cometbft/cometbft/crypto/sr25519"
+	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+)
+
+const (
+	PrivKeySize = 32
+	PrivKeyName = "tendermint/PrivKeySr25519"
+)
+
+type PrivKey struct {
+	tmsr25519.PrivKey
+}
+
+// type conversion
+func (m *PrivKey) PubKey() cryptotypes.PubKey {
+	pk, ok := m.PrivKey.PubKey().(tmsr25519.PubKey)
+	if !ok {
+		panic("invalid public key type for sr25519 private key")
+	}
+	return &PubKey{Key: pk}
+}
+
+// type conversion
+func (m *PrivKey) Equals(other cryptotypes.LedgerPrivKey) bool {
+	sk2, ok := other.(*PrivKey)
+	if !ok {
+		return false
+	}
+	return m.PrivKey.Equals(sk2.PrivKey)
+}
+
+func (m *PrivKey) ProtoMessage() {}
+
+func (m *PrivKey) Reset() {
+	m.PrivKey = tmsr25519.PrivKey{}
+}
+
+func (m *PrivKey) String() string {
+	return string(m.Bytes())
+}
+
+func GenPrivKey() *PrivKey {
+	return &PrivKey{tmsr25519.GenPrivKey()}
+}
diff --git a/relayer/chains/cosmos/keys/sr25519/pubkey.go b/relayer/chains/cosmos/keys/sr25519/pubkey.go
new file mode 100644
index 0000000..6325f99
--- /dev/null
+++ b/relayer/chains/cosmos/keys/sr25519/pubkey.go
@@ -0,0 +1,38 @@
+package sr25519
+
+import (
+	"bytes"
+
+	"github.com/cometbft/cometbft/crypto"
+	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+)
+
+const PubKeyName = "tendermint/PubKeySr25519"
+
+func (m *PubKey) Equals(other cryptotypes.PubKey) bool {
+	pk2, ok := other.(*PubKey)
+	if !ok {
+		return false
+	}
+	return bytes.Equal(m.Key, pk2.Key)
+}
+
+func (m *PubKey) Address() crypto.Address {
+	return m.Key.Address()
+}
+
+func (m PubKey) Bytes() []byte {
+	return m.Key.Bytes()
+}
+
+func (m PubKey) String() string {
+	return m.Key.String()
+}
+
+func (m PubKey) Type() string {
+	return "sr25519"
+}
+
+func (m PubKey) VerifySignature(msg []byte, sigBytes []byte) bool {
+	return m.Key.VerifySignature(msg, sigBytes)
+}
diff --git a/relayer/chains/cosmos/provider.go b/relayer/chains/cosmos/provider.go
index 58d3ca4..b202b3c 100644
--- a/relayer/chains/cosmos/provider.go
+++ b/relayer/chains/cosmos/provider.go
@@ -35,26 +35,27 @@ var (
 const cometEncodingThreshold = "v0.37.0-alpha"
 
 type CosmosProviderConfig struct {
-	KeyDirectory    string                  `json:"key-directory" yaml:"key-directory"`
-	Key             string                  `json:"key" yaml:"key"`
-	ChainName       string                  `json:"-" yaml:"-"`
-	ChainID         string                  `json:"chain-id" yaml:"chain-id"`
-	RPCAddr         string                  `json:"rpc-addr" yaml:"rpc-addr"`
-	AccountPrefix   string                  `json:"account-prefix" yaml:"account-prefix"`
-	KeyringBackend  string                  `json:"keyring-backend" yaml:"keyring-backend"`
-	GasAdjustment   float64                 `json:"gas-adjustment" yaml:"gas-adjustment"`
-	GasPrices       string                  `json:"gas-prices" yaml:"gas-prices"`
-	MinGasAmount    uint64                  `json:"min-gas-amount" yaml:"min-gas-amount"`
-	Debug           bool                    `json:"debug" yaml:"debug"`
-	Timeout         string                  `json:"timeout" yaml:"timeout"`
-	BlockTimeout    string                  `json:"block-timeout" yaml:"block-timeout"`
-	OutputFormat    string                  `json:"output-format" yaml:"output-format"`
-	SignModeStr     string                  `json:"sign-mode" yaml:"sign-mode"`
-	ExtraCodecs     []string                `json:"extra-codecs" yaml:"extra-codecs"`
-	Modules         []module.AppModuleBasic `json:"-" yaml:"-"`
-	Slip44          *int                    `json:"coin-type" yaml:"coin-type"`
-	Broadcast       provider.BroadcastMode  `json:"broadcast-mode" yaml:"broadcast-mode"`
-	MinLoopDuration time.Duration           `json:"min-loop-duration" yaml:"min-loop-duration"`
+	KeyDirectory     string                  `json:"key-directory" yaml:"key-directory"`
+	Key              string                  `json:"key" yaml:"key"`
+	ChainName        string                  `json:"-" yaml:"-"`
+	ChainID          string                  `json:"chain-id" yaml:"chain-id"`
+	RPCAddr          string                  `json:"rpc-addr" yaml:"rpc-addr"`
+	AccountPrefix    string                  `json:"account-prefix" yaml:"account-prefix"`
+	KeyringBackend   string                  `json:"keyring-backend" yaml:"keyring-backend"`
+	GasAdjustment    float64                 `json:"gas-adjustment" yaml:"gas-adjustment"`
+	GasPrices        string                  `json:"gas-prices" yaml:"gas-prices"`
+	MinGasAmount     uint64                  `json:"min-gas-amount" yaml:"min-gas-amount"`
+	Debug            bool                    `json:"debug" yaml:"debug"`
+	Timeout          string                  `json:"timeout" yaml:"timeout"`
+	BlockTimeout     string                  `json:"block-timeout" yaml:"block-timeout"`
+	OutputFormat     string                  `json:"output-format" yaml:"output-format"`
+	SignModeStr      string                  `json:"sign-mode" yaml:"sign-mode"`
+	ExtraCodecs      []string                `json:"extra-codecs" yaml:"extra-codecs"`
+	Modules          []module.AppModuleBasic `json:"-" yaml:"-"`
+	Slip44           *int                    `json:"coin-type" yaml:"coin-type"`
+	SigningAlgorithm string                  `json:"signing-algorithm" yaml:"signing-algorithm"`
+	Broadcast        provider.BroadcastMode  `json:"broadcast-mode" yaml:"broadcast-mode"`
+	MinLoopDuration  time.Duration           `json:"min-loop-duration" yaml:"min-loop-duration"`
 }
 
 func (pc CosmosProviderConfig) Validate() error {
diff --git a/relayer/chains/mock/mock_chain_processor.go b/relayer/chains/mock/mock_chain_processor.go
index 5c94a63..4faf1fe 100644
--- a/relayer/chains/mock/mock_chain_processor.go
+++ b/relayer/chains/mock/mock_chain_processor.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"time"
 
+	"github.com/cosmos/cosmos-sdk/crypto/hd"
 	"github.com/cosmos/relayer/v2/relayer/chains/cosmos"
 	"github.com/cosmos/relayer/v2/relayer/processor"
 	"github.com/cosmos/relayer/v2/relayer/provider"
@@ -49,7 +50,7 @@ func NewMockChainProcessor(ctx context.Context, log *zap.Logger, chainID string,
 	}
 	chainProvider, _ := chainProviderCfg.NewProvider(zap.NewNop(), "/tmp", true, "mock-chain-name-"+chainID)
 	_ = chainProvider.Init(ctx)
-	_, _ = chainProvider.AddKey(chainProvider.Key(), 118)
+	_, _ = chainProvider.AddKey(chainProvider.Key(), 118, string(hd.Secp256k1Type))
 	return &MockChainProcessor{
 		log:             log,
 		chainID:         chainID,
diff --git a/relayer/chains/penumbra/keys.go b/relayer/chains/penumbra/keys.go
index c9e918b..aa09fa7 100644
--- a/relayer/chains/penumbra/keys.go
+++ b/relayer/chains/penumbra/keys.go
@@ -58,7 +58,7 @@ func (cc *PenumbraProvider) KeystoreCreated(path string) bool {
 
 // AddKey generates a new mnemonic which is then converted to a private key and BIP-39 HD Path and persists it to the keystore.
 // It fails if there is an existing key with the same address.
-func (cc *PenumbraProvider) AddKey(name string, coinType uint32) (output *provider.KeyOutput, err error) {
+func (cc *PenumbraProvider) AddKey(name string, coinType uint32, signingAlgorithm string) (output *provider.KeyOutput, err error) {
 	ko, err := cc.KeyAddOrRestore(name, coinType)
 	if err != nil {
 		return nil, err
@@ -68,7 +68,7 @@ func (cc *PenumbraProvider) AddKey(name string, coinType uint32) (output *provid
 
 // RestoreKey converts a mnemonic to a private key and BIP-39 HD Path and persists it to the keystore.
 // It fails if there is an existing key with the same address.
-func (cc *PenumbraProvider) RestoreKey(name, mnemonic string, coinType uint32) (address string, err error) {
+func (cc *PenumbraProvider) RestoreKey(name, mnemonic string, coinType uint32, signingAlgorithm string) (address string, err error) {
 	ko, err := cc.KeyAddOrRestore(name, coinType, mnemonic)
 	if err != nil {
 		return "", err
diff --git a/relayer/provider/provider.go b/relayer/provider/provider.go
index b3c3160..362e9e9 100644
--- a/relayer/provider/provider.go
+++ b/relayer/provider/provider.go
@@ -15,7 +15,7 @@ import (
 	chantypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
 	commitmenttypes "github.com/cosmos/ibc-go/v7/modules/core/23-commitment/types"
 	ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported"
-	"github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint"
+	tendermint "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
 )
@@ -215,8 +215,8 @@ func (r RelayerTxResponse) MarshalLogObject(enc zapcore.ObjectEncoder) error {
 type KeyProvider interface {
 	CreateKeystore(path string) error
 	KeystoreCreated(path string) bool
-	AddKey(name string, coinType uint32) (output *KeyOutput, err error)
-	RestoreKey(name, mnemonic string, coinType uint32) (address string, err error)
+	AddKey(name string, coinType uint32, signingAlgorithm string) (output *KeyOutput, err error)
+	RestoreKey(name, mnemonic string, coinType uint32, signingAlgorithm string) (address string, err error)
 	ShowAddress(name string) (address string, err error)
 	ListAddresses() (map[string]string, error)
 	DeleteKey(name string) error


Test patch:
diff --git a/interchaintest/go.mod b/interchaintest/go.mod
index 66f66cf..58fc82b 100644
--- a/interchaintest/go.mod
+++ b/interchaintest/go.mod
@@ -5,13 +5,13 @@ go 1.20
 require (
 	cosmossdk.io/simapp v0.0.0-20230224204036-a6adb0821462
 	github.com/cometbft/cometbft v0.37.0
-	github.com/cosmos/cosmos-sdk v0.47.0
+	github.com/cosmos/cosmos-sdk v0.47.1
 	github.com/cosmos/ibc-go/v7 v7.0.0
 	github.com/cosmos/relayer/v2 v2.0.0
 	github.com/docker/docker v20.10.24+incompatible
 	github.com/icza/dyno v0.0.0-20220812133438-f0b6f8a18845
 	github.com/moby/moby v20.10.22+incompatible
-	github.com/strangelove-ventures/interchaintest/v7 v7.0.0-20230324180345-a06599d2eb8c
+	github.com/strangelove-ventures/interchaintest/v7 v7.0.0-20230405184655-2e6d60073e71
 	github.com/stretchr/testify v1.8.2
 	go.uber.org/zap v1.24.0
 	golang.org/x/sync v0.1.0
@@ -27,11 +27,11 @@ require (
 	cosmossdk.io/core v0.5.1 // indirect
 	cosmossdk.io/depinject v1.0.0-alpha.3 // indirect
 	cosmossdk.io/errors v1.0.0-beta.7 // indirect
-	cosmossdk.io/math v1.0.0-rc.0 // indirect
+	cosmossdk.io/math v1.0.0 // indirect
 	cosmossdk.io/tools/rosetta v0.2.1 // indirect
 	filippo.io/edwards25519 v1.0.0 // indirect
 	github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
-	github.com/99designs/keyring v1.2.1 // indirect
+	github.com/99designs/keyring v1.2.2 // indirect
 	github.com/BurntSushi/toml v1.2.1 // indirect
 	github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
 	github.com/ChainSafe/go-schnorrkel/1 v0.0.0-00010101000000-000000000000 // indirect
@@ -50,7 +50,7 @@ require (
 	github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
 	github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect
 	github.com/cenkalti/backoff/v4 v4.1.3 // indirect
-	github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.10 // indirect
+	github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.12 // indirect
 	github.com/cespare/xxhash v1.1.0 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/chzyer/readline v1.5.1 // indirect
@@ -211,7 +211,7 @@ require (
 	go.uber.org/atomic v1.10.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
 	golang.org/x/crypto v0.7.0 // indirect
-	golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 // indirect
+	golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
 	golang.org/x/mod v0.9.0 // indirect
 	golang.org/x/net v0.8.0 // indirect
 	golang.org/x/oauth2 v0.5.0 // indirect
@@ -223,7 +223,7 @@ require (
 	google.golang.org/api v0.110.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 // indirect
-	google.golang.org/grpc v1.53.0 // indirect
+	google.golang.org/grpc v1.54.0 // indirect
 	google.golang.org/protobuf v1.29.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
@@ -237,7 +237,7 @@ require (
 	modernc.org/mathutil v1.5.0 // indirect
 	modernc.org/memory v1.5.0 // indirect
 	modernc.org/opt v0.1.3 // indirect
-	modernc.org/sqlite v1.21.0 // indirect
+	modernc.org/sqlite v1.21.1 // indirect
 	modernc.org/strutil v1.1.3 // indirect
 	modernc.org/token v1.0.1 // indirect
 	nhooyr.io/websocket v1.8.6 // indirect
diff --git a/interchaintest/go.sum b/interchaintest/go.sum
index 656c273..f64f4e5 100644
--- a/interchaintest/go.sum
+++ b/interchaintest/go.sum
@@ -197,8 +197,8 @@ cosmossdk.io/depinject v1.0.0-alpha.3 h1:6evFIgj//Y3w09bqOUOzEpFj5tsxBqdc5CfkO7z
 cosmossdk.io/depinject v1.0.0-alpha.3/go.mod h1:eRbcdQ7MRpIPEM5YUJh8k97nxHpYbc3sMUnEtt8HPWU=
 cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w=
 cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE=
-cosmossdk.io/math v1.0.0-rc.0 h1:ml46ukocrAAoBpYKMidF0R2tQJ1Uxfns0yH8wqgMAFc=
-cosmossdk.io/math v1.0.0-rc.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k=
+cosmossdk.io/math v1.0.0 h1:ro9w7eKx23om2tZz/VM2Pf+z2WAbGX1yDQQOJ6iGeJw=
+cosmossdk.io/math v1.0.0/go.mod h1:Ygz4wBHrgc7g0N+8+MrnTfS9LLn9aaTGa9hKopuym5k=
 cosmossdk.io/simapp v0.0.0-20230224204036-a6adb0821462 h1:g8muUHnXL8vhld2Sjilyhb1UQObc+x9GVuDK43TYZns=
 cosmossdk.io/simapp v0.0.0-20230224204036-a6adb0821462/go.mod h1:4Dd3NLoLYoN90kZ0uyHoTHzVVk9+J0v4HhZRBNTAq2c=
 cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw=
@@ -208,8 +208,8 @@ filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
 filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
 github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
 github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
-github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o=
-github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA=
+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/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
@@ -348,8 +348,8 @@ github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq
 github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
 github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.10 h1:HW3XP9G3mXr0gYPfxCAQLD29u+Ys0uIeotv9RWfnhrM=
-github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.10/go.mod h1:5g1oM4Zu3BOaLpsKQ+O8PAv2kNuq+kPcA1VzFbsSqxE=
+github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.12 h1:DCYWIBOalB0mKKfUg2HhtGgIkBbMA1fnlnkZp7fHB18=
+github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.12/go.mod h1:5g1oM4Zu3BOaLpsKQ+O8PAv2kNuq+kPcA1VzFbsSqxE=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -510,8 +510,8 @@ github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk=
 github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis=
 github.com/cosmos/cosmos-proto v1.0.0-beta.2 h1:X3OKvWgK9Gsejo0F1qs5l8Qn6xJV/AzgIWR2wZ8Nua8=
 github.com/cosmos/cosmos-proto v1.0.0-beta.2/go.mod h1:+XRCLJ14pr5HFEHIUcn51IKXD1Fy3rkEQqt4WqmN4V0=
-github.com/cosmos/cosmos-sdk v0.47.0 h1:GKYtBpvjwuDEVix1vdnQpq7PuEOnItuEK0vdAL2cZ5g=
-github.com/cosmos/cosmos-sdk v0.47.0/go.mod h1:FTtZbqiHCZ2vun9WrPq6qLQafNKkAuIhLAxzLjr2TiI=
+github.com/cosmos/cosmos-sdk v0.47.1 h1:HnaCYtaAMWZp1SdlwwE1mPJ8kFlZ/TuEJ/ciNXH6Uno=
+github.com/cosmos/cosmos-sdk v0.47.1/go.mod h1:14tO5KQaTrl2q3OxBnDRfue7TRN9zkXS0cLutrSqkOo=
 github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
 github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
 github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
@@ -1370,8 +1370,8 @@ github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jH
 github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
 github.com/strangelove-ventures/go-subkey v1.0.7 h1:cOP/Lajg3uxV/tvspu0m6+0Cu+DJgygkEAbx/s+f35I=
 github.com/strangelove-ventures/go-subkey v1.0.7/go.mod h1:E34izOIEm+sZ1YmYawYRquqBQWeZBjVB4pF7bMuhc1c=
-github.com/strangelove-ventures/interchaintest/v7 v7.0.0-20230324180345-a06599d2eb8c h1:vGl7skxBHHW5qE88Dc9q+ZOxwJJIYDNjqmQTPHiPUF0=
-github.com/strangelove-ventures/interchaintest/v7 v7.0.0-20230324180345-a06599d2eb8c/go.mod h1:/pjBA7gAa1AuMphaLp8joBjVOAHqewe8UenyQ45sh9M=
+github.com/strangelove-ventures/interchaintest/v7 v7.0.0-20230405184655-2e6d60073e71 h1:bbhg6Iol/5UR65+4kPaki+3mNUgyvcQe+ZHrleorKKY=
+github.com/strangelove-ventures/interchaintest/v7 v7.0.0-20230405184655-2e6d60073e71/go.mod h1:tkBlI3o0Z1jgkZIkckOLIHJuR+S0LbGoBEGg4NQ4Aa8=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
@@ -1528,8 +1528,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
-golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 h1:LGJsf5LRplCck6jUCH3dBL2dmycNruWNF5xugkSlfXw=
-golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
+golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -2122,8 +2122,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu
 google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
 google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
 google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
-google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
-google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
+google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
+google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -2245,8 +2245,8 @@ modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
 modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
 modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
 modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
-modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
-modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
+modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU=
+modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
 modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
 modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
 modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
diff --git a/interchaintest/relayer.go b/interchaintest/relayer.go
index ded1c97..a57c2ad 100644
--- a/interchaintest/relayer.go
+++ b/interchaintest/relayer.go
@@ -85,8 +85,8 @@ func (r *Relayer) AddChainConfiguration(ctx context.Context, _ ibc.RelayerExecRe
 	return nil
 }
 
-func (r *Relayer) AddKey(ctx context.Context, _ ibc.RelayerExecReporter, chainID, keyName string, coinType string) (ibc.Wallet, error) {
-	res := r.sys().RunC(ctx, r.log(), "keys", "add", chainID, keyName, "--coin-type", coinType)
+func (r *Relayer) AddKey(ctx context.Context, _ ibc.RelayerExecReporter, chainID, keyName, coinType, signingAlgorithm string) (ibc.Wallet, error) {
+	res := r.sys().RunC(ctx, r.log(), "keys", "add", chainID, keyName, "--coin-type", coinType, "--signing-algorithm", signingAlgorithm)
 	if res.Err != nil {
 		return nil, res.Err
 	}
@@ -99,8 +99,8 @@ func (r *Relayer) AddKey(ctx context.Context, _ ibc.RelayerExecReporter, chainID
 	return w, nil
 }
 
-func (r *Relayer) RestoreKey(ctx context.Context, _ ibc.RelayerExecReporter, chainID, keyName, coinType, mnemonic string) error {
-	res := r.sys().RunC(ctx, r.log(), "keys", "restore", chainID, keyName, mnemonic, "--coin-type", coinType)
+func (r *Relayer) RestoreKey(ctx context.Context, _ ibc.RelayerExecReporter, chainID, keyName, coinType, signingAlgorithm, mnemonic string) error {
+	res := r.sys().RunC(ctx, r.log(), "keys", "restore", chainID, keyName, mnemonic, "--coin-type", coinType, "--signing-algorithm", signingAlgorithm)
 	if res.Err != nil {
 		return res.Err
 	}
diff --git a/relayer/chains/cosmos/keys_test.go b/relayer/chains/cosmos/keys_test.go
new file mode 100644
index 0000000..9879310
--- /dev/null
+++ b/relayer/chains/cosmos/keys_test.go
@@ -0,0 +1,104 @@
+package cosmos_test
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/cosmos/relayer/v2/relayer/chains/cosmos"
+	"github.com/cosmos/relayer/v2/relayer/provider"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap"
+)
+
+func testProviderWithKeystore(t *testing.T, accountPrefix string, extraCodecs []string) provider.ChainProvider {
+	homePath := t.TempDir()
+	cfg := cosmos.CosmosProviderConfig{
+		ChainID:        "test",
+		KeyDirectory:   filepath.Join(homePath, "keys"),
+		KeyringBackend: "test",
+		Timeout:        "10s",
+		AccountPrefix:  accountPrefix,
+		ExtraCodecs:    extraCodecs,
+	}
+	p, err := cfg.NewProvider(zap.NewNop(), homePath, true, "test_chain")
+	if err != nil {
+		t.Fatalf("Error creating provider: %v", err)
+	}
+	err = p.CreateKeystore(homePath)
+	if err != nil {
+		t.Fatalf("Error creating keystore: %v", err)
+	}
+	return p
+}
+
+// TestKeyRestore restores a test mnemonic
+func TestKeyRestore(t *testing.T) {
+	const (
+		keyName            = "test_key"
+		signatureAlgorithm = "secp256k1"
+		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"
+		accountPrefix      = "cosmos"
+		expectedAddress    = "cosmos15cw268ckjj2hgq8q3jf68slwjjcjlvxy57je2u"
+		coinType           = uint32(118)
+	)
+
+	p := testProviderWithKeystore(t, accountPrefix, nil)
+
+	address, err := p.RestoreKey(keyName, mnemonic, coinType, signatureAlgorithm)
+	require.NoError(t, err)
+	require.Equal(t, expectedAddress, address)
+}
+
+// TestKeyRestoreEth restores a test mnemonic
+func TestKeyRestoreEth(t *testing.T) {
+	const (
+		keyName            = "test_key"
+		signatureAlgorithm = "secp256k1"
+		mnemonic           = "three elevator silk family street child flip also leaf inmate call frame shock little legal october vivid enable fetch siege sell burger dolphin green"
+		accountPrefix      = "evmos"
+		expectedAddress    = "evmos1dea7vlekr9e34vugwkvesulglt8fx4e457vk9z"
+		coinType           = uint32(60)
+	)
+
+	p := testProviderWithKeystore(t, accountPrefix, []string{"ethermint"})
+
+	address, err := p.RestoreKey(keyName, mnemonic, coinType, signatureAlgorithm)
+	require.NoError(t, err)
+	require.Equal(t, expectedAddress, address)
+}
+
+// TestKeyRestoreInj restores a test mnemonic
+func TestKeyRestoreInj(t *testing.T) {
+	const (
+		keyName            = "inj_key"
+		signatureAlgorithm = "secp256k1"
+		mnemonic           = "three elevator silk family street child flip also leaf inmate call frame shock little legal october vivid enable fetch siege sell burger dolphin green"
+		accountPrefix      = "inj"
+		expectedAddress    = "inj1dea7vlekr9e34vugwkvesulglt8fx4e4uk2udj"
+		coinType           = uint32(60)
+	)
+
+	p := testProviderWithKeystore(t, accountPrefix, []string{"injective"})
+
+	address, err := p.RestoreKey(keyName, mnemonic, coinType, signatureAlgorithm)
+	require.NoError(t, err)
+	require.Equal(t, expectedAddress, address)
+}
+
+// TestKeyRestoreSr25519 restores a test mnemonic
+func TestKeyRestoreSr25519(t *testing.T) {
+	const (
+		keyName            = "sei_key"
+		signatureAlgorithm = "sr25519"
+		mnemonic           = "three elevator silk family street child flip also leaf inmate call frame shock little legal october vivid enable fetch siege sell burger dolphin green"
+		accountPrefix      = "sei"
+		expectedAddress    = "sei1nmlj0guznnt0qyfj4yl6q5g4xuvgly4qw0w026"
+		coinType           = uint32(118)
+	)
+
+	p := testProviderWithKeystore(t, accountPrefix, nil)
+
+	address, err := p.RestoreKey(keyName, mnemonic, coinType, signatureAlgorithm)
+	require.NoError(t, err)
+	require.Equal(t, expectedAddress, address)
+}