# Accounts

# Pre-requisite Readings

# Account Definition

In the Cosmos SDK, an account designates a pair of public key PubKey and private key PrivKey. The PubKey can be derived to generate various Addresses, which are used to identify users (among other parties) in the application. Addresses are also associated with messages to identify the sender of the message. The PrivKey is used to generate digital signatures to prove that an Address associated with the PrivKey approved of a given message.

To derive PubKeys and PrivKeys, the Cosmos SDK uses a standard called BIP32. This standard defines how to build an HD wallet, where a wallet is a set of accounts. At the core of every account, there is a seed, which takes the form of a 12 or 24-words mnemonic. From this mnemonic, it is possible to derive any number of PrivKeys using one-way cryptographic function. Then, a PubKey can be derived from the PrivKey. Naturally, the mnemonic is the most sensitive information, as private keys can always be re-generated if the mnemonic is preserved.

Copy Account 0 Account 1 Account 2 +------------------+ +------------------+ +------------------+ | | | | | | | Address 0 | | Address 1 | | Address 2 | | ^ | | ^ | | ^ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | | + | | + | | Public key 0 | | Public key 1 | | Public key 2 | | ^ | | ^ | | ^ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | | + | | + | | Private key 0 | | Private key 1 | | Private key 2 | | ^ | | ^ | | ^ | +------------------+ +------------------+ +------------------+ | | | | | | | | | +--------------------------------------------------------------------+ | | +---------+---------+ | | | Master PrivKey | | | +-------------------+ | | +---------+---------+ | | | Mnemonic (Seed) | | | +-------------------+

In the Cosmos SDK, accounts are stored and managed via an object called a Keybase.

# Keybase

A Keybase is an object that stores and manages accounts. In the Cosmos SDK, a Keybase implementation follows the Keybase interface:

Copy // Keybase exposes operations on a generic keystore type Keybase interface { // CRUD on the keystore List() ([]Info, error) // Get returns the public information about one key. Get(name string) (Info, error) // Get performs a by-address lookup and returns the public // information about one key if there's any. GetByAddress(address types.AccAddress) (Info, error) // Delete removes a key. Delete(name, passphrase string, skipPass bool) error // Sign bytes, looking up the private key to use. Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) // CreateMnemonic generates a new mnemonic, derives a hierarchical deterministic // key from that. and persists it to storage, encrypted using the provided password. // It returns the generated mnemonic and the key Info. It returns an error if it fails to // generate a key for the given algo type, or if another key is already stored under the // same name. CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) // CreateAccount converts a mnemonic to a private key using a BIP44 path 44'/118'/{account}'/0/{index} // and persists it, encrypted with the given password. CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) // Derive computes a BIP39 seed from th mnemonic and bip39Passwd. // Derive private key from the seed using the BIP44 params. // Encrypt the key to disk using encryptPasswd. // See https://github.com/cosmos/cosmos-sdk/issues/2095 Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) // CreateLedger creates, stores, and returns a new Ledger key reference CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) // CreateOffline creates, stores, and returns a new offline key reference CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) // CreateMulti creates, stores, and returns a new multsig (offline) key reference CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) // The following operations will *only* work on locally-stored keys Update(name, oldpass string, getNewpass func() (string, error)) error // Import imports ASCII armored Info objects. Import(name string, armor string) (err error) // ImportPrivKey imports a private key in ASCII armor format. // It returns an error if a key with the same name exists or a wrong encryption passphrase is // supplied. ImportPrivKey(name, armor, passphrase string) error // ImportPubKey imports ASCII-armored public keys. // Store a new Info object holding a public key only, i.e. it will // not be possible to sign with it as it lacks the secret key. ImportPubKey(name string, armor string) (err error) // Export exports an Info object in ASCII armored format. Export(name string) (armor string, err error) // ExportPubKey returns public keys in ASCII armored format. // Retrieve a Info object by its name and return the public key in // a portable format. ExportPubKey(name string) (armor string, err error) // ExportPrivKey returns a private key in ASCII armored format. // It returns an error if the key does not exist or a wrong encryption passphrase is supplied. ExportPrivKey(name, decryptPassphrase, encryptPassphrase string) (armor string, err error) // ExportPrivateKeyObject *only* works on locally-stored keys. Temporary method until we redo the exporting API ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) // CloseDB closes the database. CloseDB() }

