Skip to content

Commit

Permalink
Merge #16528: Native Descriptor Wallets using DescriptorScriptPubKeyMan
Browse files Browse the repository at this point in the history
223588b Add a --descriptors option to various tests (Andrew Chow)
869f7ab tests: Add RPCOverloadWrapper which overloads some disabled RPCs (Andrew Chow)
cf06062 Correctly check for default wallet (Andrew Chow)
886e0d7 Implement CWallet::IsSpentKey for non-LegacySPKMans (Andrew Chow)
3c19fdd Return error when no ScriptPubKeyMan is available for specified type (Andrew Chow)
388ba94 Change wallet_encryption.py to use signmessage instead of dumpprivkey (Andrew Chow)
1346e14 Functional tests for descriptor wallets (Andrew Chow)
f193ea8 add importdescriptors RPC and tests for native descriptor wallets (Hugo Nguyen)
ce24a94 Add IsLegacy to CWallet so that the GUI knows whether to show watchonly (Andrew Chow)
1cb42b2 Generate new descriptors when encrypting (Andrew Chow)
82ae02b Be able to create new wallets with DescriptorScriptPubKeyMans as backing (Andrew Chow)
b713baa Implement GetMetadata in DescriptorScriptPubKeyMan (Andrew Chow)
8b9603b Change GetMetadata to use unique_ptr<CKeyMetadata> (Andrew Chow)
72a9540 Implement FillPSBT in DescriptorScriptPubKeyMan (Andrew Chow)
84b4978 Implement SignMessage for descriptor wallets (Andrew Chow)
bde7c9f Implement SignTransaction in DescriptorScriptPubKeyMan (Andrew Chow)
d50c8dd Implement GetSolvingProvider for DescriptorScriptPubKeyMan (Andrew Chow)
f1ca5fe Implement GetKeypoolOldestTime and only display it if greater than 0 (Andrew Chow)
586b57a Implement ReturnDestination in DescriptorScriptPubKeyMan (Andrew Chow)
f866957 Implement GetReservedDestination in DescriptorScriptPubKeyMan (Andrew Chow)
a775f7c Implement Unlock and Encrypt in DescriptorScriptPubKeyMan (Andrew Chow)
bfdd073 Implement GetNewDestination for DescriptorScriptPubKeyMan (Andrew Chow)
58c7651 Implement TopUp in DescriptorScriptPubKeyMan (Andrew Chow)
e014886 Implement SetupGeneration for DescriptorScriptPubKeyMan (Andrew Chow)
46dfb99 Implement writing descriptorkeys, descriptorckeys, and descriptors to wallet file (Andrew Chow)
4cb9b69 Implement several simple functions in DescriptorScriptPubKeyMan (Andrew Chow)
d1ec3e4 Add IsSingleType to Descriptors (Andrew Chow)
953feb3 Implement loading of keys for DescriptorScriptPubKeyMan (Andrew Chow)
2363e9f Load the descriptor cache from the wallet file (Andrew Chow)
46c46ae Implement GetID for DescriptorScriptPubKeyMan (Andrew Chow)
ec2f9e1 Implement IsHDEnabled in DescriptorScriptPubKeyMan (Andrew Chow)
741122d Implement MarkUnusedAddresses in DescriptorScriptPubKeyMan (Andrew Chow)
2db7ca7 Implement IsMine for DescriptorScriptPubKeyMan (Andrew Chow)
db7177a Add LoadDescriptorScriptPubKeyMan and SetActiveScriptPubKeyMan to CWallet (Andrew Chow)
78f8a92 Implement SetType in DescriptorScriptPubKeyMan (Andrew Chow)
834de03 Store WalletDescriptor in DescriptorScriptPubKeyMan (Andrew Chow)
d813266 Add a lock cs_desc_man for DescriptorScriptPubKeyMan (Andrew Chow)
3194a7f Introduce WalletDescriptor class (Andrew Chow)
6b13cd3 Create LegacyScriptPubKeyMan when not a descriptor wallet (Andrew Chow)
aeac157 Return nullptr from GetLegacyScriptPubKeyMan if descriptor wallet (Andrew Chow)
96accc7 Add WALLET_FLAG_DESCRIPTORS (Andrew Chow)
6b8119a Introduce DescriptorScriptPubKeyMan as a dummy class (Andrew Chow)
0662030 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 223588b (rebased, nits addressed)
  jonatack:
    Code review re-ACK 223588b.
  fjahr:
    re-ACK 223588b
  instagibbs:
    light re-ACK 223588b
  meshcollider:
    Code review ACK 223588b

