diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -150,14 +150,62 @@ WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA; -/** A key pool entry */ +/** + * A key from a CWallet's keypool + * + * The wallet holds one (for pre HD-split wallets) or several keypools. These + * are sets of keys that have not yet been used to provide addresses or receive + * change. + * + * The Bitcoin ABC wallet was originally a collection of unrelated private + * keys with their associated addresses. If a non-HD wallet generated a + * key/address, gave that address out and then restored a backup from before + * that key's generation, then any funds sent to that address would be + * lost definitively. + * + * The keypool was implemented to avoid this scenario (commit: 10384941). The + * wallet would generate a set of keys (100 by default). When a new public key + * was required, either to give out as an address or to use in a change output, + * it would be drawn from the keypool. The keypool would then be topped up to + * maintain 100 keys. This ensured that as long as the wallet hadn't used more + * than 100 keys since the previous backup, all funds would be safe, since a + * restored wallet would be able to scan for all owned addresses. + * + * A keypool also allowed encrypted wallets to give out addresses without + * having to be decrypted to generate a new private key. + * + * With the introduction of HD wallets (commit: f1902510), the keypool + * essentially became an address look-ahead pool. Restoring old backups can no + * longer definitively lose funds as long as the addresses used were from the + * wallet's HD seed (since all private keys can be rederived from the seed). + * However, if many addresses were used since the backup, then the wallet may + * not know how far ahead in the HD chain to look for its addresses. The + * keypool is used to implement a 'gap limit'. The keypool maintains a set of + * keys (by default 1000) ahead of the last used key and scans for the + * addresses of those keys. This avoids the risk of not seeing transactions + * involving the wallet's addresses, or of re-using the same address. + * + * The HD-split wallet feature added a second keypool (commit: 02592f4c). There + * is an external keypool (for addresses to hand out) and an internal keypool + * (for change addresses). + * + * Keypool keys are stored in the wallet/keystore's keymap. The keypool data is + * stored as sets of indexes in the wallet (setInternalKeyPool, + * setExternalKeyPool and set_pre_split_keypool), and a map from the key to the + * index (m_pool_key_to_index). The CKeyPool object is used to + * serialize/deserialize the pool data to/from the database. + */ class CKeyPool { public: + //! The time at which the key was generated. Set in AddKeypoolPubKeyWithDB int64_t nTime; + //! The public key CPubKey vchPubKey; - // for change outputs + //! Whether this keypool entry is in the internal keypool (for change + //! outputs) bool fInternal; - // For keys generated before keypool split upgrade + //! Whether this key was generated for a keypool before the wallet was + //! upgraded to HD-split bool m_pre_split; CKeyPool(); @@ -202,24 +250,51 @@ } }; -/** A key allocated from the key pool. */ +/** + * A wrapper to reserve a key from a wallet keypool + * + * CReserveKey is used to reserve a key from the keypool. It is passed around + * during the CreateTransaction/CommitTransaction procedure. + * + * Instantiating a CReserveKey does not reserve a keypool key. To do so, + * GetReservedKey() needs to be called on the object. Once a key has been + * reserved, call KeepKey() on the CReserveKey object to make sure it is not + * returned to the keypool. Call ReturnKey() to return the key to the keypool + * so it can be re-used (for example, if the key was used in a new transaction + * and that transaction was not completed and needed to be aborted). + * + * If a key is reserved and KeepKey() is not called, then the key will be + * returned to the keypool when the CReserveObject goes out of scope. + */ class CReserveKey final : public CReserveScript { protected: + //! The wallet to reserve the keypool key from CWallet *pwallet; + //! The index of the key in the keypool int64_t nIndex{-1}; + //! The public key CPubKey vchPubKey; + //! Whether this is from the internal (change output) keypool bool fInternal{false}; public: + //! Construct a CReserveKey object. This does NOT reserve a key from the + //! keypool yet explicit CReserveKey(CWallet *pwalletIn) { pwallet = pwalletIn; } CReserveKey(const CReserveKey &) = delete; CReserveKey &operator=(const CReserveKey &) = delete; + //! Destructor. If a key has been reserved and not KeepKey'ed, it will be + //! returned to the keypool ~CReserveKey() { ReturnKey(); } - void ReturnKey(); + //! Reserve a key from the keypool bool GetReservedKey(CPubKey &pubkey, bool internal = false); + //! Return a key to the keypool + void ReturnKey(); + //! Keep the key. Do not return it to the keypool when this object goes out + //! of scope void KeepKey(); void KeepScript() override { KeepKey(); } };