bitcoin/src/wallet/walletutil.h
glozow f93d5553d1
Merge bitcoin/bitcoin#22838: descriptors: Be able to specify change and receiving in a single descriptor string
a0abcbd382 doc: Mention multipath specifier (Ava Chow)
0019f61fc5 tests: Test importing of multipath descriptors (Ava Chow)
f97d5c137d wallet, rpc: Allow importdescriptors to import multipath descriptors (Ava Chow)
32dcbca3fb rpc: Allow importmulti to import multipath descriptors correctly (Ava Chow)
64dfe3ce4b wallet: Move internal to be per key when importing (Ava Chow)
1692245525 tests: Multipath descriptors for scantxoutset and deriveaddresses (Ava Chow)
cddc0ba9a9 rpc: Have deriveaddresses derive receiving and change (Ava Chow)
360456cd22 tests: Multipath descriptors for getdescriptorinfo (Ava Chow)
a90eee444c tests: Add unit tests for multipath descriptors (Ava Chow)
1bbf46e2da descriptors: Change Parse to return vector of descriptors (Ava Chow)
0d640c6f02 descriptors: Have ParseKeypath handle multipath specifiers (Ava Chow)
a5f39b1034 descriptors: Change ParseScript to return vector of descriptors (Ava Chow)
0d55deae15 descriptors: Add DescriptorImpl::Clone (Ava Chow)
7e86541f72 descriptors: Add PubkeyProvider::Clone (Ava Chow)

Pull request description:

  It is convenient to have a descriptor which specifies both receiving and change addresses in a single string. However, as discussed in https://github.com/bitcoin/bitcoin/issues/17190#issuecomment-895515768, it is not feasible to use a generic multipath specification like BIP 88 due to combinatorial blow up and that it would result in unexpected descriptors.

  To resolve that problem, this PR proposes a targeted solution which allows only a single pair of 2 derivation indexes to be inserted in the place of a single derivation index. So instead of two descriptor `wpkh(xpub.../0/0/*)` and `wpkh(xpub.../0/1/*)` to represent receive and change addresses, this could be written as `wpkh(xpub.../0/<0;1>/*)`. The multipath specifier is of the form `<NUM;NUM>`. Each `NUM` can have its own hardened specifier, e.g. `<0;1h>` is valid. The multipath specifier can also only appear in one path index in the derivation path.

  This results in the parser returning two descriptors. The first descriptor uses the first `NUM` in all pairs present, and the second uses the second `NUM`. In our implementation, if a multipath descriptor is not provided, a pair is still returned, but the second element is just `nullptr`.

  The wallet will not output the multipath descriptors (yet). Furthermore, when a multipath descriptor is imported, it is expanded to the two descriptors and each imported on its own, with the second descriptor being implicitly for internal (change) addresses. There is no change to how the wallet stores or outputs descriptors (yet).

  Note that the path specifier is different from what was proposed. It uses angle brackets and the semicolon because these are unused characters available in the character set and I wanted to avoid conflicts with characters already in use in descriptors.

  Closes #17190

ACKs for top commit:
  darosior:
    re-ACK a0abcbd382
  mjdietzx:
    reACK a0abcbd382
  pythcoiner:
    reACK a0abcbd
  furszy:
    Code review ACK a0abcbd
  glozow:
    light code review ACK a0abcbd382

Tree-SHA512: 84ea40b3fd1b762194acd021cae018c2f09b98e595f5e87de5c832c265cfe8a6d0bc4dae25785392fa90db0f6301ddf9aea787980a29c74f81d04b711ac446c2
2024-08-28 15:56:15 +01:00

125 lines
5.3 KiB
C++

