-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
core: use loop.Keystore, support arbitrarily prefixed Cosmos addresses
- Loading branch information
Showing
10 changed files
with
306 additions
and
103 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,56 +1,62 @@ | ||
package cosmostxm | ||
|
||
import ( | ||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
"bytes" | ||
"context" | ||
|
||
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/cosmoskey" | ||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" | ||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
) | ||
|
||
var _ cryptotypes.PrivKey = KeyWrapper{} | ||
|
||
// KeyWrapper wrapper around a cosmos transmitter key | ||
// for use in the cosmos txbuilder and client, see chainlink-cosmos. | ||
// KeyWrapper uses a KeystoreAdapter to implement the cosmos-sdk PrivKey interface for a specific key. | ||
type KeyWrapper struct { | ||
key cosmoskey.Key | ||
adapter *KeystoreAdapter | ||
account string | ||
} | ||
|
||
// NewKeyWrapper create a key wrapper | ||
func NewKeyWrapper(key cosmoskey.Key) KeyWrapper { | ||
return KeyWrapper{key: key} | ||
var _ cryptotypes.PrivKey = &KeyWrapper{} | ||
|
||
func NewKeyWrapper(adapter *KeystoreAdapter, account string) *KeyWrapper { | ||
return &KeyWrapper{ | ||
adapter: adapter, | ||
account: account, | ||
} | ||
} | ||
|
||
// Reset nop | ||
func (k KeyWrapper) Reset() {} | ||
func (a *KeyWrapper) Bytes() []byte { | ||
// don't expose the private key. | ||
return nil | ||
} | ||
|
||
// ProtoMessage nop | ||
func (k KeyWrapper) ProtoMessage() {} | ||
func (a *KeyWrapper) Sign(msg []byte) ([]byte, error) { | ||
return a.adapter.Sign(context.Background(), a.account, msg) | ||
} | ||
|
||
// String nop | ||
func (k KeyWrapper) String() string { | ||
return "" | ||
func (a *KeyWrapper) PubKey() cryptotypes.PubKey { | ||
pubKey, err := a.adapter.PubKey(a.account) | ||
if err != nil { | ||
// return an empty pubkey if it's not found. | ||
return &secp256k1.PubKey{Key: []byte{}} | ||
} | ||
return pubKey | ||
} | ||
|
||
// Bytes does not expose private key | ||
func (k KeyWrapper) Bytes() []byte { | ||
return []byte{} | ||
func (a *KeyWrapper) Equals(other cryptotypes.LedgerPrivKey) bool { | ||
return bytes.Equal(a.PubKey().Bytes(), other.PubKey().Bytes()) | ||
} | ||
|
||
// Sign sign a message with key | ||
func (k KeyWrapper) Sign(msg []byte) ([]byte, error) { | ||
return k.key.ToPrivKey().Sign(msg) | ||
func (a *KeyWrapper) Type() string { | ||
return "secp256k1" | ||
} | ||
|
||
// PubKey get the pubkey | ||
func (k KeyWrapper) PubKey() cryptotypes.PubKey { | ||
return k.key.PublicKey() | ||
func (a *KeyWrapper) Reset() { | ||
// no-op | ||
} | ||
|
||
// Equals compare against another key | ||
func (k KeyWrapper) Equals(a cryptotypes.LedgerPrivKey) bool { | ||
return k.PubKey().Address().String() == a.PubKey().Address().String() | ||
func (a *KeyWrapper) String() string { | ||
return "<redacted>" | ||
} | ||
|
||
// Type nop | ||
func (k KeyWrapper) Type() string { | ||
return "" | ||
func (a *KeyWrapper) ProtoMessage() { | ||
// no-op | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package cosmostxm | ||
|
||
import ( | ||
"context" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"sync" | ||
|
||
"github.com/cometbft/cometbft/crypto" | ||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" | ||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" | ||
"github.com/cosmos/cosmos-sdk/types/bech32" | ||
"github.com/pkg/errors" | ||
"golang.org/x/crypto/ripemd160" //nolint: staticcheck | ||
|
||
"github.com/smartcontractkit/chainlink-relay/pkg/loop" | ||
) | ||
|
||
type accountInfo struct { | ||
Account string | ||
PubKey *secp256k1.PubKey | ||
} | ||
|
||
// An adapter for a Cosmos loop.Keystore to translate public keys into bech32-prefixed account addresses. | ||
type KeystoreAdapter struct { | ||
keystore loop.Keystore | ||
accountPrefix string | ||
mutex sync.RWMutex | ||
addressToPubKey map[string]*accountInfo | ||
} | ||
|
||
func NewKeystoreAdapter(keystore loop.Keystore, accountPrefix string) *KeystoreAdapter { | ||
return &KeystoreAdapter{ | ||
keystore: keystore, | ||
accountPrefix: accountPrefix, | ||
addressToPubKey: make(map[string]*accountInfo), | ||
} | ||
} | ||
|
||
func (ka *KeystoreAdapter) updateMappingLocked() error { | ||
accounts, err := ka.keystore.Accounts(context.Background()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// similar to cosmos-sdk, cache and re-use calculated bech32 addresses to prevent duplicated work. | ||
// ref: https://github.com/cosmos/cosmos-sdk/blob/3b509c187e1643757f5ef8a0b5ae3decca0c7719/types/address.go#L705 | ||
|
||
type cacheEntry struct { | ||
bech32Addr string | ||
accountInfo *accountInfo | ||
} | ||
accountCache := make(map[string]cacheEntry, len(ka.addressToPubKey)) | ||
for bech32Addr, accountInfo := range ka.addressToPubKey { | ||
accountCache[accountInfo.Account] = cacheEntry{bech32Addr: bech32Addr, accountInfo: accountInfo} | ||
} | ||
|
||
addressToPubKey := make(map[string]*accountInfo, len(accounts)) | ||
for _, account := range accounts { | ||
if prevEntry, ok := accountCache[account]; ok { | ||
addressToPubKey[prevEntry.bech32Addr] = prevEntry.accountInfo | ||
continue | ||
} | ||
pubKeyBytes, err := hex.DecodeString(account) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(pubKeyBytes) != secp256k1.PubKeySize { | ||
return errors.New("length of pubkey is incorrect") | ||
} | ||
|
||
sha := sha256.Sum256(pubKeyBytes) | ||
hasherRIPEMD160 := ripemd160.New() | ||
_, _ = hasherRIPEMD160.Write(sha[:]) | ||
address := crypto.Address(hasherRIPEMD160.Sum(nil)) | ||
|
||
bech32Addr, err := bech32.ConvertAndEncode(ka.accountPrefix, address) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
addressToPubKey[bech32Addr] = &accountInfo{ | ||
Account: account, | ||
PubKey: &secp256k1.PubKey{Key: pubKeyBytes}, | ||
} | ||
} | ||
|
||
ka.addressToPubKey = addressToPubKey | ||
return nil | ||
} | ||
|
||
func (ka *KeystoreAdapter) lookup(id string) (*accountInfo, error) { | ||
ka.mutex.RLock() | ||
ai, ok := ka.addressToPubKey[id] | ||
ka.mutex.RUnlock() | ||
if !ok { | ||
// try updating the mapping once, incase there was an update on the keystore. | ||
ka.mutex.Lock() | ||
err := ka.updateMappingLocked() | ||
if err != nil { | ||
ka.mutex.Unlock() | ||
return nil, err | ||
} | ||
ai, ok = ka.addressToPubKey[id] | ||
ka.mutex.Unlock() | ||
if !ok { | ||
return nil, errors.New("No such id") | ||
} | ||
} | ||
return ai, nil | ||
} | ||
|
||
func (ka *KeystoreAdapter) Sign(ctx context.Context, id string, hash []byte) ([]byte, error) { | ||
accountInfo, err := ka.lookup(id) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return ka.keystore.Sign(ctx, accountInfo.Account, hash) | ||
} | ||
|
||
// Returns the cosmos PubKey associated with the prefixed address. | ||
func (ka *KeystoreAdapter) PubKey(address string) (cryptotypes.PubKey, error) { | ||
accountInfo, err := ka.lookup(address) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return accountInfo.PubKey, nil | ||
} |
Oops, something went wrong.