The default implementation of Keybase of the Cosmos SDK is dbKeybase.

Copy package keys import ( "fmt" "reflect" "strings" "github.com/pkg/errors" tmcrypto "github.com/tendermint/tendermint/crypto" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/cosmos/cosmos-sdk/crypto/keys/keyerror" "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" "github.com/cosmos/cosmos-sdk/types" ) var _ Keybase = dbKeybase{} // Language is a language to create the BIP 39 mnemonic in. // Currently, only english is supported though. // Find a list of all supported languages in the BIP 39 spec (word lists). type Language int //noinspection ALL const ( // English is the default language to create a mnemonic. // It is the only supported language by this package. English Language = iota + 1 // Japanese is currently not supported. Japanese // Korean is currently not supported. Korean // Spanish is currently not supported. Spanish // ChineseSimplified is currently not supported. ChineseSimplified // ChineseTraditional is currently not supported. ChineseTraditional // French is currently not supported. French // Italian is currently not supported. Italian addressSuffix = "address" infoSuffix = "info" ) const ( // used for deriving seed from mnemonic DefaultBIP39Passphrase = "" // bits of entropy to draw when creating a mnemonic defaultEntropySize = 256 ) var ( // ErrUnsupportedSigningAlgo is raised when the caller tries to use a // different signing scheme than secp256k1. ErrUnsupportedSigningAlgo = errors.New("unsupported signing algo: only secp256k1 is supported") // ErrUnsupportedLanguage is raised when the caller tries to use a // different language than english for creating a mnemonic sentence. ErrUnsupportedLanguage = errors.New("unsupported language: only english is supported") ) // dbKeybase combines encryption and storage implementation to provide a // full-featured key manager. // // NOTE: dbKeybase will be deprecated in favor of keyringKeybase. type dbKeybase struct { base baseKeybase db dbm.DB } // newDBKeybase creates a new dbKeybase instance using the provided DB for // reading and writing keys. func newDBKeybase(db dbm.DB) Keybase { return dbKeybase{ base: baseKeybase{}, db: db, } } // NewInMemory creates a transient keybase on top of in-memory storage // instance useful for testing purposes and on-the-fly key generation. func NewInMemory() Keybase { return newDBKeybase(dbm.NewMemDB()) } // CreateMnemonic generates a new key and persists it to storage, encrypted // using the provided password. It returns the generated mnemonic and the key Info. // It returns an error if it fails to generate a key for the given key algorithm // type, or if another key is already stored under the same name. func (kb dbKeybase) CreateMnemonic( name string, language Language, passwd string, algo SigningAlgo, ) (info Info, mnemonic string, err error) { return kb.base.CreateMnemonic(kb, name, language, passwd, algo) } // CreateAccount converts a mnemonic to a private key and persists it, encrypted // with the given password. func (kb dbKeybase) CreateAccount( name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32, ) (Info, error) { return kb.base.CreateAccount(kb, name, mnemonic, bip39Passwd, encryptPasswd, account, index) } // Derive computes a BIP39 seed from th mnemonic and bip39Passwd. func (kb dbKeybase) Derive( name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params, ) (Info, error) { return kb.base.Derive(kb, name, mnemonic, bip39Passphrase, encryptPasswd, params) } // CreateLedger creates a new locally-stored reference to a Ledger keypair. // It returns the created key info and an error if the Ledger could not be queried. func (kb dbKeybase) CreateLedger( name string, algo SigningAlgo, hrp string, account, index uint32, ) (Info, error) { return kb.base.CreateLedger(kb, name, algo, hrp, account, index) } // CreateOffline creates a new reference to an offline keypair. It returns the // created key info. func (kb dbKeybase) CreateOffline(name string, pub tmcrypto.PubKey) (Info, error) { return kb.base.writeOfflineKey(kb, name, pub), nil } // CreateMulti creates a new reference to a multisig (offline) keypair. It // returns the created key info. func (kb dbKeybase) CreateMulti(name string, pub tmcrypto.PubKey) (Info, error) { return kb.base.writeMultisigKey(kb, name, pub), nil } // List returns the keys from storage in alphabetical order. func (kb dbKeybase) List() ([]Info, error) { var res []Info iter := kb.db.Iterator(nil, nil) defer iter.Close() for ; iter.Valid(); iter.Next() { key := string(iter.Key()) // need to include only keys in storage that have an info suffix if strings.HasSuffix(key, infoSuffix) { info, err := unmarshalInfo(iter.Value()) if err != nil { return nil, err } res = append(res, info) } } return res, nil } // Get returns the public information about one key. func (kb dbKeybase) Get(name string) (Info, error) { bs := kb.db.Get(infoKey(name)) if len(bs) == 0 { return nil, keyerror.NewErrKeyNotFound(name) } return unmarshalInfo(bs) } // GetByAddress returns Info based on a provided AccAddress. An error is returned // if the address does not exist. func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) { ik := kb.db.Get(addrKey(address)) if len(ik) == 0 { return nil, fmt.Errorf("key with address %s not found", address) } bs := kb.db.Get(ik) return unmarshalInfo(bs) } // Sign signs the msg with the named key. It returns an error if the key doesn't // exist or the decryption fails. func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { info, err := kb.Get(name) if err != nil { return } var priv tmcrypto.PrivKey switch i := info.(type) { case localInfo: if i.PrivKeyArmor == "" { err = fmt.Errorf("private key not available") return } priv, err = mintkey.UnarmorDecryptPrivKey(i.PrivKeyArmor, passphrase) if err != nil { return nil, nil, err } case ledgerInfo: return kb.base.SignWithLedger(info, msg) case offlineInfo, multiInfo: return kb.base.DecodeSignature(info, msg) } sig, err = priv.Sign(msg) if err != nil { return nil, nil, err } return sig, priv.PubKey(), nil } // ExportPrivateKeyObject returns a PrivKey object given the key name and // passphrase. An error is returned if the key does not exist or if the Info for // the key is invalid. func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcrypto.PrivKey, error) { info, err := kb.Get(name) if err != nil { return nil, err } var priv tmcrypto.PrivKey switch i := info.(type) { case localInfo: linfo := i if linfo.PrivKeyArmor == "" { err = fmt.Errorf("private key not available") return nil, err } priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return nil, err } case ledgerInfo, offlineInfo, multiInfo: return nil, errors.New("only works on local private keys") } return priv, nil } func (kb dbKeybase) Export(name string) (armor string, err error) { bz := kb.db.Get(infoKey(name)) if bz == nil { return "", fmt.Errorf("no key to export with name %s", name) } return mintkey.ArmorInfoBytes(bz), nil } // ExportPubKey returns public keys in ASCII armored format. It retrieves a Info // object by its name and return the public key in a portable format. func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { bz := kb.db.Get(infoKey(name)) if bz == nil { return "", fmt.Errorf("no key to export with name %s", name) } info, err := unmarshalInfo(bz) if err != nil { return } return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil } // ExportPrivKey returns a private key in ASCII armored format. // It returns an error if the key does not exist or a wrong encryption passphrase // is supplied. func (kb dbKeybase) ExportPrivKey(name string, decryptPassphrase string, encryptPassphrase string) (armor string, err error) { priv, err := kb.ExportPrivateKeyObject(name, decryptPassphrase) if err != nil { return "", err } return mintkey.EncryptArmorPrivKey(priv, encryptPassphrase), nil } // ImportPrivKey imports a private key in ASCII armor format. It returns an // error if a key with the same name exists or a wrong encryption passphrase is // supplied. func (kb dbKeybase) ImportPrivKey(name string, armor string, passphrase string) error { if _, err := kb.Get(name); err == nil { return errors.New("Cannot overwrite key " + name) } privKey, err := mintkey.UnarmorDecryptPrivKey(armor, passphrase) if err != nil { return errors.Wrap(err, "couldn't import private key") } kb.writeLocalKey(name, privKey, passphrase) return nil } func (kb dbKeybase) Import(name string, armor string) (err error) { bz := kb.db.Get(infoKey(name)) if len(bz) > 0 { return errors.New("Cannot overwrite data for name " + name) } infoBytes, err := mintkey.UnarmorInfoBytes(armor) if err != nil { return } kb.db.Set(infoKey(name), infoBytes) return nil } // ImportPubKey imports ASCII-armored public keys. Store a new Info object holding // a public key only, i.e. it will not be possible to sign with it as it lacks the // secret key. func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { bz := kb.db.Get(infoKey(name)) if len(bz) > 0 { return errors.New("Cannot overwrite data for name " + name) } pubBytes, err := mintkey.UnarmorPubKeyBytes(armor) if err != nil { return } pubKey, err := cryptoAmino.PubKeyFromBytes(pubBytes) if err != nil { return } kb.base.writeOfflineKey(kb, name, pubKey) return } // Delete removes key forever, but we must present the proper passphrase before // deleting it (for security). It returns an error if the key doesn't exist or // passphrases don't match. Passphrase is ignored when deleting references to // offline and Ledger / HW wallet keys. func (kb dbKeybase) Delete(name, passphrase string, skipPass bool) error { // verify we have the proper password before deleting info, err := kb.Get(name) if err != nil { return err } if linfo, ok := info.(localInfo); ok && !skipPass { if _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase); err != nil { return err } } kb.db.DeleteSync(addrKey(info.GetAddress())) kb.db.DeleteSync(infoKey(name)) return nil } // Update changes the passphrase with which an already stored key is // encrypted. // // oldpass must be the current passphrase used for encryption, // getNewpass is a function to get the passphrase to permanently replace // the current passphrase func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error { info, err := kb.Get(name) if err != nil { return err } switch i := info.(type) { case localInfo: linfo := i key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) if err != nil { return err } newpass, err := getNewpass() if err != nil { return err } kb.writeLocalKey(name, key, newpass) return nil default: return fmt.Errorf("locally stored key required. Received: %v", reflect.TypeOf(info).String()) } } // CloseDB releases the lock and closes the storage backend. func (kb dbKeybase) CloseDB() { kb.db.Close() } func (kb dbKeybase) writeLocalKey(name string, priv tmcrypto.PrivKey, passphrase string) Info { // encrypt private key using passphrase privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase) // make Info pub := priv.PubKey() info := newLocalInfo(name, pub, privArmor) kb.writeInfo(name, info) return info } func (kb dbKeybase) writeInfo(name string, info Info) { // write the info by key key := infoKey(name) serializedInfo := marshalInfo(info) kb.db.SetSync(key, serializedInfo) // store a pointer to the infokey by address for fast lookup kb.db.SetSync(addrKey(info.GetAddress()), key) } func addrKey(address types.AccAddress) []byte { return []byte(fmt.Sprintf("%s.%s", address.String(), addressSuffix)) } func infoKey(name string) []byte { return []byte(fmt.Sprintf("%s.%s", name, infoSuffix)) }

A few notes on the Keybase methods as implemented in dbKeybase:

  • Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) strictly deals with the signature of the message bytes. Some preliminary work should be done beforehand to prepare and encode the message into a canonical []byte form. See an example of message preparation from the auth module. Note that signature verification is not implemented in the SDK by default. It is deferred to the anteHandler. Copy // BuildSignMsg builds a single message to be signed from a TxBuilder given a // set of messages. It returns an error if a fee is supplied but cannot be // parsed. func (bldr TxBuilder) BuildSignMsg(msgs []sdk.Msg) (StdSignMsg, error) { if bldr.chainID == "" { return StdSignMsg{}, fmt.Errorf("chain ID required but not specified") } fees := bldr.fees if !bldr.gasPrices.IsZero() { if !fees.IsZero() { return StdSignMsg{}, errors.New("cannot provide both fees and gas prices") } glDec := sdk.NewDec(int64(bldr.gas)) // Derive the fees based on the provided gas prices, where // fee = ceil(gasPrice * gasLimit). fees = make(sdk.Coins, len(bldr.gasPrices)) for i, gp := range bldr.gasPrices { fee := gp.Amount.Mul(glDec) fees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt()) } } return StdSignMsg{ ChainID: bldr.chainID, AccountNumber: bldr.accountNumber, Sequence: bldr.sequence, Memo: bldr.memo, Msgs: msgs, Fee: NewStdFee(bldr.gas, fees), }, nil }
  • CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) creates a new mnemonic and prints it in the logs, but it does not persist it on disk.
  • CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) creates a new account based on the bip44 path and persists it on disk (note that the PrivKey is encrypted with a passphrase before being persisted, it is never stored unencrypted). In the context of this method, the account and address parameters refer to the segment of the BIP44 derivation path (e.g. 0, 1, 2, ...) used to derive the PrivKey and PubKey from the mnemonic (note that given the same mnemonic and account, the same PrivKey will be generated, and given the same account and address, the same PubKey and Address will be generated). Finally, note that the CreateAccount method derives keys and addresses using secp256k1 as implemented in the Tendermint library. As a result, it only works for creating account keys and addresses, not consensus keys. See Addresses for more.

