aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSamuel Dobson <dobsonsa68@gmail.com>2020-04-27 12:00:38 +1200
committerSamuel Dobson <dobsonsa68@gmail.com>2020-04-27 12:23:05 +1200
commiteef90c14ed0f559e3f6e187341009270b84f45cb (patch)
tree0c8e7cebe9eac12d05dd65594b0524ddbdc07b12 /src
parentae32e5ce3d268881cbf631de1903b2a414bb64e1 (diff)
parent223588b1bbc63dc57098bbd0baa48635e0cc0b82 (diff)
downloadbitcoin-eef90c14ed0f559e3f6e187341009270b84f45cb.tar.xz
Merge #16528: Native Descriptor Wallets using DescriptorScriptPubKeyMan
223588b1bbc63dc57098bbd0baa48635e0cc0b82 Add a --descriptors option to various tests (Andrew Chow) 869f7ab30aeb4d7fbd563c535b55467a8a0430cf tests: Add RPCOverloadWrapper which overloads some disabled RPCs (Andrew Chow) cf060628590fab87d73f278e744d70ef2d5d81db Correctly check for default wallet (Andrew Chow) 886e0d75f5fea2421190aa4812777d89f68962cc Implement CWallet::IsSpentKey for non-LegacySPKMans (Andrew Chow) 3c19fdd2a2fd5394fcfa75b2ba84ab2277cbdabf Return error when no ScriptPubKeyMan is available for specified type (Andrew Chow) 388ba94231f2f10a0be751c562cdd4650510a90a Change wallet_encryption.py to use signmessage instead of dumpprivkey (Andrew Chow) 1346e14831489f9c8f53a08f9dfed61d55d53c6f Functional tests for descriptor wallets (Andrew Chow) f193ea889ddb53d9a5c47647966681d525e38368 add importdescriptors RPC and tests for native descriptor wallets (Hugo Nguyen) ce24a944940019185efebcc5d85eac458ed26016 Add IsLegacy to CWallet so that the GUI knows whether to show watchonly (Andrew Chow) 1cb42b22b11c27e64462afc25a94b2fc50bfa113 Generate new descriptors when encrypting (Andrew Chow) 82ae02b1656819f4bd5023b8955447e1d4ea8692 Be able to create new wallets with DescriptorScriptPubKeyMans as backing (Andrew Chow) b713baa75a62335ab9c0eed9ef76a95bfec30668 Implement GetMetadata in DescriptorScriptPubKeyMan (Andrew Chow) 8b9603bd0b443e2f7984eb72bf2e21cf02af0bcb Change GetMetadata to use unique_ptr<CKeyMetadata> (Andrew Chow) 72a9540df96ffdb94f039b9c14eaacdc7d961196 Implement FillPSBT in DescriptorScriptPubKeyMan (Andrew Chow) 84b4978c02102171775c77a45f6ec198930f0a88 Implement SignMessage for descriptor wallets (Andrew Chow) bde7c9fa38775a81d53ac0484fa9c98076a0c7d1 Implement SignTransaction in DescriptorScriptPubKeyMan (Andrew Chow) d50c8ddd4190f20bf0debd410348b73408ec3143 Implement GetSolvingProvider for DescriptorScriptPubKeyMan (Andrew Chow) f1ca5feb4ad668a3e1ae543d0addd5f483f1a88f Implement GetKeypoolOldestTime and only display it if greater than 0 (Andrew Chow) 586b57a9a6b4b12a78f792785b63a5a1743bce0c Implement ReturnDestination in DescriptorScriptPubKeyMan (Andrew Chow) f866957979c23cefd41efa9dae9e53b9177818dc Implement GetReservedDestination in DescriptorScriptPubKeyMan (Andrew Chow) a775f7c7fd0b9094fcbeee6ba92206d5bbb19164 Implement Unlock and Encrypt in DescriptorScriptPubKeyMan (Andrew Chow) bfdd0734869a22217c15858d7a76d0dacc2ebc86 Implement GetNewDestination for DescriptorScriptPubKeyMan (Andrew Chow) 58c7651821b0eeff0a99dc61d78d2e9e07986580 Implement TopUp in DescriptorScriptPubKeyMan (Andrew Chow) e014886a342508f7c8d80323eee9a5f314eaf94c Implement SetupGeneration for DescriptorScriptPubKeyMan (Andrew Chow) 46dfb99768e7d03a3cf552812d5b41ceaebc06be Implement writing descriptorkeys, descriptorckeys, and descriptors to wallet file (Andrew Chow) 4cb9b69be031e1dc65d8964794781b347fd948f5 Implement several simple functions in DescriptorScriptPubKeyMan (Andrew Chow) d1ec3e4f19487b4b100f80ad02eac063c571777d Add IsSingleType to Descriptors (Andrew Chow) 953feb3d2724f5398dd48990c4957a19313d2c8c Implement loading of keys for DescriptorScriptPubKeyMan (Andrew Chow) 2363e9fcaa41b68bf11153f591b95f2d41ff9a1a Load the descriptor cache from the wallet file (Andrew Chow) 46c46aebb7943e1e2e96755e94dc6c197920bf75 Implement GetID for DescriptorScriptPubKeyMan (Andrew Chow) ec2f9e1178c8e38c0a5ca063fe81adac8f916348 Implement IsHDEnabled in DescriptorScriptPubKeyMan (Andrew Chow) 741122d4c1a62ced3e96d16d67f4eeb3a6522d99 Implement MarkUnusedAddresses in DescriptorScriptPubKeyMan (Andrew Chow) 2db7ca765c8fb2c71dd6f7c4f29ad70e68ff1720 Implement IsMine for DescriptorScriptPubKeyMan (Andrew Chow) db7177af8c159abbcc209f2caafcd45d54c181c5 Add LoadDescriptorScriptPubKeyMan and SetActiveScriptPubKeyMan to CWallet (Andrew Chow) 78f8a92910d34247fa5d04368338c598d9908267 Implement SetType in DescriptorScriptPubKeyMan (Andrew Chow) 834de0300cde57ca3f662fb7aa5b1bdaed68bc8f Store WalletDescriptor in DescriptorScriptPubKeyMan (Andrew Chow) d8132669e10c1db9ae0c2ea0d3f822d7d2f01345 Add a lock cs_desc_man for DescriptorScriptPubKeyMan (Andrew Chow) 3194a7f88ac1a32997b390b4f188c4b6a4af04a5 Introduce WalletDescriptor class (Andrew Chow) 6b13cd3fa854dfaeb9e269bff3d67cacc0e5b5dc Create LegacyScriptPubKeyMan when not a descriptor wallet (Andrew Chow) aeac157c9dc141546b45e06ba9c2e641ad86083f Return nullptr from GetLegacyScriptPubKeyMan if descriptor wallet (Andrew Chow) 96accc73f067c7c95946e9932645dd821ef67f63 Add WALLET_FLAG_DESCRIPTORS (Andrew Chow) 6b8119af53ee2fdb4c4b5b24b4e650c0dc3bd27c Introduce DescriptorScriptPubKeyMan as a dummy class (Andrew Chow) 06620302c713cae65ee8e4ff9302e4c88e2a1285 Introduce SetType function to tell ScriptPubKeyMans the type and internal-ness of it (Andrew Chow) Pull request description: Introducing the wallet of the glorious future (again): native descriptor wallets. With native descriptor wallets, addresses are generated from descriptors. Instead of generating keys and deriving addresses from keys, addresses come from the scriptPubKeys produced by a descriptor. Native descriptor wallets will be optional for now and can only be created by using `createwallet`. Descriptor wallets will store descriptors, master keys from the descriptor, and descriptor cache entries. Keys are derived from descriptors on the fly. In order to allow choosing different address types, 6 descriptors are needed for normal use. There is a pair of primary and change descriptors for each of the 3 address types. With the default keypool size of 1000, each descriptor has 1000 scriptPubKeys and descriptor cache entries pregenerated. This has a side effect of making wallets large since 6000 pubkeys are written to the wallet by default, instead of the current 2000. scriptPubKeys are kept only in memory and are generated every time a descriptor is loaded. By default, we use the standard BIP 44, 49, 84 derivation paths with an external and internal derivation chain for each. Descriptors can also be imported with a new `importdescriptors` RPC. Native descriptor wallets use the `ScriptPubKeyMan` interface introduced in #16341 to add a `DescriptorScriptPubKeyMan`. This defines a different IsMine which uses the simpler model of "does this scriptPubKey exist in this wallet". Furthermore, `DescriptorScriptPubKeyMan` does not have watchonly, so with native descriptor wallets, it is not possible to have a wallet with both watchonly and non-watchonly things. Rather a wallet with `disable_private_keys` needs to be used for watchonly things. A `--descriptor` option was added to some tests (`wallet_basic.py`, `wallet_encryption.py`, `wallet_keypool.py`, `wallet_keypool_topup.py`, and `wallet_labels.py`) to allow for these tests to use descriptor wallets. Additionally, several RPCs are disabled for descriptor wallets (`importprivkey`, `importpubkey`, `importaddress`, `importmulti`, `addmultisigaddress`, `dumpprivkey`, `dumpwallet`, `importwallet`, and `sethdseed`). ACKs for top commit: Sjors: utACK 223588b1bbc63dc57098bbd0baa48635e0cc0b82 (rebased, nits addressed) jonatack: Code review re-ACK 223588b1bbc63dc57098bbd0baa48635e0cc0b82. fjahr: re-ACK 223588b1bbc63dc57098bbd0baa48635e0cc0b82 instagibbs: light re-ACK 223588b meshcollider: Code review ACK 223588b1bbc63dc57098bbd0baa48635e0cc0b82 Tree-SHA512: 59bc52aeddbb769ed5f420d5d240d8137847ac821b588eb616b34461253510c1717d6a70bab8765631738747336ae06f45ba39603ccd17f483843e5ed9a90986
Diffstat (limited to 'src')
-rw-r--r--src/interfaces/wallet.cpp1
-rw-r--r--src/interfaces/wallet.h3
-rw-r--r--src/pubkey.h5
-rw-r--r--src/qt/createwalletdialog.cpp5
-rw-r--r--src/qt/createwalletdialog.h1
-rw-r--r--src/qt/forms/createwalletdialog.ui20
-rw-r--r--src/qt/overviewpage.cpp25
-rw-r--r--src/qt/receivecoinsdialog.cpp43
-rw-r--r--src/qt/walletcontroller.cpp3
-rw-r--r--src/rpc/client.cpp2
-rw-r--r--src/script/descriptor.cpp9
-rw-r--r--src/script/descriptor.h3
-rw-r--r--src/wallet/rpcdump.cpp294
-rw-r--r--src/wallet/rpcwallet.cpp21
-rw-r--r--src/wallet/scriptpubkeyman.cpp671
-rw-r--r--src/wallet/scriptpubkeyman.h116
-rw-r--r--src/wallet/test/wallet_tests.cpp21
-rw-r--r--src/wallet/wallet.cpp331
-rw-r--r--src/wallet/wallet.h26
-rw-r--r--src/wallet/walletdb.cpp185
-rw-r--r--src/wallet/walletdb.h14
-rw-r--r--src/wallet/walletutil.cpp7
-rw-r--r--src/wallet/walletutil.h41
23 files changed, 1707 insertions, 140 deletions
diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp
index 942952d9c6..152917ac60 100644
--- a/src/interfaces/wallet.cpp
+++ b/src/interfaces/wallet.cpp
@@ -464,6 +464,7 @@ public:
{
RemoveWallet(m_wallet);
}
+ bool isLegacy() override { return m_wallet->IsLegacy(); }
std::unique_ptr<Handler> handleUnload(UnloadFn fn) override
{
return MakeHandler(m_wallet->NotifyUnload.connect(fn));
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 0157e9cf95..5d870c5e3d 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -266,6 +266,9 @@ public:
// Remove wallet.
virtual void remove() = 0;
+ //! Return whether is a legacy wallet
+ virtual bool isLegacy() = 0;
+
//! Register handler for unload message.
using UnloadFn = std::function<void()>;
virtual std::unique_ptr<Handler> handleUnload(UnloadFn fn) = 0;
diff --git a/src/pubkey.h b/src/pubkey.h
index 2fc92c9bc6..261842b7f7 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -219,6 +219,11 @@ struct CExtPubKey {
a.pubkey == b.pubkey;
}
+ friend bool operator!=(const CExtPubKey &a, const CExtPubKey &b)
+ {
+ return !(a == b);
+ }
+
void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const;
void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]);
bool Derive(CExtPubKey& out, unsigned int nChild) const;
diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp
index 8e6474b0d4..5056e487fc 100644
--- a/src/qt/createwalletdialog.cpp
+++ b/src/qt/createwalletdialog.cpp
@@ -60,3 +60,8 @@ bool CreateWalletDialog::isMakeBlankWalletChecked() const
{
return ui->blank_wallet_checkbox->isChecked();
}
+
+bool CreateWalletDialog::isDescriptorWalletChecked() const
+{
+ return ui->descriptor_checkbox->isChecked();
+}
diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h
index 30766107b9..20cce937c8 100644
--- a/src/qt/createwalletdialog.h
+++ b/src/qt/createwalletdialog.h
@@ -27,6 +27,7 @@ public:
bool isEncryptWalletChecked() const;
bool isDisablePrivateKeysChecked() const;
bool isMakeBlankWalletChecked() const;
+ bool isDescriptorWalletChecked() const;
private:
Ui::CreateWalletDialog *ui;
diff --git a/src/qt/forms/createwalletdialog.ui b/src/qt/forms/createwalletdialog.ui
index e49bab8f3b..b592140dd7 100644
--- a/src/qt/forms/createwalletdialog.ui
+++ b/src/qt/forms/createwalletdialog.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>364</width>
- <height>185</height>
+ <height>213</height>
</rect>
</property>
<property name="windowTitle">
@@ -17,7 +17,7 @@
<property name="geometry">
<rect>
<x>10</x>
- <y>140</y>
+ <y>170</y>
<width>341</width>
<height>32</height>
</rect>
@@ -106,6 +106,22 @@
<string>Make Blank Wallet</string>
</property>
</widget>
+ <widget class="QCheckBox" name="descriptor_checkbox">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>140</y>
+ <width>171</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="toolTip">
+ <string>Use descriptors for scriptPubKey management</string>
+ </property>
+ <property name="text">
+ <string>Descriptor Wallet</string>
+ </property>
+ </widget>
</widget>
<tabstops>
<tabstop>wallet_name_line_edit</tabstop>
diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp
index c376921b72..e0ae1f9e92 100644
--- a/src/qt/overviewpage.cpp
+++ b/src/qt/overviewpage.cpp
@@ -161,20 +161,27 @@ void OverviewPage::setBalance(const interfaces::WalletBalances& balances)
{
int unit = walletModel->getOptionsModel()->getDisplayUnit();
m_balances = balances;
- if (walletModel->wallet().privateKeysDisabled()) {
- ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance, false, BitcoinUnits::separatorAlways));
- ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_watch_only_balance, false, BitcoinUnits::separatorAlways));
- ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
- ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
+ if (walletModel->wallet().isLegacy()) {
+ if (walletModel->wallet().privateKeysDisabled()) {
+ ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_watch_only_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
+ } else {
+ ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balances.balance, false, BitcoinUnits::separatorAlways));
+ ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_watch_only_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
+ ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
+ }
} else {
ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balances.balance, false, BitcoinUnits::separatorAlways));
ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_balance, false, BitcoinUnits::separatorAlways));
ui->labelImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_balance, false, BitcoinUnits::separatorAlways));
ui->labelTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, false, BitcoinUnits::separatorAlways));
- ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance, false, BitcoinUnits::separatorAlways));
- ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_watch_only_balance, false, BitcoinUnits::separatorAlways));
- ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit(unit, balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
- ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, false, BitcoinUnits::separatorAlways));
}
// only show immature (newly mined) balance if it's non-zero, so as not to complicate things
// for the non-mining users
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index 180550c5ae..5debded4ea 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -157,17 +157,40 @@ void ReceiveCoinsDialog::on_receiveButton_clicked()
}
}
address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", address_type);
- SendCoinsRecipient info(address, label,
- ui->reqAmount->value(), ui->reqMessage->text());
- ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this);
- dialog->setAttribute(Qt::WA_DeleteOnClose);
- dialog->setModel(model);
- dialog->setInfo(info);
- dialog->show();
- clear();
- /* Store request for later reference */
- model->getRecentRequestsTableModel()->addNewRequest(info);
+ switch(model->getAddressTableModel()->getEditStatus())
+ {
+ case AddressTableModel::EditStatus::OK: {
+ // Success
+ SendCoinsRecipient info(address, label,
+ ui->reqAmount->value(), ui->reqMessage->text());
+ ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this);
+ dialog->setAttribute(Qt::WA_DeleteOnClose);
+ dialog->setModel(model);
+ dialog->setInfo(info);
+ dialog->show();
+
+ /* Store request for later reference */
+ model->getRecentRequestsTableModel()->addNewRequest(info);
+ break;
+ }
+ case AddressTableModel::EditStatus::WALLET_UNLOCK_FAILURE:
+ QMessageBox::critical(this, windowTitle(),
+ tr("Could not unlock wallet."),
+ QMessageBox::Ok, QMessageBox::Ok);
+ break;
+ case AddressTableModel::EditStatus::KEY_GENERATION_FAILURE:
+ QMessageBox::critical(this, windowTitle(),
+ tr("Could not generate new %1 address").arg(QString::fromStdString(FormatOutputType(address_type))),
+ QMessageBox::Ok, QMessageBox::Ok);
+ break;
+ // These aren't valid return values for our action
+ case AddressTableModel::EditStatus::INVALID_ADDRESS:
+ case AddressTableModel::EditStatus::DUPLICATE_ADDRESS:
+ case AddressTableModel::EditStatus::NO_CHANGES:
+ assert(false);
+ }
+ clear();
}
void ReceiveCoinsDialog::on_recentRequestsView_doubleClicked(const QModelIndex &index)
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index 8e422fa962..9c1488fb0e 100644
--- a/src/qt/walletcontroller.cpp
+++ b/src/qt/walletcontroller.cpp
@@ -226,6 +226,9 @@ void CreateWalletActivity::createWallet()
if (m_create_wallet_dialog->isMakeBlankWalletChecked()) {
flags |= WALLET_FLAG_BLANK_WALLET;
}
+ if (m_create_wallet_dialog->isDescriptorWalletChecked()) {
+ flags |= WALLET_FLAG_DESCRIPTORS;
+ }
QTimer::singleShot(500, worker(), [this, name, flags] {
WalletCreationStatus status;
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 5d0f2fd703..3045a74d7a 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -131,6 +131,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "importpubkey", 2, "rescan" },
{ "importmulti", 0, "requests" },
{ "importmulti", 1, "options" },
+ { "importdescriptors", 0, "requests" },
{ "verifychain", 0, "checklevel" },
{ "verifychain", 1, "nblocks" },
{ "getblockstats", 0, "hash_or_height" },
@@ -170,6 +171,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createwallet", 1, "disable_private_keys"},
{ "createwallet", 2, "blank"},
{ "createwallet", 4, "avoid_reuse"},
+ { "createwallet", 5, "descriptors"},
{ "getnodeaddresses", 0, "count"},
{ "stop", 0, "wait" },
};
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 68d83299ee..5279f40506 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -575,6 +575,7 @@ public:
default: return nullopt;
}
}
+ bool IsSingleType() const final { return true; }
};
/** A parsed raw(H) descriptor. */
@@ -602,6 +603,7 @@ public:
default: return nullopt;
}
}
+ bool IsSingleType() const final { return true; }
};
/** A parsed pk(P) descriptor. */
@@ -611,6 +613,7 @@ protected:
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider&) const override { return Vector(GetScriptForRawPubKey(keys[0])); }
public:
PKDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "pk") {}
+ bool IsSingleType() const final { return true; }
};
/** A parsed pkh(P) descriptor. */
@@ -626,6 +629,7 @@ protected:
public:
PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "pkh") {}
Optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
+ bool IsSingleType() const final { return true; }
};
/** A parsed wpkh(P) descriptor. */
@@ -641,6 +645,7 @@ protected:
public:
WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "wpkh") {}
Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
+ bool IsSingleType() const final { return true; }
};
/** A parsed combo(P) descriptor. */
@@ -664,6 +669,7 @@ protected:
}
public:
ComboDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "combo") {}
+ bool IsSingleType() const final { return false; }
};
/** A parsed multi(...) or sortedmulti(...) descriptor */
@@ -683,6 +689,7 @@ protected:
}
public:
MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), {}, sorted ? "sortedmulti" : "multi"), m_threshold(threshold), m_sorted(sorted) {}
+ bool IsSingleType() const final { return true; }
};
/** A parsed sh(...) descriptor. */
@@ -699,6 +706,7 @@ public:
if (m_subdescriptor_arg->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT;
return OutputType::LEGACY;
}
+ bool IsSingleType() const final { return true; }
};
/** A parsed wsh(...) descriptor. */
@@ -709,6 +717,7 @@ protected:
public:
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
+ bool IsSingleType() const final { return true; }
};
////////////////////////////////////////////////////////////////////////////
diff --git a/src/script/descriptor.h b/src/script/descriptor.h
index 69850eb670..17b43e7c81 100644
--- a/src/script/descriptor.h
+++ b/src/script/descriptor.h
@@ -87,6 +87,9 @@ struct Descriptor {
/** Convert the descriptor back to a string, undoing parsing. */
virtual std::string ToString() const = 0;
+ /** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */
+ virtual bool IsSingleType() const = 0;
+
/** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 86e4e06673..128de52b58 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1458,3 +1458,297 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
return response;
}
+
+static UniValue ProcessDescriptorImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
+{
+ UniValue warnings(UniValue::VARR);
+ UniValue result(UniValue::VOBJ);
+
+ try {
+ if (!data.exists("desc")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
+ }
+
+ const std::string& descriptor = data["desc"].get_str();
+ const bool active = data.exists("active") ? data["active"].get_bool() : false;
+ const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
+ const std::string& label = data.exists("label") ? data["label"].get_str() : "";
+
+ // Parse descriptor string
+ FlatSigningProvider keys;
+ std::string error;
+ auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true);
+ if (!parsed_desc) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
+ }
+
+ // Range check
+ int64_t range_start = 0, range_end = 1, next_index = 0;
+ if (!parsed_desc->IsRange() && data.exists("range")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
+ } else if (parsed_desc->IsRange()) {
+ if (data.exists("range")) {
+ auto range = ParseDescriptorRange(data["range"]);
+ range_start = range.first;
+ range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive
+ } else {
+ warnings.push_back("Range not given, using default keypool range");
+ range_start = 0;
+ range_end = gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE);
+ }
+ next_index = range_start;
+
+ if (data.exists("next_index")) {
+ next_index = data["next_index"].get_int64();
+ // bound checks
+ if (next_index < range_start || next_index >= range_end) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range");
+ }
+ }
+ }
+
+ // Active descriptors must be ranged
+ if (active && !parsed_desc->IsRange()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
+ }
+
+ // Ranged descriptors should not have a label
+ if (data.exists("range") && data.exists("label")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label");
+ }
+
+ // Internal addresses should not have a label either
+ if (internal && data.exists("label")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
+ }
+
+ // Combo descriptor check
+ if (active && !parsed_desc->IsSingleType()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active");
+ }
+
+ // If the wallet disabled private keys, abort if private keys exist
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
+ }
+
+ // Need to ExpandPrivate to check if private keys are available for all pubkeys
+ FlatSigningProvider expand_keys;
+ std::vector<CScript> scripts;
+ parsed_desc->Expand(0, keys, scripts, expand_keys);
+ parsed_desc->ExpandPrivate(0, keys, expand_keys);
+
+ // Check if all private keys are provided
+ bool have_all_privkeys = !expand_keys.keys.empty();
+ for (const auto& entry : expand_keys.origins) {
+ const CKeyID& key_id = entry.first;
+ CKey key;
+ if (!expand_keys.GetKey(key_id, key)) {
+ have_all_privkeys = false;
+ break;
+ }
+ }
+
+ // If private keys are enabled, check some things.
+ if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ if (keys.keys.empty()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
+ }
+ if (!have_all_privkeys) {
+ warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
+ }
+ }
+
+ WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
+
+ // Check if the wallet already contains the descriptor
+ auto existing_spk_manager = pwallet->GetDescriptorScriptPubKeyMan(w_desc);
+ if (existing_spk_manager) {
+ LOCK(existing_spk_manager->cs_desc_man);
+ if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) {
+ throw JSONRPCError(RPC_INVALID_PARAMS, strprintf("range_start can only decrease; current range = [%d,%d]", existing_spk_manager->GetWalletDescriptor().range_start, existing_spk_manager->GetWalletDescriptor().range_end));
+ }
+ }
+
+ // Add descriptor to the wallet
+ auto spk_manager = pwallet->AddWalletDescriptor(w_desc, keys, label);
+ if (spk_manager == nullptr) {
+ throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
+ }
+
+ // Set descriptor as active if necessary
+ if (active) {
+ if (!w_desc.descriptor->GetOutputType()) {
+ warnings.push_back("Unknown output type, cannot set descriptor to active.");
+ } else {
+ pwallet->SetActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
+ }
+ }
+
+ result.pushKV("success", UniValue(true));
+ } catch (const UniValue& e) {
+ result.pushKV("success", UniValue(false));
+ result.pushKV("error", e);
+ } catch (...) {
+ result.pushKV("success", UniValue(false));
+
+ result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
+ }
+ if (warnings.size()) result.pushKV("warnings", warnings);
+ return result;
+}
+
+UniValue importdescriptors(const JSONRPCRequest& main_request) {
+ // Acquire the wallet
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(main_request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, main_request.fHelp)) {
+ return NullUniValue;
+ }
+
+ RPCHelpMan{"importdescriptors",
+ "\nImport descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n"
+ "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
+ "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n",
+ {
+ {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
+ {
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {
+ {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
+ {"active", RPCArg::Type::BOOL, /* default */ "false", "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
+ {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"},
+ {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"},
+ {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
+ " Use the string \"now\" to substitute the current synced blockchain time.\n"
+ " \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
+ " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
+ " of all descriptors being imported will be scanned.",
+ /* oneline_description */ "", {"timestamp | \"now\"", "integer / string"}
+ },
+ {"internal", RPCArg::Type::BOOL, /* default */ "false", "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
+ {"label", RPCArg::Type::STR, /* default */ "''", "Label to assign to the address, only allowed with internal=false"},
+ },
+ },
+ },
+ "\"requests\""},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::BOOL, "success", ""},
+ {RPCResult::Type::ARR, "warnings", /* optional */ true, "",
+ {
+ {RPCResult::Type::STR, "", ""},
+ }},
+ {RPCResult::Type::OBJ, "error", /* optional */ true, "",
+ {
+ {RPCResult::Type::ELISION, "", "JSONRPC error"},
+ }},
+ }},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
+ "{ \"desc\": \"<my desccriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
+ HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
+ },
+ }.Check(main_request);
+
+ // Make sure wallet is a descriptor wallet
+ if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets");
+ }
+
+ RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ});
+
+ WalletRescanReserver reserver(*pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
+ const UniValue& requests = main_request.params[0];
+ const int64_t minimum_timestamp = 1;
+ int64_t now = 0;
+ int64_t lowest_timestamp = 0;
+ bool rescan = false;
+ UniValue response(UniValue::VARR);
+ {
+ auto locked_chain = pwallet->chain().lock();
+ LOCK(pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(pwallet);
+
+ CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
+
+ // Get all timestamps and extract the lowest timestamp
+ for (const UniValue& request : requests.getValues()) {
+ // This throws an error if "timestamp" doesn't exist
+ const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
+ const UniValue result = ProcessDescriptorImport(pwallet, request, timestamp);
+ response.push_back(result);
+
+ if (lowest_timestamp > timestamp ) {
+ lowest_timestamp = timestamp;
+ }
+
+ // If we know the chain tip, and at least one request was successful then allow rescan
+ if (!rescan && result["success"].get_bool()) {
+ rescan = true;
+ }
+ }
+ pwallet->ConnectScriptPubKeyManNotifiers();
+ }
+
+ // Rescan the blockchain using the lowest timestamp
+ if (rescan) {
+ int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */);
+ {
+ auto locked_chain = pwallet->chain().lock();
+ LOCK(pwallet->cs_wallet);
+ pwallet->ReacceptWalletTransactions();
+ }
+
+ if (pwallet->IsAbortingRescan()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
+ }
+
+ if (scanned_time > lowest_timestamp) {
+ std::vector<UniValue> results = response.getValues();
+ response.clear();
+ response.setArray();
+
+ // Compose the response
+ for (unsigned int i = 0; i < requests.size(); ++i) {
+ const UniValue& request = requests.getValues().at(i);
+
+ // If the descriptor timestamp is within the successfully scanned
+ // range, or if the import result already has an error set, let
+ // the result stand unmodified. Otherwise replace the result
+ // with an error message.
+ if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
+ response.push_back(results.at(i));
+ } else {
+ UniValue result = UniValue(UniValue::VOBJ);
+ result.pushKV("success", UniValue(false));
+ result.pushKV(
+ "error",
+ JSONRPCError(
+ RPC_MISC_ERROR,
+ strprintf("Rescan failed for descriptor with timestamp %d. There was an error reading a "
+ "block from time %d, which is after or within %d seconds of key creation, and "
+ "could contain transactions pertaining to the desc. As a result, transactions "
+ "and coins using this desc may not appear in the wallet. This error could be "
+ "caused by pruning or data corruption (see bitcoind log for details) and could "
+ "be dealt with by downloading and rescanning the relevant blocks (see -reindex "
+ "and -rescan options).",
+ GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
+ response.push_back(std::move(result));
+ }
+ }
+ }
+ }
+
+ return response;
+}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 46efb18793..49c583c422 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1851,7 +1851,7 @@ static UniValue keypoolrefill(const JSONRPCRequest& request)
},
}.Check(request);
- if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
}
@@ -2439,7 +2439,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
{RPCResult::Type::STR_AMOUNT, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
{RPCResult::Type::STR_AMOUNT, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
{RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
- {RPCResult::Type::NUM_TIME, "keypoololdest", "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool"},
+ {RPCResult::Type::NUM_TIME, "keypoololdest", "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool. Legacy wallets only."},
{RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
{RPCResult::Type::NUM, "keypoolsize_hd_internal", "how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)"},
{RPCResult::Type::NUM_TIME, "unlocked_until", "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked"},
@@ -2452,6 +2452,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
{RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
{RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
}},
+ {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
}},
},
RPCExamples{
@@ -2471,13 +2472,16 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
const auto bal = pwallet->GetBalance();
+ int64_t kp_oldest = pwallet->GetOldestKeyPoolTime();
obj.pushKV("walletname", pwallet->GetName());
obj.pushKV("walletversion", pwallet->GetVersion());
obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
obj.pushKV("txcount", (int)pwallet->mapWallet.size());
- obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
+ if (kp_oldest > 0) {
+ obj.pushKV("keypoololdest", kp_oldest);
+ }
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
@@ -2505,6 +2509,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
} else {
obj.pushKV("scanning", false);
}
+ obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
return obj;
}
@@ -2701,6 +2706,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
{"blank", RPCArg::Type::BOOL, /* default */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
+ {"descriptors", RPCArg::Type::BOOL, /* default */ "false", "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2737,6 +2743,9 @@ static UniValue createwallet(const JSONRPCRequest& request)
if (!request.params[4].isNull() && request.params[4].get_bool()) {
flags |= WALLET_FLAG_AVOID_REUSE;
}
+ if (!request.params[5].isNull() && request.params[5].get_bool()) {
+ flags |= WALLET_FLAG_DESCRIPTORS;
+ }
std::string error;
std::shared_ptr<CWallet> wallet;
@@ -3815,7 +3824,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey);
if (spk_man) {
- if (const CKeyMetadata* meta = spk_man->GetMetadata(dest)) {
+ if (const std::unique_ptr<CKeyMetadata> meta = spk_man->GetMetadata(dest)) {
ret.pushKV("timestamp", meta->nCreateTime);
if (meta->has_key_origin) {
ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
@@ -4280,6 +4289,7 @@ UniValue importwallet(const JSONRPCRequest& request);
UniValue importprunedfunds(const JSONRPCRequest& request);
UniValue removeprunedfunds(const JSONRPCRequest& request);
UniValue importmulti(const JSONRPCRequest& request);
+UniValue importdescriptors(const JSONRPCRequest& request);
void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique_ptr<interfaces::Handler>>& handlers)
{
@@ -4293,7 +4303,7 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
- { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse"} },
+ { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
@@ -4309,6 +4319,7 @@ static const CRPCCommand commands[] =
{ "wallet", "getbalances", &getbalances, {} },
{ "wallet", "getwalletinfo", &getwalletinfo, {} },
{ "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} },
+ { "wallet", "importdescriptors", &importdescriptors, {"requests"} },
{ "wallet", "importmulti", &importmulti, {"requests","options"} },
{ "wallet", "importprivkey", &importprivkey, {"privkey","label","rescan"} },
{ "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} },
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index ec75f06e5e..ecb95d599d 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -568,7 +568,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb
return TransactionError::OK;
}
-const CKeyMetadata* LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const
+std::unique_ptr<CKeyMetadata> LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const
{
LOCK(cs_KeyStore);
@@ -576,14 +576,14 @@ const CKeyMetadata* LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& des
if (!key_id.IsNull()) {
auto it = mapKeyMetadata.find(key_id);
if (it != mapKeyMetadata.end()) {
- return &it->second;
+ return MakeUnique<CKeyMetadata>(it->second);
}
}
CScript scriptPubKey = GetScriptForDestination(dest);
auto it = m_script_metadata.find(CScriptID(scriptPubKey));
if (it != m_script_metadata.end()) {
- return &it->second;
+ return MakeUnique<CKeyMetadata>(it->second);
}
return nullptr;
@@ -1497,3 +1497,668 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
}
return set_address;
}
+
+void LegacyScriptPubKeyMan::SetType(OutputType type, bool internal) {}
+
+bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
+{
+ // Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later
+ if (!CanGetAddresses(m_internal)) {
+ error = "No addresses available";
+ return false;
+ }
+ {
+ LOCK(cs_desc_man);
+ assert(m_wallet_descriptor.descriptor->IsSingleType()); // This is a combo descriptor which should not be an active descriptor
+ if (type != m_address_type) {
+ throw std::runtime_error(std::string(__func__) + ": Types are inconsistent");
+ }
+
+ TopUp();
+
+ // Get the scriptPubKey from the descriptor
+ FlatSigningProvider out_keys;
+ std::vector<CScript> scripts_temp;
+ if (m_wallet_descriptor.range_end <= m_max_cached_index && !TopUp(1)) {
+ // We can't generate anymore keys
+ error = "Error: Keypool ran out, please call keypoolrefill first";
+ return false;
+ }
+ if (!m_wallet_descriptor.descriptor->ExpandFromCache(m_wallet_descriptor.next_index, m_wallet_descriptor.cache, scripts_temp, out_keys)) {
+ // We can't generate anymore keys
+ error = "Error: Keypool ran out, please call keypoolrefill first";
+ return false;
+ }
+
+ Optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType();
+ if (out_script_type && out_script_type == type) {
+ ExtractDestination(scripts_temp[0], dest);
+ } else {
+ throw std::runtime_error(std::string(__func__) + ": Types are inconsistent. Stored type does not match type of newly generated address");
+ }
+ m_wallet_descriptor.next_index++;
+ WalletBatch(m_storage.GetDatabase()).WriteDescriptor(GetID(), m_wallet_descriptor);
+ return true;
+ }
+}
+
+isminetype DescriptorScriptPubKeyMan::IsMine(const CScript& script) const
+{
+ LOCK(cs_desc_man);
+ if (m_map_script_pub_keys.count(script) > 0) {
+ return ISMINE_SPENDABLE;
+ }
+ return ISMINE_NO;
+}
+
+bool DescriptorScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys)
+{
+ LOCK(cs_desc_man);
+ if (!m_map_keys.empty()) {
+ return false;
+ }
+
+ bool keyPass = m_map_crypted_keys.empty(); // Always pass when there are no encrypted keys
+ bool keyFail = false;
+ for (const auto& mi : m_map_crypted_keys) {
+ const CPubKey &pubkey = mi.second.first;
+ const std::vector<unsigned char> &crypted_secret = mi.second.second;
+ CKey key;
+ if (!DecryptKey(master_key, crypted_secret, pubkey, key)) {
+ keyFail = true;
+ break;
+ }
+ keyPass = true;
+ if (m_decryption_thoroughly_checked)
+ break;
+ }
+ if (keyPass && keyFail) {
+ LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
+ throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt.");
+ }
+ if (keyFail || (!keyPass && !accept_no_keys)) {
+ return false;
+ }
+ m_decryption_thoroughly_checked = true;
+ return true;
+}
+
+bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch)
+{
+ LOCK(cs_desc_man);
+ if (!m_map_crypted_keys.empty()) {
+ return false;
+ }
+
+ for (const KeyMap::value_type& key_in : m_map_keys)
+ {
+ const CKey &key = key_in.second;
+ CPubKey pubkey = key.GetPubKey();
+ CKeyingMaterial secret(key.begin(), key.end());
+ std::vector<unsigned char> crypted_secret;
+ if (!EncryptSecret(master_key, secret, pubkey.GetHash(), crypted_secret)) {
+ return false;
+ }
+ m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret);
+ batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret);
+ }
+ m_map_keys.clear();
+ return true;
+}
+
+bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool)
+{
+ LOCK(cs_desc_man);
+ std::string error;
+ bool result = GetNewDestination(type, address, error);
+ index = m_wallet_descriptor.next_index - 1;
+ return result;
+}
+
+void DescriptorScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CTxDestination& addr)
+{
+ LOCK(cs_desc_man);
+ // Only return when the index was the most recent
+ if (m_wallet_descriptor.next_index - 1 == index) {
+ m_wallet_descriptor.next_index--;
+ }
+ WalletBatch(m_storage.GetDatabase()).WriteDescriptor(GetID(), m_wallet_descriptor);
+ NotifyCanGetAddressesChanged();
+}
+
+std::map<CKeyID, CKey> DescriptorScriptPubKeyMan::GetKeys() const
+{
+ AssertLockHeld(cs_desc_man);
+ if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked()) {
+ KeyMap keys;
+ for (auto key_pair : m_map_crypted_keys) {
+ const CPubKey& pubkey = key_pair.second.first;
+ const std::vector<unsigned char>& crypted_secret = key_pair.second.second;
+ CKey key;
+ DecryptKey(m_storage.GetEncryptionKey(), crypted_secret, pubkey, key);
+ keys[pubkey.GetID()] = key;
+ }
+ return keys;
+ }
+ return m_map_keys;
+}
+
+bool DescriptorScriptPubKeyMan::TopUp(unsigned int size)
+{
+ LOCK(cs_desc_man);
+ unsigned int target_size;
+ if (size > 0) {
+ target_size = size;
+ } else {
+ target_size = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 1);
+ }
+
+ // Calculate the new range_end
+ int32_t new_range_end = std::max(m_wallet_descriptor.next_index + (int32_t)target_size, m_wallet_descriptor.range_end);
+
+ // If the descriptor is not ranged, we actually just want to fill the first cache item
+ if (!m_wallet_descriptor.descriptor->IsRange()) {
+ new_range_end = 1;
+ m_wallet_descriptor.range_end = 1;
+ m_wallet_descriptor.range_start = 0;
+ }
+
+ FlatSigningProvider provider;
+ provider.keys = GetKeys();
+
+ WalletBatch batch(m_storage.GetDatabase());
+ uint256 id = GetID();
+ for (int32_t i = m_max_cached_index + 1; i < new_range_end; ++i) {
+ FlatSigningProvider out_keys;
+ std::vector<CScript> scripts_temp;
+ DescriptorCache temp_cache;
+ // Maybe we have a cached xpub and we can expand from the cache first
+ if (!m_wallet_descriptor.descriptor->ExpandFromCache(i, m_wallet_descriptor.cache, scripts_temp, out_keys)) {
+ if (!m_wallet_descriptor.descriptor->Expand(i, provider, scripts_temp, out_keys, &temp_cache)) return false;
+ }
+ // Add all of the scriptPubKeys to the scriptPubKey set
+ for (const CScript& script : scripts_temp) {
+ m_map_script_pub_keys[script] = i;
+ }
+ for (const auto& pk_pair : out_keys.pubkeys) {
+ const CPubKey& pubkey = pk_pair.second;
+ if (m_map_pubkeys.count(pubkey) != 0) {
+ // We don't need to give an error here.
+ // It doesn't matter which of many valid indexes the pubkey has, we just need an index where we can derive it and it's private key
+ continue;
+ }
+ m_map_pubkeys[pubkey] = i;
+ }
+ // Write the cache
+ for (const auto& parent_xpub_pair : temp_cache.GetCachedParentExtPubKeys()) {
+ CExtPubKey xpub;
+ if (m_wallet_descriptor.cache.GetCachedParentExtPubKey(parent_xpub_pair.first, xpub)) {
+ if (xpub != parent_xpub_pair.second) {
+ throw std::runtime_error(std::string(__func__) + ": New cached parent xpub does not match already cached parent xpub");
+ }
+ continue;
+ }
+ if (!batch.WriteDescriptorParentCache(parent_xpub_pair.second, id, parent_xpub_pair.first)) {
+ throw std::runtime_error(std::string(__func__) + ": writing cache item failed");
+ }
+ m_wallet_descriptor.cache.CacheParentExtPubKey(parent_xpub_pair.first, parent_xpub_pair.second);
+ }
+ for (const auto& derived_xpub_map_pair : temp_cache.GetCachedDerivedExtPubKeys()) {
+ for (const auto& derived_xpub_pair : derived_xpub_map_pair.second) {
+ CExtPubKey xpub;
+ if (m_wallet_descriptor.cache.GetCachedDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, xpub)) {
+ if (xpub != derived_xpub_pair.second) {
+ throw std::runtime_error(std::string(__func__) + ": New cached derived xpub does not match already cached derived xpub");
+ }
+ continue;
+ }
+ if (!batch.WriteDescriptorDerivedCache(derived_xpub_pair.second, id, derived_xpub_map_pair.first, derived_xpub_pair.first)) {
+ throw std::runtime_error(std::string(__func__) + ": writing cache item failed");
+ }
+ m_wallet_descriptor.cache.CacheDerivedExtPubKey(derived_xpub_map_pair.first, derived_xpub_pair.first, derived_xpub_pair.second);
+ }
+ }
+ m_max_cached_index++;
+ }
+ m_wallet_descriptor.range_end = new_range_end;
+ batch.WriteDescriptor(GetID(), m_wallet_descriptor);
+
+ // By this point, the cache size should be the size of the entire range
+ assert(m_wallet_descriptor.range_end - 1 == m_max_cached_index);
+
+ NotifyCanGetAddressesChanged();
+ return true;
+}
+
+void DescriptorScriptPubKeyMan::MarkUnusedAddresses(const CScript& script)
+{
+ LOCK(cs_desc_man);
+ if (IsMine(script)) {
+ int32_t index = m_map_script_pub_keys[script];
+ if (index >= m_wallet_descriptor.next_index) {
+ WalletLogPrintf("%s: Detected a used keypool item at index %d, mark all keypool items up to this item as used\n", __func__, index);
+ m_wallet_descriptor.next_index = index + 1;
+ }
+ if (!TopUp()) {
+ WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
+ }
+ }
+}
+
+void DescriptorScriptPubKeyMan::AddDescriptorKey(const CKey& key, const CPubKey &pubkey)
+{
+ LOCK(cs_desc_man);
+ WalletBatch batch(m_storage.GetDatabase());
+ if (!AddDescriptorKeyWithDB(batch, key, pubkey)) {
+ throw std::runtime_error(std::string(__func__) + ": writing descriptor private key failed");
+ }
+}
+
+bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey)
+{
+ AssertLockHeld(cs_desc_man);
+ assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+
+ if (m_storage.HasEncryptionKeys()) {
+ if (m_storage.IsLocked()) {
+ return false;
+ }
+
+ std::vector<unsigned char> crypted_secret;
+ CKeyingMaterial secret(key.begin(), key.end());
+ if (!EncryptSecret(m_storage.GetEncryptionKey(), secret, pubkey.GetHash(), crypted_secret)) {
+ return false;
+ }
+
+ m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret);
+ return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret);
+ } else {
+ m_map_keys[pubkey.GetID()] = key;
+ return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey());
+ }
+}
+
+bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key)
+{
+ LOCK(cs_desc_man);
+ assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+
+ // Ignore when there is already a descriptor
+ if (m_wallet_descriptor.descriptor) {
+ return false;
+ }
+
+ int64_t creation_time = GetTime();
+
+ std::string xpub = EncodeExtPubKey(master_key.Neuter());
+
+ // Build descriptor string
+ std::string desc_prefix;
+ std::string desc_suffix = "/*)";
+ switch (m_address_type) {
+ case OutputType::LEGACY: {
+ desc_prefix = "pkh(" + xpub + "/44'";
+ break;
+ }
+ case OutputType::P2SH_SEGWIT: {
+ desc_prefix = "sh(wpkh(" + xpub + "/49'";
+ desc_suffix += ")";
+ break;
+ }
+ case OutputType::BECH32: {
+ desc_prefix = "wpkh(" + xpub + "/84'";
+ break;
+ }
+ default: assert(false);
+ }
+
+ // Mainnet derives at 0', testnet and regtest derive at 1'
+ if (Params().IsTestChain()) {
+ desc_prefix += "/1'";
+ } else {
+ desc_prefix += "/0'";
+ }
+
+ std::string internal_path = m_internal ? "/1" : "/0";
+ std::string desc_str = desc_prefix + "/0'" + internal_path + desc_suffix;
+
+ // Make the descriptor
+ FlatSigningProvider keys;
+ std::string error;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, error, false);
+ WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
+ m_wallet_descriptor = w_desc;
+
+ // Store the master private key, and descriptor
+ WalletBatch batch(m_storage.GetDatabase());
+ if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey())) {
+ throw std::runtime_error(std::string(__func__) + ": writing descriptor master private key failed");
+ }
+ if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) {
+ throw std::runtime_error(std::string(__func__) + ": writing descriptor failed");
+ }
+
+ // TopUp
+ TopUp();
+
+ m_storage.UnsetBlankWalletFlag(batch);
+ return true;
+}
+
+bool DescriptorScriptPubKeyMan::IsHDEnabled() const
+{
+ LOCK(cs_desc_man);
+ return m_wallet_descriptor.descriptor->IsRange();
+}
+
+bool DescriptorScriptPubKeyMan::CanGetAddresses(bool internal) const
+{
+ // We can only give out addresses from descriptors that are single type (not combo), ranged,
+ // and either have cached keys or can generate more keys (ignoring encryption)
+ LOCK(cs_desc_man);
+ return m_wallet_descriptor.descriptor->IsSingleType() &&
+ m_wallet_descriptor.descriptor->IsRange() &&
+ (HavePrivateKeys() || m_wallet_descriptor.next_index < m_wallet_descriptor.range_end);
+}
+
+bool DescriptorScriptPubKeyMan::HavePrivateKeys() const
+{
+ LOCK(cs_desc_man);
+ return m_map_keys.size() > 0 || m_map_crypted_keys.size() > 0;
+}
+
+int64_t DescriptorScriptPubKeyMan::GetOldestKeyPoolTime() const
+{
+ // This is only used for getwalletinfo output and isn't relevant to descriptor wallets.
+ // The magic number 0 indicates that it shouldn't be displayed so that's what we return.
+ return 0;
+}
+
+size_t DescriptorScriptPubKeyMan::KeypoolCountExternalKeys() const
+{
+ if (m_internal) {
+ return 0;
+ }
+ return GetKeyPoolSize();
+}
+
+unsigned int DescriptorScriptPubKeyMan::GetKeyPoolSize() const
+{
+ LOCK(cs_desc_man);
+ return m_wallet_descriptor.range_end - m_wallet_descriptor.next_index;
+}
+
+int64_t DescriptorScriptPubKeyMan::GetTimeFirstKey() const
+{
+ LOCK(cs_desc_man);
+ return m_wallet_descriptor.creation_time;
+}
+
+std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvider(const CScript& script, bool include_private) const
+{
+ LOCK(cs_desc_man);
+
+ // Find the index of the script
+ auto it = m_map_script_pub_keys.find(script);
+ if (it == m_map_script_pub_keys.end()) {
+ return nullptr;
+ }
+ int32_t index = it->second;
+
+ return GetSigningProvider(index, include_private);
+}
+
+std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvider(const CPubKey& pubkey) const
+{
+ LOCK(cs_desc_man);
+
+ // Find index of the pubkey
+ auto it = m_map_pubkeys.find(pubkey);
+ if (it == m_map_pubkeys.end()) {
+ return nullptr;
+ }
+ int32_t index = it->second;
+
+ // Always try to get the signing provider with private keys. This function should only be called during signing anyways
+ return GetSigningProvider(index, true);
+}
+
+std::unique_ptr<FlatSigningProvider> DescriptorScriptPubKeyMan::GetSigningProvider(int32_t index, bool include_private) const
+{
+ AssertLockHeld(cs_desc_man);
+ // Get the scripts, keys, and key origins for this script
+ std::unique_ptr<FlatSigningProvider> out_keys = MakeUnique<FlatSigningProvider>();
+ std::vector<CScript> scripts_temp;
+ if (!m_wallet_descriptor.descriptor->ExpandFromCache(index, m_wallet_descriptor.cache, scripts_temp, *out_keys)) return nullptr;
+
+ if (HavePrivateKeys() && include_private) {
+ FlatSigningProvider master_provider;
+ master_provider.keys = GetKeys();
+ m_wallet_descriptor.descriptor->ExpandPrivate(index, master_provider, *out_keys);
+ }
+
+ return out_keys;
+}
+
+std::unique_ptr<SigningProvider> DescriptorScriptPubKeyMan::GetSolvingProvider(const CScript& script) const
+{
+ return GetSigningProvider(script, false);
+}
+
+bool DescriptorScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sigdata)
+{
+ return IsMine(script);
+}
+
+bool DescriptorScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
+{
+ std::unique_ptr<FlatSigningProvider> keys = MakeUnique<FlatSigningProvider>();
+ for (const auto& coin_pair : coins) {
+ std::unique_ptr<FlatSigningProvider> coin_keys = GetSigningProvider(coin_pair.second.out.scriptPubKey, true);
+ if (!coin_keys) {
+ continue;
+ }
+ *keys = Merge(*keys, *coin_keys);
+ }
+
+ return ::SignTransaction(tx, keys.get(), coins, sighash, input_errors);
+}
+
+SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const
+{
+ std::unique_ptr<FlatSigningProvider> keys = GetSigningProvider(GetScriptForDestination(pkhash), true);
+ if (!keys) {
+ return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
+ }
+
+ CKeyID key_id(pkhash);
+ CKey key;
+ if (!keys->GetKey(key_id, key)) {
+ return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
+ }
+
+ if (!MessageSign(key, message, str_sig)) {
+ return SigningResult::SIGNING_FAILED;
+ }
+ return SigningResult::OK;
+}
+
+TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs) const
+{
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ const CTxIn& txin = psbtx.tx->vin[i];
+ PSBTInput& input = psbtx.inputs.at(i);
+
+ if (PSBTInputSigned(input)) {
+ continue;
+ }
+
+ // Verify input looks sane. This will check that we have at most one uxto, witness or non-witness.
+ if (!input.IsSane()) {
+ return TransactionError::INVALID_PSBT;
+ }
+
+ // Get the Sighash type
+ if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
+ return TransactionError::SIGHASH_MISMATCH;
+ }
+
+ // Get the scriptPubKey to know which SigningProvider to use
+ CScript script;
+ if (!input.witness_utxo.IsNull()) {
+ script = input.witness_utxo.scriptPubKey;
+ } else if (input.non_witness_utxo) {
+ if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
+ return TransactionError::MISSING_INPUTS;
+ }
+ script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey;
+ } else {
+ // There's no UTXO so we can just skip this now
+ continue;
+ }
+ SignatureData sigdata;
+ input.FillSignatureData(sigdata);
+
+ std::unique_ptr<FlatSigningProvider> keys = MakeUnique<FlatSigningProvider>();
+ std::unique_ptr<FlatSigningProvider> script_keys = GetSigningProvider(script, sign);
+ if (script_keys) {
+ *keys = Merge(*keys, *script_keys);
+ } else {
+ // Maybe there are pubkeys listed that we can sign for
+ script_keys = MakeUnique<FlatSigningProvider>();
+ for (const auto& pk_pair : input.hd_keypaths) {
+ const CPubKey& pubkey = pk_pair.first;
+ std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey);
+ if (pk_keys) {
+ *keys = Merge(*keys, *pk_keys);
+ }
+ }
+ }
+
+ SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, sighash_type);
+ }
+
+ // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
+ for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
+ std::unique_ptr<SigningProvider> keys = GetSolvingProvider(psbtx.tx->vout.at(i).scriptPubKey);
+ if (!keys) {
+ continue;
+ }
+ UpdatePSBTOutput(HidingSigningProvider(keys.get(), true, !bip32derivs), psbtx, i);
+ }
+
+ return TransactionError::OK;
+}
+
+std::unique_ptr<CKeyMetadata> DescriptorScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const
+{
+ std::unique_ptr<SigningProvider> provider = GetSigningProvider(GetScriptForDestination(dest));
+ if (provider) {
+ KeyOriginInfo orig;
+ CKeyID key_id = GetKeyForDestination(*provider, dest);
+ if (provider->GetKeyOrigin(key_id, orig)) {
+ LOCK(cs_desc_man);
+ std::unique_ptr<CKeyMetadata> meta = MakeUnique<CKeyMetadata>();
+ meta->key_origin = orig;
+ meta->has_key_origin = true;
+ meta->nCreateTime = m_wallet_descriptor.creation_time;
+ return meta;
+ }
+ }
+ return nullptr;
+}
+
+uint256 DescriptorScriptPubKeyMan::GetID() const
+{
+ LOCK(cs_desc_man);
+ std::string desc_str = m_wallet_descriptor.descriptor->ToString();
+ uint256 id;
+ CSHA256().Write((unsigned char*)desc_str.data(), desc_str.size()).Finalize(id.begin());
+ return id;
+}
+
+void DescriptorScriptPubKeyMan::SetType(OutputType type, bool internal)
+{
+ this->m_address_type = type;
+ this->m_internal = internal;
+}
+
+void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache)
+{
+ LOCK(cs_desc_man);
+ m_wallet_descriptor.cache = cache;
+ for (int32_t i = m_wallet_descriptor.range_start; i < m_wallet_descriptor.range_end; ++i) {
+ FlatSigningProvider out_keys;
+ std::vector<CScript> scripts_temp;
+ if (!m_wallet_descriptor.descriptor->ExpandFromCache(i, m_wallet_descriptor.cache, scripts_temp, out_keys)) {
+ throw std::runtime_error("Error: Unable to expand wallet descriptor from cache");
+ }
+ // Add all of the scriptPubKeys to the scriptPubKey set
+ for (const CScript& script : scripts_temp) {
+ if (m_map_script_pub_keys.count(script) != 0) {
+ throw std::runtime_error(strprintf("Error: Already loaded script at index %d as being at index %d", i, m_map_script_pub_keys[script]));
+ }
+ m_map_script_pub_keys[script] = i;
+ }
+ for (const auto& pk_pair : out_keys.pubkeys) {
+ const CPubKey& pubkey = pk_pair.second;
+ if (m_map_pubkeys.count(pubkey) != 0) {
+ // We don't need to give an error here.
+ // It doesn't matter which of many valid indexes the pubkey has, we just need an index where we can derive it and it's private key
+ continue;
+ }
+ m_map_pubkeys[pubkey] = i;
+ }
+ m_max_cached_index++;
+ }
+}
+
+bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key)
+{
+ LOCK(cs_desc_man);
+ m_map_keys[key_id] = key;
+ return true;
+}
+
+bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key)
+{
+ LOCK(cs_desc_man);
+ if (!m_map_keys.empty()) {
+ return false;
+ }
+
+ m_map_crypted_keys[key_id] = make_pair(pubkey, crypted_key);
+ return true;
+}
+
+bool DescriptorScriptPubKeyMan::HasWalletDescriptor(const WalletDescriptor& desc) const
+{
+ LOCK(cs_desc_man);
+ return m_wallet_descriptor.descriptor != nullptr && desc.descriptor != nullptr && m_wallet_descriptor.descriptor->ToString() == desc.descriptor->ToString();
+}
+
+void DescriptorScriptPubKeyMan::WriteDescriptor()
+{
+ LOCK(cs_desc_man);
+ WalletBatch batch(m_storage.GetDatabase());
+ if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) {
+ throw std::runtime_error(std::string(__func__) + ": writing descriptor failed");
+ }
+}
+
+const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const
+{
+ return m_wallet_descriptor;
+}
+
+const std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() const
+{
+ LOCK(cs_desc_man);
+ std::vector<CScript> script_pub_keys;
+ script_pub_keys.reserve(m_map_script_pub_keys.size());
+
+ for (auto const& script_pub_key: m_map_script_pub_keys) {
+ script_pub_keys.push_back(script_pub_key.first);
+ }
+ return script_pub_keys;
+}
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 8512eadf31..3117b13d35 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -6,6 +6,7 @@
#define BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
#include <psbt.h>
+#include <script/descriptor.h>
#include <script/signingprovider.h>
#include <script/standard.h>
#include <util/error.h>
@@ -204,7 +205,7 @@ public:
virtual int64_t GetTimeFirstKey() const { return 0; }
- virtual const CKeyMetadata* GetMetadata(const CTxDestination& dest) const { return nullptr; }
+ virtual std::unique_ptr<CKeyMetadata> GetMetadata(const CTxDestination& dest) const { return nullptr; }
virtual std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const { return nullptr; }
@@ -222,6 +223,8 @@ public:
virtual uint256 GetID() const { return uint256(); }
+ virtual void SetType(OutputType type, bool internal) {}
+
/** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
template<typename... Params>
void WalletLogPrintf(std::string fmt, Params... parameters) const {
@@ -352,7 +355,7 @@ public:
int64_t GetTimeFirstKey() const override;
- const CKeyMetadata* GetMetadata(const CTxDestination& dest) const override;
+ std::unique_ptr<CKeyMetadata> GetMetadata(const CTxDestination& dest) const override;
bool CanGetAddresses(bool internal = false) const override;
@@ -366,6 +369,8 @@ public:
uint256 GetID() const override;
+ void SetType(OutputType type, bool internal) override;
+
// Map from Key ID to key metadata.
std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore);
@@ -477,4 +482,111 @@ public:
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override { return m_spk_man.GetKeyOrigin(keyid, info); }
};
+class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
+{
+private:
+ WalletDescriptor m_wallet_descriptor GUARDED_BY(cs_desc_man);
+
+ using ScriptPubKeyMap = std::map<CScript, int32_t>; // Map of scripts to descriptor range index
+ using PubKeyMap = std::map<CPubKey, int32_t>; // Map of pubkeys involved in scripts to descriptor range index
+ using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
+ using KeyMap = std::map<CKeyID, CKey>;
+
+ ScriptPubKeyMap m_map_script_pub_keys GUARDED_BY(cs_desc_man);
+ PubKeyMap m_map_pubkeys GUARDED_BY(cs_desc_man);
+ int32_t m_max_cached_index = -1;
+
+ OutputType m_address_type;
+ bool m_internal;
+
+ KeyMap m_map_keys GUARDED_BY(cs_desc_man);
+ CryptedKeyMap m_map_crypted_keys GUARDED_BY(cs_desc_man);
+
+ bool SetCrypted();
+
+ //! keeps track of whether Unlock has run a thorough check before
+ bool m_decryption_thoroughly_checked = false;
+
+ bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey);
+
+ KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
+
+ // Fetch the SigningProvider for the given script and optionally include private keys
+ std::unique_ptr<FlatSigningProvider> GetSigningProvider(const CScript& script, bool include_private = false) const;
+ // Fetch the SigningProvider for the given pubkey and always include private keys. This should only be called by signing code.
+ std::unique_ptr<FlatSigningProvider> GetSigningProvider(const CPubKey& pubkey) const;
+ // Fetch the SigningProvider for a given index and optionally include private keys. Called by the above functions.
+ std::unique_ptr<FlatSigningProvider> GetSigningProvider(int32_t index, bool include_private = false) const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
+
+public:
+ DescriptorScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor)
+ : ScriptPubKeyMan(storage),
+ m_wallet_descriptor(descriptor)
+ {}
+ DescriptorScriptPubKeyMan(WalletStorage& storage, OutputType address_type, bool internal)
+ : ScriptPubKeyMan(storage),
+ m_address_type(address_type), m_internal(internal)
+ {}
+
+ mutable RecursiveMutex cs_desc_man;
+
+ bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override;
+ isminetype IsMine(const CScript& script) const override;
+
+ bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
+ bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override;
+
+ bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override;
+ void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) override;
+
+ // Tops up the descriptor cache and m_map_script_pub_keys. The cache is stored in the wallet file
+ // and is used to expand the descriptor in GetNewDestination. DescriptorScriptPubKeyMan relies
+ // more on ephemeral data than LegacyScriptPubKeyMan. For wallets using unhardened derivation
+ // (with or without private keys), the "keypool" is a single xpub.
+ bool TopUp(unsigned int size = 0) override;
+
+ void MarkUnusedAddresses(const CScript& script) override;
+
+ bool IsHDEnabled() const override;
+
+ //! Setup descriptors based on the given CExtkey
+ bool SetupDescriptorGeneration(const CExtKey& master_key);
+
+ bool HavePrivateKeys() const override;
+
+ int64_t GetOldestKeyPoolTime() const override;
+ size_t KeypoolCountExternalKeys() const override;
+ unsigned int GetKeyPoolSize() const override;
+
+ int64_t GetTimeFirstKey() const override;
+
+ std::unique_ptr<CKeyMetadata> GetMetadata(const CTxDestination& dest) const override;
+
+ bool CanGetAddresses(bool internal = false) const override;
+
+ std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const override;
+
+ bool CanProvide(const CScript& script, SignatureData& sigdata) override;
+
+ bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override;
+ SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
+ TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false) const override;
+
+ uint256 GetID() const override;
+
+ void SetType(OutputType type, bool internal) override;
+
+ void SetCache(const DescriptorCache& cache);
+
+ bool AddKey(const CKeyID& key_id, const CKey& key);
+ bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key);
+
+ bool HasWalletDescriptor(const WalletDescriptor& desc) const;
+ void AddDescriptorKey(const CKey& key, const CPubKey &pubkey);
+ void WriteDescriptor();
+
+ const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
+ const std::vector<CScript> GetScriptPubKeys() const;
+};
+
#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 42ae738207..2bdeb89f3c 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -637,4 +637,25 @@ BOOST_FIXTURE_TEST_CASE(dummy_input_size_test, TestChain100Setup)
BOOST_CHECK_EQUAL(CalculateNestedKeyhashInputSize(true), DUMMY_NESTED_P2WPKH_INPUT_SIZE);
}
+bool malformed_descriptor(std::ios_base::failure e)
+{
+ std::string s(e.what());
+ return s.find("Missing checksum") != std::string::npos;
+}
+
+BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
+{
+ std::vector<unsigned char> malformed_record;
+ CVectorWriter vw(0, 0, malformed_record, 0);
+ vw << std::string("notadescriptor");
+ vw << (uint64_t)0;
+ vw << (int32_t)0;
+ vw << (int32_t)0;
+ vw << (int32_t)1;
+
+ VectorReader vr(0, 0, malformed_record, 0);
+ WalletDescriptor w_desc;
+ BOOST_CHECK_EXCEPTION(vr >> w_desc, std::ios_base::failure, malformed_descriptor);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 7a6deab1a8..b6f25de64e 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -228,10 +228,14 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString&
// Set a seed for the wallet
{
LOCK(wallet->cs_wallet);
- for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) {
- if (!spk_man->SetupGeneration()) {
- error = "Unable to generate initial keys";
- return WalletCreationStatus::CREATION_FAILED;
+ if (wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ wallet->SetupDescriptorScriptPubKeyMans();
+ } else {
+ for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) {
+ if (!spk_man->SetupGeneration()) {
+ error = "Unable to generate initial keys";
+ return WalletCreationStatus::CREATION_FAILED;
+ }
}
}
}
@@ -588,8 +592,11 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
Lock();
Unlock(strWalletPassphrase);
- // if we are using HD, replace the HD seed with a new one
- if (auto spk_man = GetLegacyScriptPubKeyMan()) {
+ // If we are using descriptors, make new descriptors with a new seed
+ if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) {
+ SetupDescriptorScriptPubKeyMans();
+ } else if (auto spk_man = GetLegacyScriptPubKeyMan()) {
+ // if we are using HD, replace the HD seed with a new one
if (spk_man->IsHDEnabled()) {
if (!spk_man->SetupGeneration(true)) {
return false;
@@ -747,22 +754,29 @@ bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const
const CWalletTx* srctx = GetWalletTx(hash);
if (srctx) {
assert(srctx->tx->vout.size() > n);
- LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
- // When descriptor wallets arrive, these additional checks are
- // likely superfluous and can be optimized out
- assert(spk_man != nullptr);
- for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
- WitnessV0KeyHash wpkh_dest(keyid);
- if (GetDestData(wpkh_dest, "used", nullptr)) {
- return true;
- }
- ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
- if (GetDestData(sh_wpkh_dest, "used", nullptr)) {
- return true;
- }
- PKHash pkh_dest(keyid);
- if (GetDestData(pkh_dest, "used", nullptr)) {
- return true;
+ CTxDestination dest;
+ if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) {
+ return false;
+ }
+ if (GetDestData(dest, "used", nullptr)) {
+ return true;
+ }
+ if (IsLegacy()) {
+ LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
+ assert(spk_man != nullptr);
+ for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
+ WitnessV0KeyHash wpkh_dest(keyid);
+ if (GetDestData(wpkh_dest, "used", nullptr)) {
+ return true;
+ }
+ ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
+ if (GetDestData(sh_wpkh_dest, "used", nullptr)) {
+ return true;
+ }
+ PKHash pkh_dest(keyid);
+ if (GetDestData(pkh_dest, "used", nullptr)) {
+ return true;
+ }
}
}
}
@@ -1331,9 +1345,10 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
bool CWallet::IsHDEnabled() const
{
+ // All Active ScriptPubKeyMans must be HD for this to be true
bool result = true;
- for (const auto& spk_man_pair : m_spk_managers) {
- result &= spk_man_pair.second->IsHDEnabled();
+ for (const auto& spk_man : GetActiveScriptPubKeyMans()) {
+ result &= spk_man->IsHDEnabled();
}
return result;
}
@@ -2427,11 +2442,17 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const
bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
{
- // Sign the tx with ScriptPubKeyMans
- // Because each ScriptPubKeyMan can sign more than one input, we need to keep track of each ScriptPubKeyMan that has signed this transaction.
- // Each iteration, we may sign more txins than the txin that is specified in that iteration.
- // We assume that each input is signed by only one ScriptPubKeyMan.
- std::set<uint256> visited_spk_mans;
+ // Try to sign with all ScriptPubKeyMans
+ for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
+ // spk_man->SignTransaction will return true if the transaction is complete,
+ // so we can exit early and return true if that happens
+ if (spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
+ return true;
+ }
+ }
+
+ // At this point, one input was not fully signed otherwise we would have exited already
+ // Find that input and figure out what went wrong.
for (unsigned int i = 0; i < tx.vin.size(); i++) {
// Get the prevout
CTxIn& txin = tx.vin[i];
@@ -2443,33 +2464,10 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint,
// Check if this input is complete
SignatureData sigdata = DataFromTransaction(tx, i, coin->second.out);
- if (sigdata.complete) {
- continue;
- }
-
- // Input needs to be signed, find the right ScriptPubKeyMan
- std::set<ScriptPubKeyMan*> spk_mans = GetScriptPubKeyMans(coin->second.out.scriptPubKey, sigdata);
- if (spk_mans.size() == 0) {
+ if (!sigdata.complete) {
input_errors[i] = "Unable to sign input, missing keys";
continue;
}
-
- for (auto& spk_man : spk_mans) {
- // If we've already been signed by this spk_man, skip it
- if (visited_spk_mans.count(spk_man->GetID()) > 0) {
- continue;
- }
-
- // Sign the tx.
- // spk_man->SignTransaction will return true if the transaction is complete,
- // so we can exit early and return true if that happens.
- if (spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
- return true;
- }
-
- // Add this spk_man to visited_spk_mans so we can skip it later
- visited_spk_mans.insert(spk_man->GetID());
- }
}
return false;
}
@@ -2505,52 +2503,10 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
}
// Fill in information from ScriptPubKeyMans
- // Because each ScriptPubKeyMan may be able to fill more than one input, we need to keep track of each ScriptPubKeyMan that has filled this psbt.
- // Each iteration, we may fill more inputs than the input that is specified in that iteration.
- // We assume that each input is filled by only one ScriptPubKeyMan
- std::set<uint256> visited_spk_mans;
- for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
- const CTxIn& txin = psbtx.tx->vin[i];
- PSBTInput& input = psbtx.inputs.at(i);
-
- if (PSBTInputSigned(input)) {
- continue;
- }
-
- // Get the scriptPubKey to know which ScriptPubKeyMan to use
- CScript script;
- if (!input.witness_utxo.IsNull()) {
- script = input.witness_utxo.scriptPubKey;
- } else if (input.non_witness_utxo) {
- if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
- return TransactionError::MISSING_INPUTS;
- }
- script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey;
- } else {
- // There's no UTXO so we can just skip this now
- continue;
- }
- SignatureData sigdata;
- input.FillSignatureData(sigdata);
- std::set<ScriptPubKeyMan*> spk_mans = GetScriptPubKeyMans(script, sigdata);
- if (spk_mans.size() == 0) {
- continue;
- }
-
- for (auto& spk_man : spk_mans) {
- // If we've already been signed by this spk_man, skip it
- if (visited_spk_mans.count(spk_man->GetID()) > 0) {
- continue;
- }
-
- // Fill in the information from the spk_man
- TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs);
- if (res != TransactionError::OK) {
- return res;
- }
-
- // Add this spk_man to visited_spk_mans so we can skip it later
- visited_spk_mans.insert(spk_man->GetID());
+ for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
+ TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs);
+ if (res != TransactionError::OK) {
+ return res;
}
}
@@ -3278,6 +3234,8 @@ bool CWallet::GetNewDestination(const OutputType type, const std::string label,
if (spk_man) {
spk_man->TopUp();
result = spk_man->GetNewDestination(type, dest, error);
+ } else {
+ error = strprintf("Error: No %s addresses available.", FormatOutputType(type));
}
if (result) {
SetAddressBook(dest, label, "receive");
@@ -3834,15 +3792,23 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
walletInstance->SetWalletFlags(wallet_creation_flags, false);
- // Always create LegacyScriptPubKeyMan for now
- walletInstance->SetupLegacyScriptPubKeyMan();
+ // Only create LegacyScriptPubKeyMan when not descriptor wallet
+ if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ walletInstance->SetupLegacyScriptPubKeyMan();
+ }
if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
LOCK(walletInstance->cs_wallet);
- for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
- if (!spk_man->SetupGeneration()) {
- error = _("Unable to generate initial keys").translated;
- return nullptr;
+ if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ walletInstance->SetupDescriptorScriptPubKeyMans();
+ // SetupDescriptorScriptPubKeyMans already calls SetupGeneration for us so we don't need to call SetupGeneration separately
+ } else {
+ // Legacy wallets need SetupGeneration here.
+ for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
+ if (!spk_man->SetupGeneration()) {
+ error = _("Unable to generate initial keys").translated;
+ return nullptr;
+ }
}
}
}
@@ -4353,6 +4319,9 @@ std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& scri
LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const
{
+ if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ return nullptr;
+ }
// Legacy wallets only have one ScriptPubKeyMan which is a LegacyScriptPubKeyMan.
// Everything in m_internal_spk_managers and m_external_spk_managers point to the same legacyScriptPubKeyMan.
auto it = m_internal_spk_managers.find(OutputType::LEGACY);
@@ -4368,7 +4337,7 @@ LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan()
void CWallet::SetupLegacyScriptPubKeyMan()
{
- if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty()) {
+ if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
return;
}
@@ -4397,3 +4366,153 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
spk_man->NotifyCanGetAddressesChanged.connect(NotifyCanGetAddressesChanged);
}
}
+
+void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
+{
+ auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
+ m_spk_managers[id] = std::move(spk_manager);
+}
+
+void CWallet::SetupDescriptorScriptPubKeyMans()
+{
+ AssertLockHeld(cs_wallet);
+
+ // Make a seed
+ CKey seed_key;
+ seed_key.MakeNewKey(true);
+ CPubKey seed = seed_key.GetPubKey();
+ assert(seed_key.VerifyPubKey(seed));
+
+ // Get the extended key
+ CExtKey master_key;
+ master_key.SetSeed(seed_key.begin(), seed_key.size());
+
+ for (bool internal : {false, true}) {
+ for (OutputType t : OUTPUT_TYPES) {
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, t, internal));
+ if (IsCrypted()) {
+ if (IsLocked()) {
+ throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
+ }
+ if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
+ throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
+ }
+ }
+ spk_manager->SetupDescriptorGeneration(master_key);
+ uint256 id = spk_manager->GetID();
+ m_spk_managers[id] = std::move(spk_manager);
+ SetActiveScriptPubKeyMan(id, t, internal);
+ }
+ }
+}
+
+void CWallet::SetActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal, bool memonly)
+{
+ WalletLogPrintf("Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal));
+ auto& spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers;
+ auto spk_man = m_spk_managers.at(id).get();
+ spk_man->SetType(type, internal);
+ spk_mans[type] = spk_man;
+
+ if (!memonly) {
+ WalletBatch batch(*database);
+ if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id, internal)) {
+ throw std::runtime_error(std::string(__func__) + ": writing active ScriptPubKeyMan id failed");
+ }
+ }
+ NotifyCanGetAddressesChanged();
+}
+
+bool CWallet::IsLegacy() const
+{
+ if (m_internal_spk_managers.count(OutputType::LEGACY) == 0) {
+ return false;
+ }
+ auto spk_man = dynamic_cast<LegacyScriptPubKeyMan*>(m_internal_spk_managers.at(OutputType::LEGACY));
+ return spk_man != nullptr;
+}
+
+DescriptorScriptPubKeyMan* CWallet::GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const
+{
+ for (auto& spk_man_pair : m_spk_managers) {
+ // Try to downcast to DescriptorScriptPubKeyMan then check if the descriptors match
+ DescriptorScriptPubKeyMan* spk_manager = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man_pair.second.get());
+ if (spk_manager != nullptr && spk_manager->HasWalletDescriptor(desc)) {
+ return spk_manager;
+ }
+ }
+
+ return nullptr;
+}
+
+ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label)
+{
+ if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ WalletLogPrintf("Cannot add WalletDescriptor to a non-descriptor wallet\n");
+ return nullptr;
+ }
+
+ LOCK(cs_wallet);
+ auto new_spk_man = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
+
+ // If we already have this descriptor, remove it from the maps but add the existing cache to desc
+ auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
+ if (old_spk_man) {
+ WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString());
+
+ {
+ LOCK(old_spk_man->cs_desc_man);
+ new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
+ }
+
+ // Remove from maps of active spkMans
+ auto old_spk_man_id = old_spk_man->GetID();
+ for (bool internal : {false, true}) {
+ for (OutputType t : OUTPUT_TYPES) {
+ auto active_spk_man = GetScriptPubKeyMan(t, internal);
+ if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) {
+ if (internal) {
+ m_internal_spk_managers.erase(t);
+ } else {
+ m_external_spk_managers.erase(t);
+ }
+ break;
+ }
+ }
+ }
+ m_spk_managers.erase(old_spk_man_id);
+ }
+
+ // Add the private keys to the descriptor
+ for (const auto& entry : signing_provider.keys) {
+ const CKey& key = entry.second;
+ new_spk_man->AddDescriptorKey(key, key.GetPubKey());
+ }
+
+ // Top up key pool, the manager will generate new scriptPubKeys internally
+ new_spk_man->TopUp();
+
+ // Apply the label if necessary
+ // Note: we disable labels for ranged descriptors
+ if (!desc.descriptor->IsRange()) {
+ auto script_pub_keys = new_spk_man->GetScriptPubKeys();
+ if (script_pub_keys.empty()) {
+ WalletLogPrintf("Could not generate scriptPubKeys (cache is empty)\n");
+ return nullptr;
+ }
+
+ CTxDestination dest;
+ if (ExtractDestination(script_pub_keys.at(0), dest)) {
+ SetAddressBook(dest, label, "receive");
+ }
+ }
+
+ // Save the descriptor to memory
+ auto ret = new_spk_man.get();
+ m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
+
+ // Save the descriptor to DB
+ ret->WriteDescriptor();
+
+ return ret;
+}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 176c483572..24c78fceb3 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -111,7 +111,8 @@ static constexpr uint64_t KNOWN_WALLET_FLAGS =
WALLET_FLAG_AVOID_REUSE
| WALLET_FLAG_BLANK_WALLET
| WALLET_FLAG_KEY_ORIGIN_METADATA
- | WALLET_FLAG_DISABLE_PRIVATE_KEYS;
+ | WALLET_FLAG_DISABLE_PRIVATE_KEYS
+ | WALLET_FLAG_DESCRIPTORS;
static constexpr uint64_t MUTABLE_WALLET_FLAGS =
WALLET_FLAG_AVOID_REUSE;
@@ -121,6 +122,7 @@ static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{
{"blank", WALLET_FLAG_BLANK_WALLET},
{"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA},
{"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS},
+ {"descriptor_wallet", WALLET_FLAG_DESCRIPTORS},
};
extern const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS;
@@ -1163,6 +1165,9 @@ public:
returns false if unknown, non-tolerable flags are present */
bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly);
+ /** Determine if we are a legacy wallet */
+ bool IsLegacy() const;
+
/** Returns a bracketed wallet name for displaying in logs, will return [default wallet] if the wallet has no name */
const std::string GetDisplayName() const override {
std::string wallet_name = GetName().length() == 0 ? "default wallet" : GetName();
@@ -1232,6 +1237,25 @@ public:
//! Connect the signals from ScriptPubKeyMans to the signals in CWallet
void ConnectScriptPubKeyManNotifiers();
+
+ //! Instantiate a descriptor ScriptPubKeyMan from the WalletDescriptor and load it
+ void LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc);
+
+ //! Sets the active ScriptPubKeyMan for the specified type and internal
+ //! @param[in] id The unique id for the ScriptPubKeyMan
+ //! @param[in] type The OutputType this ScriptPubKeyMan provides addresses for
+ //! @param[in] internal Whether this ScriptPubKeyMan provides change addresses
+ //! @param[in] memonly Whether to record this update to the database. Set to true for wallet loading, normally false when actually updating the wallet.
+ void SetActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal, bool memonly = false);
+
+ //! Create new DescriptorScriptPubKeyMans and add them to the wallet
+ void SetupDescriptorScriptPubKeyMans();
+
+ //! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is already in the wallet
+ DescriptorScriptPubKeyMan* GetDescriptorScriptPubKeyMan(const WalletDescriptor& desc) const;
+
+ //! Add a descriptor to the wallet, return a ScriptPubKeyMan & associated output type
+ ScriptPubKeyMan* AddWalletDescriptor(WalletDescriptor& desc, const FlatSigningProvider& signing_provider, const std::string& label);
};
/**
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index f457db0a56..79316ca3e7 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -21,6 +21,8 @@
namespace DBKeys {
const std::string ACENTRY{"acentry"};
+const std::string ACTIVEEXTERNALSPK{"activeexternalspk"};
+const std::string ACTIVEINTERNALSPK{"activeinternalspk"};
const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"};
const std::string BESTBLOCK{"bestblock"};
const std::string CRYPTED_KEY{"ckey"};
@@ -41,6 +43,10 @@ const std::string PURPOSE{"purpose"};
const std::string SETTINGS{"settings"};
const std::string TX{"tx"};
const std::string VERSION{"version"};
+const std::string WALLETDESCRIPTOR{"walletdescriptor"};
+const std::string WALLETDESCRIPTORCACHE{"walletdescriptorcache"};
+const std::string WALLETDESCRIPTORCKEY{"walletdescriptorckey"};
+const std::string WALLETDESCRIPTORKEY{"walletdescriptorkey"};
const std::string WATCHMETA{"watchmeta"};
const std::string WATCHS{"watchs"};
} // namespace DBKeys
@@ -179,6 +185,51 @@ bool WalletBatch::WriteMinVersion(int nVersion)
return WriteIC(DBKeys::MINVERSION, nVersion);
}
+bool WalletBatch::WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bool internal)
+{
+ std::string key = internal ? DBKeys::ACTIVEINTERNALSPK : DBKeys::ACTIVEEXTERNALSPK;
+ return WriteIC(make_pair(key, type), id);
+}
+
+bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey)
+{
+ // hash pubkey/privkey to accelerate wallet load
+ std::vector<unsigned char> key;
+ key.reserve(pubkey.size() + privkey.size());
+ key.insert(key.end(), pubkey.begin(), pubkey.end());
+ key.insert(key.end(), privkey.begin(), privkey.end());
+
+ return WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey)), std::make_pair(privkey, Hash(key.begin(), key.end())), false);
+}
+
+bool WalletBatch::WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector<unsigned char>& secret)
+{
+ if (!WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORCKEY, std::make_pair(desc_id, pubkey)), secret, false)) {
+ return false;
+ }
+ EraseIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey)));
+ return true;
+}
+
+bool WalletBatch::WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor)
+{
+ return WriteIC(make_pair(DBKeys::WALLETDESCRIPTOR, desc_id), descriptor);
+}
+
+bool WalletBatch::WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index)
+{
+ std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
+ xpub.Encode(ser_xpub.data());
+ return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), std::make_pair(key_exp_index, der_index)), ser_xpub);
+}
+
+bool WalletBatch::WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index)
+{
+ std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
+ xpub.Encode(ser_xpub.data());
+ return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), key_exp_index), ser_xpub);
+}
+
class CWalletScanState {
public:
unsigned int nKeys{0};
@@ -189,6 +240,11 @@ public:
bool fIsEncrypted{false};
bool fAnyUnordered{false};
std::vector<uint256> vWalletUpgrade;
+ std::map<OutputType, uint256> m_active_external_spks;
+ std::map<OutputType, uint256> m_active_internal_spks;
+ std::map<uint256, DescriptorCache> m_descriptor_caches;
+ std::map<std::pair<uint256, CKeyID>, CKey> m_descriptor_keys;
+ std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys;
CWalletScanState() {
}
@@ -404,6 +460,108 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
} else if (strType == DBKeys::OLD_KEY) {
strErr = "Found unsupported 'wkey' record, try loading with version 0.18";
return false;
+ } else if (strType == DBKeys::ACTIVEEXTERNALSPK || strType == DBKeys::ACTIVEINTERNALSPK) {
+ uint8_t type;
+ ssKey >> type;
+ uint256 id;
+ ssValue >> id;
+
+ bool internal = strType == DBKeys::ACTIVEINTERNALSPK;
+ auto& spk_mans = internal ? wss.m_active_internal_spks : wss.m_active_external_spks;
+ if (spk_mans.count(static_cast<OutputType>(type)) > 0) {
+ strErr = "Multiple ScriptPubKeyMans specified for a single type";
+ return false;
+ }
+ spk_mans[static_cast<OutputType>(type)] = id;
+ } else if (strType == DBKeys::WALLETDESCRIPTOR) {
+ uint256 id;
+ ssKey >> id;
+ WalletDescriptor desc;
+ ssValue >> desc;
+ if (wss.m_descriptor_caches.count(id) == 0) {
+ wss.m_descriptor_caches[id] = DescriptorCache();
+ }
+ pwallet->LoadDescriptorScriptPubKeyMan(id, desc);
+ } else if (strType == DBKeys::WALLETDESCRIPTORCACHE) {
+ bool parent = true;
+ uint256 desc_id;
+ uint32_t key_exp_index;
+ uint32_t der_index;
+ ssKey >> desc_id;
+ ssKey >> key_exp_index;
+
+ // if the der_index exists, it's a derived xpub
+ try
+ {
+ ssKey >> der_index;
+ parent = false;
+ }
+ catch (...) {}
+
+ std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);
+ ssValue >> ser_xpub;
+ CExtPubKey xpub;
+ xpub.Decode(ser_xpub.data());
+ if (wss.m_descriptor_caches.count(desc_id)) {
+ wss.m_descriptor_caches[desc_id] = DescriptorCache();
+ }
+ if (parent) {
+ wss.m_descriptor_caches[desc_id].CacheParentExtPubKey(key_exp_index, xpub);
+ } else {
+ wss.m_descriptor_caches[desc_id].CacheDerivedExtPubKey(key_exp_index, der_index, xpub);
+ }
+ } else if (strType == DBKeys::WALLETDESCRIPTORKEY) {
+ uint256 desc_id;
+ CPubKey pubkey;
+ ssKey >> desc_id;
+ ssKey >> pubkey;
+ if (!pubkey.IsValid())
+ {
+ strErr = "Error reading wallet database: CPubKey corrupt";
+ return false;
+ }
+ CKey key;
+ CPrivKey pkey;
+ uint256 hash;
+
+ wss.nKeys++;
+ ssValue >> pkey;
+ ssValue >> hash;
+
+ // hash pubkey/privkey to accelerate wallet load
+ std::vector<unsigned char> to_hash;
+ to_hash.reserve(pubkey.size() + pkey.size());
+ to_hash.insert(to_hash.end(), pubkey.begin(), pubkey.end());
+ to_hash.insert(to_hash.end(), pkey.begin(), pkey.end());
+
+ if (Hash(to_hash.begin(), to_hash.end()) != hash)
+ {
+ strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt";
+ return false;
+ }
+
+ if (!key.Load(pkey, pubkey, true))
+ {
+ strErr = "Error reading wallet database: CPrivKey corrupt";
+ return false;
+ }
+ wss.m_descriptor_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), key));
+ } else if (strType == DBKeys::WALLETDESCRIPTORCKEY) {
+ uint256 desc_id;
+ CPubKey pubkey;
+ ssKey >> desc_id;
+ ssKey >> pubkey;
+ if (!pubkey.IsValid())
+ {
+ strErr = "Error reading wallet database: CPubKey corrupt";
+ return false;
+ }
+ std::vector<unsigned char> privkey;
+ ssValue >> privkey;
+ wss.nCKeys++;
+
+ wss.m_descriptor_crypt_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), std::make_pair(pubkey, privkey)));
+ wss.fIsEncrypted = true;
} else if (strType != DBKeys::BESTBLOCK && strType != DBKeys::BESTBLOCK_NOMERKLE &&
strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY &&
strType != DBKeys::VERSION && strType != DBKeys::SETTINGS) {
@@ -497,6 +655,31 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
result = DBErrors::CORRUPT;
}
+ // Set the active ScriptPubKeyMans
+ for (auto spk_man_pair : wss.m_active_external_spks) {
+ pwallet->SetActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ false, /* memonly */ true);
+ }
+ for (auto spk_man_pair : wss.m_active_internal_spks) {
+ pwallet->SetActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ true, /* memonly */ true);
+ }
+
+ // Set the descriptor caches
+ for (auto desc_cache_pair : wss.m_descriptor_caches) {
+ auto spk_man = pwallet->GetScriptPubKeyMan(desc_cache_pair.first);
+ assert(spk_man);
+ ((DescriptorScriptPubKeyMan*)spk_man)->SetCache(desc_cache_pair.second);
+ }
+
+ // Set the descriptor keys
+ for (auto desc_key_pair : wss.m_descriptor_keys) {
+ auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first);
+ ((DescriptorScriptPubKeyMan*)spk_man)->AddKey(desc_key_pair.first.second, desc_key_pair.second);
+ }
+ for (auto desc_key_pair : wss.m_descriptor_crypt_keys) {
+ auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first);
+ ((DescriptorScriptPubKeyMan*)spk_man)->AddCryptedKey(desc_key_pair.first.second, desc_key_pair.second.first, desc_key_pair.second.second);
+ }
+
if (fNoncriticalErrors && result == DBErrors::LOAD_OK)
result = DBErrors::NONCRITICAL_ERROR;
@@ -516,7 +699,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records);
// nTimeFirstKey is only reliable if all keys have metadata
- if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) {
+ if (pwallet->IsLegacy() && (wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) {
auto spk_man = pwallet->GetOrCreateLegacyScriptPubKeyMan();
if (spk_man) {
LOCK(spk_man->cs_KeyStore);
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index a436f60ab3..2701481c58 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -9,6 +9,7 @@
#include <amount.h>
#include <script/sign.h>
#include <wallet/db.h>
+#include <wallet/walletutil.h>
#include <key.h>
#include <stdint.h>
@@ -54,6 +55,8 @@ enum class DBErrors
namespace DBKeys {
extern const std::string ACENTRY;
+extern const std::string ACTIVEEXTERNALSPK;
+extern const std::string ACTIVEINTERNALSPK;
extern const std::string BESTBLOCK;
extern const std::string BESTBLOCK_NOMERKLE;
extern const std::string CRYPTED_KEY;
@@ -74,6 +77,9 @@ extern const std::string PURPOSE;
extern const std::string SETTINGS;
extern const std::string TX;
extern const std::string VERSION;
+extern const std::string WALLETDESCRIPTOR;
+extern const std::string WALLETDESCRIPTORCKEY;
+extern const std::string WALLETDESCRIPTORKEY;
extern const std::string WATCHMETA;
extern const std::string WATCHS;
} // namespace DBKeys
@@ -240,11 +246,19 @@ public:
bool WriteMinVersion(int nVersion);
+ bool WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey);
+ bool WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector<unsigned char>& secret);
+ bool WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor);
+ bool WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index);
+ bool WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index);
+
/// Write destination data key,value tuple to database
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
/// Erase destination data tuple from wallet database
bool EraseDestData(const std::string &address, const std::string &key);
+ bool WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bool internal);
+
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
index 04c2407a89..8bac0608a9 100644
--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -100,5 +100,10 @@ WalletLocation::WalletLocation(const std::string& name)
bool WalletLocation::Exists() const
{
- return fs::symlink_status(m_path).type() != fs::file_not_found;
+ fs::path path = m_path;
+ // For the default wallet, check specifically for the wallet.dat file
+ if (m_name.empty()) {
+ path = fs::absolute("wallet.dat", m_path);
+ }
+ return fs::symlink_status(path).type() != fs::file_not_found;
}
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index c91c9aca96..d7e07ed04c 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -6,6 +6,7 @@
#define BITCOIN_WALLET_WALLETUTIL_H
#include <fs.h>
+#include <script/descriptor.h>
#include <vector>
@@ -55,6 +56,9 @@ enum WalletFlags : uint64_t {
//! 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),
};
//! Get the path of the wallet directory.
@@ -83,4 +87,41 @@ public:
bool Exists() const;
};
+/** Descriptor with some wallet metadata */
+class WalletDescriptor
+{
+public:
+ std::shared_ptr<Descriptor> descriptor;
+ uint64_t creation_time;
+ int32_t range_start; // First item in range; start of range, inclusive, i.e. [range_start, range_end). This never changes.
+ int32_t range_end; // Item after the last; end of range, exclusive, i.e. [range_start, range_end). This will increment with each TopUp()
+ int32_t next_index; // Position of the next item to generate
+ DescriptorCache cache;
+
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ if (ser_action.ForRead()) {
+ std::string desc;
+ std::string error;
+ READWRITE(desc);
+ FlatSigningProvider keys;
+ descriptor = Parse(desc, keys, error, true);
+ if (!descriptor) {
+ throw std::ios_base::failure("Invalid descriptor: " + error);
+ }
+ } else {
+ READWRITE(descriptor->ToString());
+ }
+ READWRITE(creation_time);
+ READWRITE(next_index);
+ READWRITE(range_start);
+ READWRITE(range_end);
+ }
+
+ WalletDescriptor() {}
+ WalletDescriptor(std::shared_ptr<Descriptor> descriptor, uint64_t creation_time, int32_t range_start, int32_t range_end, int32_t next_index) : descriptor(descriptor), creation_time(creation_time), range_start(range_start), range_end(range_end), next_index(next_index) {}
+};
+
#endif // BITCOIN_WALLET_WALLETUTIL_H