The Bitcoin Core wallet consists of:
- An in memory keystore (in /src/keystore.cpp), which contains:
- private keys stored as
mapKeys
- a map from the CKeyID to the private key. - watchonly public keys, stored as
mapWatchKeys
- a map from the CKeyID to the public key. - scripts, stored as
mapScripts
- a map from the CScriptID to the secrialized script. - watchonly scripts, stored as
setWatch
- a set of serialized scripts.
- private keys stored as
- Additional key metadata such as key birthday, hd derivation path. See
CKeyMetadata
in /src/wallet/walletdb.h - A keypool, to draw new receive and change addresses from. This is necessary because it is not possible to create new keys if the wallet is locked. (in fact there are two separate keypools - one for receive addresses and one for change addresses).
- An address book, stored as
mapAddressBook
- a map from the CTxDestination to the label - Transactions that the wallet is interested in:
- the set of transactions that include an output belong to the wallet stored as
mapWallet
- a map from the the txid to the CWalletTx object.
- the set of transactions that include an output belong to the wallet stored as
- An IsMine interface (in /src/script/ismine.cpp), which takes a scriptPubKey and returns whether the is WATCHONLY or SPENDABLE.
- A signing interface (in /src/script/sign.cpp)
- A database, which persists data to disk (see
WalletBatch::LoadWallet()
andReadKeyValue()
in /src/wallet/walletdb.cpp for data that is stored to the database)
- pubkey - a public key, used to verify signatures. In Bitcoin, public keys are points on the secp256k1 curve.
- privkey - a private key, kept secret and used to sign data. In Bitcoin, private keys are scalars in the secp256k1 group.
- CKeyID - a key identifier, which is the RIPEMD160(SHA256(pubkey)). This is the hash used to create a P2PKH or P2WPKH address.
- CTxDestination - a txout script template with a specific destination. Defined in /src/script/standard.h) Stored as a variant variable. Can be a:
- CNoDestination: no destination set
- CKeyID:
TX_PUBKEYHASH
destination (P2PKH) - CScriptID:
TX_SCRIPTHASH
destination (P2SH) - WitnessV0ScriptHash:
TX_WITNESS_V0_SCRIPTHASH
destination (P2WSH) - WitnessV0KeyHash:
TX_WITNESS_V0_KEYHASH
destination (P2WPKH)
Imported scripts/addresses are categorised by solvability and spendability:
- A script is spendable if the wallet can construct the transaction input to spend outputs to the script with all required signatures or witness data.
- If the wallet can do everything except produce the required signatures or witness data, then the script is solvable.
There are three cases:
- for a script/address to be solvable and spendable the redeem script (for a P2SH script), witness script (for a P2WSH script) and private keys for all the pubkeys and pubkey hashes must be provided. The 'watchonly' flag must be omitted or set to False.
- for a script/address to be solvable but not spendable the redeem script (for a P2SH script), witness script (for a P2WSH script) and public keys for all the pubkey hashes must be provided. Additionally some (but not all) of the private keys can be provided. The 'watchonly' flag should be set to True to avoid warnings.
- for a script/address to be unsolvable and unspendable the redeem script/ witness script should not be provided, and no public or private keys should be provided. The 'watchonly' flag should be set to True to avoid warnings.
(spendable but not solvable does not exist)
Conceptually, a wallet currently contains mapKeys
, mapScripts
, and setWatchOnly
, which together determine which outputs
are considered "ours" (IsMine), and how to solve and sign for them. Then the wallet further contains key metadata, key chains, and key pool at the wallet level that feed into this.
We determine whether outputs are ours using ad-hoc logic that mostly answers the question "Could we sign this?", but with some notable exceptions:
- In the case of multisig outputs, all private keys must be available (rather than just the multisig's threshold).
- For witness outputs, we require the P2SH-wrapped version of the witness program to be in
mapScripts
, even for non-P2SH witness outputs. - Outputs that are in
setWatchOnly
are ours regardless.
This logic has many problems:
- It is overly complicated, doing too many things at once (it also answers whether something is solvable/signable)
- Despite doing pretty much the same as signing, no code is shared between the two.
- It is inefficient due to pattern matching all scripts and trying to recurse into them.
- It is not selective: it can't be made to match just P2PKH or just P2PK or just P2WPKH, except by marking one of them as watch-only (which then removes the ability to sign).
- It is hard to support extra key chains (for example publically derivable HD chains for hardware wallets).