Skip to content

Commit 2e41886

Browse files
committed
Merge branch 'opt_wallet_segwit2' into timebomb_knots
2 parents 1248d0d + 8fa6f07 commit 2e41886

15 files changed

+356
-26
lines changed

src/dummywallet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
4545
"-wallet=<path>",
4646
"-walletbroadcast",
4747
"-walletdir=<dir>",
48+
"-walletimplicitsegwit",
4849
"-walletnotify=<cmd>",
4950
"-walletrbf",
5051
"-dblogsize=<n>",

src/outputtype.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key)
7272
{
7373
PKHash keyid(key);
7474
CTxDestination p2pkh{keyid};
75-
if (key.IsCompressed()) {
75+
if (key.IsCompressed() && g_implicit_segwit) {
7676
CTxDestination segwit = WitnessV0KeyHash(keyid);
7777
CTxDestination p2sh = ScriptHash(GetScriptForDestination(segwit));
7878
return Vector(std::move(p2pkh), std::move(p2sh), std::move(segwit));

src/script/descriptor.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,8 +823,11 @@ class PKDescriptor final : public DescriptorImpl
823823
private:
824824
const bool m_xonly;
825825
protected:
826-
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override
826+
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override
827827
{
828+
CKeyID id = keys[0].GetID();
829+
out.pubkeys.emplace(id, keys[0]);
830+
828831
if (m_xonly) {
829832
CScript script = CScript() << ToByteVector(XOnlyPubKey(keys[0])) << OP_CHECKSIG;
830833
return Vector(std::move(script));

src/script/signingprovider.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#include <logging.h>
1111

12+
bool g_implicit_segwit = true;
13+
1214
const SigningProvider& DUMMY_SIGNING_PROVIDER = SigningProvider();
1315

1416
template<typename M, typename K, typename V>
@@ -102,7 +104,7 @@ void FillableSigningProvider::ImplicitlyLearnRelatedKeyScripts(const CPubKey& pu
102104
// "Implicitly" refers to fact that scripts are derived automatically from
103105
// existing keys, and are present in memory, even without being explicitly
104106
// loaded (e.g. from a file).
105-
if (pubkey.IsCompressed()) {
107+
if (pubkey.IsCompressed() && g_implicit_segwit) {
106108
CScript script = GetScriptForDestination(WitnessV0KeyHash(key_id));
107109
// This does not use AddCScript, as it may be overridden.
108110
CScriptID id(script);

src/script/signingprovider.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
#include <script/script.h>
1515
#include <sync.h>
1616

17+
static const bool DEFAULT_WALLET_IMPLICIT_SEGWIT = false;
18+
19+
extern bool g_implicit_segwit;
20+
1721
struct ShortestVectorFirstComparator
1822
{
1923
bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const

src/wallet/init.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <node/context.h>
1515
#include <node/interface_ui.h>
1616
#include <outputtype.h>
17+
#include <script/signingprovider.h>
1718
#include <univalue.h>
1819
#include <util/check.h>
1920
#include <util/moneystr.h>
@@ -76,6 +77,7 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
7677
argsman.AddArg("-wallet=<path>", "Specify wallet path to load at startup. Can be used multiple times to load multiple wallets. Path is to a directory containing wallet data and log files. If the path is not absolute, it is interpreted relative to <walletdir>. This only loads existing wallets and does not create new ones. For backwards compatibility this also accepts names of existing top-level data files in <walletdir>.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
7778
argsman.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
7879
argsman.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
80+
argsman.AddArg("-walletimplicitsegwit", strprintf("Support segwit when restoring wallet backups and importing keys (default: %u)", DEFAULT_WALLET_IMPLICIT_SEGWIT), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
7981
#if HAVE_SYSTEM
8082
argsman.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes. %s in cmd is replaced by TxID, %w is replaced by wallet name, %b is replaced by the hash of the block including the transaction (set to 'unconfirmed' if the transaction is not included) and %h is replaced by the block height (-1 if not included). %w is not currently implemented on windows. On systems where %w is supported, it should NOT be quoted because this would break shell escaping used to invoke the command.", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
8183
#endif
@@ -119,6 +121,13 @@ bool WalletInit::ParameterInteraction() const
119121
LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__);
120122
}
121123

124+
g_implicit_segwit = gArgs.GetBoolArg("-walletimplicitsegwit", DEFAULT_WALLET_IMPLICIT_SEGWIT);
125+
if (!g_implicit_segwit) {
126+
if (gArgs.SoftSetArg("-addresstype", "legacy")) {
127+
LogPrintf("%s: parameter interaction: -walletimplicitsegwit=%u -> setting -addresstype=legacy\n", __func__, g_implicit_segwit);
128+
}
129+
}
130+
122131
return true;
123132
}
124133

src/wallet/rpc/addresses.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ RPCHelpMan getaddressinfo()
531531
{RPCResult::Type::STR, "address", "The bitcoin address validated."},
532532
{RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded output script generated by the address."},
533533
{RPCResult::Type::BOOL, "ismine", "If the address is yours."},
534+
{RPCResult::Type::BOOL, "isactive", "If the key is in the active keypool (always equal to \"ismine\" in descriptor wallets)."},
534535
{RPCResult::Type::BOOL, "iswatchonly", "If the address is watchonly."},
535536
{RPCResult::Type::BOOL, "solvable", "If we know how to spend coins sent to this address, ignoring the possible lack of private keys."},
536537
{RPCResult::Type::STR, "desc", /*optional=*/true, "A descriptor for spending coins sent to this address (only when solvable)."},
@@ -601,6 +602,7 @@ RPCHelpMan getaddressinfo()
601602

602603
isminetype mine = pwallet->IsMine(dest);
603604
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
605+
ret.pushKV("isactive", pwallet->IsDestinationActive(dest));
604606

605607
if (provider) {
606608
auto inferred = InferDescriptor(scriptPubKey, *provider);

src/wallet/scriptpubkeyman.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,23 @@ std::vector<WalletDestination> LegacyScriptPubKeyMan::MarkUnusedAddresses(const
402402
return result;
403403
}
404404

405+
bool LegacyDataSPKM::IsKeyActive(const CScript& script) const
406+
{
407+
LOCK(cs_KeyStore);
408+
409+
if (!IsMine(script)) return false; // Not in the keystore at all
410+
411+
for (const auto& key_id : GetAffectedKeys(script, *this)) {
412+
const auto it = mapKeyMetadata.find(key_id);
413+
if (it == mapKeyMetadata.end()) return false; // This key must be really old
414+
415+
if (!it->second.hd_seed_id.IsNull() && it->second.hd_seed_id == m_hd_chain.seed_id) return true;
416+
}
417+
418+
// Imported or dumped for a new keypool
419+
return false;
420+
}
421+
405422
void LegacyScriptPubKeyMan::UpgradeKeyMetadata()
406423
{
407424
LOCK(cs_KeyStore);
@@ -1505,6 +1522,7 @@ void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType t
15051522

15061523
void LegacyScriptPubKeyMan::LearnAllRelatedScripts(const CPubKey& key)
15071524
{
1525+
if (!g_implicit_segwit) return;
15081526
// OutputType::P2SH_SEGWIT always adds all necessary scripts for all types.
15091527
LearnRelatedScripts(key, OutputType::P2SH_SEGWIT);
15101528
}

src/wallet/scriptpubkeyman.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ class ScriptPubKeyMan
205205
*/
206206
virtual std::vector<WalletDestination> MarkUnusedAddresses(const CScript& script) { return {}; }
207207

208+
/* Determines if address is derived from active key manager */
209+
virtual bool IsKeyActive(const CScript& script) const = 0;
210+
208211
/** Sets up the key generation stuff, i.e. generates new HD seeds and sets them as active.
209212
* Returns false if already setup or setup fails, true if setup is successful
210213
* Set force=true to make it re-setup if already setup, used for upgrades
@@ -313,6 +316,7 @@ class LegacyDataSPKM : public ScriptPubKeyMan, public FillableSigningProvider
313316

314317
// ScriptPubKeyMan overrides
315318
bool CheckDecryptionKey(const CKeyingMaterial& master_key) override;
319+
[[nodiscard]] bool IsKeyActive(const CScript& script) const override;
316320
std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;
317321
std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const override;
318322
uint256 GetID() const override { return uint256::ONE; }
@@ -650,6 +654,8 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
650654

651655
std::vector<WalletDestination> MarkUnusedAddresses(const CScript& script) override;
652656

657+
[[nodiscard]] bool IsKeyActive(const CScript& script) const override { return IsMine(script); }
658+
653659
bool IsHDEnabled() const override;
654660

655661
//! Setup descriptors based on the given CExtkey

src/wallet/test/scriptpubkeyman_tests.cpp

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,174 @@ BOOST_AUTO_TEST_CASE(CanProvide)
4040
BOOST_CHECK(keyman.CanProvide(p2sh_script, data));
4141
}
4242

43+
static void legacy_IsKeyActive(const node::NodeContext& node, bool implicit_segwit)
44+
{
45+
const bool save_g_implicit_segwit{g_implicit_segwit};
46+
g_implicit_segwit = implicit_segwit;
47+
CWallet wallet(node.chain.get(), "", CreateMockableWalletDatabase());
48+
{
49+
LOCK(wallet.cs_wallet);
50+
wallet.SetMinVersion(FEATURE_LATEST);
51+
wallet.m_keypool_size = 10;
52+
}
53+
LegacyScriptPubKeyMan& spkm = *wallet.GetOrCreateLegacyScriptPubKeyMan();
54+
55+
// Start off empty
56+
BOOST_CHECK(spkm.GetScriptPubKeys().empty());
57+
58+
// Generate 20 keypool keys (10 internal, 10 external)
59+
{
60+
LOCK(wallet.cs_wallet);
61+
spkm.SetupGeneration();
62+
}
63+
64+
// 4 scripts per keypool key (P2PK, P2PKH, P2WPKH, P2SH-P2WPKH)
65+
// Plus 4 scripts for the seed key
66+
// (If !implicit_segwit, P2WPKH and P2SH-P2WPKH are not generated.)
67+
auto scripts1 = spkm.GetScriptPubKeys();
68+
BOOST_CHECK_EQUAL(scripts1.size(), implicit_segwit ? 84 : 42);
69+
70+
// All keys are active
71+
for (const CScript& script : scripts1) {
72+
BOOST_CHECK(spkm.IsKeyActive(script));
73+
}
74+
75+
// Requesting single from spkm should not deactivate key
76+
CTxDestination dest1;
77+
{
78+
LOCK(wallet.cs_wallet);
79+
auto result = spkm.GetNewDestination(OutputType::BECH32);
80+
dest1 = result.value();
81+
}
82+
CScript script = GetScriptForDestination(dest1);
83+
BOOST_CHECK(spkm.IsKeyActive(script));
84+
85+
// Key pool size did not change
86+
// (If !implicit_segwit, the two segwit addresses are added back.)
87+
auto scripts2 = spkm.GetScriptPubKeys();
88+
BOOST_CHECK_EQUAL(scripts2.size(), implicit_segwit ? 84 : 44);
89+
90+
// Use key that is not the next key
91+
// (i.e. address gap in wallet recovery)
92+
{
93+
LOCK(wallet.cs_wallet);
94+
LOCK(spkm.cs_KeyStore);
95+
auto keys = spkm.MarkReserveKeysAsUsed(5);
96+
BOOST_CHECK_EQUAL(keys.size(), 4); // Because we already used one with GetNewDestination
97+
}
98+
99+
// Key pool size did not change
100+
auto scripts3 = spkm.GetScriptPubKeys();
101+
BOOST_CHECK_EQUAL(scripts3.size(), implicit_segwit ? 84 : 44);
102+
103+
// All keys are still active
104+
for (const CScript& script : scripts3) {
105+
BOOST_CHECK(spkm.IsKeyActive(script));
106+
}
107+
108+
// When user encrypts wallet for the first time,
109+
// all existing keys are removed from active keypool
110+
{
111+
LOCK(wallet.cs_wallet);
112+
// called by EncryptWallet()
113+
spkm.SetupGeneration(true);
114+
}
115+
116+
// 20 new keys were added
117+
auto scripts4 = spkm.GetScriptPubKeys();
118+
BOOST_CHECK_EQUAL(scripts4.size(), (implicit_segwit ? 84 : 43) * 2);
119+
120+
// All 10 original keys are now inactive
121+
for (const CScript& script : scripts3) {
122+
BOOST_CHECK(!spkm.IsKeyActive(script));
123+
}
124+
g_implicit_segwit = save_g_implicit_segwit;
125+
}
126+
127+
BOOST_AUTO_TEST_CASE(Legacy_IsKeyActive)
128+
{
129+
legacy_IsKeyActive(m_node, /*implicit_segwit=*/true);
130+
}
131+
132+
BOOST_AUTO_TEST_CASE(Legacy_IsKeyActive_no_implicit_segwit)
133+
{
134+
legacy_IsKeyActive(m_node, /*implicit_segwit=*/false);
135+
}
136+
137+
BOOST_AUTO_TEST_CASE(Descriptor_IsKeyActive)
138+
{
139+
CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
140+
{
141+
LOCK(wallet.cs_wallet);
142+
wallet.LoadMinVersion(FEATURE_LATEST);
143+
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
144+
wallet.m_keypool_size = 10;
145+
wallet.SetupDescriptorScriptPubKeyMans();
146+
}
147+
DescriptorScriptPubKeyMan* spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(wallet.GetScriptPubKeyMan(OutputType::BECH32, /*internal=*/false));
148+
149+
// Start off with 10 pre-generated keys, 1 script each
150+
auto scripts1 = spkm->GetScriptPubKeys();
151+
BOOST_CHECK_EQUAL(scripts1.size(), 10);
152+
153+
// All keys are active
154+
for (const CScript& script : scripts1) {
155+
BOOST_CHECK(spkm->IsKeyActive(script));
156+
}
157+
158+
// Requesting single key from spkm should not deactivate key
159+
auto dest1 = spkm->GetNewDestination(OutputType::BECH32);
160+
CScript script = GetScriptForDestination(dest1.value());
161+
BOOST_CHECK(spkm->IsKeyActive(script));
162+
163+
// Key pool size did not change
164+
auto scripts2 = spkm->GetScriptPubKeys();
165+
BOOST_CHECK_EQUAL(scripts2.size(), 10);
166+
167+
// Use key that is not the next key
168+
// (i.e. address gap in wallet recovery)
169+
{
170+
LOCK(spkm->cs_desc_man);
171+
WalletDescriptor descriptor = spkm->GetWalletDescriptor();
172+
FlatSigningProvider provider;
173+
std::vector<CScript> scripts3;
174+
descriptor.descriptor->ExpandFromCache(/*pos=*/5, descriptor.cache, scripts3, provider);
175+
176+
BOOST_CHECK_EQUAL(scripts3.size(), 1);
177+
spkm->MarkUnusedAddresses(scripts3.front());
178+
}
179+
180+
// Key pool size increased to replace used keys
181+
auto scripts4 = spkm->GetScriptPubKeys();
182+
BOOST_CHECK_EQUAL(scripts4.size(), 16);
183+
184+
// All keys are still active
185+
for (const CScript& script : scripts4) {
186+
BOOST_CHECK(spkm->IsKeyActive(script));
187+
}
188+
189+
// When user encrypts wallet for the first time,
190+
// all existing keys are removed from active keypool
191+
{
192+
LOCK(wallet.cs_wallet);
193+
// called by EncryptWallet()
194+
wallet.SetupDescriptorScriptPubKeyMans();
195+
}
196+
197+
// This SPKM is not affected
198+
for (const CScript& script : scripts4) {
199+
BOOST_CHECK(spkm->IsKeyActive(script));
200+
}
201+
202+
// ...but at the wallet level all the keys from that SPKM are deactivated
203+
int num_script_keys_not_found = 0;
204+
for (const CScript& script : scripts4) {
205+
if (!wallet.IsDestinationActive(WitnessV0ScriptHash(script))) {
206+
++num_script_keys_not_found;
207+
}
208+
}
209+
BOOST_CHECK_EQUAL(num_script_keys_not_found, 16);
210+
}
211+
43212
BOOST_AUTO_TEST_SUITE_END()
44213
} // namespace wallet

0 commit comments

Comments
 (0)