Line data Source code
1 : // Copyright (c) 2009-2014 The Bitcoin developers
2 : // Copyright (c) 2014-2015 The Dash developers
3 : // Copyright (c) 2015-2021 The PIVX Core developers
4 : // Distributed under the MIT software license, see the accompanying
5 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
6 :
7 : #include "bip38.h"
8 : #include "key_io.h"
9 : #include "destination_io.h"
10 : #include "rpc/server.h"
11 : #include "sapling/key_io_sapling.h"
12 : #include "script/script.h"
13 : #include "script/standard.h"
14 : #include "sync.h"
15 : #include "util/system.h"
16 : #include "utilstrencodings.h"
17 : #include "utiltime.h"
18 : #include "wallet/rpcwallet.h"
19 : #include "wallet.h"
20 : #include "validation.h"
21 :
22 : #include <secp256k1.h>
23 : #include <stdint.h>
24 :
25 : #include <boost/algorithm/string.hpp>
26 : #include <boost/date_time/posix_time/posix_time.hpp>
27 :
28 : #include <univalue.h>
29 :
30 :
31 1867 : int64_t static DecodeDumpTime(const std::string& str)
32 : {
33 1867 : static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
34 1867 : static const std::locale loc(std::locale::classic(),
35 1871 : new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ"));
36 3734 : std::istringstream iss(str);
37 1867 : iss.imbue(loc);
38 1867 : boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
39 1867 : iss >> ptime;
40 1867 : if (ptime.is_not_a_date_time())
41 : return 0;
42 1867 : return (ptime - epoch).total_seconds();
43 : }
44 :
45 77 : std::string static EncodeDumpString(const std::string& str)
46 : {
47 77 : std::stringstream ret;
48 84 : for (unsigned char c : str) {
49 7 : if (c <= 32 || c >= 128 || c == '%') {
50 0 : ret << '%' << HexStr(Span<const unsigned char>(&c, 1));
51 : } else {
52 14 : ret << c;
53 : }
54 : }
55 77 : return ret.str();
56 : }
57 :
58 15 : std::string DecodeDumpString(const std::string& str)
59 : {
60 15 : std::stringstream ret;
61 15 : for (unsigned int pos = 0; pos < str.length(); pos++) {
62 0 : unsigned char c = str[pos];
63 0 : if (c == '%' && pos + 2 < str.length()) {
64 0 : c = (((str[pos + 1] >> 6) * 9 + ((str[pos + 1] - '0') & 15)) << 4) |
65 0 : ((str[pos + 2] >> 6) * 9 + ((str[pos + 2] - '0') & 15));
66 0 : pos += 2;
67 : }
68 0 : ret << c;
69 : }
70 15 : return ret.str();
71 : }
72 :
73 1892 : bool IsStakingDerPath(KeyOriginInfo keyOrigin)
74 : {
75 1892 : return keyOrigin.path.size() > 3 && keyOrigin.path[3] == (2 | BIP32_HARDENED_KEY_LIMIT);
76 : }
77 :
78 9 : UniValue importprivkey(const JSONRPCRequest& request)
79 : {
80 9 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
81 :
82 9 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
83 0 : return NullUniValue;
84 :
85 9 : if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
86 0 : throw std::runtime_error(
87 : "importprivkey \"privkey\" ( \"label\" rescan is_staking_address )\n"
88 0 : "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" +
89 0 : HelpRequiringPassphrase(pwallet) + "\n"
90 : "\nArguments:\n"
91 : "1. \"privkey\" (string, required) The private key (see dumpprivkey)\n"
92 : "2. \"label\" (string, optional, default=\"\") An optional label\n"
93 : "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
94 : "4. is_staking_address (boolean, optional, default=false) Whether this key refers to a (cold) staking address\n"
95 : "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
96 : "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"
97 : "\nExamples:\n"
98 0 : "\nDump a private key\n" +
99 0 : HelpExampleCli("dumpprivkey", "\"myaddress\"") +
100 0 : "\nImport the private key with rescan\n" +
101 0 : HelpExampleCli("importprivkey", "\"mykey\"") +
102 0 : "\nImport using a label and without rescan\n" +
103 0 : HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
104 0 : "\nAs a JSON-RPC call\n" +
105 0 : HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false"));
106 :
107 18 : const std::string strSecret = request.params[0].get_str();
108 25 : const std::string strLabel = (request.params.size() > 1 ? request.params[1].get_str() : "");
109 9 : const bool fRescan = (request.params.size() > 2 ? request.params[2].get_bool() : true);
110 :
111 18 : WalletRescanReserver reserver(pwallet);
112 9 : if (fRescan && !reserver.reserve()) {
113 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
114 : }
115 :
116 9 : const bool fStakingAddress = (request.params.size() > 3 ? request.params[3].get_bool() : false);
117 :
118 18 : CKey key = KeyIO::DecodeSecret(strSecret);
119 9 : if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
120 :
121 9 : CPubKey pubkey = key.GetPubKey();
122 9 : assert(key.VerifyPubKey(pubkey));
123 9 : CKeyID vchAddress = pubkey.GetID();
124 9 : {
125 18 : LOCK2(cs_main, pwallet->cs_wallet);
126 9 : EnsureWalletIsUnlocked(pwallet);
127 :
128 9 : pwallet->MarkDirty();
129 18 : pwallet->SetAddressBook(vchAddress, strLabel, (
130 : fStakingAddress ?
131 : AddressBook::AddressBookPurpose::COLD_STAKING :
132 : AddressBook::AddressBookPurpose::RECEIVE));
133 :
134 : // Don't throw error in case a key is already there
135 9 : if (pwallet->HaveKey(vchAddress))
136 0 : return NullUniValue;
137 :
138 : // whenever a key is imported, we need to scan the whole chain
139 9 : pwallet->UpdateTimeFirstKey(1);
140 9 : pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
141 :
142 9 : if (!pwallet->AddKeyPubKey(key, pubkey))
143 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
144 : }
145 9 : if (fRescan) {
146 7 : pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
147 : }
148 :
149 9 : return NullUniValue;
150 : }
151 :
152 0 : UniValue abortrescan(const JSONRPCRequest& request)
153 : {
154 0 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
155 :
156 0 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
157 0 : return NullUniValue;
158 :
159 0 : if (request.fHelp || request.params.size() > 0)
160 0 : throw std::runtime_error(
161 : "abortrescan\n"
162 : "\nStops current wallet rescan triggered e.g. by an importprivkey call.\n"
163 : "\nExamples:\n"
164 : "\nImport a private key\n"
165 0 : + HelpExampleCli("importprivkey", "\"mykey\"") +
166 : "\nAbort the running wallet rescan\n"
167 0 : + HelpExampleCli("abortrescan", "") +
168 : "\nAs a JSON-RPC call\n"
169 0 : + HelpExampleRpc("abortrescan", "")
170 0 : );
171 :
172 0 : if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
173 0 : pwallet->AbortRescan();
174 0 : return true;
175 : }
176 :
177 : static void ImportAddress(CWallet* const pwallet, const CTxDestination& dest, const std::string& strLabel, const std::string& strPurpose);
178 :
179 19 : static void ImportScript(CWallet* const pwallet, const CScript& script, const std::string& strLabel, bool isRedeemScript)
180 : {
181 19 : if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE)
182 0 : throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
183 :
184 19 : pwallet->MarkDirty();
185 :
186 19 : if (!pwallet->HaveWatchOnly(script) && !pwallet->AddWatchOnly(script))
187 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
188 :
189 19 : if (isRedeemScript) {
190 1 : if (!pwallet->HaveCScript(script) && !pwallet->AddCScript(script))
191 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
192 2 : ImportAddress(pwallet, CScriptID(script), strLabel, "receive");
193 : }
194 19 : }
195 :
196 14 : static void ImportAddress(CWallet* const pwallet, const CTxDestination& dest, const std::string& strLabel, const std::string& strPurpose)
197 : {
198 14 : CScript script = GetScriptForDestination(dest);
199 14 : ImportScript(pwallet, script, strLabel, false);
200 : // add to address book or update label
201 14 : if (IsValidDestination(dest)) {
202 28 : pwallet->SetAddressBook(dest, strLabel, strPurpose);
203 : }
204 14 : }
205 :
206 10 : UniValue importaddress(const JSONRPCRequest& request)
207 : {
208 10 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
209 :
210 10 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
211 0 : return NullUniValue;
212 :
213 10 : if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
214 0 : throw std::runtime_error(
215 : "importaddress \"script\" ( \"label\" rescan )\n"
216 : "\nAdds a script (in hex), or address, that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
217 : "\nArguments:\n"
218 : "1. \"script\" (string, required) hex-encoded script (or address)\n"
219 : "2. \"label\" (string, optional, default=\"\") An optional label\n"
220 : "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
221 : "4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n"
222 : "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
223 : "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"
224 : "\nExamples:\n"
225 0 : "\nImport a script with rescan\n" +
226 0 : HelpExampleCli("importaddress", "\"myscript\"") +
227 0 : "\nImport using a label without rescan\n" +
228 0 : HelpExampleCli("importaddress", "\"myscript\" \"testing\" false") +
229 0 : "\nAs a JSON-RPC call\n" +
230 0 : HelpExampleRpc("importaddress", "\"myscript\", \"testing\", false"));
231 :
232 28 : const std::string strLabel = (request.params.size() > 1 ? request.params[1].get_str() : "");
233 : // Whether to perform rescan after import
234 10 : const bool fRescan = (request.params.size() > 2 ? request.params[2].get_bool() : true);
235 :
236 20 : WalletRescanReserver reserver(pwallet);
237 10 : if (fRescan && !reserver.reserve()) {
238 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
239 : }
240 :
241 : // Whether to import a p2sh version, too
242 10 : const bool fP2SH = (request.params.size() > 3 ? request.params[3].get_bool() : false);
243 :
244 10 : {
245 20 : LOCK2(cs_main, pwallet->cs_wallet);
246 :
247 10 : bool isStaking = false;
248 10 : bool isExchange = false;
249 20 : CTxDestination dest = DecodeDestination(request.params[0].get_str(), isStaking, isExchange);
250 :
251 10 : if (IsValidDestination(dest)) {
252 9 : if (fP2SH)
253 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
254 14 : ImportAddress(pwallet, dest, strLabel, isStaking ?
255 : AddressBook::AddressBookPurpose::COLD_STAKING :
256 : AddressBook::AddressBookPurpose::RECEIVE);
257 :
258 1 : } else if (IsHex(request.params[0].get_str())) {
259 2 : std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
260 2 : ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH);
261 : } else {
262 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address or script");
263 : }
264 : }
265 10 : if (fRescan) {
266 7 : pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
267 7 : pwallet->ReacceptWalletTransactions();
268 : }
269 :
270 10 : return NullUniValue;
271 : }
272 :
273 4 : UniValue importpubkey(const JSONRPCRequest& request)
274 : {
275 4 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
276 :
277 4 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
278 0 : return NullUniValue;
279 :
280 4 : if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
281 0 : throw std::runtime_error(
282 : "importpubkey \"pubkey\" ( \"label\" rescan )\n"
283 : "\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"
284 : "\nArguments:\n"
285 : "1. \"pubkey\" (string, required) The hex-encoded public key\n"
286 : "2. \"label\" (string, optional, default=\"\") An optional label\n"
287 : "3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
288 : "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
289 : "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"
290 : "\nExamples:\n"
291 : "\nImport a public key with rescan\n"
292 0 : + HelpExampleCli("importpubkey", "\"mypubkey\"") +
293 : "\nImport using a label without rescan\n"
294 0 : + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
295 : "\nAs a JSON-RPC call\n"
296 0 : + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")
297 0 : );
298 :
299 12 : const std::string strLabel = (request.params.size() > 1 ? request.params[1].get_str() : "");
300 : // Whether to perform rescan after import
301 4 : const bool fRescan = (request.params.size() > 2 ? request.params[2].get_bool() : true);
302 :
303 8 : WalletRescanReserver reserver(pwallet);
304 4 : if (fRescan && !reserver.reserve()) {
305 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
306 : }
307 :
308 4 : if (!IsHex(request.params[0].get_str()))
309 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
310 8 : std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
311 4 : CPubKey pubKey(data.begin(), data.end());
312 4 : if (!pubKey.IsFullyValid())
313 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
314 :
315 4 : {
316 8 : LOCK2(cs_main, pwallet->cs_wallet);
317 :
318 8 : ImportAddress(pwallet, pubKey.GetID(), strLabel, "receive");
319 8 : ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
320 : }
321 4 : if (fRescan) {
322 2 : pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
323 2 : pwallet->ReacceptWalletTransactions();
324 : }
325 :
326 4 : return NullUniValue;
327 : }
328 :
329 : // TODO: Needs further review over the HD flow, staking addresses and multisig import.
330 4 : UniValue importwallet(const JSONRPCRequest& request)
331 : {
332 4 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
333 :
334 4 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
335 0 : return NullUniValue;
336 :
337 4 : if (request.fHelp || request.params.size() != 1)
338 0 : throw std::runtime_error(
339 : "importwallet \"filename\"\n"
340 0 : "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup.\n" +
341 0 : HelpRequiringPassphrase(pwallet) + "\n"
342 : "\nArguments:\n"
343 : "1. \"filename\" (string, required) The wallet file\n"
344 :
345 : "\nExamples:\n"
346 0 : "\nDump the wallet\n" +
347 0 : HelpExampleCli("dumpwallet", "\"test\"") +
348 0 : "\nImport the wallet\n" +
349 0 : HelpExampleCli("importwallet", "\"test\"") +
350 0 : "\nImport using the json rpc call\n" +
351 0 : HelpExampleRpc("importwallet", "\"test\""));
352 :
353 8 : fsbridge::ifstream file;
354 8 : file.open(request.params[0].get_str(), std::ios::in | std::ios::ate);
355 4 : if (!file.is_open()) {
356 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
357 : }
358 :
359 8 : WalletRescanReserver reserver(pwallet);
360 4 : if (!reserver.reserve()) {
361 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
362 : }
363 :
364 4 : int64_t nTimeBegin = 0;
365 4 : bool fGood = true;
366 4 : {
367 8 : LOCK2(cs_main, pwallet->cs_wallet);
368 4 : EnsureWalletIsUnlocked(pwallet);
369 :
370 8 : nTimeBegin = chainActive.Tip()->GetBlockTime();
371 4 : int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
372 4 : file.seekg(0, file.beg);
373 :
374 8 : pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
375 988 : while (file.good()) {
376 2893 : pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
377 1918 : std::string line;
378 984 : std::getline(file, line);
379 984 : if (line.empty() || line[0] == '#')
380 100 : continue;
381 :
382 1868 : std::vector<std::string> vstr;
383 934 : boost::split(vstr, line, boost::is_any_of(" "));
384 934 : if (vstr.size() < 2)
385 0 : continue;
386 :
387 : // Sapling keys
388 : // Let's see if the address is a valid PIVX spending key
389 934 : if (pwallet->HasSaplingSPKM()) {
390 1866 : libzcash::SpendingKey spendingkey = KeyIO::DecodeSpendingKey(vstr[0]);
391 933 : int64_t nTime = DecodeDumpTime(vstr[1]);
392 933 : if (IsValidSpendingKey(spendingkey)) {
393 0 : libzcash::SaplingExtendedSpendingKey saplingSpendingKey = *boost::get<libzcash::SaplingExtendedSpendingKey>(&spendingkey);
394 0 : auto addResult = pwallet->GetSaplingScriptPubKeyMan()->AddSpendingKeyToWallet(
395 0 : Params().GetConsensus(), saplingSpendingKey, nTime);
396 0 : if (addResult == KeyAlreadyExists) {
397 0 : LogPrint(BCLog::SAPLING, "Skipping import of shielded addr (key already present)\n");
398 0 : } else if (addResult == KeyNotAdded) {
399 : // Something went wrong
400 0 : fGood = false;
401 : }
402 0 : continue;
403 : }
404 : }
405 :
406 1868 : CKey key = KeyIO::DecodeSecret(vstr[0]);
407 934 : if (!key.IsValid())
408 0 : continue;
409 934 : CPubKey pubkey = key.GetPubKey();
410 934 : assert(key.VerifyPubKey(pubkey));
411 934 : CKeyID keyid = pubkey.GetID();
412 934 : if (pwallet->HaveKey(keyid)) {
413 0 : LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
414 0 : continue;
415 : }
416 934 : int64_t nTime = DecodeDumpTime(vstr[1]);
417 1868 : std::string strLabel;
418 934 : bool fLabel = true;
419 1868 : for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
420 1868 : const std::string& type = vstr[nStr];
421 1868 : if (boost::algorithm::starts_with(type, "#"))
422 : break;
423 934 : if (type == "change=1")
424 : fLabel = false;
425 915 : else if (type == "reserve=1")
426 : fLabel = false;
427 18 : else if (type == "hdseed")
428 0 : fLabel = false;
429 934 : if (boost::algorithm::starts_with(type, "label=")) {
430 15 : strLabel = DecodeDumpString(vstr[nStr].substr(6));
431 15 : fLabel = true;
432 : }
433 : }
434 1868 : LogPrintf("Importing %s...\n", EncodeDestination(keyid));
435 934 : if (!pwallet->AddKeyPubKey(key, pubkey)) {
436 0 : fGood = false;
437 0 : continue;
438 : }
439 934 : pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
440 934 : if (fLabel) // TODO: This is not entirely true.. needs to be reviewed properly.
441 36 : pwallet->SetAddressBook(keyid, strLabel, AddressBook::AddressBookPurpose::RECEIVE);
442 937 : nTimeBegin = std::min(nTimeBegin, nTime);
443 : }
444 4 : file.close();
445 4 : pwallet->ShowProgress("", 100); // hide progress dialog in GUI
446 4 : pwallet->UpdateTimeFirstKey(nTimeBegin);
447 : }
448 4 : pwallet->RescanFromTime(nTimeBegin, reserver, false /* update */);
449 4 : pwallet->MarkDirty();
450 :
451 4 : if (!fGood)
452 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
453 :
454 4 : return NullUniValue;
455 : }
456 :
457 53 : UniValue dumpprivkey(const JSONRPCRequest& request)
458 : {
459 53 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
460 :
461 53 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
462 0 : return NullUniValue;
463 :
464 53 : if (request.fHelp || request.params.size() != 1)
465 0 : throw std::runtime_error(
466 : "dumpprivkey \"address\"\n"
467 : "\nReveals the private key corresponding to 'address'.\n"
468 0 : "Then the importprivkey can be used with this output\n" +
469 0 : HelpRequiringPassphrase(pwallet) + "\n"
470 :
471 : "\nArguments:\n"
472 : "1. \"address\" (string, required) The pivx address for the private key\n"
473 :
474 : "\nResult:\n"
475 : "\"key\" (string) The private key\n"
476 :
477 0 : "\nExamples:\n" +
478 0 : HelpExampleCli("dumpprivkey", "\"myaddress\"") + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\""));
479 :
480 156 : LOCK2(cs_main, pwallet->cs_wallet);
481 :
482 53 : EnsureWalletIsUnlocked(pwallet);
483 :
484 100 : std::string strAddress = request.params[0].get_str();
485 100 : CTxDestination dest = DecodeDestination(strAddress);
486 50 : if (!IsValidDestination(dest))
487 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address");
488 50 : const CKeyID* keyID = boost::get<CKeyID>(&dest);
489 50 : if (!keyID)
490 0 : throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
491 100 : CKey vchSecret;
492 50 : if (!pwallet->GetKey(*keyID, vchSecret))
493 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
494 100 : return KeyIO::EncodeSecret(vchSecret);
495 : }
496 :
497 11 : UniValue dumpwallet(const JSONRPCRequest& request)
498 : {
499 11 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
500 :
501 11 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
502 0 : return NullUniValue;
503 :
504 11 : if (request.fHelp || request.params.size() != 1)
505 0 : throw std::runtime_error(
506 : "dumpwallet \"filename\"\n"
507 : "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n"
508 : "Imported scripts are not currently included in wallet dumps, these must be backed up separately.\n"
509 : "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"
510 0 : "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n" +
511 0 : HelpRequiringPassphrase(pwallet) + "\n"
512 : "\nArguments:\n"
513 : "1. \"filename\" (string, required) The filename\n"
514 :
515 : "\nResult:\n"
516 : "{\n"
517 : " \"filename\": \"xxxx\", (string) The full path to the wallet dump file.\n"
518 : " \"warning\": \"xxxx\" (string) A warning message about the exact contents of this file.\n"
519 : "}\n"
520 :
521 0 : "\nExamples:\n" +
522 0 : HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\""));
523 :
524 11 : if (request.params[0].get_str().find("bug") != std::string::npos ||
525 10 : request.params[0].get_str().find("log") != std::string::npos) {
526 4 : throw JSONRPCError(RPC_MISC_ERROR, "Scam attempt detected!");
527 : }
528 :
529 : // Make sure the results are valid at least up to the most recent block
530 : // the user could have gotten from another RPC command prior to now
531 9 : pwallet->BlockUntilSyncedToCurrentChain();
532 :
533 26 : LOCK2(cs_main, pwallet->cs_wallet);
534 :
535 9 : EnsureWalletIsUnlocked(pwallet);
536 :
537 9 : ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan();
538 :
539 18 : fs::path filepath = request.params[0].get_str().c_str();
540 28 : filepath = fs::absolute(filepath);
541 :
542 : /* Prevent arbitrary files from being overwritten. There have been reports
543 : * that users have overwritten wallet files this way:
544 : * https://github.com/bitcoin/bitcoin/issues/9934
545 : * It may also avoid other security issues.
546 : */
547 17 : if (fs::exists(filepath)) {
548 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are sure this is what you want, move it out of the way first");
549 : }
550 :
551 16 : fsbridge::ofstream file;
552 8 : file.open(filepath);
553 8 : if (!file.is_open())
554 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
555 :
556 16 : std::map<CKeyID, int64_t> mapKeyBirth;
557 8 : pwallet->GetKeyBirthTimes(mapKeyBirth);
558 8 : const std::map<CKeyID, int64_t>& mapKeyPool = spk_man->GetAllReserveKeys();
559 :
560 :
561 : // sort time/key pairs
562 16 : std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
563 1981 : for (const auto& entry : mapKeyBirth) {
564 1973 : vKeyBirth.emplace_back(entry.second, entry.first);
565 : }
566 8 : mapKeyBirth.clear();
567 8 : std::sort(vKeyBirth.begin(), vKeyBirth.end());
568 :
569 8 : CBlockIndex* tip = chainActive.Tip();
570 : // produce output
571 16 : file << strprintf("# Wallet dump created by PIVX %s\n", CLIENT_BUILD);
572 24 : file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime()));
573 8 : if (tip) {
574 16 : file << strprintf("# * Best block at time of backup was %i (%s),\n", tip->nHeight,
575 16 : tip->GetBlockHash().ToString());
576 32 : file << strprintf("# mined on %s\n", FormatISO8601DateTime(tip->GetBlockTime()));
577 : } else {
578 0 : file << "# Missing tip information\n";
579 : }
580 8 : file << "\n";
581 :
582 : // Add the base58check encoded extended master if the wallet uses HD
583 8 : CKeyID seed_id = spk_man->GetHDChain().GetID();
584 54 : if (!seed_id.IsNull())
585 : {
586 12 : CKey seed;
587 6 : if (pwallet->GetKey(seed_id, seed)) {
588 12 : CExtKey masterKey;
589 12 : masterKey.SetSeed(seed.begin(), seed.size());
590 :
591 18 : file << "# extended private masterkey: " << KeyIO::EncodeExtKey(masterKey) << "\n\n";
592 : }
593 : }
594 :
595 1981 : for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
596 1973 : const CKeyID& keyid = it->second;
597 3946 : std::string strTime = FormatISO8601DateTime(it->first);
598 3946 : CKey key;
599 1973 : if (pwallet->GetKey(keyid, key)) {
600 1973 : const CKeyMetadata& metadata = pwallet->mapKeyMetadata[keyid];
601 1892 : std::string strAddr = EncodeDestination(keyid, (metadata.HasKeyOrigin() && IsStakingDerPath(metadata.key_origin) ?
602 : CChainParams::STAKING_ADDRESS :
603 7811 : CChainParams::PUBKEY_ADDRESS));
604 :
605 5919 : file << strprintf("%s %s ", KeyIO::EncodeSecret(key), strTime);
606 1973 : if (pwallet->HasAddressBook(keyid)) {
607 154 : file << strprintf("label=%s", EncodeDumpString(pwallet->GetNameForAddressBookEntry(keyid)));
608 1896 : } else if (keyid == seed_id) {
609 6 : file << "hdseed=1";
610 1890 : } else if (mapKeyPool.count(keyid)) {
611 1499 : file << "reserve=1";
612 : } else {
613 391 : file << "change=1";
614 : }
615 9694 : file << strprintf(" # addr=%s%s\n", strAddr, (metadata.HasKeyOrigin() ? " hdkeypath="+metadata.key_origin.pathToString() : ""));
616 : }
617 : }
618 :
619 : // Sapling
620 8 : file << "# Sapling keys\n";
621 8 : file << "\n";
622 16 : std::set<libzcash::SaplingPaymentAddress> saplingAddresses;
623 8 : pwallet->GetSaplingPaymentAddresses(saplingAddresses);
624 8 : file << "\n";
625 8 : for (const auto& addr : saplingAddresses) {
626 0 : libzcash::SaplingExtendedSpendingKey extsk;
627 0 : if (pwallet->GetSaplingExtendedSpendingKey(addr, extsk)) {
628 0 : auto ivk = extsk.expsk.full_viewing_key().in_viewing_key();
629 0 : CKeyMetadata keyMeta = pwallet->GetSaplingScriptPubKeyMan()->mapSaplingZKeyMetadata[ivk];
630 0 : std::string strTime = FormatISO8601DateTime(keyMeta.nCreateTime);
631 : // Keys imported with importsaplingkey do not have key origin metadata
632 0 : file << strprintf("%s %s # shielded_addr=%s%s\n",
633 0 : KeyIO::EncodeSpendingKey(extsk),
634 : strTime,
635 0 : KeyIO::EncodePaymentAddress(addr),
636 0 : (keyMeta.HasKeyOrigin() ? " hdkeypath=" + keyMeta.key_origin.pathToString() : "")
637 0 : );
638 : }
639 : }
640 :
641 8 : file << "\n";
642 8 : file << "# End of dump\n";
643 8 : file.close();
644 :
645 16 : UniValue reply(UniValue::VOBJ);
646 8 : reply.pushKV("filename", filepath.string());
647 16 : reply.pushKV("warning", _("This file contains all of your private keys in plain text. DO NOT send this file to anyone!"));
648 :
649 8 : return reply;
650 : }
651 :
652 32 : static UniValue processImport(CWallet* const pwallet, const UniValue& data, const int64_t timestamp)
653 : {
654 32 : try {
655 32 : bool success = false;
656 :
657 : // Required fields.
658 32 : const UniValue& scriptPubKey = data["scriptPubKey"];
659 :
660 : // Should have script or JSON with "address".
661 75 : if (!(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address")) && !(scriptPubKey.getType() == UniValue::VSTR)) {
662 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scriptPubKey");
663 : }
664 :
665 : // Optional fields.
666 102 : const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
667 110 : const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
668 82 : const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
669 71 : const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
670 75 : const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
671 82 : const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : "";
672 :
673 32 : bool isScript = scriptPubKey.getType() == UniValue::VSTR;
674 32 : bool isP2SH = strRedeemScript.length() > 0;
675 65 : const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
676 :
677 : // Parse the output.
678 64 : CScript script;
679 64 : CTxDestination dest;
680 :
681 32 : if (!isScript) {
682 44 : dest = DecodeDestination(output);
683 22 : if (!IsValidDestination(dest)) {
684 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
685 : }
686 21 : script = GetScriptForDestination(dest);
687 : } else {
688 10 : if (!IsHex(output)) {
689 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey");
690 : }
691 20 : std::vector<unsigned char> vData(ParseHex(output));
692 10 : script = CScript(vData.begin(), vData.end());
693 : }
694 :
695 : // Watchonly and private keys
696 31 : if (watchOnly && keys.size()) {
697 4 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between watchonly and keys");
698 : }
699 :
700 : // Internal + Label
701 54 : if (internal && data.exists("label")) {
702 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Incompatibility found between internal and label");
703 : }
704 :
705 : // Not having Internal + Script
706 29 : if (!internal && isScript) {
707 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set for hex scriptPubKey");
708 : }
709 :
710 : // Keys / PubKeys size check.
711 26 : if (!isP2SH && (keys.size() > 1 || pubKeys.size() > 1)) { // Address / scriptPubKey
712 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "More than private key given for one address");
713 : }
714 :
715 : // Invalid P2SH redeemScript
716 26 : if (isP2SH && !IsHex(strRedeemScript)) {
717 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script");
718 : }
719 :
720 : // Process. //
721 :
722 : // P2SH
723 26 : if (isP2SH) {
724 : // Import redeem script.
725 4 : std::vector<unsigned char> vData(ParseHex(strRedeemScript));
726 4 : CScript redeemScript = CScript(vData.begin(), vData.end());
727 :
728 : // Invalid P2SH address
729 2 : if (!script.IsPayToScriptHash()) {
730 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid P2SH address / script");
731 : }
732 :
733 2 : pwallet->MarkDirty();
734 :
735 2 : if (!pwallet->HaveWatchOnly(redeemScript) && !pwallet->AddWatchOnly(redeemScript)) {
736 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
737 : }
738 :
739 2 : if (!pwallet->HaveCScript(redeemScript) && !pwallet->AddCScript(redeemScript)) {
740 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet");
741 : }
742 :
743 4 : CTxDestination redeem_dest = CScriptID(redeemScript);
744 4 : CScript redeemDestination = GetScriptForDestination(redeem_dest);
745 :
746 2 : if (::IsMine(*pwallet, redeemDestination) == ISMINE_SPENDABLE) {
747 0 : throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
748 : }
749 :
750 2 : pwallet->MarkDirty();
751 :
752 2 : if (!pwallet->HaveWatchOnly(redeemDestination) && !pwallet->AddWatchOnly(redeemDestination)) {
753 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
754 : }
755 :
756 : // add to address book or update label
757 2 : if (IsValidDestination(dest)) {
758 6 : pwallet->SetAddressBook(dest, label, "receive");
759 : }
760 :
761 : // Import private keys.
762 2 : if (keys.size()) {
763 3 : for (size_t i = 0; i < keys.size(); i++) {
764 2 : const std::string& privkey = keys[i].get_str();
765 4 : CKey key = KeyIO::DecodeSecret(privkey);
766 :
767 2 : if (!key.IsValid()) {
768 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
769 : }
770 :
771 2 : CPubKey pubkey = key.GetPubKey();
772 2 : assert(key.VerifyPubKey(pubkey));
773 :
774 2 : CKeyID vchAddress = pubkey.GetID();
775 2 : pwallet->MarkDirty();
776 6 : pwallet->SetAddressBook(vchAddress, label, "receive");
777 :
778 2 : if (pwallet->HaveKey(vchAddress)) {
779 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key");
780 : }
781 :
782 2 : pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
783 :
784 2 : if (!pwallet->AddKeyPubKey(key, pubkey)) {
785 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
786 : }
787 :
788 2 : if (timestamp < pwallet->nTimeFirstKey) {
789 0 : pwallet->nTimeFirstKey = timestamp;
790 : }
791 : }
792 : }
793 :
794 2 : success = true;
795 : } else {
796 : // Import public keys.
797 24 : if (pubKeys.size() && keys.size() == 0) {
798 7 : const std::string& strPubKey = pubKeys[0].get_str();
799 :
800 7 : if (!IsHex(strPubKey)) {
801 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
802 : }
803 :
804 14 : std::vector<unsigned char> vData(ParseHex(strPubKey));
805 7 : CPubKey pubKey(vData.begin(), vData.end());
806 :
807 7 : if (!pubKey.IsFullyValid()) {
808 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
809 : }
810 :
811 14 : CTxDestination pubkey_dest = pubKey.GetID();
812 :
813 : // Consistency check.
814 7 : if (!isScript && !(pubkey_dest == dest)) {
815 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
816 : }
817 :
818 : // Consistency check.
819 6 : if (isScript) {
820 4 : CTxDestination destination;
821 2 : if (ExtractDestination(script, destination) && !(destination == pubkey_dest)) {
822 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
823 : }
824 : }
825 :
826 10 : CScript pubKeyScript = GetScriptForDestination(pubkey_dest);
827 :
828 5 : if (::IsMine(*pwallet, pubKeyScript) == ISMINE_SPENDABLE) {
829 0 : throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
830 : }
831 :
832 5 : pwallet->MarkDirty();
833 :
834 5 : if (!pwallet->HaveWatchOnly(pubKeyScript) && !pwallet->AddWatchOnly(pubKeyScript)) {
835 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
836 : }
837 :
838 : // add to address book or update label
839 5 : if (IsValidDestination(pubkey_dest)) {
840 15 : pwallet->SetAddressBook(pubkey_dest, label, "receive");
841 : }
842 :
843 : // TODO Is this necessary?
844 10 : CScript scriptRawPubKey = GetScriptForRawPubKey(pubKey);
845 :
846 5 : if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) {
847 0 : throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
848 : }
849 :
850 5 : pwallet->MarkDirty();
851 :
852 5 : if (!pwallet->HaveWatchOnly(scriptRawPubKey) && !pwallet->AddWatchOnly(scriptRawPubKey)) {
853 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
854 : }
855 :
856 5 : success = true;
857 : }
858 :
859 : // Import private keys.
860 22 : if (keys.size()) {
861 8 : const std::string& strPrivkey = keys[0].get_str();
862 16 : CKey key = KeyIO::DecodeSecret(strPrivkey);
863 :
864 8 : if (!key.IsValid()) {
865 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
866 : }
867 :
868 8 : CPubKey pubKey = key.GetPubKey();
869 8 : assert(key.VerifyPubKey(pubKey));
870 :
871 16 : CTxDestination pubkey_dest = pubKey.GetID();
872 :
873 : // Consistency check.
874 8 : if (!isScript && !(pubkey_dest == dest)) {
875 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
876 : }
877 :
878 : // Consistency check.
879 7 : if (isScript) {
880 4 : CTxDestination destination;
881 2 : if (ExtractDestination(script, destination) && !(destination == pubkey_dest)) {
882 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Consistency check failed");
883 : }
884 : }
885 :
886 6 : CKeyID vchAddress = pubKey.GetID();
887 6 : pwallet->MarkDirty();
888 18 : pwallet->SetAddressBook(vchAddress, label, "receive");
889 :
890 6 : if (pwallet->HaveKey(vchAddress)) {
891 2 : throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
892 : }
893 :
894 5 : pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
895 :
896 5 : if (!pwallet->AddKeyPubKey(key, pubKey)) {
897 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
898 : }
899 :
900 5 : if (timestamp < pwallet->nTimeFirstKey) {
901 0 : pwallet->nTimeFirstKey = timestamp;
902 : }
903 :
904 5 : success = true;
905 : }
906 :
907 : // Import scriptPubKey only.
908 19 : if (pubKeys.size() == 0 && keys.size() == 0) {
909 9 : if (::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
910 0 : throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
911 : }
912 :
913 9 : pwallet->MarkDirty();
914 :
915 9 : if (!pwallet->HaveWatchOnly(script) && !pwallet->AddWatchOnly(script)) {
916 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
917 : }
918 :
919 9 : if (scriptPubKey.getType() == UniValue::VOBJ) {
920 : // add to address book or update label
921 6 : if (IsValidDestination(dest)) {
922 18 : pwallet->SetAddressBook(dest, label, "receive");
923 : }
924 : }
925 :
926 : success = true;
927 : }
928 : }
929 :
930 42 : UniValue result = UniValue(UniValue::VOBJ);
931 42 : result.pushKV("success", UniValue(success));
932 21 : return result;
933 22 : } catch (const UniValue& e) {
934 22 : UniValue result = UniValue(UniValue::VOBJ);
935 22 : result.pushKV("success", UniValue(false));
936 11 : result.pushKV("error", e);
937 11 : return result;
938 0 : } catch (...) {
939 0 : UniValue result = UniValue(UniValue::VOBJ);
940 0 : result.pushKV("success", UniValue(false));
941 0 : result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
942 0 : return result;
943 : }
944 : }
945 :
946 66 : static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
947 : {
948 132 : if (data.exists("timestamp")) {
949 65 : const UniValue& timestamp = data["timestamp"];
950 65 : if (timestamp.isNum()) {
951 22 : return timestamp.get_int64();
952 43 : } else if (timestamp.isStr() && timestamp.get_str() == "now") {
953 : return now;
954 : }
955 2 : throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
956 : }
957 2 : throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
958 : }
959 :
960 33 : UniValue importmulti(const JSONRPCRequest& mainRequest)
961 : {
962 33 : CWallet * const pwallet = GetWalletForJSONRPCRequest(mainRequest);
963 :
964 33 : if (!EnsureWalletIsAvailable(pwallet, mainRequest.fHelp))
965 0 : return NullUniValue;
966 :
967 33 : if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2)
968 0 : throw std::runtime_error(
969 : "importmulti \"requests\" ( \"options\" )\n"
970 0 : "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n" +
971 0 : HelpRequiringPassphrase(pwallet) + "\n"
972 :
973 : "\nArguments:\n"
974 : "1. requests (array, required) Data to be imported\n"
975 : " [ (array of json objects)\n"
976 : " {\n"
977 : " \"scriptPubKey\": \"script\" | { \"address\":\"address\" }, (string / JSON, required) Type of scriptPubKey (string for script, json for address)\n"
978 : " \"timestamp\": timestamp | \"now\" (integer / string, required) Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n"
979 : " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
980 : " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
981 : " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
982 : " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
983 : " creation time of all keys being imported by the importmulti call will be scanned.\n"
984 : " \"redeemscript\": \"script\", (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n"
985 : " \"pubkeys\": [\"pubKey\", ... ], (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
986 : " \"keys\": [\"key\", ... ], (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
987 : " \"internal\": true|false, (boolean, optional, default: false) Stating whether matching outputs should be be treated as not incoming payments\n"
988 : " \"watchonly\": true|false, (boolean, optional, default: false) Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty\n"
989 : " \"label\": label, (string, optional, default: '') Label to assign to the address, only allowed with internal=false\n"
990 : " }\n"
991 : " ,...\n"
992 : " ]\n"
993 : "2. options (JSON, optional)\n"
994 : " {\n"
995 : " \"rescan\": true|false, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n"
996 : " }\n"
997 :
998 : "\nResult:\n"
999 : "[ (Array) An array with the same size as the input that has the execution result\n"
1000 : " {\n"
1001 : " \"success\": true|false, (boolean) True if import succeeded, otherwise false\n"
1002 : " \"error\": { (JSON Object) Object containing error information. Only present when import fails\n"
1003 : " \"code\": n, (numeric) The error code\n"
1004 : " \"message\": xxxx (string) The error message\n"
1005 : " }\n"
1006 : " }\n"
1007 : " ,...\n"
1008 : "]\n"
1009 : "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
1010 : "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n"
1011 0 : "\nExamples:\n" +
1012 0 : HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
1013 0 : "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
1014 0 : HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'"));
1015 :
1016 33 : RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ});
1017 33 : const UniValue& requests = mainRequest.params[0];
1018 :
1019 : //Default options
1020 33 : bool fRescan = true;
1021 :
1022 33 : if (mainRequest.params.size() > 1) {
1023 9 : const UniValue& options = mainRequest.params[1];
1024 :
1025 18 : if (options.exists("rescan")) {
1026 9 : fRescan = options["rescan"].get_bool();
1027 : }
1028 : }
1029 :
1030 66 : WalletRescanReserver reserver(pwallet);
1031 33 : if (fRescan && !reserver.reserve()) {
1032 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1033 : }
1034 :
1035 33 : int64_t now = 0;
1036 33 : bool fRunScan = false;
1037 33 : int64_t nLowestTimestamp = 0;
1038 :
1039 66 : UniValue response(UniValue::VARR);
1040 33 : {
1041 68 : LOCK2(cs_main, pwallet->cs_wallet);
1042 33 : EnsureWalletIsUnlocked(pwallet);
1043 :
1044 : // Verify all timestamps are present before importing any keys.
1045 33 : int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
1046 65 : for (const UniValue& data : requests.getValues()) {
1047 34 : GetImportTimestamp(data, now);
1048 : }
1049 :
1050 31 : const int64_t minimumTimestamp = 1;
1051 :
1052 59 : if (fRescan && chainActive.Tip()) {
1053 28 : nLowestTimestamp = chainActive.Tip()->GetBlockTime();
1054 : } else {
1055 : fRescan = false;
1056 : }
1057 :
1058 63 : for (const UniValue& data: requests.getValues()) {
1059 32 : const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
1060 61 : const UniValue result = processImport(pwallet, data, timestamp);
1061 32 : response.push_back(result);
1062 :
1063 32 : if (!fRescan) {
1064 3 : continue;
1065 : }
1066 :
1067 : // If at least one request was successful then allow rescan.
1068 29 : if (result["success"].get_bool()) {
1069 18 : fRunScan = true;
1070 : }
1071 :
1072 : // Get the lowest timestamp.
1073 29 : if (timestamp < nLowestTimestamp) {
1074 7 : nLowestTimestamp = timestamp;
1075 : }
1076 : }
1077 : }
1078 31 : if (fRescan && fRunScan && requests.size()) {
1079 17 : int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
1080 17 : pwallet->ReacceptWalletTransactions();
1081 :
1082 17 : if (scannedTime > nLowestTimestamp) {
1083 0 : std::vector<UniValue> results = response.getValues();
1084 0 : response.clear();
1085 0 : response.setArray();
1086 0 : size_t i = 0;
1087 0 : for (const UniValue& request : requests.getValues()) {
1088 : // If key creation date is within the successfully scanned
1089 : // range, or if the import result already has an error set, let
1090 : // the result stand unmodified. Otherwise replace the result
1091 : // with an error message.
1092 0 : if (scannedTime <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
1093 0 : response.push_back(results.at(i));
1094 : } else {
1095 0 : UniValue result = UniValue(UniValue::VOBJ);
1096 0 : result.pushKV("success", UniValue(false));
1097 0 : result.pushKV("error", JSONRPCError(RPC_MISC_ERROR,
1098 0 : strprintf("Rescan failed for key with creation timestamp %d. There was an error reading a "
1099 : "block from time %d, which is after or within %d seconds of key creation, and "
1100 : "could contain transactions pertaining to the key. As a result, transactions "
1101 : "and coins using this key may not appear in the wallet. This error could be "
1102 : "caused by pruning or data corruption (see pivxd log for details) and could "
1103 : "be dealt with by downloading and rescanning the relevant blocks (see -reindex "
1104 : "and -rescan options).",
1105 0 : GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
1106 0 : response.push_back(std::move(result));
1107 : }
1108 0 : ++i;
1109 : }
1110 : }
1111 : }
1112 :
1113 31 : return response;
1114 : }
1115 :
1116 1 : UniValue bip38encrypt(const JSONRPCRequest& request)
1117 : {
1118 1 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
1119 :
1120 1 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
1121 0 : return NullUniValue;
1122 :
1123 1 : if (request.fHelp || request.params.size() != 2)
1124 0 : throw std::runtime_error(
1125 : "bip38encrypt \"address\" \"passphrase\"\n"
1126 0 : "\nEncrypts a private key corresponding to 'address'.\n" +
1127 0 : HelpRequiringPassphrase(pwallet) + "\n"
1128 :
1129 : "\nArguments:\n"
1130 : "1. \"address\" (string, required) The pivx address for the private key (you must hold the key already)\n"
1131 : "2. \"passphrase\" (string, required) The passphrase you want the private key to be encrypted with - Valid special chars: !#$%&'()*+,-./:;<=>?`{|}~ \n"
1132 :
1133 : "\nResult:\n"
1134 : "\"key\" (string) The encrypted private key\n"
1135 :
1136 0 : "\nExamples:\n" +
1137 0 : HelpExampleCli("bip38encrypt", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\" \"mypasphrase\"") +
1138 0 : HelpExampleRpc("bip38encrypt", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\" \"mypasphrase\""));
1139 :
1140 3 : LOCK2(cs_main, pwallet->cs_wallet);
1141 :
1142 1 : EnsureWalletIsUnlocked(pwallet);
1143 :
1144 2 : std::string strAddress = request.params[0].get_str();
1145 2 : std::string strPassphrase = request.params[1].get_str();
1146 :
1147 2 : CTxDestination address = DecodeDestination(strAddress);
1148 1 : if (!IsValidDestination(address))
1149 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid PIVX address");
1150 1 : const CKeyID* keyID = boost::get<CKeyID>(&address);
1151 1 : if (!keyID)
1152 0 : throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
1153 2 : CKey vchSecret;
1154 1 : if (!pwallet->GetKey(*keyID, vchSecret))
1155 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
1156 :
1157 1 : uint256 privKey = vchSecret.GetPrivKey_256();
1158 5 : std::string encryptedOut = BIP38_Encrypt(strAddress, strPassphrase, privKey, vchSecret.IsCompressed());
1159 :
1160 2 : UniValue result(UniValue::VOBJ);
1161 1 : result.pushKV("Address", strAddress);
1162 1 : result.pushKV("Encrypted Key", encryptedOut);
1163 :
1164 1 : return result;
1165 : }
1166 :
1167 1 : UniValue bip38decrypt(const JSONRPCRequest& request)
1168 : {
1169 1 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
1170 :
1171 1 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
1172 0 : return NullUniValue;
1173 :
1174 1 : if (request.fHelp || request.params.size() != 2)
1175 0 : throw std::runtime_error(
1176 : "bip38decrypt \"encrypted_key\" \"passphrase\"\n"
1177 0 : "\nDecrypts and then imports password protected private key.\n" +
1178 0 : HelpRequiringPassphrase(pwallet) + "\n"
1179 :
1180 : "\nArguments:\n"
1181 : "1. \"encrypted_key\" (string, required) The encrypted private key\n"
1182 : "2. \"passphrase\" (string, required) The passphrase you want the private key to be encrypted with\n"
1183 :
1184 : "\nResult:\n"
1185 : "\"key\" (string) The decrypted private key\n"
1186 :
1187 0 : "\nExamples:\n" +
1188 0 : HelpExampleCli("bip38decrypt", "\"encryptedkey\" \"mypassphrase\"") +
1189 0 : HelpExampleRpc("bip38decrypt", "\"encryptedkey\" \"mypassphrase\""));
1190 :
1191 :
1192 : /** Collect private key and passphrase **/
1193 2 : std::string strKey = request.params[0].get_str();
1194 2 : std::string strPassphrase = request.params[1].get_str();
1195 :
1196 1 : uint256 privKey;
1197 1 : bool fCompressed;
1198 4 : if (!BIP38_Decrypt(strPassphrase, strKey, privKey, fCompressed))
1199 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Failed To Decrypt");
1200 :
1201 2 : UniValue result(UniValue::VOBJ);
1202 2 : result.pushKV("privatekey", HexStr(privKey));
1203 :
1204 2 : CKey key;
1205 1 : key.Set(privKey.begin(), privKey.end(), fCompressed);
1206 :
1207 1 : if (!key.IsValid())
1208 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Private Key Not Valid");
1209 :
1210 2 : WalletRescanReserver reserver(pwallet);
1211 1 : if (!reserver.reserve()) {
1212 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1213 : }
1214 :
1215 1 : CPubKey pubkey = key.GetPubKey();
1216 1 : assert(key.VerifyPubKey(pubkey));
1217 3 : result.pushKV("Address", EncodeDestination(pubkey.GetID()));
1218 1 : CKeyID vchAddress = pubkey.GetID();
1219 1 : {
1220 2 : LOCK2(cs_main, pwallet->cs_wallet);
1221 1 : EnsureWalletIsUnlocked(pwallet);
1222 1 : pwallet->MarkDirty();
1223 3 : pwallet->SetAddressBook(vchAddress, "", AddressBook::AddressBookPurpose::RECEIVE);
1224 :
1225 : // Don't throw error in case a key is already there
1226 1 : if (pwallet->HaveKey(vchAddress))
1227 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Key already held by wallet");
1228 :
1229 1 : pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
1230 :
1231 1 : if (!pwallet->AddKeyPubKey(key, pubkey))
1232 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
1233 : }
1234 :
1235 : // whenever a key is imported, we need to scan the whole chain
1236 2 : pwallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, true);
1237 :
1238 1 : return result;
1239 : }
1240 :
1241 : // Sapling
1242 :
1243 1008 : UniValue importsaplingkey(const JSONRPCRequest& request)
1244 : {
1245 1008 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
1246 :
1247 1008 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
1248 0 : return NullUniValue;
1249 :
1250 1008 : if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
1251 1 : throw std::runtime_error(
1252 : "importsaplingkey \"key\" ( rescan height )\n"
1253 : "\nAdds a key (as returned by exportsaplingkey) to your wallet.\n"
1254 2 : + HelpRequiringPassphrase(pwallet) + "\n"
1255 :
1256 : "\nArguments:\n"
1257 : "1. \"key\" (string, required) The zkey (see exportsaplingkey)\n"
1258 : "2. rescan (string, optional, default=\"whenkeyisnew\") Rescan the wallet for transactions - can be \"yes\", \"no\" or \"whenkeyisnew\"\n"
1259 : "3. height (numeric, optional, default=0) Block height to start rescan from\n"
1260 : "\nNote: This call can take minutes to complete if rescan is true.\n"
1261 :
1262 : "\nResult:\n"
1263 : "{\n"
1264 : " \"address\" : \"address|DefaultAddress\", (string) The address corresponding to the spending key (the default address).\n"
1265 : "}\n"
1266 :
1267 : "\nExamples:\n"
1268 : "\nExport a zkey\n"
1269 5 : + HelpExampleCli("exportsaplingkey", "\"myaddress\"") +
1270 : "\nImport the key with rescan\n"
1271 5 : + HelpExampleCli("importsaplingkey", "\"mykey\"") +
1272 : "\nImport the key with partial rescan\n"
1273 6 : + HelpExampleCli("importsaplingkey", "\"mykey\" whenkeyisnew 30000") +
1274 : "\nRe-import the key with longer partial rescan\n"
1275 6 : + HelpExampleCli("importsaplingkey", "\"mykey\" yes 20000") +
1276 : "\nAs a JSON-RPC call\n"
1277 5 : + HelpExampleRpc("importsaplingkey", "\"mykey\", \"no\"")
1278 3 : );
1279 :
1280 : // Whether to perform rescan after import
1281 1007 : bool fRescan = true;
1282 1007 : bool fIgnoreExistingKey = true;
1283 1007 : if (request.params.size() > 1) {
1284 4 : auto rescan = request.params[1].get_str();
1285 2 : if (rescan.compare("whenkeyisnew") != 0) {
1286 2 : fIgnoreExistingKey = false;
1287 2 : if (rescan.compare("yes") == 0) {
1288 : fRescan = true;
1289 0 : } else if (rescan.compare("no") == 0) {
1290 : fRescan = false;
1291 : } else {
1292 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "rescan must be \"yes\", \"no\" or \"whenkeyisnew\"");
1293 : }
1294 : }
1295 : }
1296 :
1297 2014 : WalletRescanReserver reserver(pwallet);
1298 1007 : if (fRescan && !reserver.reserve()) {
1299 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1300 : }
1301 :
1302 2014 : UniValue result(UniValue::VOBJ);
1303 1007 : CBlockIndex* pindexRescan{nullptr};
1304 1007 : {
1305 2013 : LOCK2(cs_main, pwallet->cs_wallet);
1306 1007 : EnsureWalletIsUnlocked(pwallet);
1307 :
1308 : // Height to rescan from
1309 1007 : int nRescanHeight = 0;
1310 1007 : if (request.params.size() > 2)
1311 0 : nRescanHeight = request.params[2].get_int();
1312 1007 : if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) {
1313 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
1314 : }
1315 :
1316 2013 : std::string strSecret = request.params[0].get_str();
1317 2013 : auto spendingkey = KeyIO::DecodeSpendingKey(strSecret);
1318 1007 : if (!IsValidSpendingKey(spendingkey)) {
1319 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key");
1320 : }
1321 :
1322 1007 : libzcash::SaplingExtendedSpendingKey saplingSpendingKey = *boost::get<libzcash::SaplingExtendedSpendingKey>(&spendingkey);
1323 3021 : result.pushKV("address", KeyIO::EncodePaymentAddress( saplingSpendingKey.DefaultAddress()));
1324 :
1325 : // Sapling support
1326 1007 : auto addResult = pwallet->GetSaplingScriptPubKeyMan()->AddSpendingKeyToWallet(Params().GetConsensus(), saplingSpendingKey, -1);
1327 1007 : if (addResult == KeyAlreadyExists && fIgnoreExistingKey) {
1328 1 : return result;
1329 : }
1330 1006 : pwallet->MarkDirty();
1331 1006 : if (addResult == KeyNotAdded) {
1332 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet");
1333 : }
1334 2012 : pindexRescan = chainActive[nRescanHeight];
1335 : }
1336 :
1337 : // We want to scan for transactions and notes
1338 1006 : if (fRescan) {
1339 1006 : pwallet->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true);
1340 : }
1341 :
1342 1006 : return result;
1343 : }
1344 :
1345 3 : UniValue importsaplingviewingkey(const JSONRPCRequest& request)
1346 : {
1347 3 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
1348 :
1349 3 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
1350 0 : return NullUniValue;
1351 :
1352 3 : if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
1353 0 : throw std::runtime_error(
1354 : "importsaplingviewingkey \"vkey\" ( rescan height )\n"
1355 : "\nAdds a viewing key (as returned by exportsaplingviewingkey) to your wallet.\n"
1356 0 : + HelpRequiringPassphrase(pwallet) + "\n"
1357 :
1358 : "\nArguments:\n"
1359 : "1. \"vkey\" (string, required) The viewing key (see exportsaplingviewingkey)\n"
1360 : "2. rescan (string, optional, default=\"whenkeyisnew\") Rescan the wallet for transactions - can be \"yes\", \"no\" or \"whenkeyisnew\"\n"
1361 : "3. height (numeric, optional, default=0) Block height to start rescan from\n"
1362 : "\nNote: This call can take minutes to complete if rescan is true.\n"
1363 :
1364 : "\nResult:\n"
1365 : "{\n"
1366 : " \"address\" : \"address|DefaultAddress\", (string) The address corresponding to the viewing key (for Sapling, this is the default address).\n"
1367 : "}\n"
1368 :
1369 : "\nExamples:\n"
1370 : "\nImport a viewing key\n"
1371 0 : + HelpExampleCli("importsaplingviewingkey", "\"vkey\"") +
1372 : "\nImport the viewing key without rescan\n"
1373 0 : + HelpExampleCli("importsaplingviewingkey", "\"vkey\", no") +
1374 : "\nImport the viewing key with partial rescan\n"
1375 0 : + HelpExampleCli("importsaplingviewingkey", "\"vkey\" whenkeyisnew 30000") +
1376 : "\nRe-import the viewing key with longer partial rescan\n"
1377 0 : + HelpExampleCli("importsaplingviewingkey", "\"vkey\" yes 20000") +
1378 : "\nAs a JSON-RPC call\n"
1379 0 : + HelpExampleRpc("importsaplingviewingkey", "\"vkey\", \"no\"")
1380 0 : );
1381 :
1382 : // Whether to perform rescan after import
1383 3 : bool fRescan = true;
1384 3 : bool fIgnoreExistingKey = true;
1385 3 : if (request.params.size() > 1) {
1386 6 : auto rescan = request.params[1].get_str();
1387 3 : if (rescan.compare("whenkeyisnew") != 0) {
1388 2 : fIgnoreExistingKey = false;
1389 2 : if (rescan.compare("no") == 0) {
1390 : fRescan = false;
1391 2 : } else if (rescan.compare("yes") != 0) {
1392 0 : throw JSONRPCError(
1393 : RPC_INVALID_PARAMETER,
1394 0 : "rescan must be \"yes\", \"no\" or \"whenkeyisnew\"");
1395 : }
1396 : }
1397 : }
1398 :
1399 6 : WalletRescanReserver reserver(pwallet);
1400 3 : if (fRescan && !reserver.reserve()) {
1401 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1402 : }
1403 :
1404 6 : UniValue result(UniValue::VOBJ);
1405 3 : CBlockIndex* pindexRescan{nullptr};
1406 3 : {
1407 6 : LOCK2(cs_main, pwallet->cs_wallet);
1408 3 : EnsureWalletIsUnlocked(pwallet);
1409 :
1410 : // Height to rescan from
1411 3 : int nRescanHeight = 0;
1412 3 : if (request.params.size() > 2) {
1413 1 : nRescanHeight = request.params[2].get_int();
1414 : }
1415 3 : if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) {
1416 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
1417 : }
1418 :
1419 6 : std::string strVKey = request.params[0].get_str();
1420 6 : libzcash::ViewingKey viewingkey = KeyIO::DecodeViewingKey(strVKey);
1421 3 : if (!IsValidViewingKey(viewingkey)) {
1422 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key");
1423 : }
1424 3 : libzcash::SaplingExtendedFullViewingKey efvk = *boost::get<libzcash::SaplingExtendedFullViewingKey>(&viewingkey);
1425 9 : result.pushKV("address", KeyIO::EncodePaymentAddress(efvk.DefaultAddress()));
1426 :
1427 3 : auto addResult = pwallet->GetSaplingScriptPubKeyMan()->AddViewingKeyToWallet(efvk);
1428 3 : if (addResult == SpendingKeyExists) {
1429 0 : throw JSONRPCError(
1430 : RPC_WALLET_ERROR,
1431 0 : "The wallet already contains the private key for this viewing key");
1432 3 : } else if (addResult == KeyAlreadyExists && fIgnoreExistingKey) {
1433 0 : return result;
1434 : }
1435 3 : pwallet->MarkDirty();
1436 3 : if (addResult == KeyNotAdded) {
1437 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
1438 : }
1439 :
1440 6 : pindexRescan = chainActive[nRescanHeight];
1441 : }
1442 :
1443 : // We want to scan for transactions and notes
1444 3 : if (fRescan) {
1445 3 : pwallet->ScanForWalletTransactions(pindexRescan, nullptr, reserver, true);
1446 : }
1447 :
1448 3 : return result;
1449 : }
1450 :
1451 3 : UniValue exportsaplingviewingkey(const JSONRPCRequest& request)
1452 : {
1453 3 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
1454 :
1455 3 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
1456 0 : return NullUniValue;
1457 :
1458 3 : if (request.fHelp || request.params.size() != 1)
1459 0 : throw std::runtime_error(
1460 : "exportsaplingviewingkey \"shield_addr\"\n"
1461 : "\nReveals the viewing key corresponding to 'shield_addr'.\n"
1462 : "Then the importsaplingviewingkey can be used with this output\n"
1463 0 : + HelpRequiringPassphrase(pwallet) + "\n"
1464 :
1465 : "\nArguments:\n"
1466 : "1. \"shield_addr\" (string, required) The shield addr for the viewing key\n"
1467 :
1468 : "\nResult:\n"
1469 : "\"vkey\" (string) The viewing key\n"
1470 :
1471 : "\nExamples:\n"
1472 0 : + HelpExampleCli("exportsaplingviewingkey", "\"myaddress\"")
1473 0 : + HelpExampleRpc("exportsaplingviewingkey", "\"myaddress\"")
1474 0 : );
1475 :
1476 9 : LOCK2(cs_main, pwallet->cs_wallet);
1477 :
1478 3 : EnsureWalletIsUnlocked(pwallet);
1479 :
1480 6 : std::string strAddress = request.params[0].get_str();
1481 6 : auto address = KeyIO::DecodePaymentAddress(strAddress);
1482 3 : if (!IsValidPaymentAddress(address)) {
1483 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid shield addr");
1484 : }
1485 3 : const libzcash::SaplingPaymentAddress &sapAddr = *boost::get<libzcash::SaplingPaymentAddress>(&address);
1486 6 : auto vk = pwallet->GetSaplingScriptPubKeyMan()->GetViewingKeyForPaymentAddress(sapAddr);
1487 3 : if (vk) {
1488 6 : return KeyIO::EncodeViewingKey(vk.get());
1489 : } else {
1490 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private key or viewing key for this shield addr");
1491 : }
1492 : }
1493 :
1494 1006 : UniValue exportsaplingkey(const JSONRPCRequest& request)
1495 : {
1496 1006 : CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
1497 :
1498 1006 : if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
1499 0 : return NullUniValue;
1500 :
1501 1006 : if (request.fHelp || request.params.size() != 1)
1502 2 : throw std::runtime_error(
1503 : "exportsaplingkey \"shield_addr\"\n"
1504 : "\nReveals the key corresponding to the 'shield_addr'.\n"
1505 : "Then the importsaplingkey can be used with this output\n"
1506 4 : + HelpRequiringPassphrase(pwallet) + "\n"
1507 :
1508 : "\nArguments:\n"
1509 : "1. \"addr\" (string, required) The shield addr for the private key\n"
1510 :
1511 : "\nResult:\n"
1512 : "\"key\" (string) The private key\n"
1513 :
1514 : "\nExamples:\n"
1515 10 : + HelpExampleCli("exportsaplingkey", "\"myaddress\"")
1516 10 : + HelpExampleRpc("exportsaplingkey", "\"myaddress\"")
1517 6 : );
1518 :
1519 3012 : LOCK2(cs_main, pwallet->cs_wallet);
1520 :
1521 1004 : EnsureWalletIsUnlocked(pwallet);
1522 :
1523 2008 : std::string strAddress = request.params[0].get_str();
1524 :
1525 2008 : auto address = KeyIO::DecodePaymentAddress(strAddress);
1526 1004 : if (!IsValidPaymentAddress(address)) {
1527 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid shield addr");
1528 : }
1529 1004 : libzcash::SaplingPaymentAddress addr = *boost::get<libzcash::SaplingPaymentAddress>(&address);
1530 :
1531 : // Sapling support
1532 2008 : Optional<libzcash::SaplingExtendedSpendingKey> sk = pwallet->GetSaplingScriptPubKeyMan()->GetSpendingKeyForPaymentAddress(addr);
1533 1004 : if (!sk) {
1534 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private key for this shield addr");
1535 : }
1536 2008 : return KeyIO::EncodeSpendingKey(libzcash::SpendingKey(sk.get()));
1537 : }
|