The current implementation of dbKeybase is basic and does not offer on-demand locking. If an instance of dbKeybase is created, the underlying db is locked meaning no other process can access it besides the one in which it was instantiated. This is the reason why the default SDK client uses another implementation of the Keybase interface called lazyKeybase:

Copy package keys import ( "fmt" "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" sdk "github.com/cosmos/cosmos-sdk/types" ) var _ Keybase = lazyKeybase{} // NOTE: lazyKeybase will be deprecated in favor of lazyKeybaseKeyring. type lazyKeybase struct { name string dir string } // New creates a new instance of a lazy keybase. func New(name, dir string) Keybase { if err := cmn.EnsureDir(dir, 0700); err != nil { panic(fmt.Sprintf("failed to create Keybase directory: %s", err)) } return lazyKeybase{name: name, dir: dir} } func (lkb lazyKeybase) List() ([]Info, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).List() } func (lkb lazyKeybase) Get(name string) (Info, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).Get(name) } func (lkb lazyKeybase) GetByAddress(address sdk.AccAddress) (Info, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).GetByAddress(address) } func (lkb lazyKeybase) Delete(name, passphrase string, skipPass bool) error { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() return newDBKeybase(db).Delete(name, passphrase, skipPass) } func (lkb lazyKeybase) Sign(name, passphrase string, msg []byte) ([]byte, crypto.PubKey, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, nil, err } defer db.Close() return newDBKeybase(db).Sign(name, passphrase, msg) } func (lkb lazyKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, seed string, err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, "", err } defer db.Close() return newDBKeybase(db).CreateMnemonic(name, language, passwd, algo) } func (lkb lazyKeybase) CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd string, account uint32, index uint32) (Info, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).CreateAccount(name, mnemonic, bip39Passwd, encryptPasswd, account, index) } func (lkb lazyKeybase) Derive(name, mnemonic, bip39Passwd, encryptPasswd string, params hd.BIP44Params) (Info, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).Derive(name, mnemonic, bip39Passwd, encryptPasswd, params) } func (lkb lazyKeybase) CreateLedger(name string, algo SigningAlgo, hrp string, account, index uint32) (info Info, err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).CreateLedger(name, algo, hrp, account, index) } func (lkb lazyKeybase) CreateOffline(name string, pubkey crypto.PubKey) (info Info, err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).CreateOffline(name, pubkey) } func (lkb lazyKeybase) CreateMulti(name string, pubkey crypto.PubKey) (info Info, err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).CreateMulti(name, pubkey) } func (lkb lazyKeybase) Update(name, oldpass string, getNewpass func() (string, error)) error { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() return newDBKeybase(db).Update(name, oldpass, getNewpass) } func (lkb lazyKeybase) Import(name string, armor string) (err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() return newDBKeybase(db).Import(name, armor) } func (lkb lazyKeybase) ImportPrivKey(name string, armor string, passphrase string) error { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() return newDBKeybase(db).ImportPrivKey(name, armor, passphrase) } func (lkb lazyKeybase) ImportPubKey(name string, armor string) (err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return err } defer db.Close() return newDBKeybase(db).ImportPubKey(name, armor) } func (lkb lazyKeybase) Export(name string) (armor string, err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return "", err } defer db.Close() return newDBKeybase(db).Export(name) } func (lkb lazyKeybase) ExportPubKey(name string) (armor string, err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return "", err } defer db.Close() return newDBKeybase(db).ExportPubKey(name) } func (lkb lazyKeybase) ExportPrivateKeyObject(name string, passphrase string) (crypto.PrivKey, error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return nil, err } defer db.Close() return newDBKeybase(db).ExportPrivateKeyObject(name, passphrase) } func (lkb lazyKeybase) ExportPrivKey(name string, decryptPassphrase string, encryptPassphrase string) (armor string, err error) { db, err := sdk.NewLevelDB(lkb.name, lkb.dir) if err != nil { return "", err } defer db.Close() return newDBKeybase(db).ExportPrivKey(name, decryptPassphrase, encryptPassphrase) } func (lkb lazyKeybase) CloseDB() {}

