github.com/InjectiveLabs/sdk-go@v1.53.0/client/chain/keys.go (about)

     1  package chain
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"io"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/cosmos/cosmos-sdk/codec"
    12  	cosmcrypto "github.com/cosmos/cosmos-sdk/crypto"
    13  	"github.com/cosmos/cosmos-sdk/crypto/keyring"
    14  	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
    15  	cosmtypes "github.com/cosmos/cosmos-sdk/types"
    16  	"github.com/pkg/errors"
    17  
    18  	crypto_cdc "github.com/InjectiveLabs/sdk-go/chain/crypto/codec"
    19  	"github.com/InjectiveLabs/sdk-go/chain/crypto/ethsecp256k1"
    20  	"github.com/InjectiveLabs/sdk-go/chain/crypto/hd"
    21  	"github.com/InjectiveLabs/sdk-go/client/common"
    22  )
    23  
    24  const defaultKeyringKeyName = "validator"
    25  
    26  var emptyCosmosAddress = cosmtypes.AccAddress{}
    27  
    28  func InitCosmosKeyring(
    29  	cosmosKeyringDir string,
    30  	cosmosKeyringAppName string,
    31  	cosmosKeyringBackend string,
    32  	cosmosKeyFrom string,
    33  	cosmosKeyPassphrase string,
    34  	cosmosPrivKey string,
    35  	cosmosUseLedger bool,
    36  ) (cosmtypes.AccAddress, keyring.Keyring, error) {
    37  	switch {
    38  	case cosmosPrivKey != "":
    39  		if cosmosUseLedger {
    40  			err := errors.New("cannot combine ledger and privkey options")
    41  			return emptyCosmosAddress, nil, err
    42  		}
    43  
    44  		pkBytes, err := common.HexToBytes(cosmosPrivKey)
    45  		if err != nil {
    46  			err = errors.Wrap(err, "failed to hex-decode cosmos account privkey")
    47  			return emptyCosmosAddress, nil, err
    48  		}
    49  
    50  		// Specific to Injective chain with Ethermint keys
    51  		// Should be secp256k1.PrivKey for generic Cosmos chain
    52  		cosmosAccPk := &ethsecp256k1.PrivKey{
    53  			Key: pkBytes,
    54  		}
    55  
    56  		addressFromPk := cosmtypes.AccAddress(cosmosAccPk.PubKey().Address().Bytes())
    57  
    58  		var keyName string
    59  
    60  		// check that if cosmos 'From' specified separately, it must match the provided privkey,
    61  		if cosmosKeyFrom != "" {
    62  			addressFrom, err := cosmtypes.AccAddressFromBech32(cosmosKeyFrom)
    63  			if err == nil {
    64  				if !bytes.Equal(addressFrom.Bytes(), addressFromPk.Bytes()) {
    65  					err = errors.Errorf("expected account address %s but got %s from the private key", addressFrom.String(), addressFromPk.String())
    66  					return emptyCosmosAddress, nil, err
    67  				}
    68  			} else {
    69  				// use it as a name then
    70  				keyName = cosmosKeyFrom
    71  			}
    72  		}
    73  
    74  		if keyName == "" {
    75  			keyName = defaultKeyringKeyName
    76  		}
    77  
    78  		// wrap a PK into a Keyring
    79  		kb, err := KeyringForPrivKey(keyName, cosmosAccPk)
    80  		return addressFromPk, kb, err
    81  
    82  	case cosmosKeyFrom != "":
    83  		var fromIsAddress bool
    84  		addressFrom, err := cosmtypes.AccAddressFromBech32(cosmosKeyFrom)
    85  		if err == nil {
    86  			fromIsAddress = true
    87  		}
    88  
    89  		var passReader io.Reader = os.Stdin
    90  		if cosmosKeyPassphrase != "" {
    91  			passReader = newPassReader(cosmosKeyPassphrase)
    92  		}
    93  
    94  		var absoluteKeyringDir string
    95  		if filepath.IsAbs(cosmosKeyringDir) {
    96  			absoluteKeyringDir = cosmosKeyringDir
    97  		} else {
    98  			absoluteKeyringDir, _ = filepath.Abs(cosmosKeyringDir)
    99  		}
   100  
   101  		kb, err := keyring.New(
   102  			cosmosKeyringAppName,
   103  			cosmosKeyringBackend,
   104  			absoluteKeyringDir,
   105  			passReader,
   106  			getCryptoCodec(),
   107  			hd.EthSecp256k1Option(),
   108  		)
   109  		if err != nil {
   110  			err = errors.Wrap(err, "failed to init keyring")
   111  			return emptyCosmosAddress, nil, err
   112  		}
   113  
   114  		var keyInfo *keyring.Record
   115  		if fromIsAddress {
   116  			if keyInfo, err = kb.KeyByAddress(addressFrom); err != nil {
   117  				err = errors.Wrapf(err, "couldn't find an entry for the key %s in keybase", addressFrom.String())
   118  				return emptyCosmosAddress, nil, err
   119  			}
   120  		} else {
   121  			if keyInfo, err = kb.Key(cosmosKeyFrom); err != nil {
   122  				err = errors.Wrapf(err, "could not find an entry for the key '%s' in keybase", cosmosKeyFrom)
   123  				return emptyCosmosAddress, nil, err
   124  			}
   125  		}
   126  
   127  		switch keyType := keyInfo.GetType(); keyType {
   128  		case keyring.TypeLocal:
   129  			// kb has a key and it's totally usable
   130  			addr, err := keyInfo.GetAddress()
   131  			if err != nil {
   132  				err = errors.Wrapf(err, "failed to get address for key '%s'", keyInfo.Name)
   133  				return emptyCosmosAddress, nil, err
   134  			}
   135  			return addr, kb, nil
   136  		case keyring.TypeLedger:
   137  			// the kb stores references to ledger keys, so we must explicitly
   138  			// check that. kb doesn't know how to scan HD keys - they must be added manually before
   139  			if cosmosUseLedger {
   140  				addr, err := keyInfo.GetAddress()
   141  				if err != nil {
   142  					err = errors.Wrapf(err, "failed to get address for key '%s'", keyInfo.Name)
   143  					return emptyCosmosAddress, nil, err
   144  				}
   145  				return addr, kb, nil
   146  			}
   147  			err := errors.Errorf("'%s' key is a ledger reference, enable ledger option", keyInfo.Name)
   148  			return emptyCosmosAddress, nil, err
   149  		case keyring.TypeOffline:
   150  			err := errors.Errorf("'%s' key is an offline key, not supported yet", keyInfo.Name)
   151  			return emptyCosmosAddress, nil, err
   152  		case keyring.TypeMulti:
   153  			err := errors.Errorf("'%s' key is an multisig key, not supported yet", keyInfo.Name)
   154  			return emptyCosmosAddress, nil, err
   155  		default:
   156  			err := errors.Errorf("'%s' key  has unsupported type: %s", keyInfo.Name, keyType)
   157  			return emptyCosmosAddress, nil, err
   158  		}
   159  
   160  	default:
   161  		err := errors.New("insufficient cosmos key details provided")
   162  		return emptyCosmosAddress, nil, err
   163  	}
   164  }
   165  
   166  func newPassReader(pass string) io.Reader {
   167  	return &passReader{
   168  		pass: pass,
   169  		buf:  new(bytes.Buffer),
   170  	}
   171  }
   172  
   173  type passReader struct {
   174  	pass string
   175  	buf  *bytes.Buffer
   176  }
   177  
   178  var _ io.Reader = &passReader{}
   179  
   180  func (r *passReader) Read(p []byte) (n int, err error) {
   181  	n, err = r.buf.Read(p)
   182  	if err == io.EOF || n == 0 {
   183  		r.buf.WriteString(r.pass + "\n")
   184  
   185  		n, err = r.buf.Read(p)
   186  	}
   187  
   188  	return
   189  }
   190  
   191  func getCryptoCodec() *codec.ProtoCodec {
   192  	registry := NewInterfaceRegistry()
   193  	crypto_cdc.RegisterInterfaces(registry)
   194  	return codec.NewProtoCodec(registry)
   195  }
   196  
   197  // KeyringForPrivKey creates a temporary in-mem keyring for a PrivKey.
   198  // Allows to init Context when the key has been provided in plaintext and parsed.
   199  func KeyringForPrivKey(name string, privKey cryptotypes.PrivKey) (keyring.Keyring, error) {
   200  	kb := keyring.NewInMemory(getCryptoCodec(), hd.EthSecp256k1Option())
   201  	tmpPhrase := randPhrase(64)
   202  	armored := cosmcrypto.EncryptArmorPrivKey(privKey, tmpPhrase, privKey.Type())
   203  	err := kb.ImportPrivKey(name, armored, tmpPhrase)
   204  	if err != nil {
   205  		err = errors.Wrap(err, "failed to import privkey")
   206  		return nil, err
   207  	}
   208  
   209  	return kb, nil
   210  }
   211  
   212  func randPhrase(size int) string {
   213  	buf := make([]byte, size)
   214  	_, err := rand.Read(buf)
   215  	orPanic(err)
   216  
   217  	return string(buf)
   218  }
   219  
   220  func orPanic(err error) {
   221  	if err != nil {
   222  		log.Panicln()
   223  	}
   224  }