// Copyright (c) 2017-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_WALLETUTIL_H
#define BITCOIN_WALLET_WALLETUTIL_H
#include <script/descriptor.h>
#include <util/fs.h>
#include <vector>
namespace wallet {
/** (client) version numbers for particular wallet features */
enum WalletFeature
{
FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getwalletinfo's clientversion output)
FEATURE_WALLETCRYPT = 40000, // wallet encryption
FEATURE_COMPRPUBKEY = 60000, // compressed public keys
FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet)
FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k)
FEATURE_NO_DEFAULT_KEY = 159900, // Wallet without a default key written
FEATURE_PRE_SPLIT_KEYPOOL = 169900, // Upgraded to HD SPLIT and can have a pre-split keypool
FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL
};
bool IsFeatureSupported(int wallet_version, int feature_version);
WalletFeature GetClosestWalletFeature(int version);
enum WalletFlags : uint64_t {
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
// unknown wallet flags in the lower section <= (1 << 31) will be tolerated
// will categorize coins as clean (not reused) and dirty (reused), and handle
// them with privacy considerations in mind
WALLET_FLAG_AVOID_REUSE = (1ULL << 0),
// Indicates that the metadata has already been upgraded to contain key origins
WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1),
// Indicates that the descriptor cache has been upgraded to cache last hardened xpubs
WALLET_FLAG_LAST_HARDENED_XPUB_CACHED = (1ULL << 2),
// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
//! Flag set when a wallet contains no HD seed and no private keys, scripts,
//! addresses, and other watch only things, and is therefore "blank."
//!
//! The main function this flag serves is to distinguish a blank wallet from
//! a newly created wallet when the wallet database is loaded, to avoid
//! initialization that should only happen on first run.
//!
//! A secondary function of this flag, which applies to descriptor wallets
//! only, is to serve as an ongoing indication that descriptors in the
//! wallet should be created manually, and that the wallet should not
//! generate automatically generate new descriptors if it is later
//! encrypted. To support this behavior, descriptor wallets unlike legacy
//! wallets do not automatically unset the BLANK flag when things are
//! imported.
//!
//! This flag is also a mandatory flag to prevent previous versions of
//! bitcoin from opening the wallet, thinking it was newly created, and
//! then improperly reinitializing it.
WALLET_FLAG_BLANK_WALLET = (1ULL << 33),
//! Indicate that this wallet supports DescriptorScriptPubKeyMan
WALLET_FLAG_DESCRIPTORS = (1ULL << 34),
//! Indicates that the wallet needs an external signer
WALLET_FLAG_EXTERNAL_SIGNER = (1ULL << 35),
};
//! Get the path of the wallet directory.
fs::path GetWalletDir();
/** Descriptor with some wallet metadata */
class WalletDescriptor
{
public:
std::shared_ptr<Descriptor> descriptor;
uint256 id; // Descriptor ID (calculated once at descriptor initialization/deserialization)
uint64_t creation_time = 0;
int32_t range_start = 0; // First item in range; start of range, inclusive, i.e. [range_start, range_end). This never changes.
int32_t range_end = 0; // Item after the last; end of range, exclusive, i.e. [range_start, range_end). This will increment with each TopUp()
int32_t next_index = 0; // Position of the next item to generate
DescriptorCache cache;
void DeserializeDescriptor(const std::string& str)
{
std::string error;
FlatSigningProvider keys;
auto descs = Parse(str, keys, error, true);
if (descs.empty()) {
throw std::ios_base::failure("Invalid descriptor: " + error);
}
if (descs.size() > 1) {
throw std::ios_base::failure("Can't load a multipath descriptor from databases");
}
descriptor = std::move(descs.at(0));
id = DescriptorID(*descriptor);
}
SERIALIZE_METHODS(WalletDescriptor, obj)
{
std::string descriptor_str;
SER_WRITE(obj, descriptor_str = obj.descriptor->ToString());
READWRITE(descriptor_str, obj.creation_time, obj.next_index, obj.range_start, obj.range_end);
SER_READ(obj, obj.DeserializeDescriptor(descriptor_str));
}
WalletDescriptor() = default;
WalletDescriptor(std::shared_ptr<Descriptor> descriptor, uint64_t creation_time, int32_t range_start, int32_t range_end, int32_t next_index) : descriptor(descriptor), id(DescriptorID(*descriptor)), creation_time(creation_time), range_start(range_start), range_end(range_end), next_index(next_index) { }
};
WalletDescriptor GenerateWalletDescriptor(const CExtPubKey& master_key, const OutputType& output_type, bool internal);
} // namespace wallet
#endif // BITCOIN_WALLET_WALLETUTIL_H