lazyKeybase is simple wrapper around dbKeybase which locks the database only when operations are to be performed and unlocks it immediately after. With the lazyKeybase, it is possible for the command-line interface to create a new account while the rest server is running. It is also possible to pipe multiple CLI commands.

# Addresses and PubKeys

Addresses and PubKeys are both public information that identify actors in the application. There are 3 main types of Addresses/PubKeys available by default in the Cosmos SDK:

  • Addresses and Keys for accounts, which identify users (e.g. the sender of a message). They are derived using the secp256k1 curve.
  • Addresses and Keys for validator operators, which identify the operators of validators. They are derived using the secp256k1 curve.
  • Addresses and Keys for consensus nodes, which identify the validator nodes participating in consensus. They are derived using the ed25519 curve.
Address bech32 Prefix Pubkey bech32 Prefix Curve Address byte length Pubkey byte length
Accounts cosmos cosmospub secp256k1 20 33
Validator Operator cosmosvaloper cosmosvaloperpub secp256k1 20 33
Consensus Nodes cosmosvalcons cosmosvalconspub ed25519 20 32

# PubKeys

PubKeys used in the Cosmos SDK follow the Pubkey interface defined in tendermint's crypto package:

Copy type PubKey interface { Address() Address Bytes() []byte VerifyBytes(msg []byte, sig []byte) bool Equals(PubKey) bool }