Tree-SHA512: 59bc52aeddbb769ed5f420d5d240d8137847ac821b588eb616b34461253510c1717d6a70bab8765631738747336ae06f45ba39603ccd17f483843e5ed9a90986
  • Loading branch information
meshcollider committed Apr 27, 2020
2 parents ae32e5c + 223588b commit eef90c1
Show file tree
Hide file tree
Showing 40 changed files with 2,802 additions and 335 deletions.
1 change: 1 addition & 0 deletions src/interfaces/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ class WalletImpl : public Wallet
{
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));
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,9 @@ class Wallet
// 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;
Expand Down
5 changes: 5 additions & 0 deletions src/pubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/qt/createwalletdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ bool CreateWalletDialog::isMakeBlankWalletChecked() const
{
return ui->blank_wallet_checkbox->isChecked();
}

bool CreateWalletDialog::isDescriptorWalletChecked() const
{
return ui->descriptor_checkbox->isChecked();
}
1 change: 1 addition & 0 deletions src/qt/createwalletdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class CreateWalletDialog : public QDialog
bool isEncryptWalletChecked() const;
bool isDisablePrivateKeysChecked() const;
bool isMakeBlankWalletChecked() const;
bool isDescriptorWalletChecked() const;

private:
Ui::CreateWalletDialog *ui;
Expand Down
20 changes: 18 additions & 2 deletions src/qt/forms/createwalletdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>364</width>
<height>185</height>
<height>213</height>
</rect>
</property>
<property name="windowTitle">
Expand All @@ -17,7 +17,7 @@
<property name="geometry">
<rect>
<x>10</x>
<y>140</y>
<y>170</y>
<width>341</width>
<height>32</height>
</rect>
Expand Down Expand Up @@ -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>
Expand Down
25 changes: 16 additions & 9 deletions src/qt/overviewpage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
43 changes: 33 additions & 10 deletions src/qt/receivecoinsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/qt/walletcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down Expand Up @@ -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" },
};
Expand Down
9 changes: 9 additions & 0 deletions src/script/descriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ class AddressDescriptor final : public DescriptorImpl
default: return nullopt;
}
}
bool IsSingleType() const final { return true; }
};

/** A parsed raw(H) descriptor. */
Expand Down Expand Up @@ -602,6 +603,7 @@ class RawDescriptor final : public DescriptorImpl
default: return nullopt;
}
}
bool IsSingleType() const final { return true; }
};

/** A parsed pk(P) descriptor. */
Expand All @@ -611,6 +613,7 @@ class PKDescriptor final : public DescriptorImpl
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. */
Expand All @@ -626,6 +629,7 @@ class PKHDescriptor final : public DescriptorImpl
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. */
Expand All @@ -641,6 +645,7 @@ class WPKHDescriptor final : public DescriptorImpl
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. */
Expand All @@ -664,6 +669,7 @@ class ComboDescriptor final : public DescriptorImpl
}
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 */
Expand All @@ -683,6 +689,7 @@ class MultisigDescriptor final : public DescriptorImpl
}
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. */
Expand All @@ -699,6 +706,7 @@ class SHDescriptor final : public DescriptorImpl
if (m_subdescriptor_arg->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT;
return OutputType::LEGACY;
}
bool IsSingleType() const final { return true; }
};

/** A parsed wsh(...) descriptor. */
Expand All @@ -709,6 +717,7 @@ class WSHDescriptor final : public DescriptorImpl
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; }
};

////////////////////////////////////////////////////////////////////////////
Expand Down
3 changes: 3 additions & 0 deletions src/script/descriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading

0 comments on commit eef90c1

Please sign in to comment.