Skip to content

Instantly share code, notes, and snippets.

@jnewbery
Last active January 9, 2019 22:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jnewbery/287da57404e4f932f2cda6dcf9dfd375 to your computer and use it in GitHub Desktop.
Save jnewbery/287da57404e4f932f2cda6dcf9dfd375 to your computer and use it in GitHub Desktop.

Bitcoin Core wallet design notes

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.
  • 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.
  • 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() and ReadKeyValue() in /src/wallet/walletdb.cpp for data that is stored to the database)

Glossary

  • 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)

notes

Importing scripts and addresses

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)

Sipa's wallet design thoughts

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).

Output script descriptors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment