Line data Source code
1 : // Copyright (c) 2009-2021 The Bitcoin Core developers
2 : // Copyright (c) 2014-2025 The Dash Core developers
3 : // Distributed under the MIT software license, see the accompanying
4 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 :
6 : #include <chain.h>
7 : #include <chainparams.h>
8 : #include <clientversion.h>
9 : #include <core_io.h>
10 : #include <fs.h>
11 : #include <interfaces/chain.h>
12 : #include <key_io.h>
13 : #include <merkleblock.h>
14 : #include <rpc/server.h>
15 : #include <rpc/util.h>
16 : #include <script/descriptor.h>
17 : #include <script/script.h>
18 : #include <script/standard.h>
19 : #include <sync.h>
20 : #include <util/bip32.h>
21 : #include <util/system.h>
22 : #include <util/time.h>
23 : #include <util/translation.h>
24 : #include <validation.h>
25 : #include <wallet/wallet.h>
26 : #include <wallet/rpc/util.h>
27 :
28 : #include <cstdint>
29 : #include <fstream>
30 : #include <tuple>
31 : #include <string>
32 :
33 : #include <univalue.h>
34 :
35 : using interfaces::FoundBlock;
36 :
37 : namespace wallet {
38 180 : std::string static EncodeDumpString(const std::string &str) {
39 180 : std::stringstream ret;
40 297 : for (const unsigned char c : str) {
41 117 : if (c <= 32 || c >= 128 || c == '%') {
42 0 : ret << '%' << HexStr({&c, 1});
43 0 : } else {
44 117 : ret << c;
45 : }
46 : }
47 180 : return ret.str();
48 180 : }
49 :
50 114 : static std::string DecodeDumpString(const std::string &str) {
51 114 : std::stringstream ret;
52 186 : for (unsigned int pos = 0; pos < str.length(); pos++) {
53 72 : unsigned char c = str[pos];
54 72 : if (c == '%' && pos+2 < str.length()) {
55 0 : c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) |
56 0 : ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15));
57 0 : pos += 2;
58 0 : }
59 72 : ret << c;
60 72 : }
61 114 : return ret.str();
62 114 : }
63 :
64 3644 : static bool GetWalletAddressesForKey(const LegacyScriptPubKeyMan* spk_man, const CWallet& wallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
65 : {
66 3644 : const PKHash& dest = PKHash(keyid);
67 3644 : strAddr = EncodeDestination(dest);
68 3644 : if (const auto* address_book_entry = wallet.FindAddressBookEntry(dest); address_book_entry != nullptr) {
69 180 : strLabel = EncodeDumpString(address_book_entry->GetLabel());
70 180 : return true;
71 : }
72 3464 : return false;
73 3644 : }
74 :
75 : static const int64_t TIMESTAMP_MIN = 0;
76 :
77 568 : static void RescanWallet(CWallet& wallet, const WalletRescanReserver& reserver, int64_t time_begin = TIMESTAMP_MIN, bool update = true)
78 : {
79 568 : int64_t scanned_time = wallet.RescanFromTime(time_begin, reserver, update);
80 568 : if (wallet.IsAbortingRescan()) {
81 0 : throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
82 568 : } else if (scanned_time > time_begin) {
83 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Rescan was unable to fully rescan the blockchain. Some transactions may be missing.");
84 : }
85 568 : }
86 :
87 3392 : RPCHelpMan importprivkey()
88 : {
89 6784 : return RPCHelpMan{"importprivkey",
90 3392 : "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n"
91 : "Hint: use importmulti to import more than one private key.\n"
92 : "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
93 : "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
94 : "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
95 : "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
96 : "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"combo(X)\" for descriptor wallets.\n",
97 13568 : {
98 3392 : {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"},
99 3392 : {"label", RPCArg::Type::STR, RPCArg::DefaultHint{"current label if address exists, otherwise \"\""}, "An optional label"},
100 3392 : {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
101 : },
102 3392 : RPCResult{RPCResult::Type::NONE, "", ""},
103 3392 : RPCExamples{
104 : "\nDump a private key\n"
105 3392 : + HelpExampleCli("dumpprivkey", "\"myaddress\"") +
106 : "\nImport the private key with rescan\n"
107 3392 : + HelpExampleCli("importprivkey", "\"mykey\"") +
108 : "\nImport using a label and without rescan\n"
109 3392 : + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
110 : "\nImport using default blank label and without rescan\n"
111 3392 : + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") +
112 : "\nAs a JSON-RPC call\n"
113 3392 : + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
114 : },
115 3888 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
116 : {
117 496 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
118 496 : if (!pwallet) return UniValue::VNULL;
119 :
120 496 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
121 4 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
122 : }
123 :
124 492 : EnsureLegacyScriptPubKeyMan(*pwallet, true);
125 :
126 490 : WalletBatch batch(pwallet->GetDatabase());
127 490 : WalletRescanReserver reserver(*pwallet);
128 490 : bool fRescan = true;
129 : {
130 490 : LOCK(pwallet->cs_wallet);
131 :
132 490 : EnsureWalletIsUnlocked(*pwallet);
133 :
134 490 : std::string strSecret = request.params[0].get_str();
135 490 : std::string strLabel;
136 490 : if (!request.params[1].isNull())
137 427 : strLabel = request.params[1].get_str();
138 :
139 : // Whether to perform rescan after import
140 490 : if (!request.params[2].isNull())
141 420 : fRescan = request.params[2].get_bool();
142 :
143 490 : if (fRescan && pwallet->chain().havePruned()) {
144 : // Exit early and print an error.
145 : // If a block is pruned after this check, we will import the key(s),
146 : // but fail the rescan with a generic error.
147 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
148 : }
149 :
150 490 : if (fRescan && !reserver.reserve()) {
151 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
152 : }
153 :
154 490 : CKey key = DecodeSecret(strSecret);
155 490 : if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
156 :
157 487 : CPubKey pubkey = key.GetPubKey();
158 487 : CHECK_NONFATAL(key.VerifyPubKey(pubkey));
159 487 : PKHash vchAddress = PKHash(pubkey);
160 : {
161 487 : pwallet->MarkDirty();
162 :
163 487 : if (!request.params[1].isNull() || !pwallet->FindAddressBookEntry(vchAddress)) {
164 478 : pwallet->SetAddressBook(vchAddress, strLabel, "receive");
165 478 : }
166 :
167 : // Use timestamp of 1 to scan the whole chain
168 487 : if (!pwallet->ImportPrivKeys({{ToKeyID(vchAddress), key}}, 1)) {
169 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
170 : }
171 : }
172 490 : }
173 487 : if (fRescan) {
174 475 : RescanWallet(*pwallet, reserver);
175 475 : }
176 487 : return UniValue::VNULL;
177 503 : },
178 : };
179 0 : }
180 :
181 2900 : RPCHelpMan abortrescan()
182 : {
183 5800 : return RPCHelpMan{"abortrescan",
184 2900 : "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n",
185 2900 : {},
186 2900 : RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
187 2900 : RPCExamples{
188 : "\nImport a private key\n"
189 2900 : + HelpExampleCli("importprivkey", "\"mykey\"") +
190 : "\nAbort the running wallet rescan\n"
191 2900 : + HelpExampleCli("abortrescan", "") +
192 : "\nAs a JSON-RPC call\n"
193 2900 : + HelpExampleRpc("abortrescan", "")
194 : },
195 2904 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
196 : {
197 4 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
198 4 : if (!pwallet) return UniValue::VNULL;
199 :
200 4 : if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
201 0 : pwallet->AbortRescan();
202 0 : return true;
203 4 : },
204 : };
205 0 : }
206 :
207 2989 : RPCHelpMan importaddress()
208 : {
209 5978 : return RPCHelpMan{"importaddress",
210 2989 : "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
211 : "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
212 : "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
213 : "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
214 : "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
215 : "If you have the full public key, you should call importpubkey instead of this.\n"
216 : "Hint: use importmulti to import more than one address.\n"
217 : "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
218 : "as change, and not show up in many RPCs.\n"
219 : "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"addr(X)\" for descriptor wallets.\n",
220 14945 : {
221 2989 : {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Dash address (or hex-encoded script)"},
222 2989 : {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"},
223 2989 : {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
224 2989 : {"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Add the P2SH version of the script as well"},
225 : },
226 2989 : RPCResult{RPCResult::Type::NONE, "", ""},
227 2989 : RPCExamples{
228 : "\nImport an address with rescan\n"
229 2989 : + HelpExampleCli("importaddress", "\"myaddress\"") +
230 : "\nImport using a label without rescan\n"
231 2989 : + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") +
232 : "\nAs a JSON-RPC call\n"
233 2989 : + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false")
234 : },
235 3082 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
236 : {
237 93 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
238 93 : if (!pwallet) return UniValue::VNULL;
239 :
240 93 : EnsureLegacyScriptPubKeyMan(*pwallet, true);
241 :
242 91 : std::string strLabel;
243 91 : if (!request.params[1].isNull())
244 42 : strLabel = request.params[1].get_str();
245 :
246 : // Whether to perform rescan after import
247 91 : bool fRescan = true;
248 91 : if (!request.params[2].isNull())
249 44 : fRescan = request.params[2].get_bool();
250 :
251 91 : if (fRescan && pwallet->chain().havePruned()) {
252 : // Exit early and print an error.
253 : // If a block is pruned after this check, we will import the key(s),
254 : // but fail the rescan with a generic error.
255 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
256 : }
257 :
258 91 : WalletRescanReserver reserver(*pwallet);
259 91 : if (fRescan && !reserver.reserve()) {
260 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
261 : }
262 :
263 : // Whether to import a p2sh version, too
264 91 : bool fP2SH = false;
265 91 : if (!request.params[3].isNull())
266 9 : fP2SH = request.params[3].get_bool();
267 :
268 : {
269 91 : LOCK(pwallet->cs_wallet);
270 :
271 91 : CTxDestination dest = DecodeDestination(request.params[0].get_str());
272 91 : if (IsValidDestination(dest)) {
273 76 : if (fP2SH) {
274 3 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
275 : }
276 :
277 73 : pwallet->MarkDirty();
278 :
279 73 : pwallet->ImportScriptPubKeys(strLabel, {GetScriptForDestination(dest)}, false /* have_solving_data */, true /* apply_label */, 1 /* timestamp */);
280 88 : } else if (IsHex(request.params[0].get_str())) {
281 6 : std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
282 6 : CScript redeem_script(data.begin(), data.end());
283 :
284 6 : std::set<CScript> scripts = {redeem_script};
285 6 : pwallet->ImportScripts(scripts, 0 /* timestamp */);
286 :
287 6 : if (fP2SH) {
288 6 : scripts.insert(GetScriptForDestination(ScriptHash(redeem_script)));
289 6 : }
290 :
291 6 : pwallet->ImportScriptPubKeys(strLabel, scripts, false /* have_solving_data */, true /* apply_label */, 1 /* timestamp */);
292 6 : } else {
293 9 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Dash address or script");
294 : }
295 91 : }
296 79 : if (fRescan)
297 : {
298 50 : RescanWallet(*pwallet, reserver);
299 : {
300 50 : LOCK(pwallet->cs_wallet);
301 50 : pwallet->ReacceptWalletTransactions();
302 50 : }
303 50 : }
304 :
305 79 : return UniValue::VNULL;
306 105 : },
307 : };
308 0 : }
309 :
310 2924 : RPCHelpMan importprunedfunds()
311 : {
312 5848 : return RPCHelpMan{"importprunedfunds",
313 2924 : "\nImports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n",
314 8772 : {
315 2924 : {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
316 2924 : {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
317 : },
318 2924 : RPCResult{RPCResult::Type::NONE, "", ""},
319 2924 : RPCExamples{""},
320 2952 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
321 : {
322 28 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
323 28 : if (!pwallet) return UniValue::VNULL;
324 :
325 28 : CMutableTransaction tx;
326 28 : if (!DecodeHexTx(tx, request.params[0].get_str())) {
327 4 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
328 : }
329 24 : uint256 hashTx = tx.GetHash();
330 :
331 24 : CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK, PROTOCOL_VERSION);
332 24 : CMerkleBlock merkleBlock;
333 24 : ssMB >> merkleBlock;
334 :
335 : //Search partial merkle tree in proof for our transaction and index in valid block
336 24 : std::vector<uint256> vMatch;
337 24 : std::vector<unsigned int> vIndex;
338 24 : if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) {
339 4 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
340 : }
341 :
342 20 : LOCK(pwallet->cs_wallet);
343 : int height;
344 20 : if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) {
345 4 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
346 : }
347 :
348 16 : std::vector<uint256>::const_iterator it;
349 16 : if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx)) == vMatch.end()) {
350 4 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
351 : }
352 :
353 12 : unsigned int txnIndex = vIndex[it - vMatch.begin()];
354 :
355 12 : CTransactionRef tx_ref = MakeTransactionRef(tx);
356 12 : if (pwallet->IsMine(*tx_ref)) {
357 8 : pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)});
358 8 : return UniValue::VNULL;
359 : }
360 :
361 4 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
362 48 : },
363 : };
364 0 : }
365 :
366 2908 : RPCHelpMan removeprunedfunds()
367 : {
368 5816 : return RPCHelpMan{"removeprunedfunds",
369 2908 : "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n",
370 5816 : {
371 2908 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
372 : },
373 2908 : RPCResult{RPCResult::Type::NONE, "", ""},
374 2908 : RPCExamples{
375 2908 : HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
376 : "\nAs a JSON-RPC call\n"
377 2908 : + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
378 : },
379 2920 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
380 : {
381 12 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
382 12 : if (!pwallet) return UniValue::VNULL;
383 :
384 12 : LOCK(pwallet->cs_wallet);
385 :
386 12 : uint256 hash(ParseHashV(request.params[0], "txid"));
387 12 : std::vector<uint256> vHash;
388 12 : vHash.push_back(hash);
389 12 : std::vector<uint256> vHashOut;
390 :
391 12 : if (pwallet->ZapSelectTx(vHash, vHashOut) != DBErrors::LOAD_OK) {
392 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction.");
393 : }
394 :
395 12 : if(vHashOut.empty()) {
396 4 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet.");
397 : }
398 :
399 8 : return UniValue::VNULL;
400 16 : },
401 : };
402 0 : }
403 :
404 2944 : RPCHelpMan importpubkey()
405 : {
406 5888 : return RPCHelpMan{"importpubkey",
407 2944 : "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
408 : "Hint: use importmulti to import more than one public key.\n"
409 : "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
410 : "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
411 : "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
412 : "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
413 : "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"combo(X)\" for descriptor wallets.\n",
414 11776 : {
415 2944 : {"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"},
416 2944 : {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"},
417 2944 : {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
418 : },
419 2944 : RPCResult{RPCResult::Type::NONE, "", ""},
420 2944 : RPCExamples{
421 : "\nImport a public key with rescan\n"
422 2944 : + HelpExampleCli("importpubkey", "\"mypubkey\"") +
423 : "\nImport using a label without rescan\n"
424 2944 : + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
425 : "\nAs a JSON-RPC call\n"
426 2944 : + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")
427 : },
428 2992 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
429 : {
430 48 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
431 48 : if (!pwallet) return UniValue::VNULL;
432 :
433 48 : EnsureLegacyScriptPubKeyMan(*pwallet, true);
434 :
435 46 : std::string strLabel;
436 46 : if (!request.params[1].isNull())
437 30 : strLabel = request.params[1].get_str();
438 :
439 : // Whether to perform rescan after import
440 46 : bool fRescan = true;
441 46 : if (!request.params[2].isNull())
442 22 : fRescan = request.params[2].get_bool();
443 :
444 46 : if (fRescan && pwallet->chain().havePruned()) {
445 : // Exit early and print an error.
446 : // If a block is pruned after this check, we will import the key(s),
447 : // but fail the rescan with a generic error.
448 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
449 : }
450 :
451 46 : WalletRescanReserver reserver(*pwallet);
452 46 : if (fRescan && !reserver.reserve()) {
453 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
454 : }
455 :
456 46 : if (!IsHex(request.params[0].get_str()))
457 3 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
458 43 : std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
459 43 : CPubKey pubKey(data);
460 43 : if (!pubKey.IsFullyValid())
461 3 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
462 :
463 : {
464 40 : LOCK(pwallet->cs_wallet);
465 :
466 40 : std::set<CScript> script_pub_keys;
467 40 : script_pub_keys.insert(GetScriptForDestination(PKHash(pubKey)));
468 :
469 40 : pwallet->MarkDirty();
470 :
471 40 : pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, true /* have_solving_data */, true /* apply_label */, 1 /* timestamp */);
472 :
473 40 : pwallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}} , {} /* key_origins */, false /* add_keypool */, false /* internal */, 1 /* timestamp */);
474 40 : }
475 40 : if (fRescan)
476 : {
477 30 : RescanWallet(*pwallet, reserver);
478 : {
479 30 : LOCK(pwallet->cs_wallet);
480 30 : pwallet->ReacceptWalletTransactions();
481 30 : }
482 30 : }
483 :
484 40 : return UniValue::VNULL;
485 54 : },
486 : };
487 0 : }
488 :
489 2911 : RPCHelpMan importwallet()
490 : {
491 5822 : return RPCHelpMan{"importwallet",
492 2911 : "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n"
493 : "Note: Blockchain and Mempool will be rescanned after a successful import. Use \"getwalletinfo\" to query the scanning progress.\n"
494 : "Note: This command is only compatible with legacy wallets.\n",
495 5822 : {
496 2911 : {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"},
497 : },
498 2911 : RPCResult{RPCResult::Type::NONE, "", ""},
499 2911 : RPCExamples{
500 : "\nDump the wallet\n"
501 2911 : + HelpExampleCli("dumpwallet", "\"test\"") +
502 : "\nImport the wallet\n"
503 2911 : + HelpExampleCli("importwallet", "\"test\"") +
504 : "\nImport using the json rpc call\n"
505 2911 : + HelpExampleRpc("importwallet", "\"test\"")
506 : },
507 2926 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
508 : {
509 15 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
510 15 : if (!pwallet) return UniValue::VNULL;
511 :
512 15 : EnsureLegacyScriptPubKeyMan(*pwallet, true);
513 :
514 13 : if (pwallet->chain().havePruned()) {
515 : // Exit early and print an error.
516 : // If a block is pruned after this check, we will import the key(s),
517 : // but fail the rescan with a generic error.
518 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when blocks are pruned");
519 : }
520 :
521 13 : WalletBatch batch(pwallet->GetDatabase());
522 13 : WalletRescanReserver reserver(*pwallet);
523 13 : if (!reserver.reserve()) {
524 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
525 : }
526 :
527 13 : int64_t nTimeBegin = 0;
528 13 : bool fGood = true;
529 : {
530 13 : LOCK(pwallet->cs_wallet);
531 :
532 13 : EnsureWalletIsUnlocked(*pwallet);
533 :
534 13 : std::ifstream file;
535 13 : file.open(fs::u8path(request.params[0].get_str()), std::ios::in | std::ios::ate);
536 13 : if (!file.is_open()) {
537 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
538 : }
539 13 : CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin)));
540 :
541 13 : int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
542 13 : file.seekg(0, file.beg);
543 :
544 : // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which
545 : // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button.
546 13 : pwallet->chain().showProgress(strprintf("%s " + _("Importing…").translated, pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI
547 13 : std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys;
548 13 : std::vector<std::pair<CScript, int64_t>> scripts;
549 2763 : while (file.good()) {
550 2750 : pwallet->chain().showProgress("", std::max(1, std::min(50, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false);
551 2750 : std::string line;
552 2750 : std::getline(file, line);
553 2750 : if (line.empty() || line[0] == '#')
554 249 : continue;
555 :
556 2501 : std::vector<std::string> vstr = SplitString(line, ' ');
557 2501 : if (vstr.size() < 2)
558 0 : continue;
559 2501 : CKey key = DecodeSecret(vstr[0]);
560 2501 : if (key.IsValid()) {
561 2495 : int64_t nTime = ParseISO8601DateTime(vstr[1]);
562 2495 : std::string strLabel;
563 2495 : bool fLabel = true;
564 4990 : for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
565 4990 : if (vstr[nStr].front() == '#')
566 2495 : break;
567 2495 : if (vstr[nStr] == "change=1")
568 50 : fLabel = false;
569 2495 : if (vstr[nStr] == "reserve=1")
570 2331 : fLabel = false;
571 2495 : if (vstr[nStr].substr(0,6) == "label=") {
572 114 : strLabel = DecodeDumpString(vstr[nStr].substr(6));
573 114 : fLabel = true;
574 114 : }
575 2495 : }
576 2495 : keys.emplace_back(key, nTime, fLabel, strLabel);
577 2501 : } else if(IsHex(vstr[0])) {
578 6 : std::vector<unsigned char> vData(ParseHex(vstr[0]));
579 6 : CScript script = CScript(vData.begin(), vData.end());
580 6 : int64_t birth_time = ParseISO8601DateTime(vstr[1]);
581 6 : scripts.emplace_back(script, birth_time);
582 6 : }
583 2750 : }
584 13 : file.close();
585 : // We now know whether we are importing private keys, so we can error if private keys are disabled
586 13 : if (keys.size() > 0 && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
587 0 : pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI
588 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when private keys are disabled");
589 : }
590 13 : double total = (double)(keys.size() + scripts.size());
591 13 : double progress = 0;
592 13 : LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
593 13 : if (spk_man == nullptr) {
594 0 : if (total > 0) {
595 0 : throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command");
596 : }
597 0 : } else {
598 13 : LOCK(spk_man->cs_KeyStore);
599 2508 : for (const auto& key_tuple : keys) {
600 2495 : pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false);
601 2495 : const CKey& key = std::get<0>(key_tuple);
602 2495 : int64_t time = std::get<1>(key_tuple);
603 2495 : bool has_label = std::get<2>(key_tuple);
604 2495 : std::string label = std::get<3>(key_tuple);
605 :
606 2495 : CPubKey pubkey = key.GetPubKey();
607 2495 : CHECK_NONFATAL(key.VerifyPubKey(pubkey));
608 2495 : PKHash pkhash = PKHash(pubkey);
609 2495 : CKeyID keyid = pubkey.GetID();
610 2495 : pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(pkhash));
611 2495 : if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) {
612 0 : pwallet->WalletLogPrintf("Error importing key for %s\n", EncodeDestination(pkhash));
613 0 : fGood = false;
614 0 : continue;
615 : }
616 2495 : if (has_label)
617 114 : pwallet->SetAddressBook(pkhash, label, "receive");
618 :
619 2495 : nTimeBegin = std::min(nTimeBegin, time);
620 2495 : progress++;
621 2495 : }
622 19 : for (const auto& script_pair : scripts) {
623 6 : pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false);
624 6 : const CScript& script = script_pair.first;
625 6 : int64_t time = script_pair.second;
626 6 : if (!pwallet->ImportScripts({script}, time)) {
627 0 : pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script));
628 0 : fGood = false;
629 0 : continue;
630 : }
631 6 : if (time > 0) {
632 0 : nTimeBegin = std::min(nTimeBegin, time);
633 0 : }
634 6 : progress++;
635 : }
636 13 : pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI
637 13 : }
638 13 : }
639 13 : pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI
640 13 : RescanWallet(*pwallet, reserver, nTimeBegin, false /* update */);
641 13 : pwallet->MarkDirty();
642 :
643 13 : if (!fGood)
644 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet");
645 :
646 13 : return UniValue::VNULL;
647 15 : },
648 : };
649 0 : }
650 :
651 2896 : RPCHelpMan importelectrumwallet()
652 : {
653 5792 : return RPCHelpMan{"importelectrumwallet",
654 2896 : "\nImports keys from an Electrum wallet export file (.csv or .json)\n"
655 : "Note: This command is only compatible with legacy wallets.\n",
656 8688 : {
657 2896 : {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The Electrum wallet export file, should be in csv or json format"},
658 2896 : {"index", RPCArg::Type::NUM, RPCArg::Default{0}, "Rescan the wallet for transactions starting from this block index"},
659 : },
660 2896 : RPCResult{RPCResult::Type::NONE, "", ""},
661 2896 : RPCExamples{
662 : "\nImport the wallet\n"
663 2896 : + HelpExampleCli("importelectrumwallet", "\"test.csv\"")
664 2896 : + HelpExampleCli("importelectrumwallet", "\"test.json\"") +
665 : "\nImport using the json rpc call\n"
666 2896 : + HelpExampleRpc("importelectrumwallet", "\"test.csv\"")
667 2896 : + HelpExampleRpc("importelectrumwallet", "\"test.json\"")
668 : },
669 2896 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
670 : {
671 :
672 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
673 0 : if (!pwallet) return UniValue::VNULL;
674 :
675 0 : if (pwallet->chain().havePruned())
676 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
677 :
678 0 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
679 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
680 : }
681 :
682 0 : LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet);
683 :
684 0 : LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
685 :
686 0 : EnsureWalletIsUnlocked(*pwallet);
687 :
688 0 : std::ifstream file;
689 0 : std::string strFileName = request.params[0].get_str();
690 0 : size_t nDotPos = strFileName.find_last_of(".");
691 0 : if(nDotPos == std::string::npos)
692 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "File has no extension, should be .json or .csv");
693 :
694 0 : std::string strFileExt = strFileName.substr(nDotPos+1);
695 0 : if(strFileExt != "json" && strFileExt != "csv")
696 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "File has wrong extension, should be .json or .csv");
697 :
698 0 : file.open(strFileName, std::ios::in | std::ios::ate);
699 0 : if (!file.is_open())
700 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open Electrum wallet export file");
701 :
702 0 : bool fGood = true;
703 :
704 0 : WalletBatch batch(pwallet->GetDatabase());
705 :
706 0 : int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
707 0 : file.seekg(0, file.beg);
708 :
709 0 : pwallet->ShowProgress(_("Importing…").translated, 0); // show progress dialog in GUI
710 :
711 : // Electrum backups were modified to include a prefix before the private key
712 : // The new format of the private_key field is: "prefix:private key"
713 : // Where prefix is, for example, "p2pkh" or "p2sh"
714 0 : if(strFileExt == "csv") {
715 0 : while (file.good()) {
716 0 : pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
717 0 : std::string line;
718 0 : std::getline(file, line);
719 0 : if (line.empty() || line == "address,private_key")
720 0 : continue;
721 0 : std::vector<std::string> vstr = SplitString(line, ',');
722 0 : if (vstr.size() < 2)
723 0 : continue;
724 0 : std::vector<std::string> vstr2 = SplitString(vstr[1], ':');
725 0 : CKey key;
726 0 : if (vstr2.size() < 1 || vstr2.size() > 2) {
727 0 : continue;
728 : }
729 0 : else if (vstr2.size() == 1) {
730 : // Legacy format with only private key in the private_key field
731 0 : key = DecodeSecret(vstr[1]);
732 0 : }
733 : else {
734 : // New format with "prefix:private key" in the private_key field
735 0 : key = DecodeSecret(vstr2[1]);
736 : }
737 0 : if (!key.IsValid()) {
738 0 : continue;
739 : }
740 0 : CPubKey pubkey = key.GetPubKey();
741 0 : CHECK_NONFATAL(key.VerifyPubKey(pubkey));
742 0 : CKeyID keyid = pubkey.GetID();
743 0 : if (spk_man.HaveKey(keyid)) {
744 0 : pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(PKHash(keyid)));
745 0 : continue;
746 : }
747 0 : pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid)));
748 0 : if (!spk_man.AddKeyPubKeyWithDB(batch, key, pubkey)) {
749 0 : fGood = false;
750 0 : continue;
751 : }
752 0 : }
753 0 : } else {
754 : // json
755 0 : UniValue data(UniValue::VOBJ);
756 : {
757 0 : auto buffer = std::make_unique<char[]>(nFilesize);
758 0 : file.read(buffer.get(), nFilesize);
759 0 : if(!data.read(buffer.get()))
760 0 : throw JSONRPCError(RPC_TYPE_ERROR, "Cannot parse Electrum wallet export file");
761 0 : }
762 :
763 0 : std::vector<std::string> vKeys = data.getKeys();
764 :
765 0 : for (size_t i = 0; i < data.size(); i++) {
766 0 : pwallet->ShowProgress("", std::max(1, std::min(99, int(i*100/data.size()))));
767 0 : if(!data[vKeys[i]].isStr())
768 0 : continue;
769 0 : std::vector<std::string> vstr2 = SplitString(data[vKeys[i]].get_str(), ':');
770 0 : CKey key;
771 0 : if (vstr2.size() < 1 || vstr2.size() > 2) {
772 0 : continue;
773 : }
774 0 : else if (vstr2.size() == 1) {
775 : // Legacy format with only private key in the private_key field
776 0 : key = DecodeSecret(data[vKeys[i]].get_str());
777 0 : }
778 : else {
779 : // New format with "prefix:private key" in the private_key field
780 0 : key = DecodeSecret(vstr2[1]);
781 : }
782 0 : if (!key.IsValid()) {
783 0 : continue;
784 : }
785 0 : CPubKey pubkey = key.GetPubKey();
786 0 : CHECK_NONFATAL(key.VerifyPubKey(pubkey));
787 0 : CKeyID keyid = pubkey.GetID();
788 0 : if (spk_man.HaveKey(keyid)) {
789 0 : pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(PKHash(keyid)));
790 0 : continue;
791 : }
792 0 : pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid)));
793 0 : if (!spk_man.AddKeyPubKeyWithDB(batch, key, pubkey)) {
794 0 : fGood = false;
795 0 : continue;
796 : }
797 0 : }
798 0 : }
799 0 : file.close();
800 0 : pwallet->ShowProgress("", 100); // hide progress dialog in GUI
801 :
802 0 : const int32_t tip_height = pwallet->chain().getHeight().value_or(std::numeric_limits<int32_t>::max());
803 :
804 : // Whether to perform rescan after import
805 0 : int nStartHeight = 0;
806 0 : if (!request.params[1].isNull())
807 0 : nStartHeight = request.params[1].getInt<int>();
808 0 : if (tip_height < nStartHeight)
809 0 : nStartHeight = tip_height;
810 :
811 : // Assume that electrum wallet was created at that block
812 : int64_t nTimeBegin;
813 0 : CHECK_NONFATAL(pwallet->chain().findFirstBlockWithTimeAndHeight(0, nStartHeight, FoundBlock().time(nTimeBegin)));
814 0 : spk_man.UpdateTimeFirstKey(nTimeBegin);
815 :
816 0 : pwallet->WalletLogPrintf("Rescanning %i blocks\n", tip_height - nStartHeight + 1);
817 0 : WalletRescanReserver reserver(*pwallet);
818 0 : if (!reserver.reserve()) {
819 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
820 : }
821 0 : pwallet->ScanForWalletTransactions(pwallet->chain().getBlockHash(nStartHeight), nStartHeight, {}, reserver, /*fUpdate=*/true, /*save_progress=*/false);
822 :
823 0 : if (!fGood)
824 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
825 :
826 0 : return UniValue::VNULL;
827 0 : },
828 : };
829 0 : }
830 :
831 3143 : RPCHelpMan dumpprivkey()
832 : {
833 6286 : return RPCHelpMan{"dumpprivkey",
834 3143 : "\nReveals the private key corresponding to 'address'.\n"
835 : "Then the importprivkey can be used with this output\n"
836 : "Note: This command is only compatible with legacy wallets.\n",
837 6286 : {
838 3143 : {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Dash address for the private key"},
839 : },
840 3143 : RPCResult{
841 3143 : RPCResult::Type::STR, "key", "The private key"
842 : },
843 3143 : RPCExamples{
844 3143 : HelpExampleCli("dumpprivkey", "\"myaddress\"")
845 3143 : + HelpExampleCli("importprivkey", "\"mykey\"")
846 3143 : + HelpExampleRpc("dumpprivkey", "\"myaddress\"")
847 : },
848 3390 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
849 : {
850 247 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
851 247 : if (!pwallet) return UniValue::VNULL;
852 :
853 247 : const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(*pwallet);
854 :
855 245 : LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
856 :
857 245 : EnsureWalletIsUnlocked(*pwallet);
858 :
859 245 : std::string strAddress = request.params[0].get_str();
860 245 : CTxDestination dest = DecodeDestination(strAddress);
861 245 : if (!IsValidDestination(dest)) {
862 3 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Dash address");
863 : }
864 242 : const PKHash *pkhash= std::get_if<PKHash>(&dest);
865 242 : if (!pkhash) {
866 0 : throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
867 : }
868 242 : CKey vchSecret;
869 242 : if (!spk_man.GetKey(ToKeyID(*pkhash), vchSecret)) {
870 3 : throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
871 : }
872 239 : return EncodeSecret(vchSecret);
873 253 : },
874 : };
875 0 : }
876 :
877 2950 : RPCHelpMan dumphdinfo()
878 : {
879 5900 : return RPCHelpMan{"dumphdinfo",
880 2950 : "Returns an object containing sensitive private info about this HD wallet.\n"
881 : "Note: This command is only compatible with legacy wallets.\n",
882 2950 : {},
883 2950 : RPCResult{
884 2950 : RPCResult::Type::OBJ, "", "",
885 11800 : {
886 2950 : {RPCResult::Type::STR_HEX, "hdseed", "The HD seed (BIP32, in hex)"},
887 2950 : {RPCResult::Type::STR, "mnemonic", "The mnemonic for this HD wallet (BIP39, english words)"},
888 2950 : {RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this HD wallet (BIP39)"},
889 : }
890 : },
891 2950 : RPCExamples{
892 2950 : HelpExampleCli("dumphdinfo", "")
893 2950 : + HelpExampleRpc("dumphdinfo", "")
894 : },
895 3004 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
896 : {
897 54 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
898 54 : if (!pwallet) return UniValue::VNULL;
899 :
900 54 : LOCK(pwallet->cs_wallet);
901 :
902 54 : EnsureWalletIsUnlocked(*pwallet);
903 :
904 48 : LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet);
905 48 : CHDChain hdChainCurrent;
906 48 : if (!spk_man.GetHDChain(hdChainCurrent))
907 0 : throw JSONRPCError(RPC_WALLET_ERROR, "This wallet is not a HD wallet.");
908 :
909 48 : if (!spk_man.GetDecryptedHDChain(hdChainCurrent))
910 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD seed");
911 :
912 48 : SecureString ssMnemonic;
913 48 : SecureString ssMnemonicPassphrase;
914 48 : hdChainCurrent.GetMnemonic(ssMnemonic, ssMnemonicPassphrase);
915 :
916 48 : UniValue obj(UniValue::VOBJ);
917 48 : obj.pushKV("hdseed", HexStr(hdChainCurrent.GetSeed()));
918 48 : obj.pushKV("mnemonic", ssMnemonic);
919 48 : obj.pushKV("mnemonicpassphrase", ssMnemonicPassphrase);
920 :
921 48 : return obj;
922 54 : },
923 : };
924 0 : }
925 :
926 2920 : RPCHelpMan dumpwallet()
927 : {
928 5840 : return RPCHelpMan{"dumpwallet",
929 2920 : "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n"
930 : "Imported scripts are included in the dumpfile too, their corresponding addresses will be added automatically by importwallet.\n"
931 : "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n"
932 : "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n"
933 : "Note: This command is only compatible with legacy wallets.\n",
934 5840 : {
935 2920 : {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (absolute path recommended)"},
936 : },
937 2920 : RPCResult{
938 2920 : RPCResult::Type::OBJ, "", "",
939 11680 : {
940 2920 : {RPCResult::Type::NUM, "keys", "The number of keys contained in the wallet dump"},
941 2920 : {RPCResult::Type::STR, "filename", "The filename with full absolute path"},
942 2920 : {RPCResult::Type::STR, "warning", "A warning about not sharing the wallet dump with anyone"},
943 : }
944 : },
945 2920 : RPCExamples{
946 2920 : HelpExampleCli("dumpwallet", "\"test\"")
947 2920 : + HelpExampleRpc("dumpwallet", "\"test\"")
948 : },
949 2944 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
950 : {
951 24 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
952 24 : if (!pwallet) return UniValue::VNULL;
953 :
954 24 : const CWallet& wallet = *pwallet;
955 24 : const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(wallet);
956 :
957 : // Make sure the results are valid at least up to the most recent block
958 : // the user could have gotten from another RPC command prior to now
959 22 : wallet.BlockUntilSyncedToCurrentChain();
960 :
961 22 : LOCK(wallet.cs_wallet);
962 :
963 22 : EnsureWalletIsUnlocked(wallet);
964 :
965 22 : fs::path filepath = fs::u8path(request.params[0].get_str());
966 22 : filepath = fs::absolute(filepath);
967 :
968 : /* Prevent arbitrary files from being overwritten. There have been reports
969 : * that users have overwritten wallet files this way:
970 : * https://github.com/bitcoin/bitcoin/issues/9934
971 : * It may also avoid other security issues.
972 : */
973 22 : if (fs::exists(filepath)) {
974 3 : throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.utf8string() + " already exists. If you are sure this is what you want, move it out of the way first");
975 : }
976 :
977 19 : std::ofstream file;
978 19 : file.open(filepath);
979 19 : if (!file.is_open())
980 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
981 :
982 19 : std::map<CKeyID, int64_t> mapKeyBirth;
983 19 : wallet.GetKeyBirthTimes(mapKeyBirth);
984 :
985 19 : int64_t block_time = 0;
986 19 : CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), FoundBlock().time(block_time)));
987 :
988 : // Note: To avoid a lock order issue, access to cs_main must be locked before cs_KeyStore.
989 : // So we do the two things in this function that lock cs_main first: GetKeyBirthTimes, and findBlock.
990 19 : LOCK(spk_man.cs_KeyStore);
991 :
992 19 : const std::map<CKeyID, int64_t>& mapKeyPool = spk_man.GetAllReserveKeys();
993 19 : std::set<CScriptID> scripts = spk_man.GetCScripts();
994 :
995 : // sort time/key pairs
996 19 : std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
997 3663 : for (const auto& entry : mapKeyBirth) {
998 3644 : vKeyBirth.emplace_back(entry.second, entry.first);
999 : }
1000 19 : mapKeyBirth.clear();
1001 19 : std::sort(vKeyBirth.begin(), vKeyBirth.end());
1002 :
1003 : // produce output
1004 19 : file << strprintf("# Wallet dump created by %s %s\n", PACKAGE_NAME, FormatFullVersion());
1005 19 : file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime()));
1006 19 : file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString());
1007 19 : file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time));
1008 19 : file << "\n";
1009 :
1010 19 : UniValue obj(UniValue::VOBJ);
1011 19 : obj.pushKV("dashcoreversion", FormatFullVersion());
1012 19 : obj.pushKV("lastblockheight", wallet.GetLastBlockHeight());
1013 19 : obj.pushKV("lastblockhash", wallet.GetLastBlockHash().ToString());
1014 19 : obj.pushKV("lastblocktime", block_time);
1015 :
1016 : // add the base58check encoded extended master if the wallet uses HD
1017 19 : CHDChain hdChainCurrent;
1018 19 : if (spk_man.GetHDChain(hdChainCurrent))
1019 : {
1020 :
1021 18 : if (!spk_man.GetDecryptedHDChain(hdChainCurrent))
1022 0 : throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot decrypt HD chain");
1023 :
1024 18 : SecureString ssMnemonic;
1025 18 : SecureString ssMnemonicPassphrase;
1026 18 : hdChainCurrent.GetMnemonic(ssMnemonic, ssMnemonicPassphrase);
1027 18 : file << "# mnemonic: " << ssMnemonic << "\n";
1028 18 : file << "# mnemonic passphrase: " << ssMnemonicPassphrase << "\n\n";
1029 :
1030 18 : SecureVector vchSeed = hdChainCurrent.GetSeed();
1031 18 : file << "# HD seed: " << HexStr(vchSeed) << "\n\n";
1032 :
1033 18 : CExtKey masterKey;
1034 18 : masterKey.SetSeed(MakeByteSpan(vchSeed));
1035 :
1036 18 : file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n";
1037 :
1038 18 : CExtPubKey masterPubkey;
1039 18 : masterPubkey = masterKey.Neuter();
1040 :
1041 18 : file << "# extended public masterkey: " << EncodeExtPubKey(masterPubkey) << "\n\n";
1042 :
1043 36 : for (size_t i = 0; i < hdChainCurrent.CountAccounts(); ++i)
1044 : {
1045 18 : CHDAccount acc;
1046 18 : if(hdChainCurrent.GetAccount(i, acc)) {
1047 18 : file << "# external chain counter: " << acc.nExternalChainCounter << "\n";
1048 18 : file << "# internal chain counter: " << acc.nInternalChainCounter << "\n\n";
1049 18 : } else {
1050 0 : file << "# WARNING: ACCOUNT " << i << " IS MISSING!" << "\n\n";
1051 : }
1052 18 : }
1053 18 : obj.pushKV("hdaccounts", hdChainCurrent.CountAccounts());
1054 18 : }
1055 :
1056 3663 : for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
1057 3644 : const CKeyID &keyid = it->second;
1058 3644 : std::string strTime = FormatISO8601DateTime(it->first);
1059 3644 : std::string strAddr;
1060 3644 : std::string strLabel;
1061 3644 : CKey key;
1062 3644 : if (spk_man.GetKey(keyid, key)) {
1063 3644 : CKeyMetadata metadata;
1064 3644 : const auto it{spk_man.mapKeyMetadata.find(keyid)};
1065 3644 : if (it != spk_man.mapKeyMetadata.end()) metadata = it->second;
1066 3644 : file << strprintf("%s %s ", EncodeSecret(key), strTime);
1067 3644 : if (GetWalletAddressesForKey(&spk_man, wallet, keyid, strAddr, strLabel)) {
1068 180 : file << strprintf("label=%s", strLabel);
1069 3644 : } else if (mapKeyPool.count(keyid)) {
1070 3411 : file << "reserve=1";
1071 3411 : } else {
1072 53 : file << "change=1";
1073 : }
1074 3644 : file << strprintf(" # addr=%s%s\n", strAddr, (metadata.has_key_origin ? " hdkeypath="+WriteHDKeypath(metadata.key_origin.path, /*apostrophe=*/true) : ""));
1075 3644 : }
1076 3644 : }
1077 19 : file << "\n";
1078 31 : for (const CScriptID &scriptid : scripts) {
1079 12 : CScript script;
1080 12 : std::string create_time = "0";
1081 12 : std::string address = EncodeDestination(ScriptHash(scriptid));
1082 : // get birth times for scripts with metadata
1083 12 : auto it = spk_man.m_script_metadata.find(scriptid);
1084 12 : if (it != spk_man.m_script_metadata.end()) {
1085 0 : create_time = FormatISO8601DateTime(it->second.nCreateTime);
1086 0 : }
1087 12 : if(spk_man.GetCScript(scriptid, script)) {
1088 12 : file << strprintf("%s %s script=1", HexStr(script), create_time);
1089 12 : file << strprintf(" # addr=%s\n", address);
1090 12 : }
1091 12 : }
1092 19 : file << "\n";
1093 19 : file << "# End of dump\n";
1094 19 : file.close();
1095 :
1096 19 : std::string strWarning = strprintf(_("%s file contains all private keys from this wallet. Do not share it with anyone!").translated, request.params[0].get_str());
1097 19 : obj.pushKV("keys", vKeyBirth.size());
1098 19 : obj.pushKV("filename", filepath.utf8string());
1099 19 : obj.pushKV("warning", strWarning);
1100 :
1101 19 : return obj;
1102 27 : },
1103 : };
1104 0 : }
1105 :
1106 : struct ImportData
1107 : {
1108 : // Input data
1109 : std::unique_ptr<CScript> redeemscript; //!< Provided redeemScript; will be moved to `import_scripts` if relevant.
1110 :
1111 : // Output data
1112 : std::set<CScript> import_scripts;
1113 : std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability)
1114 : std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> key_origins;
1115 : };
1116 :
1117 : enum class ScriptContext
1118 : {
1119 : TOP, //! Top-level scriptPubKey
1120 : P2SH, //! P2SH redeemScript
1121 : };
1122 :
1123 : // Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used.
1124 : // Returns an error string, or the empty string for success.
1125 102 : static std::string RecurseImportData(const CScript& script, ImportData& import_data, const ScriptContext script_ctx)
1126 : {
1127 : // Use Solver to obtain script type and parsed pubkeys or hashes:
1128 102 : std::vector<std::vector<unsigned char>> solverdata;
1129 102 : TxoutType script_type = Solver(script, solverdata);
1130 :
1131 102 : switch (script_type) {
1132 : case TxoutType::PUBKEY: {
1133 0 : CPubKey pubkey(solverdata[0]);
1134 0 : import_data.used_keys.emplace(pubkey.GetID(), false);
1135 0 : return "";
1136 : }
1137 : case TxoutType::PUBKEYHASH: {
1138 90 : CKeyID id = CKeyID(uint160(solverdata[0]));
1139 90 : import_data.used_keys[id] = true;
1140 90 : return "";
1141 : }
1142 : case TxoutType::SCRIPTHASH: {
1143 6 : if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH");
1144 6 : CHECK_NONFATAL(script_ctx == ScriptContext::TOP);
1145 6 : CScriptID id = CScriptID(uint160(solverdata[0]));
1146 6 : auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later.
1147 6 : if (!subscript) return "missing redeemscript";
1148 6 : if (CScriptID(*subscript) != id) return "redeemScript does not match the scriptPubKey";
1149 6 : import_data.import_scripts.emplace(*subscript);
1150 6 : return RecurseImportData(*subscript, import_data, ScriptContext::P2SH);
1151 6 : }
1152 : case TxoutType::MULTISIG: {
1153 24 : for (size_t i = 1; i + 1< solverdata.size(); ++i) {
1154 18 : CPubKey pubkey(solverdata[i]);
1155 18 : import_data.used_keys.emplace(pubkey.GetID(), false);
1156 18 : }
1157 6 : return "";
1158 : }
1159 : case TxoutType::NULL_DATA:
1160 0 : return "unspendable script";
1161 : case TxoutType::NONSTANDARD:
1162 0 : return "unrecognized script";
1163 : } // no default case, so the compiler can warn about missing cases
1164 0 : NONFATAL_UNREACHABLE();
1165 102 : }
1166 :
1167 146 : static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys)
1168 : {
1169 154 : UniValue warnings(UniValue::VARR);
1170 :
1171 : // First ensure scriptPubKey has either a script or JSON with "address" string
1172 146 : const UniValue& scriptPubKey = data["scriptPubKey"];
1173 146 : bool isScript = scriptPubKey.getType() == UniValue::VSTR;
1174 226 : if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) {
1175 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string");
1176 : }
1177 146 : const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
1178 :
1179 : // Optional fields.
1180 146 : const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
1181 146 : const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
1182 146 : const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
1183 146 : const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
1184 146 : const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
1185 :
1186 146 : if (data.exists("range")) {
1187 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for a non-descriptor import");
1188 : }
1189 :
1190 : // Generate the script and destination for the scriptPubKey provided
1191 146 : CScript script;
1192 146 : if (!isScript) {
1193 80 : CTxDestination dest = DecodeDestination(output);
1194 80 : if (!IsValidDestination(dest)) {
1195 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\"");
1196 : }
1197 78 : script = GetScriptForDestination(dest);
1198 78 : } else {
1199 66 : if (!IsHex(output)) {
1200 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\"");
1201 : }
1202 66 : std::vector<unsigned char> vData(ParseHex(output));
1203 66 : script = CScript(vData.begin(), vData.end());
1204 66 : CTxDestination dest;
1205 66 : if (!ExtractDestination(script, dest) && !internal) {
1206 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports.");
1207 : }
1208 66 : }
1209 138 : script_pub_keys.emplace(script);
1210 :
1211 : // Parse all arguments
1212 138 : if (strRedeemScript.size()) {
1213 6 : if (!IsHex(strRedeemScript)) {
1214 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string");
1215 : }
1216 6 : auto parsed_redeemscript = ParseHex(strRedeemScript);
1217 6 : import_data.redeemscript = std::make_unique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end());
1218 6 : }
1219 180 : for (size_t i = 0; i < pubKeys.size(); ++i) {
1220 42 : const auto& str = pubKeys[i].get_str();
1221 42 : if (!IsHex(str)) {
1222 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string");
1223 : }
1224 42 : auto parsed_pubkey = ParseHex(str);
1225 42 : CPubKey pubkey(parsed_pubkey);
1226 42 : if (!pubkey.IsFullyValid()) {
1227 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key");
1228 : }
1229 42 : pubkey_map.emplace(pubkey.GetID(), pubkey);
1230 42 : ordered_pubkeys.push_back(pubkey.GetID());
1231 42 : }
1232 194 : for (size_t i = 0; i < keys.size(); ++i) {
1233 56 : const auto& str = keys[i].get_str();
1234 56 : CKey key = DecodeSecret(str);
1235 56 : if (!key.IsValid()) {
1236 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
1237 : }
1238 56 : CPubKey pubkey = key.GetPubKey();
1239 56 : CKeyID id = pubkey.GetID();
1240 56 : if (pubkey_map.count(id)) {
1241 0 : pubkey_map.erase(id);
1242 0 : }
1243 56 : privkey_map.emplace(id, key);
1244 56 : }
1245 :
1246 :
1247 : // Verify and process input data
1248 138 : have_solving_data = import_data.redeemscript || pubkey_map.size() || privkey_map.size();
1249 138 : if (have_solving_data) {
1250 : // Match up data in import_data with the scriptPubKey in script.
1251 96 : auto error = RecurseImportData(script, import_data, ScriptContext::TOP);
1252 :
1253 : // Verify whether the watchonly option corresponds to the availability of private keys.
1254 197 : bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; });
1255 96 : if (!watchOnly && !spendable) {
1256 18 : warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
1257 18 : }
1258 96 : if (watchOnly && spendable) {
1259 2 : warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag.");
1260 2 : }
1261 :
1262 : // Check that all required keys for solvability are provided.
1263 96 : if (error.empty()) {
1264 204 : for (const auto& require_key : import_data.used_keys) {
1265 108 : if (!require_key.second) continue; // Not a required key
1266 90 : if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) {
1267 8 : error = "some required keys are missing";
1268 8 : }
1269 : }
1270 96 : }
1271 :
1272 96 : if (!error.empty()) {
1273 8 : warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, or redeemscript.");
1274 8 : import_data = ImportData();
1275 8 : pubkey_map.clear();
1276 8 : privkey_map.clear();
1277 8 : have_solving_data = false;
1278 8 : } else {
1279 : // RecurseImportData() removes any relevant redeemscript from import_data, so we can use that to discover if a superfluous one was provided.
1280 88 : if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script.");
1281 140 : for (auto it = privkey_map.begin(); it != privkey_map.end(); ) {
1282 52 : auto oldit = it++;
1283 52 : if (import_data.used_keys.count(oldit->first) == 0) {
1284 0 : warnings.push_back("Ignoring irrelevant private key.");
1285 0 : privkey_map.erase(oldit);
1286 0 : }
1287 : }
1288 126 : for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) {
1289 38 : auto oldit = it++;
1290 38 : auto key_data_it = import_data.used_keys.find(oldit->first);
1291 38 : if (key_data_it == import_data.used_keys.end() || !key_data_it->second) {
1292 0 : warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH.");
1293 0 : pubkey_map.erase(oldit);
1294 0 : }
1295 : }
1296 : }
1297 96 : }
1298 :
1299 138 : return warnings;
1300 154 : }
1301 :
1302 84 : static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys)
1303 : {
1304 96 : UniValue warnings(UniValue::VARR);
1305 :
1306 84 : const std::string& descriptor = data["desc"].get_str();
1307 84 : FlatSigningProvider keys;
1308 84 : std::string error;
1309 84 : auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true);
1310 84 : if (!parsed_desc) {
1311 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
1312 : }
1313 :
1314 84 : have_solving_data = parsed_desc->IsSolvable();
1315 84 : const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
1316 :
1317 84 : int64_t range_start = 0, range_end = 0;
1318 146 : if (!parsed_desc->IsRange() && data.exists("range")) {
1319 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
1320 84 : } else if (parsed_desc->IsRange()) {
1321 22 : if (!data.exists("range")) {
1322 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range");
1323 : }
1324 20 : std::tie(range_start, range_end) = ParseDescriptorRange(data["range"]);
1325 10 : }
1326 :
1327 72 : const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
1328 :
1329 : // Expand all descriptors to get public keys and scripts, and private keys if available.
1330 754 : for (int i = range_start; i <= range_end; ++i) {
1331 682 : FlatSigningProvider out_keys;
1332 682 : std::vector<CScript> scripts_temp;
1333 682 : parsed_desc->Expand(i, keys, scripts_temp, out_keys);
1334 682 : std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
1335 1360 : for (const auto& key_pair: out_keys.pubkeys) {
1336 678 : ordered_pubkeys.push_back(key_pair.first);
1337 : }
1338 :
1339 702 : for (const auto& x: out_keys.scripts) {
1340 20 : import_data.import_scripts.emplace(x.second);
1341 : }
1342 :
1343 682 : parsed_desc->ExpandPrivate(i, keys, out_keys);
1344 :
1345 682 : std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
1346 682 : std::copy(out_keys.keys.begin(), out_keys.keys.end(), std::inserter(privkey_map, privkey_map.end()));
1347 682 : import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
1348 682 : }
1349 :
1350 72 : for (size_t i = 0; i < priv_keys.size(); ++i) {
1351 0 : const auto& str = priv_keys[i].get_str();
1352 0 : CKey key = DecodeSecret(str);
1353 0 : if (!key.IsValid()) {
1354 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
1355 : }
1356 0 : CPubKey pubkey = key.GetPubKey();
1357 0 : CKeyID id = pubkey.GetID();
1358 :
1359 : // Check if this private key corresponds to a public key from the descriptor
1360 0 : if (!pubkey_map.count(id)) {
1361 0 : warnings.push_back("Ignoring irrelevant private key.");
1362 0 : } else {
1363 0 : privkey_map.emplace(id, key);
1364 : }
1365 0 : }
1366 :
1367 : // Check if all the public keys have corresponding private keys in the import for spendability.
1368 : // This does not take into account threshold multisigs which could be spendable without all keys.
1369 : // Thus, threshold multisigs without all keys will be considered not spendable here, even if they are,
1370 : // perhaps triggering a false warning message. This is consistent with the current wallet IsMine check.
1371 216 : bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(),
1372 142 : [&](const std::pair<CKeyID, CPubKey>& used_key) {
1373 70 : return privkey_map.count(used_key.first) > 0;
1374 72 : }) && std::all_of(import_data.key_origins.begin(), import_data.key_origins.end(),
1375 46 : [&](const std::pair<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& entry) {
1376 24 : return privkey_map.count(entry.first) > 0;
1377 : });
1378 72 : if (!watch_only && !spendable) {
1379 26 : warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
1380 26 : }
1381 72 : if (watch_only && spendable) {
1382 0 : warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag.");
1383 0 : }
1384 :
1385 72 : return warnings;
1386 86 : }
1387 :
1388 238 : static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
1389 : {
1390 238 : UniValue warnings(UniValue::VARR);
1391 238 : UniValue result(UniValue::VOBJ);
1392 :
1393 : try {
1394 238 : const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
1395 : // Internal addresses should not have a label
1396 268 : if (internal && data.exists("label")) {
1397 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
1398 : }
1399 236 : const std::string& label = data.exists("label") ? data["label"].get_str() : "";
1400 236 : const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false;
1401 :
1402 : // Add to keypool only works with privkeys disabled
1403 236 : if (add_keypool && !wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1404 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled");
1405 : }
1406 :
1407 234 : ImportData import_data;
1408 234 : std::map<CKeyID, CPubKey> pubkey_map;
1409 234 : std::map<CKeyID, CKey> privkey_map;
1410 234 : std::set<CScript> script_pub_keys;
1411 234 : std::vector<CKeyID> ordered_pubkeys;
1412 : bool have_solving_data;
1413 :
1414 382 : if (data.exists("scriptPubKey") && data.exists("desc")) {
1415 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided.");
1416 232 : } else if (data.exists("scriptPubKey")) {
1417 146 : warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys);
1418 224 : } else if (data.exists("desc")) {
1419 84 : warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys);
1420 72 : } else {
1421 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided.");
1422 : }
1423 :
1424 : // If private keys are disabled, abort if private keys are being imported
1425 210 : if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) {
1426 4 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
1427 : }
1428 :
1429 : // Check whether we have any work to do
1430 1020 : for (const CScript& script : script_pub_keys) {
1431 816 : if (wallet.IsMine(script) & ISMINE_SPENDABLE) {
1432 2 : throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script) + "\")");
1433 : }
1434 : }
1435 :
1436 : // All good, time to import
1437 204 : wallet.MarkDirty();
1438 204 : if (!wallet.ImportScripts(import_data.import_scripts, timestamp)) {
1439 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet");
1440 : }
1441 204 : if (!wallet.ImportPrivKeys(privkey_map, timestamp)) {
1442 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
1443 : }
1444 204 : if (!wallet.ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) {
1445 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
1446 : }
1447 204 : if (!wallet.ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) {
1448 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
1449 : }
1450 :
1451 204 : result.pushKV("success", UniValue(true));
1452 238 : } catch (const UniValue& e) {
1453 34 : result.pushKV("success", UniValue(false));
1454 34 : result.pushKV("error", e);
1455 34 : } catch (...) {
1456 0 : result.pushKV("success", UniValue(false));
1457 :
1458 0 : result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
1459 34 : }
1460 238 : if (warnings.size()) result.pushKV("warnings", warnings);
1461 238 : return result;
1462 272 : }
1463 :
1464 951 : static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
1465 : {
1466 955 : if (data.exists("timestamp")) {
1467 949 : const UniValue& timestamp = data["timestamp"];
1468 949 : if (timestamp.isNum()) {
1469 423 : return timestamp.getInt<int64_t>();
1470 526 : } else if (timestamp.isStr() && timestamp.get_str() == "now") {
1471 524 : return now;
1472 : }
1473 2 : throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
1474 : }
1475 2 : throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
1476 951 : }
1477 :
1478 3129 : RPCHelpMan importmulti()
1479 : {
1480 6258 : return RPCHelpMan{"importmulti",
1481 3129 : "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), optionally rescanning the blockchain from the earliest creation time of the imported scripts. Requires a new wallet backup.\n"
1482 : "If an address/script is imported without all of the private keys required to spend from that address, it will be watchonly. The 'watchonly' option must be set to true in this case or a warning will be returned.\n"
1483 : "Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n"
1484 : "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
1485 : "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n"
1486 : "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
1487 : "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
1488 : "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" for descriptor wallets.\n",
1489 9387 : {
1490 6258 : {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
1491 6258 : {
1492 6258 : {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
1493 37548 : {
1494 3129 : {"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"},
1495 6258 : {"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor",
1496 3129 : /*oneline_description=*/"", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}
1497 : },
1498 6258 : {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n"
1499 : " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
1500 : " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
1501 : " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
1502 : " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
1503 : " creation time of all keys being imported by the importmulti call will be scanned.",
1504 3129 : /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"}
1505 : },
1506 3129 : {"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey"},
1507 6258 : {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Array of strings giving pubkeys to import. They must occur in P2PKH or P2WPKH scripts. They are not required when the private key is also provided (see the \"keys\" argument).",
1508 6258 : {
1509 3129 : {"pubKey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
1510 : }
1511 : },
1512 6258 : {"keys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript.",
1513 6258 : {
1514 3129 : {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
1515 : }
1516 : },
1517 3129 : {"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"},
1518 3129 : {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether matching outputs should be treated as not incoming payments (also known as change)"},
1519 3129 : {"watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether matching outputs should be considered watchonly."},
1520 3129 : {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false"},
1521 3129 : {"keypool", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether imported public keys should be added to the keypool for when users request new addresses. Only allowed when wallet private keys are disabled"},
1522 : },
1523 : },
1524 : },
1525 3129 : "\"requests\""},
1526 6258 : {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
1527 6258 : {
1528 3129 : {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions after all imports."},
1529 : },
1530 3129 : "\"options\""},
1531 : },
1532 3129 : RPCResult{
1533 3129 : RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
1534 6258 : {
1535 6258 : {RPCResult::Type::OBJ, "", "",
1536 12516 : {
1537 3129 : {RPCResult::Type::BOOL, "success", ""},
1538 6258 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
1539 6258 : {
1540 3129 : {RPCResult::Type::STR, "", ""},
1541 : }},
1542 6258 : {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
1543 6258 : {
1544 3129 : {RPCResult::Type::ELISION, "", "JSONRPC error"},
1545 : }},
1546 : }},
1547 : }
1548 : },
1549 3129 : RPCExamples{
1550 3129 : HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
1551 3129 : "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
1552 3129 : HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'")
1553 : },
1554 3362 : [&](const RPCHelpMan& self, const JSONRPCRequest& mainRequest) -> UniValue
1555 : {
1556 241 : RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ});
1557 :
1558 233 : const UniValue& requests = mainRequest.params[0];
1559 :
1560 233 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(mainRequest);
1561 233 : if (!pwallet) return UniValue::VNULL;
1562 233 : CWallet& wallet{*pwallet};
1563 :
1564 : // Make sure the results are valid at least up to the most recent block
1565 : // the user could have gotten from another RPC command prior to now
1566 233 : wallet.BlockUntilSyncedToCurrentChain();
1567 :
1568 233 : EnsureLegacyScriptPubKeyMan(*pwallet, true);
1569 :
1570 : //Default options
1571 231 : bool fRescan = true;
1572 :
1573 231 : if (!mainRequest.params[1].isNull()) {
1574 96 : const UniValue& options = mainRequest.params[1];
1575 :
1576 96 : if (options.exists("rescan")) {
1577 96 : fRescan = options["rescan"].get_bool();
1578 96 : }
1579 96 : }
1580 :
1581 231 : WalletRescanReserver reserver(*pwallet);
1582 231 : if (fRescan && !reserver.reserve()) {
1583 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1584 : }
1585 :
1586 231 : int64_t now = 0;
1587 231 : bool fRunScan = false;
1588 231 : int64_t nLowestTimestamp = 0;
1589 231 : UniValue response(UniValue::VARR);
1590 : {
1591 231 : LOCK(pwallet->cs_wallet);
1592 :
1593 : // Check all requests are watchonly
1594 231 : bool is_watchonly{true};
1595 327 : for (size_t i = 0; i < requests.size(); ++i) {
1596 237 : const UniValue& request = requests[i];
1597 237 : if (!request.exists("watchonly") || !request["watchonly"].get_bool()) {
1598 141 : is_watchonly = false;
1599 141 : break;
1600 : }
1601 96 : }
1602 : // Wallet does not need to be unlocked if all requests are watchonly
1603 231 : if (!is_watchonly) EnsureWalletIsUnlocked(wallet);
1604 :
1605 : // Verify all timestamps are present before importing any keys.
1606 229 : CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now)));
1607 467 : for (const UniValue& data : requests.getValues()) {
1608 242 : GetImportTimestamp(data, now);
1609 : }
1610 :
1611 225 : const int64_t minimumTimestamp = 1;
1612 :
1613 463 : for (const UniValue& data : requests.getValues()) {
1614 238 : const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
1615 238 : const UniValue result = ProcessImport(*pwallet, data, timestamp);
1616 238 : response.push_back(result);
1617 :
1618 238 : if (!fRescan) {
1619 36 : continue;
1620 : }
1621 :
1622 : // If at least one request was successful then allow rescan.
1623 202 : if (result["success"].get_bool()) {
1624 168 : fRunScan = true;
1625 168 : }
1626 :
1627 : // Get the lowest timestamp.
1628 202 : if (timestamp < nLowestTimestamp) {
1629 129 : nLowestTimestamp = timestamp;
1630 129 : }
1631 238 : }
1632 231 : }
1633 225 : if (fRescan && fRunScan && requests.size()) {
1634 155 : int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
1635 : {
1636 155 : LOCK(pwallet->cs_wallet);
1637 155 : pwallet->ReacceptWalletTransactions();
1638 155 : }
1639 :
1640 155 : if (pwallet->IsAbortingRescan()) {
1641 0 : throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
1642 : }
1643 155 : if (scannedTime > nLowestTimestamp) {
1644 1 : std::vector<UniValue> results = response.getValues();
1645 1 : response.clear();
1646 1 : response.setArray();
1647 1 : size_t i = 0;
1648 3 : for (const UniValue& request : requests.getValues()) {
1649 : // If key creation date is within the successfully scanned
1650 : // range, or if the import result already has an error set, let
1651 : // the result stand unmodified. Otherwise replace the result
1652 : // with an error message.
1653 2 : if (scannedTime <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
1654 1 : response.push_back(results.at(i));
1655 1 : } else {
1656 1 : UniValue result = UniValue(UniValue::VOBJ);
1657 1 : result.pushKV("success", UniValue(false));
1658 1 : result.pushKV(
1659 1 : "error",
1660 1 : JSONRPCError(
1661 : RPC_MISC_ERROR,
1662 1 : strprintf("Rescan failed for key with creation timestamp %d. There was an error reading a "
1663 : "block from time %d, which is after or within %d seconds of key creation, and "
1664 : "could contain transactions pertaining to the key. As a result, transactions "
1665 : "and coins using this key may not appear in the wallet. This error could be "
1666 : "caused by pruning or data corruption (see dashd log for details) and could "
1667 : "be dealt with by downloading and rescanning the relevant blocks (see -reindex "
1668 : "and -rescan options).",
1669 1 : GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
1670 1 : response.push_back(std::move(result));
1671 1 : }
1672 2 : ++i;
1673 : }
1674 1 : }
1675 155 : }
1676 :
1677 225 : return response;
1678 239 : },
1679 : };
1680 0 : }
1681 :
1682 468 : static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
1683 : {
1684 468 : UniValue warnings(UniValue::VARR);
1685 468 : UniValue result(UniValue::VOBJ);
1686 :
1687 : try {
1688 468 : if (!data.exists("desc")) {
1689 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
1690 : }
1691 :
1692 466 : const std::string& descriptor = data["desc"].get_str();
1693 466 : const bool active = data.exists("active") ? data["active"].get_bool() : false;
1694 466 : const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
1695 466 : const std::string& label = data.exists("label") ? data["label"].get_str() : "";
1696 :
1697 : // Parse descriptor string
1698 466 : FlatSigningProvider keys;
1699 466 : std::string error;
1700 466 : auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true);
1701 466 : if (!parsed_desc) {
1702 16 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
1703 : }
1704 :
1705 : // Range check
1706 450 : int64_t range_start = 0, range_end = 1, next_index = 0;
1707 753 : if (!parsed_desc->IsRange() && data.exists("range")) {
1708 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
1709 448 : } else if (parsed_desc->IsRange()) {
1710 147 : if (data.exists("range")) {
1711 101 : auto range = ParseDescriptorRange(data["range"]);
1712 91 : range_start = range.first;
1713 91 : range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive
1714 91 : } else {
1715 46 : warnings.push_back("Range not given, using default keypool range");
1716 46 : range_start = 0;
1717 46 : range_end = gArgs.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE);
1718 : }
1719 137 : next_index = range_start;
1720 :
1721 137 : if (data.exists("next_index")) {
1722 38 : next_index = data["next_index"].getInt<int64_t>();
1723 : // bound checks
1724 38 : if (next_index < range_start || next_index >= range_end) {
1725 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range");
1726 : }
1727 38 : }
1728 137 : }
1729 :
1730 : // Active descriptors must be ranged
1731 438 : if (active && !parsed_desc->IsRange()) {
1732 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
1733 : }
1734 :
1735 : // Ranged descriptors should not have a label
1736 527 : if (data.exists("range") && data.exists("label")) {
1737 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label");
1738 : }
1739 :
1740 : // Internal addresses should not have a label either
1741 482 : if (internal && data.exists("label")) {
1742 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
1743 : }
1744 :
1745 : // Combo descriptor check
1746 432 : if (active && !parsed_desc->IsSingleType()) {
1747 2 : throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active");
1748 : }
1749 :
1750 : // If the wallet disabled private keys, abort if private keys exist
1751 430 : if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
1752 6 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
1753 : }
1754 :
1755 : // Need to ExpandPrivate to check if private keys are available for all pubkeys
1756 424 : FlatSigningProvider expand_keys;
1757 424 : std::vector<CScript> scripts;
1758 424 : if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
1759 2 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
1760 : }
1761 422 : parsed_desc->ExpandPrivate(0, keys, expand_keys);
1762 :
1763 : // Check if all private keys are provided
1764 422 : bool have_all_privkeys = !expand_keys.keys.empty();
1765 730 : for (const auto& entry : expand_keys.origins) {
1766 415 : const CKeyID& key_id = entry.first;
1767 415 : CKey key;
1768 415 : if (!expand_keys.GetKey(key_id, key)) {
1769 107 : have_all_privkeys = false;
1770 107 : break;
1771 : }
1772 415 : }
1773 :
1774 : // If private keys are enabled, check some things.
1775 422 : if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1776 304 : if (keys.keys.empty()) {
1777 2 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
1778 : }
1779 302 : if (!have_all_privkeys) {
1780 12 : warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
1781 12 : }
1782 302 : }
1783 :
1784 420 : WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
1785 :
1786 : // Check if the wallet already contains the descriptor
1787 420 : auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
1788 420 : if (existing_spk_manager) {
1789 34 : if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
1790 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, error);
1791 : }
1792 28 : }
1793 :
1794 : // Add descriptor to the wallet
1795 414 : auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, internal);
1796 414 : if (spk_manager == nullptr) {
1797 0 : throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
1798 : }
1799 :
1800 : // Set descriptor as active if necessary
1801 414 : if (active) {
1802 101 : if (!w_desc.descriptor->GetOutputType()) {
1803 0 : warnings.push_back("Unknown output type, cannot set descriptor to active.");
1804 0 : } else {
1805 101 : wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), internal);
1806 : }
1807 101 : } else {
1808 313 : if (w_desc.descriptor->GetOutputType()) {
1809 311 : wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), internal);
1810 311 : }
1811 : }
1812 :
1813 414 : result.pushKV("success", UniValue(true));
1814 468 : } catch (const UniValue& e) {
1815 54 : result.pushKV("success", UniValue(false));
1816 54 : result.pushKV("error", e);
1817 54 : }
1818 468 : if (warnings.size()) result.pushKV("warnings", warnings);
1819 468 : return result;
1820 522 : }
1821 :
1822 3325 : RPCHelpMan importdescriptors() {
1823 6650 : return RPCHelpMan{"importdescriptors",
1824 3325 : "\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"
1825 : "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
1826 : "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
1827 : "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
1828 6650 : {
1829 6650 : {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
1830 6650 : {
1831 6650 : {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
1832 26600 : {
1833 3325 : {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
1834 3325 : {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
1835 3325 : {"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"},
1836 3325 : {"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"},
1837 6650 : {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
1838 : " Use the string \"now\" to substitute the current synced blockchain time.\n"
1839 : " \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
1840 : " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
1841 : " of all descriptors being imported will be scanned as well as the mempool.",
1842 3325 : /*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"}
1843 : },
1844 3325 : {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
1845 3325 : {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
1846 : },
1847 : },
1848 : },
1849 3325 : "\"requests\""},
1850 : },
1851 3325 : RPCResult{
1852 3325 : RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
1853 6650 : {
1854 6650 : {RPCResult::Type::OBJ, "", "",
1855 13300 : {
1856 3325 : {RPCResult::Type::BOOL, "success", ""},
1857 6650 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
1858 6650 : {
1859 3325 : {RPCResult::Type::STR, "", ""},
1860 : }},
1861 6650 : {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
1862 6650 : {
1863 3325 : {RPCResult::Type::ELISION, "", "JSONRPC error"},
1864 : }},
1865 : }},
1866 : }
1867 : },
1868 3325 : RPCExamples{
1869 3325 : HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
1870 3325 : "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
1871 3325 : HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my wallet>\" }]'")
1872 : },
1873 3754 : [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue
1874 : {
1875 : // Acquire the wallet
1876 429 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
1877 429 : if (!pwallet) return UniValue::VNULL;
1878 429 : CWallet& wallet{*pwallet};
1879 :
1880 : // Make sure the results are valid at least up to the most recent block
1881 : // the user could have gotten from another RPC command prior to now
1882 429 : wallet.BlockUntilSyncedToCurrentChain();
1883 :
1884 : // Make sure wallet is a descriptor wallet
1885 429 : if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
1886 0 : throw JSONRPCError(RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets");
1887 : }
1888 :
1889 429 : RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ});
1890 :
1891 429 : WalletRescanReserver reserver(*pwallet);
1892 429 : if (!reserver.reserve()) {
1893 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1894 : }
1895 :
1896 429 : const UniValue& requests = main_request.params[0];
1897 429 : const int64_t minimum_timestamp = 1;
1898 429 : int64_t now = 0;
1899 429 : int64_t lowest_timestamp = 0;
1900 429 : bool rescan = false;
1901 429 : UniValue response(UniValue::VARR);
1902 : {
1903 429 : LOCK(pwallet->cs_wallet);
1904 429 : EnsureWalletIsUnlocked(*pwallet);
1905 :
1906 429 : CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
1907 :
1908 : // Get all timestamps and extract the lowest timestamp
1909 897 : for (const UniValue& request : requests.getValues()) {
1910 : // This throws an error if "timestamp" doesn't exist
1911 468 : const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
1912 468 : const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp);
1913 468 : response.push_back(result);
1914 :
1915 468 : if (lowest_timestamp > timestamp ) {
1916 384 : lowest_timestamp = timestamp;
1917 384 : }
1918 :
1919 : // If we know the chain tip, and at least one request was successful then allow rescan
1920 897 : if (!rescan && result["success"].get_bool()) {
1921 379 : rescan = true;
1922 379 : }
1923 468 : }
1924 429 : pwallet->ConnectScriptPubKeyManNotifiers();
1925 429 : }
1926 :
1927 : // Rescan the blockchain using the lowest timestamp
1928 429 : if (rescan) {
1929 379 : int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */);
1930 : {
1931 379 : LOCK(pwallet->cs_wallet);
1932 379 : pwallet->ReacceptWalletTransactions();
1933 379 : }
1934 :
1935 379 : if (pwallet->IsAbortingRescan()) {
1936 0 : throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
1937 : }
1938 :
1939 379 : if (scanned_time > lowest_timestamp) {
1940 0 : std::vector<UniValue> results = response.getValues();
1941 0 : response.clear();
1942 0 : response.setArray();
1943 :
1944 : // Compose the response
1945 0 : for (unsigned int i = 0; i < requests.size(); ++i) {
1946 0 : const UniValue& request = requests.getValues().at(i);
1947 :
1948 : // If the descriptor timestamp is within the successfully scanned
1949 : // range, or if the import result already has an error set, let
1950 : // the result stand unmodified. Otherwise replace the result
1951 : // with an error message.
1952 0 : if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
1953 0 : response.push_back(results.at(i));
1954 0 : } else {
1955 0 : UniValue result = UniValue(UniValue::VOBJ);
1956 0 : result.pushKV("success", UniValue(false));
1957 0 : result.pushKV(
1958 0 : "error",
1959 0 : JSONRPCError(
1960 : RPC_MISC_ERROR,
1961 0 : strprintf("Rescan failed for descriptor with timestamp %d. There was an error reading a "
1962 : "block from time %d, which is after or within %d seconds of key creation, and "
1963 : "could contain transactions pertaining to the desc. As a result, transactions "
1964 : "and coins using this desc may not appear in the wallet. This error could be "
1965 : "caused by pruning or data corruption (see dashd log for details) and could "
1966 : "be dealt with by downloading and rescanning the relevant blocks (see -reindex "
1967 : "and -rescan options).",
1968 0 : GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
1969 0 : response.push_back(std::move(result));
1970 0 : }
1971 0 : }
1972 0 : }
1973 379 : }
1974 :
1975 429 : return response;
1976 429 : },
1977 : };
1978 0 : }
1979 :
1980 2992 : RPCHelpMan listdescriptors()
1981 : {
1982 2992 : return RPCHelpMan{
1983 2992 : "listdescriptors",
1984 2992 : "\nList descriptors imported into a descriptor-enabled wallet.\n",
1985 5984 : {
1986 2992 : {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
1987 : },
1988 8976 : RPCResult{RPCResult::Type::OBJ, "", "", {
1989 2992 : {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
1990 5984 : {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
1991 5984 : {
1992 32912 : {RPCResult::Type::OBJ, "", "", {
1993 2992 : {RPCResult::Type::STR, "desc", "Descriptor string representation"},
1994 2992 : {RPCResult::Type::STR, "mnemonic", "The mnemonic for this descriptor wallet (BIP39, english words). Presented only if private=true and created with a mnemonic"},
1995 2992 : {RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this descriptor wallet (BIP39). Presented only if private=true and created with a mnemonic"},
1996 2992 : {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
1997 2992 : {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
1998 2992 : {RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"},
1999 2992 : {RPCResult::Type::BOOL, "coinjoin", /*optional=*/true, "True if this descriptor is used to generate CoinJoin addresses; defined only if it is True."},
2000 8976 : {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
2001 2992 : {RPCResult::Type::NUM, "", "Range start inclusive"},
2002 2992 : {RPCResult::Type::NUM, "", "Range end inclusive"},
2003 : }},
2004 2992 : {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
2005 2992 : {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
2006 : }},
2007 : }}
2008 : }},
2009 2992 : RPCExamples{
2010 2992 : HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
2011 2992 : + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
2012 : },
2013 3086 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2014 : {
2015 94 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
2016 94 : if (!wallet) return UniValue::VNULL;
2017 :
2018 94 : if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
2019 2 : throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
2020 : }
2021 :
2022 150 : const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
2023 92 : if (priv) {
2024 56 : EnsureWalletIsUnlocked(*wallet);
2025 52 : }
2026 :
2027 88 : LOCK(wallet->cs_wallet);
2028 :
2029 88 : const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans();
2030 :
2031 : struct WalletDescInfo {
2032 : std::string descriptor;
2033 : uint64_t creation_time;
2034 : bool active;
2035 : std::optional<bool> internal;
2036 : std::optional<std::pair<int64_t,int64_t>> range;
2037 : int64_t next_index;
2038 : bool is_coinjoin;
2039 : SecureString mnemonic;
2040 : SecureString mnemonic_passphrase;
2041 : };
2042 :
2043 88 : std::vector<WalletDescInfo> wallet_descriptors;
2044 358 : for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) {
2045 270 : const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
2046 270 : if (!desc_spk_man) {
2047 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type.");
2048 : }
2049 270 : LOCK(desc_spk_man->cs_desc_man);
2050 270 : const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
2051 270 : std::string descriptor;
2052 270 : if (!desc_spk_man->GetDescriptorString(descriptor, priv)) {
2053 2 : throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string.");
2054 : }
2055 268 : SecureString mnemonic;
2056 268 : SecureString mnemonic_passphrase;
2057 268 : if (priv) {
2058 176 : if (!desc_spk_man->GetMnemonicString(mnemonic, mnemonic_passphrase)) {
2059 10 : mnemonic.clear();
2060 10 : mnemonic_passphrase.clear();
2061 10 : }
2062 176 : }
2063 268 : const auto& type = wallet_descriptor.descriptor->GetOutputType();
2064 268 : std::string match = strprintf("/%dh/%sh/4h/0h", BIP32_PURPOSE_FEATURE, Params().ExtCoinType());
2065 : // Fallback: also match old-format descriptors that used apostrophe marker
2066 268 : std::string match_legacy = strprintf("/%d'/%s'/4'/0'", BIP32_PURPOSE_FEATURE, Params().ExtCoinType());
2067 536 : bool is_cj = type != std::nullopt && (descriptor.find(match) != std::string::npos || descriptor.find(match_legacy) != std::string::npos);
2068 :
2069 268 : const bool is_range = wallet_descriptor.descriptor->IsRange();
2070 1072 : wallet_descriptors.push_back({
2071 268 : descriptor,
2072 268 : wallet_descriptor.creation_time,
2073 268 : active_spk_mans.count(desc_spk_man) != 0,
2074 268 : wallet->IsInternalScriptPubKeyMan(desc_spk_man),
2075 268 : is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt,
2076 268 : wallet_descriptor.next_index,
2077 268 : is_cj,
2078 268 : mnemonic,
2079 268 : mnemonic_passphrase,
2080 : });
2081 270 : }
2082 :
2083 355 : std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
2084 269 : return a.descriptor < b.descriptor;
2085 : });
2086 :
2087 86 : UniValue descriptors(UniValue::VARR);
2088 354 : for (const WalletDescInfo& info : wallet_descriptors) {
2089 268 : UniValue spk(UniValue::VOBJ);
2090 268 : spk.pushKV("desc", info.descriptor);
2091 268 : if (!info.mnemonic.empty()) {
2092 142 : spk.pushKV("mnemonic", info.mnemonic);
2093 142 : spk.pushKV("mnemonicpassphrase", info.mnemonic_passphrase);
2094 142 : }
2095 268 : spk.pushKV("timestamp", info.creation_time);
2096 268 : spk.pushKV("active", info.active);
2097 268 : if (info.internal.has_value()) {
2098 144 : spk.pushKV("internal", info.internal.value());
2099 144 : }
2100 268 : if (info.range.has_value()) {
2101 234 : UniValue range(UniValue::VARR);
2102 234 : range.push_back(info.range->first);
2103 234 : range.push_back(info.range->second - 1);
2104 234 : if (info.is_coinjoin) {
2105 72 : spk.pushKV("coinjoin", info.is_coinjoin);
2106 72 : }
2107 234 : spk.pushKV("range", range);
2108 234 : spk.pushKV("next", info.next_index);
2109 234 : spk.pushKV("next_index", info.next_index);
2110 234 : }
2111 268 : descriptors.push_back(spk);
2112 268 : }
2113 :
2114 86 : UniValue response(UniValue::VOBJ);
2115 86 : response.pushKV("wallet_name", wallet->GetName());
2116 86 : response.pushKV("descriptors", descriptors);
2117 :
2118 86 : return response;
2119 98 : },
2120 : };
2121 0 : }
2122 :
2123 3008 : RPCHelpMan backupwallet()
2124 : {
2125 6016 : return RPCHelpMan{"backupwallet",
2126 3008 : "\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n",
2127 6016 : {
2128 3008 : {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
2129 : },
2130 3008 : RPCResult{RPCResult::Type::NONE, "", ""},
2131 3008 : RPCExamples{
2132 3008 : HelpExampleCli("backupwallet", "\"backup.dat\"")
2133 3008 : + HelpExampleRpc("backupwallet", "\"backup.dat\"")
2134 : },
2135 3120 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2136 : {
2137 112 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
2138 112 : if (!pwallet) return UniValue::VNULL;
2139 :
2140 : // Make sure the results are valid at least up to the most recent block
2141 : // the user could have gotten from another RPC command prior to now
2142 112 : pwallet->BlockUntilSyncedToCurrentChain();
2143 :
2144 112 : LOCK(pwallet->cs_wallet);
2145 :
2146 112 : std::string strDest = request.params[0].get_str();
2147 112 : if (!pwallet->BackupWallet(strDest)) {
2148 24 : throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
2149 : }
2150 :
2151 88 : return UniValue::VNULL;
2152 136 : },
2153 : };
2154 0 : }
2155 :
2156 2942 : RPCHelpMan restorewallet()
2157 : {
2158 2942 : return RPCHelpMan{
2159 2942 : "restorewallet",
2160 2942 : "\nRestore and loads a wallet from backup.\n"
2161 : "\nThe rescan is significantly faster if a descriptor wallet is restored"
2162 : "\nand block filters are available (using startup option \"-blockfilterindex=1\").\n",
2163 11768 : {
2164 2942 : {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
2165 2942 : {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
2166 2942 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
2167 : },
2168 2942 : RPCResult{
2169 2942 : RPCResult::Type::OBJ, "", "",
2170 8826 : {
2171 2942 : {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
2172 2942 : {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
2173 : }
2174 : },
2175 2942 : RPCExamples{
2176 2942 : HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
2177 2942 : + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
2178 2942 : + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
2179 2942 : + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
2180 : },
2181 2988 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2182 : {
2183 :
2184 46 : WalletContext& context = EnsureWalletContext(request.context);
2185 :
2186 46 : auto backup_file = fs::u8path(request.params[1].get_str());
2187 :
2188 46 : std::string wallet_name = request.params[0].get_str();
2189 :
2190 46 : std::optional<bool> load_on_start = request.params[2].isNull() ? std::nullopt : std::optional<bool>(request.params[2].get_bool());
2191 :
2192 : DatabaseStatus status;
2193 46 : bilingual_str error;
2194 46 : std::vector<bilingual_str> warnings;
2195 :
2196 46 : const std::shared_ptr<CWallet> wallet = RestoreWallet(context, backup_file, wallet_name, load_on_start, status, error, warnings);
2197 :
2198 46 : HandleWalletError(wallet, status, error);
2199 :
2200 24 : UniValue obj(UniValue::VOBJ);
2201 24 : obj.pushKV("name", wallet->GetName());
2202 24 : obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
2203 :
2204 24 : return obj;
2205 :
2206 46 : },
2207 : };
2208 0 : }
2209 : } // namespace wallet
|