scriptpubkeyman_tests.cpp raw
1 // Copyright (c) 2020-2021 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <key.h>
6 #include <key_io.h>
7 #include <test/util/setup_common.h>
8 #include <script/solver.h>
9 #include <wallet/scriptpubkeyman.h>
10 #include <wallet/wallet.h>
11 #include <wallet/test/util.h>
12
13 #include <boost/test/unit_test.hpp>
14
15 namespace wallet {
16 BOOST_FIXTURE_TEST_SUITE(scriptpubkeyman_tests, BasicTestingSetup)
17
18 // Test LegacyScriptPubKeyMan::CanProvide behavior, making sure it returns true
19 // for recognized scripts even when keys may not be available for signing.
20 BOOST_AUTO_TEST_CASE(CanProvide)
21 {
22 // Set up wallet and keyman variables.
23 CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
24 LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan();
25
26 // Make a 1 of 2 multisig script
27 std::vector<CKey> keys(2);
28 std::vector<CPubKey> pubkeys;
29 for (CKey& key : keys) {
30 key.MakeNewKey(true);
31 pubkeys.emplace_back(key.GetPubKey());
32 }
33 CScript multisig_script = GetScriptForMultisig(1, pubkeys);
34 CScript p2sh_script = GetScriptForDestination(ScriptHash(multisig_script));
35 SignatureData data;
36
37 // Verify the p2sh(multisig) script is not recognized until the multisig
38 // script is added to the keystore to make it solvable
39 BOOST_CHECK(!keyman.CanProvide(p2sh_script, data));
40 keyman.AddCScript(multisig_script);
41 BOOST_CHECK(keyman.CanProvide(p2sh_script, data));
42 }
43
44 BOOST_AUTO_TEST_CASE(DescriptorScriptPubKeyManTests)
45 {
46 std::unique_ptr<interfaces::Chain>& chain = m_node.chain;
47
48 CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
49 auto key_scriptpath = GenerateRandomKey();
50
51 // Verify that a SigningProvider for a pubkey is only returned if its corresponding private key is available
52 auto key_internal = GenerateRandomKey();
53 std::string desc_str = "tr(" + EncodeSecret(key_internal) + ",pk(" + HexStr(key_scriptpath.GetPubKey()) + "))";
54 auto spk_man1 = dynamic_cast<DescriptorScriptPubKeyMan*>(CreateDescriptor(keystore, desc_str, true));
55 BOOST_CHECK(spk_man1 != nullptr);
56 auto signprov_keypath_spendable = spk_man1->GetSigningProvider(key_internal.GetPubKey());
57 BOOST_CHECK(signprov_keypath_spendable != nullptr);
58
59 desc_str = "tr(" + HexStr(XOnlyPubKey::NUMS_H) + ",pk(" + HexStr(key_scriptpath.GetPubKey()) + "))";
60 auto spk_man2 = dynamic_cast<DescriptorScriptPubKeyMan*>(CreateDescriptor(keystore, desc_str, true));
61 BOOST_CHECK(spk_man2 != nullptr);
62 auto signprov_keypath_nums_h = spk_man2->GetSigningProvider(XOnlyPubKey::NUMS_H.GetEvenCorrespondingCPubKey());
63 BOOST_CHECK(signprov_keypath_nums_h == nullptr);
64 }
65
66 static void legacy_IsKeyActive(const node::NodeContext& node, bool implicit_segwit)
67 {
68 const bool save_g_implicit_segwit{g_implicit_segwit};
69 g_implicit_segwit = implicit_segwit;
70 CWallet wallet(node.chain.get(), "", CreateMockableWalletDatabase());
71 {
72 LOCK(wallet.cs_wallet);
73 wallet.SetMinVersion(FEATURE_LATEST);
74 wallet.m_keypool_size = 10;
75 }
76 LegacyScriptPubKeyMan& spkm = *wallet.GetOrCreateLegacyScriptPubKeyMan();
77
78 // Start off empty
79 BOOST_CHECK(spkm.GetScriptPubKeys().empty());
80
81 // Generate 20 keypool keys (10 internal, 10 external)
82 {
83 LOCK(wallet.cs_wallet);
84 spkm.SetupGeneration();
85 }
86
87 // 4 scripts per keypool key (P2PK, P2PKH, P2WPKH, P2SH-P2WPKH)
88 // Plus 4 scripts for the seed key
89 // (If !implicit_segwit, P2WPKH and P2SH-P2WPKH are not generated.)
90 auto scripts1 = spkm.GetScriptPubKeys();
91 BOOST_CHECK_EQUAL(scripts1.size(), implicit_segwit ? 84 : 42);
92
93 // All keys are active
94 for (const CScript& script : scripts1) {
95 BOOST_CHECK(spkm.IsKeyActive(script));
96 }
97
98 // Requesting single from spkm should not deactivate key
99 CTxDestination dest1;
100 {
101 LOCK(wallet.cs_wallet);
102 auto result = spkm.GetNewDestination(OutputType::BECH32);
103 dest1 = result.value();
104 }
105 CScript script = GetScriptForDestination(dest1);
106 BOOST_CHECK(spkm.IsKeyActive(script));
107
108 // Key pool size did not change
109 // (If !implicit_segwit, the two segwit addresses are added back.)
110 auto scripts2 = spkm.GetScriptPubKeys();
111 BOOST_CHECK_EQUAL(scripts2.size(), implicit_segwit ? 84 : 44);
112
113 // Use key that is not the next key
114 // (i.e. address gap in wallet recovery)
115 {
116 LOCK(wallet.cs_wallet);
117 LOCK(spkm.cs_KeyStore);
118 auto keys = spkm.MarkReserveKeysAsUsed(5);
119 BOOST_CHECK_EQUAL(keys.size(), 4); // Because we already used one with GetNewDestination
120 }
121
122 // Key pool size did not change
123 auto scripts3 = spkm.GetScriptPubKeys();
124 BOOST_CHECK_EQUAL(scripts3.size(), implicit_segwit ? 84 : 44);
125
126 // All keys are still active
127 for (const CScript& script : scripts3) {
128 BOOST_CHECK(spkm.IsKeyActive(script));
129 }
130
131 // When user encrypts wallet for the first time,
132 // all existing keys are removed from active keypool
133 {
134 LOCK(wallet.cs_wallet);
135 // called by EncryptWallet()
136 spkm.SetupGeneration(true);
137 }
138
139 // 20 new keys were added
140 auto scripts4 = spkm.GetScriptPubKeys();
141 BOOST_CHECK_EQUAL(scripts4.size(), (implicit_segwit ? 84 : 43) * 2);
142
143 // All 10 original keys are now inactive
144 for (const CScript& script : scripts3) {
145 BOOST_CHECK(!spkm.IsKeyActive(script));
146 }
147 g_implicit_segwit = save_g_implicit_segwit;
148 }
149
150 BOOST_AUTO_TEST_CASE(Legacy_IsKeyActive)
151 {
152 legacy_IsKeyActive(m_node, /*implicit_segwit=*/true);
153 }
154
155 BOOST_AUTO_TEST_CASE(Legacy_IsKeyActive_no_implicit_segwit)
156 {
157 legacy_IsKeyActive(m_node, /*implicit_segwit=*/false);
158 }
159
160 BOOST_AUTO_TEST_CASE(Descriptor_IsKeyActive)
161 {
162 CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
163 {
164 LOCK(wallet.cs_wallet);
165 wallet.LoadMinVersion(FEATURE_LATEST);
166 wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
167 wallet.m_keypool_size = 10;
168 wallet.SetupDescriptorScriptPubKeyMans();
169 }
170 DescriptorScriptPubKeyMan* spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(wallet.GetScriptPubKeyMan(OutputType::BECH32, /*internal=*/false));
171
172 // Start off with 10 pre-generated keys, 1 script each
173 auto scripts1 = spkm->GetScriptPubKeys();
174 BOOST_CHECK_EQUAL(scripts1.size(), 10);
175
176 // All keys are active
177 for (const CScript& script : scripts1) {
178 BOOST_CHECK(spkm->IsKeyActive(script));
179 }
180
181 // Requesting single key from spkm should not deactivate key
182 auto dest1 = spkm->GetNewDestination(OutputType::BECH32);
183 CScript script = GetScriptForDestination(dest1.value());
184 BOOST_CHECK(spkm->IsKeyActive(script));
185
186 // Key pool size did not change
187 auto scripts2 = spkm->GetScriptPubKeys();
188 BOOST_CHECK_EQUAL(scripts2.size(), 10);
189
190 // Use key that is not the next key
191 // (i.e. address gap in wallet recovery)
192 {
193 LOCK(spkm->cs_desc_man);
194 WalletDescriptor descriptor = spkm->GetWalletDescriptor();
195 FlatSigningProvider provider;
196 std::vector<CScript> scripts3;
197 descriptor.descriptor->ExpandFromCache(/*pos=*/5, descriptor.cache, scripts3, provider);
198
199 BOOST_CHECK_EQUAL(scripts3.size(), 1);
200 spkm->MarkUnusedAddresses(scripts3.front());
201 }
202
203 // Key pool size increased to replace used keys
204 auto scripts4 = spkm->GetScriptPubKeys();
205 BOOST_CHECK_EQUAL(scripts4.size(), 16);
206
207 // All keys are still active
208 for (const CScript& script : scripts4) {
209 BOOST_CHECK(spkm->IsKeyActive(script));
210 }
211
212 // When user encrypts wallet for the first time,
213 // all existing keys are removed from active keypool
214 {
215 LOCK(wallet.cs_wallet);
216 // called by EncryptWallet()
217 wallet.SetupDescriptorScriptPubKeyMans();
218 }
219
220 // This SPKM is not affected
221 for (const CScript& script : scripts4) {
222 BOOST_CHECK(spkm->IsKeyActive(script));
223 }
224
225 // ...but at the wallet level all the keys from that SPKM are deactivated
226 int num_script_keys_not_found = 0;
227 for (const CScript& script : scripts4) {
228 if (!wallet.IsDestinationActive(WitnessV0ScriptHash(script))) {
229 ++num_script_keys_not_found;
230 }
231 }
232 BOOST_CHECK_EQUAL(num_script_keys_not_found, 16);
233 }
234
235 BOOST_AUTO_TEST_SUITE_END()
236 } // namespace wallet
237