For secp256k1 keys, the actual implementation can be found here. For ed25519 keys, it can be found here.

Note that in the Cosmos SDK, Pubkeys are not manipulated in their raw form. Instead, they are double encoded using Amino and bech32. In the SDK is done by first calling the Bytes() method on the raw Pubkey (which applies amino encoding), and then the ConvertAndEncode method of bech32.

Copy func Bech32ifyAccPub(pub crypto.PubKey) (string, error) { bech32PrefixAccPub := GetConfig().GetBech32AccountPubPrefix() return bech32.ConvertAndEncode(bech32PrefixAccPub, pub.Bytes()) } // MustBech32ifyAccPub returns the result of Bech32ifyAccPub panicing on failure. func MustBech32ifyAccPub(pub crypto.PubKey) string { enc, err := Bech32ifyAccPub(pub) if err != nil { panic(err) } return enc } // Bech32ifyValPub returns a Bech32 encoded string containing the // Bech32PrefixValPub prefix for a given validator operator's PubKey. func Bech32ifyValPub(pub crypto.PubKey) (string, error) { bech32PrefixValPub := GetConfig().GetBech32ValidatorPubPrefix() return bech32.ConvertAndEncode(bech32PrefixValPub, pub.Bytes()) } // MustBech32ifyValPub returns the result of Bech32ifyValPub panicing on failure. func MustBech32ifyValPub(pub crypto.PubKey) string { enc, err := Bech32ifyValPub(pub) if err != nil { panic(err) } return enc } // Bech32ifyConsPub returns a Bech32 encoded string containing the // Bech32PrefixConsPub prefixfor a given consensus node's PubKey. func Bech32ifyConsPub(pub crypto.PubKey) (string, error) { bech32PrefixConsPub := GetConfig().GetBech32ConsensusPubPrefix() return bech32.ConvertAndEncode(bech32PrefixConsPub, pub.Bytes()) } // MustBech32ifyConsPub returns the result of Bech32ifyConsPub panicing on // failure. func MustBech32ifyConsPub(pub crypto.PubKey) string { enc, err := Bech32ifyConsPub(pub) if err != nil { panic(err) } return enc } // GetAccPubKeyBech32 creates a PubKey for an account with a given public key // string using the Bech32 Bech32PrefixAccPub prefix. func GetAccPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { bech32PrefixAccPub := GetConfig().GetBech32AccountPubPrefix() bz, err := GetFromBech32(pubkey, bech32PrefixAccPub) if err != nil { return nil, err } pk, err = cryptoAmino.PubKeyFromBytes(bz) if err != nil { return nil, err } return pk, nil } // MustGetAccPubKeyBech32 returns the result of GetAccPubKeyBech32 panicing on // failure. func MustGetAccPubKeyBech32(pubkey string) (pk crypto.PubKey) { pk, err := GetAccPubKeyBech32(pubkey) if err != nil { panic(err) } return pk } // GetValPubKeyBech32 creates a PubKey for a validator's operator with a given // public key string using the Bech32 Bech32PrefixValPub prefix. func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { bech32PrefixValPub := GetConfig().GetBech32ValidatorPubPrefix() bz, err := GetFromBech32(pubkey, bech32PrefixValPub) if err != nil { return nil, err } pk, err = cryptoAmino.PubKeyFromBytes(bz) if err != nil { return nil, err } return pk, nil } // MustGetValPubKeyBech32 returns the result of GetValPubKeyBech32 panicing on // failure. func MustGetValPubKeyBech32(pubkey string) (pk crypto.PubKey) { pk, err := GetValPubKeyBech32(pubkey) if err != nil { panic(err) } return pk } // GetConsPubKeyBech32 creates a PubKey for a consensus node with a given public // key string using the Bech32 Bech32PrefixConsPub prefix. func GetConsPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { bech32PrefixConsPub := GetConfig().GetBech32ConsensusPubPrefix() bz, err := GetFromBech32(pubkey, bech32PrefixConsPub) if err != nil { return nil, err } pk, err = cryptoAmino.PubKeyFromBytes(bz) if err != nil { return nil, err } return pk, nil } // MustGetConsPubKeyBech32 returns the result of GetConsPubKeyBech32 panicing on // failure. func MustGetConsPubKeyBech32(pubkey string) (pk crypto.PubKey) { pk, err := GetConsPubKeyBech32(pubkey) if err != nil { panic(err) } return pk } // GetFromBech32 decodes a bytestring from a Bech32 encoded string. func GetFromBech32(bech32str, prefix string) ([]byte, error) { if len(bech32str) == 0 { return nil, errors.New("decoding Bech32 address failed: must provide an address") } hrp, bz, err := bech32.DecodeAndConvert(bech32str) if err != nil { return nil, err } if hrp != prefix { return nil, fmt.Errorf("invalid Bech32 prefix; expected %s, got %s", prefix, hrp) } return bz, nil }

# Addresses

The Cosmos SDK comes by default with 3 types of addresses:

  • AccAddress for accounts.
  • ValAddress for validator operators.
  • ConsAddress for validator nodes.

Each of these address types are an alias for an hex-encoded []byte array of length 20. Here is the standard way to obtain an address aa from a Pubkey pub:

Copy aa := sdk.AccAddress(pub.Address().Bytes())

These addresses implement the Address interface:

Copy // Address is a common interface for different types of addresses used by the SDK type Address interface { Equals(Address) bool Empty() bool Marshal() ([]byte, error) MarshalJSON() ([]byte, error) Bytes() []byte String() string Format(s fmt.State, verb rune) }

Of note, the Marhsal() and Bytes() method both return the same raw []byte form of the address, the former being needed for Protobuff compatibility. Also, the String() method is used to return the bech32 encoded form of the address, which should be the only address format with which end-user interract. Next is an example:

Copy // String implements the Stringer interface. func (aa AccAddress) String() string { if aa.Empty() { return "" } bech32PrefixAccAddr := GetConfig().GetBech32AccountAddrPrefix() bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixAccAddr, aa.Bytes()) if err != nil { panic(err) } return bech32Addr }

# Next

Learn about gas and fees