addresses.cpp raw
1 // Copyright (c) 2011-present The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <limenka-build-config.h> // IWYU pragma: keep
6
7 #include <core_io.h>
8 #include <key_io.h>
9 #include <rpc/util.h>
10 #include <script/script.h>
11 #include <script/solver.h>
12 #include <util/bip32.h>
13 #include <util/translation.h>
14 #include <wallet/receive.h>
15 #include <wallet/rpc/util.h>
16 #include <wallet/wallet.h>
17
18 #include <univalue.h>
19
20 namespace wallet {
21 RPCHelpMan getnewaddress()
22 {
23 return RPCHelpMan{"getnewaddress",
24 "\nReturns a new Limenka address for receiving payments.\n"
25 "If 'label' is specified, it is added to the address book \n"
26 "so payments received with the address will be associated with 'label'.\n",
27 {
28 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "The label name for the address to be linked to. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."},
29 {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
30 },
31 RPCResult{
32 RPCResult::Type::STR, "address", "The new limenka address"
33 },
34 RPCExamples{
35 HelpExampleCli("getnewaddress", "")
36 + HelpExampleRpc("getnewaddress", "")
37 },
38 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
39 {
40 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
41 if (!pwallet) return UniValue::VNULL;
42
43 LOCK(pwallet->cs_wallet);
44
45 if (!pwallet->CanGetAddresses()) {
46 throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
47 }
48
49 // Parse the label first so we don't generate a key if there's an error
50 const std::string label{LabelFromValue(request.params[0])};
51
52 OutputType output_type = pwallet->m_default_address_type;
53 if (!request.params[1].isNull()) {
54 std::optional<OutputType> parsed = ParseOutputType(request.params[1].get_str());
55 if (!parsed) {
56 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
57 } else if (parsed.value() == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
58 throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
59 }
60 output_type = parsed.value();
61 }
62
63 auto op_dest = pwallet->GetNewDestination(output_type, label);
64 if (!op_dest) {
65 throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
66 }
67
68 return EncodeDestination(*op_dest);
69 },
70 };
71 }
72
73 RPCHelpMan getrawchangeaddress()
74 {
75 return RPCHelpMan{"getrawchangeaddress",
76 "\nReturns a new Limenka address, for receiving change.\n"
77 "This is for use with raw transactions, NOT normal use.\n",
78 {
79 {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
80 },
81 RPCResult{
82 RPCResult::Type::STR, "address", "The address"
83 },
84 RPCExamples{
85 HelpExampleCli("getrawchangeaddress", "")
86 + HelpExampleRpc("getrawchangeaddress", "")
87 },
88 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
89 {
90 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
91 if (!pwallet) return UniValue::VNULL;
92
93 LOCK(pwallet->cs_wallet);
94
95 if (!pwallet->CanGetAddresses(true)) {
96 throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
97 }
98
99 OutputType output_type = pwallet->m_default_change_type.value_or(pwallet->m_default_address_type);
100 if (!request.params[0].isNull()) {
101 std::optional<OutputType> parsed = ParseOutputType(request.params[0].get_str());
102 if (!parsed) {
103 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
104 } else if (parsed.value() == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
105 throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
106 }
107 output_type = parsed.value();
108 }
109
110 auto op_dest = pwallet->GetNewChangeDestination(output_type);
111 if (!op_dest) {
112 throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, util::ErrorString(op_dest).original);
113 }
114 return EncodeDestination(*op_dest);
115 },
116 };
117 }
118
119
120 RPCHelpMan setlabel()
121 {
122 return RPCHelpMan{"setlabel",
123 "\nSets the label associated with the given address.\n",
124 {
125 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The limenka address to be associated with a label."},
126 {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label to assign to the address."},
127 },
128 RPCResult{RPCResult::Type::NONE, "", ""},
129 RPCExamples{
130 HelpExampleCli("setlabel", "\"" + EXAMPLE_ADDRESS[0] + "\" \"tabby\"")
131 + HelpExampleRpc("setlabel", "\"" + EXAMPLE_ADDRESS[0] + "\", \"tabby\"")
132 },
133 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
134 {
135 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
136 if (!pwallet) return UniValue::VNULL;
137
138 LOCK(pwallet->cs_wallet);
139
140 CTxDestination dest = DecodeDestination(request.params[0].get_str());
141 if (!IsValidDestination(dest)) {
142 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Limenka address");
143 }
144
145 const std::string label{LabelFromValue(request.params[1])};
146
147 if (pwallet->IsMine(dest)) {
148 pwallet->SetAddressBook(dest, label, AddressPurpose::RECEIVE);
149 } else {
150 pwallet->SetAddressBook(dest, label, AddressPurpose::SEND);
151 }
152
153 return UniValue::VNULL;
154 },
155 };
156 }
157
158 RPCHelpMan listaddressgroupings()
159 {
160 return RPCHelpMan{"listaddressgroupings",
161 "\nLists groups of addresses which have had their common ownership\n"
162 "made public by common use as inputs or as the resulting change\n"
163 "in past transactions\n",
164 {},
165 RPCResult{
166 RPCResult::Type::ARR, "", "",
167 {
168 {RPCResult::Type::ARR, "", "",
169 {
170 {RPCResult::Type::ARR_FIXED, "", "",
171 {
172 {RPCResult::Type::STR, "address", "The limenka address"},
173 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
174 {RPCResult::Type::STR, "label", /*optional=*/true, "The label"},
175 }},
176 }},
177 }
178 },
179 RPCExamples{
180 HelpExampleCli("listaddressgroupings", "")
181 + HelpExampleRpc("listaddressgroupings", "")
182 },
183 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
184 {
185 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
186 if (!pwallet) return UniValue::VNULL;
187
188 // Make sure the results are valid at least up to the most recent block
189 // the user could have gotten from another RPC command prior to now
190 pwallet->BlockUntilSyncedToCurrentChain();
191
192 LOCK(pwallet->cs_wallet);
193
194 UniValue jsonGroupings(UniValue::VARR);
195 std::map<CTxDestination, CAmount> balances = GetAddressBalances(*pwallet);
196 for (const std::set<CTxDestination>& grouping : GetAddressGroupings(*pwallet)) {
197 UniValue jsonGrouping(UniValue::VARR);
198 for (const CTxDestination& address : grouping)
199 {
200 UniValue addressInfo(UniValue::VARR);
201 addressInfo.push_back(EncodeDestination(address));
202 addressInfo.push_back(ValueFromAmount(balances[address]));
203 {
204 const auto* address_book_entry = pwallet->FindAddressBookEntry(address);
205 if (address_book_entry) {
206 addressInfo.push_back(address_book_entry->GetLabel());
207 }
208 }
209 jsonGrouping.push_back(std::move(addressInfo));
210 }
211 jsonGroupings.push_back(std::move(jsonGrouping));
212 }
213 return jsonGroupings;
214 },
215 };
216 }
217
218 RPCHelpMan addmultisigaddress()
219 {
220 return RPCHelpMan{"addmultisigaddress",
221 "\nAdd an nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n"
222 "Each key is a Limenka address or hex-encoded public key.\n"
223 "This functionality is only intended for use with non-watchonly addresses.\n"
224 "See `importaddress` for watchonly p2sh address support.\n"
225 "If 'label' is specified, assign address to that label.\n"
226 "Public keys can be sorted according to BIP67 during the request if required.\n"
227 "Note: This command is only compatible with legacy wallets.\n",
228 {
229 {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys or addresses."},
230 {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The limenka addresses or hex-encoded public keys",
231 {
232 {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "limenka address or hex-encoded public key"},
233 },
234 },
235 {"options|label", {RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Type::STR}, RPCArg::Optional::OMITTED, "",
236 {
237 {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".", RPCArgOptions{.also_positional = true}},
238 {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A label to assign the address to.", RPCArgOptions{.also_positional = true}},
239 {"sort", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to sort public keys according to BIP67."},
240 },
241 RPCArgOptions{.oneline_description="\"options\""}},
242 {"address_type", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.hidden=true}},
243 },
244 RPCResult{
245 RPCResult::Type::OBJ, "", "",
246 {
247 {RPCResult::Type::STR, "address", "The value of the new multisig address"},
248 {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script"},
249 {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
250 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Any warnings resulting from the creation of this multisig",
251 {
252 {RPCResult::Type::STR, "", ""},
253 }},
254 }
255 },
256 RPCExamples{
257 "\nAdd a multisig address from 2 addresses\n"
258 + HelpExampleCli("addmultisigaddress", "2 \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"") +
259 "\nAs a JSON-RPC call\n"
260 + HelpExampleRpc("addmultisigaddress", "2, [\"" + EXAMPLE_ADDRESS[0] + "\",\"" + EXAMPLE_ADDRESS[1] + "\"]")
261 },
262 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
263 {
264 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
265 if (!pwallet) return UniValue::VNULL;
266
267 LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet);
268
269 LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
270
271 int required = request.params[0].getInt<int>();
272
273 std::string label;
274 OutputType output_type = pwallet->m_default_address_type;
275 bool sort = false;
276
277 if (!request.params[2].isNull()) {
278 if (request.params[2].type() == UniValue::VSTR) {
279 // Backward compatibility
280 label = LabelFromValue(request.params[2]);
281 } else {
282 const UniValue& options = request.params[2];
283 RPCTypeCheckObj(options,
284 {
285 {"address_type", UniValueType(UniValue::VSTR)},
286 {"label", UniValueType(UniValue::VSTR)},
287 {"sort", UniValueType(UniValue::VBOOL)},
288 },
289 true, true);
290
291 if (options.exists("address_type")) {
292 if (!request.params[3].isNull()) {
293 throw JSONRPCError(RPC_MISC_ERROR, "address_type provided in both options and 4th parameter");
294 }
295 std::optional<OutputType> parsed = ParseOutputType(options["address_type"].get_str());
296 if (!parsed) {
297 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", options["address_type"].get_str()));
298 }
299 output_type = parsed.value();
300 }
301
302 if (options.exists("label")) {
303 label = LabelFromValue(options["label"]);
304 }
305
306 if (options.exists("sort")) {
307 sort = options["sort"].get_bool();
308 }
309 }
310 }
311
312 // Get the public keys
313 const UniValue& keys_or_addrs = request.params[1].get_array();
314 std::vector<CPubKey> pubkeys;
315 for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) {
316 if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) {
317 pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str()));
318 } else {
319 pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str()));
320 }
321 if (sort && !pubkeys.back().IsCompressed()) {
322 throw std::runtime_error(strprintf("Compressed key required for BIP67: %s", keys_or_addrs[i].get_str()));
323 }
324 }
325
326 if (!request.params[3].isNull()) {
327 std::optional<OutputType> parsed = ParseOutputType(request.params[3].get_str());
328 if (!parsed) {
329 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
330 } else if (parsed.value() == OutputType::BECH32M) {
331 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m multisig addresses cannot be created with legacy wallets");
332 }
333 output_type = parsed.value();
334 }
335
336 // Construct multisig scripts
337 FlatSigningProvider provider;
338 CScript inner;
339 CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, provider, inner, sort);
340
341 // Import scripts into the wallet
342 for (const auto& [id, script] : provider.scripts) {
343 // Due to a bug in the legacy wallet, the p2sh maximum script size limit is also imposed on 'p2sh-segwit' and 'bech32' redeem scripts.
344 // Even when redeem scripts over MAX_SCRIPT_ELEMENT_SIZE bytes are valid for segwit output types, we don't want to
345 // enable it because:
346 // 1) It introduces a compatibility-breaking change requiring downgrade protection; older wallets would be unable to interact with these "new" legacy wallets.
347 // 2) Considering the ongoing deprecation of the legacy spkm, this issue adds another good reason to transition towards descriptors.
348 if (script.size() > MAX_SCRIPT_ELEMENT_SIZE) throw JSONRPCError(RPC_WALLET_ERROR, "Unsupported multisig script size for legacy wallet. Upgrade to descriptors to overcome this limitation for p2sh-segwit or bech32 scripts");
349
350 if (!spk_man.AddCScript(script)) {
351 if (CScript inner_script; spk_man.GetCScript(CScriptID(script), inner_script)) {
352 CHECK_NONFATAL(inner_script == script); // Nothing to add, script already contained by the wallet
353 continue;
354 }
355 throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error importing script into the wallet"));
356 }
357 }
358
359 // Store destination in the addressbook
360 pwallet->SetAddressBook(dest, label, AddressPurpose::SEND);
361
362 // Make the descriptor
363 std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), spk_man);
364
365 UniValue result(UniValue::VOBJ);
366 result.pushKV("address", EncodeDestination(dest));
367 result.pushKV("redeemScript", HexStr(inner));
368 result.pushKV("descriptor", descriptor->ToString());
369
370 UniValue warnings(UniValue::VARR);
371 if (descriptor->GetOutputType() != output_type) {
372 // Only warns if the user has explicitly chosen an address type we cannot generate
373 warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
374 }
375 PushWarnings(warnings, result);
376
377 return result;
378 },
379 };
380 }
381
382 RPCHelpMan keypoolrefill()
383 {
384 return RPCHelpMan{"keypoolrefill",
385 "Refills each descriptor keypool in the wallet up to the specified number of new keys.\n"
386 "By default, descriptor wallets have 4 active ranged descriptors (\"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"), each with " + util::ToString(DEFAULT_KEYPOOL_SIZE) + " entries.\n" +
387 HELP_REQUIRING_PASSPHRASE,
388 {
389 {"newsize", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%u, or as set by -keypool", DEFAULT_KEYPOOL_SIZE)}, "The new keypool size"},
390 },
391 RPCResult{RPCResult::Type::NONE, "", ""},
392 RPCExamples{
393 HelpExampleCli("keypoolrefill", "")
394 + HelpExampleRpc("keypoolrefill", "")
395 },
396 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
397 {
398 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
399 if (!pwallet) return UniValue::VNULL;
400
401 if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
402 throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
403 }
404
405 LOCK(pwallet->cs_wallet);
406
407 // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool
408 unsigned int kpSize = 0;
409 if (!request.params[0].isNull()) {
410 if (request.params[0].getInt<int>() < 0)
411 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size.");
412 kpSize = (unsigned int)request.params[0].getInt<int>();
413 }
414
415 EnsureWalletIsUnlocked(*pwallet);
416 pwallet->TopUpKeyPool(kpSize);
417
418 if (pwallet->GetKeyPoolSize() < kpSize) {
419 throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool.");
420 }
421
422 return UniValue::VNULL;
423 },
424 };
425 }
426
427 RPCHelpMan newkeypool()
428 {
429 return RPCHelpMan{"newkeypool",
430 "\nEntirely clears and refills the keypool.\n"
431 "WARNING: On non-HD wallets, this will require a new backup immediately, to include the new keys.\n"
432 "When restoring a backup of an HD wallet created before the newkeypool command is run, funds received to\n"
433 "new addresses may not appear automatically. They have not been lost, but the wallet may not find them.\n"
434 "This can be fixed by running the newkeypool command on the backup and then rescanning, so the wallet\n"
435 "re-generates the required keys." +
436 HELP_REQUIRING_PASSPHRASE,
437 {},
438 RPCResult{RPCResult::Type::NONE, "", ""},
439 RPCExamples{
440 HelpExampleCli("newkeypool", "")
441 + HelpExampleRpc("newkeypool", "")
442 },
443 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
444 {
445 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
446 if (!pwallet) return UniValue::VNULL;
447
448 LOCK(pwallet->cs_wallet);
449
450 LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
451 spk_man.NewKeyPool();
452
453 return UniValue::VNULL;
454 },
455 };
456 }
457
458
459 class DescribeWalletAddressVisitor
460 {
461 public:
462 const SigningProvider * const provider;
463
464 // NOLINTNEXTLINE(misc-no-recursion)
465 void ProcessSubScript(const CScript& subscript, UniValue& obj) const
466 {
467 // Always present: script type and redeemscript
468 std::vector<std::vector<unsigned char>> solutions_data;
469 TxoutType which_type = Solver(subscript, solutions_data);
470 obj.pushKV("script", GetTxnOutputType(which_type));
471 obj.pushKV("hex", HexStr(subscript));
472
473 CTxDestination embedded;
474 if (ExtractDestination(subscript, embedded)) {
475 // Only when the script corresponds to an address.
476 UniValue subobj(UniValue::VOBJ);
477 UniValue detail = DescribeAddress(embedded);
478 subobj.pushKVs(std::move(detail));
479 UniValue wallet_detail = std::visit(*this, embedded);
480 subobj.pushKVs(std::move(wallet_detail));
481 subobj.pushKV("address", EncodeDestination(embedded));
482 subobj.pushKV("scriptPubKey", HexStr(subscript));
483 // Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works.
484 if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]);
485 obj.pushKV("embedded", std::move(subobj));
486 } else if (which_type == TxoutType::MULTISIG) {
487 // Also report some information on multisig scripts (which do not have a corresponding address).
488 obj.pushKV("sigsrequired", solutions_data[0][0]);
489 UniValue pubkeys(UniValue::VARR);
490 for (size_t i = 1; i < solutions_data.size() - 1; ++i) {
491 CPubKey key(solutions_data[i].begin(), solutions_data[i].end());
492 pubkeys.push_back(HexStr(key));
493 }
494 obj.pushKV("pubkeys", std::move(pubkeys));
495 }
496 }
497
498 explicit DescribeWalletAddressVisitor(const SigningProvider* _provider) : provider(_provider) {}
499
500 UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
501 UniValue operator()(const PubKeyDestination& dest) const { return UniValue(UniValue::VOBJ); }
502
503 UniValue operator()(const PKHash& pkhash) const
504 {
505 CKeyID keyID{ToKeyID(pkhash)};
506 UniValue obj(UniValue::VOBJ);
507 CPubKey vchPubKey;
508 if (provider && provider->GetPubKey(keyID, vchPubKey)) {
509 obj.pushKV("pubkey", HexStr(vchPubKey));
510 obj.pushKV("iscompressed", vchPubKey.IsCompressed());
511 }
512 return obj;
513 }
514
515 // NOLINTNEXTLINE(misc-no-recursion)
516 UniValue operator()(const ScriptHash& scripthash) const
517 {
518 UniValue obj(UniValue::VOBJ);
519 CScript subscript;
520 if (provider && provider->GetCScript(ToScriptID(scripthash), subscript)) {
521 ProcessSubScript(subscript, obj);
522 }
523 return obj;
524 }
525
526 UniValue operator()(const WitnessV0KeyHash& id) const
527 {
528 UniValue obj(UniValue::VOBJ);
529 CPubKey pubkey;
530 if (provider && provider->GetPubKey(ToKeyID(id), pubkey)) {
531 obj.pushKV("pubkey", HexStr(pubkey));
532 }
533 return obj;
534 }
535
536 // NOLINTNEXTLINE(misc-no-recursion)
537 UniValue operator()(const WitnessV0ScriptHash& id) const
538 {
539 UniValue obj(UniValue::VOBJ);
540 CScript subscript;
541 CRIPEMD160 hasher;
542 uint160 hash;
543 hasher.Write(id.begin(), 32).Finalize(hash.begin());
544 if (provider && provider->GetCScript(CScriptID(hash), subscript)) {
545 ProcessSubScript(subscript, obj);
546 }
547 return obj;
548 }
549
550 UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); }
551 UniValue operator()(const WitnessV3SpkHash& id) const {
552 UniValue obj(UniValue::VOBJ);
553 XOnlyPubKey pubkey;
554 if (provider && provider->GetSpkPubKey(uint256(id), pubkey)) {
555 obj.pushKV("pubkey", HexStr(pubkey));
556 }
557 return obj;
558 }
559 UniValue operator()(const PayToAnchor& id) const { return UniValue(UniValue::VOBJ); }
560 UniValue operator()(const WitnessV4StealthAddress& id) const {
561 UniValue obj(UniValue::VOBJ);
562 obj.pushKV("view_key", HexStr(id.view));
563 obj.pushKV("spend_key", HexStr(id.spend));
564 return obj;
565 }
566 UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); }
567 };
568
569 static UniValue DescribeWalletAddress(const CWallet& wallet, const CTxDestination& dest)
570 {
571 UniValue ret(UniValue::VOBJ);
572 UniValue detail = DescribeAddress(dest);
573 CScript script = GetScriptForDestination(dest);
574 std::unique_ptr<SigningProvider> provider = nullptr;
575 provider = wallet.GetSolvingProvider(script);
576 ret.pushKVs(std::move(detail));
577 ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest));
578 return ret;
579 }
580
581 RPCHelpMan getaddressinfo()
582 {
583 return RPCHelpMan{"getaddressinfo",
584 "\nReturn information about the given limenka address.\n"
585 "Some of the information will only be present if the address is in the active wallet.\n",
586 {
587 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The limenka address for which to get information."},
588 },
589 RPCResult{
590 RPCResult::Type::OBJ, "", "",
591 {
592 {RPCResult::Type::STR, "address", "The limenka address validated."},
593 {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded output script generated by the address."},
594 {RPCResult::Type::BOOL, "ismine", "If the address is yours."},
595 {RPCResult::Type::BOOL, "isactive", "If the key is in the active keypool (always equal to \"ismine\" in descriptor wallets)."},
596 {RPCResult::Type::BOOL, "iswatchonly", "If the address is watchonly."},
597 {RPCResult::Type::BOOL, "solvable", "If we know how to spend coins sent to this address, ignoring the possible lack of private keys."},
598 {RPCResult::Type::STR, "desc", /*optional=*/true, "A descriptor for spending coins sent to this address (only when solvable)."},
599 {RPCResult::Type::STR, "parent_desc", /*optional=*/true, "The descriptor used to derive this address if this is a descriptor wallet"},
600 {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script."},
601 {RPCResult::Type::BOOL, "ischange", "If the address was used for change output."},
602 {RPCResult::Type::BOOL, "iswitness", "If the address is a witness address."},
603 {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program."},
604 {RPCResult::Type::STR_HEX, "witness_program", /*optional=*/true, "The hex value of the witness program."},
605 {RPCResult::Type::STR, "script", /*optional=*/true, "The output script type. Only if isscript is true and the redeemscript is known. Possible\n"
606 "types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n"
607 "witness_v0_scripthash, witness_unknown."},
608 {RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "The redeemscript for the p2sh address."},
609 {RPCResult::Type::ARR, "pubkeys", /*optional=*/true, "Array of pubkeys associated with the known redeemscript (only if script is multisig).",
610 {
611 {RPCResult::Type::STR, "pubkey", ""},
612 }},
613 {RPCResult::Type::NUM, "sigsrequired", /*optional=*/true, "The number of signatures required to spend multisig output (only if script is multisig)."},
614 {RPCResult::Type::STR_HEX, "pubkey", /*optional=*/true, "The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH)."},
615 {RPCResult::Type::OBJ, "embedded", /*optional=*/true, "Information about the address embedded in P2SH or P2WSH, if relevant and known.",
616 {
617 {RPCResult::Type::ELISION, "", "Includes all getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath, hdseedid)\n"
618 "and relation to the wallet (ismine, iswatchonly)."},
619 }},
620 {RPCResult::Type::BOOL, "iscompressed", /*optional=*/true, "If the pubkey is compressed."},
621 {RPCResult::Type::NUM_TIME, "timestamp", /*optional=*/true, "The creation time of the key, if available, expressed in " + UNIX_EPOCH_TIME + "."},
622 {RPCResult::Type::STR, "hdkeypath", /*optional=*/true, "The HD keypath, if the key is HD and available."},
623 {RPCResult::Type::STR_HEX, "hdseedid", /*optional=*/true, "The Hash160 of the HD seed."},
624 {RPCResult::Type::STR_HEX, "hdmasterfingerprint", /*optional=*/true, "The fingerprint of the master key."},
625 {RPCResult::Type::ARR, "labels", "Array of labels associated with the address. Currently limited to one label but returned\n"
626 "as an array to keep the API stable if multiple labels are enabled in the future.",
627 {
628 {RPCResult::Type::STR, "label name", "Label name (defaults to \"\")."},
629 }},
630 {RPCResult::Type::ARR, "use_txids", "",
631 {
632 {RPCResult::Type::STR_HEX, "txid", "The ids of transactions involving this wallet which received with the address"},
633 }},
634 }
635 },
636 RPCExamples{
637 HelpExampleCli("getaddressinfo", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
638 HelpExampleRpc("getaddressinfo", "\"" + EXAMPLE_ADDRESS[0] + "\"")
639 },
640 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
641 {
642 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
643 if (!pwallet) return UniValue::VNULL;
644
645 LOCK(pwallet->cs_wallet);
646
647 std::string error_msg;
648 CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
649
650 // Make sure the destination is valid
651 if (!IsValidDestination(dest)) {
652 // Set generic error message in case 'DecodeDestination' didn't set it
653 if (error_msg.empty()) error_msg = "Invalid address";
654
655 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error_msg);
656 }
657
658 UniValue ret(UniValue::VOBJ);
659
660 std::string currentAddress = EncodeDestination(dest);
661 ret.pushKV("address", currentAddress);
662
663 CScript scriptPubKey = GetScriptForDestination(dest);
664 ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
665
666 std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
667
668 isminetype mine = pwallet->IsMine(dest);
669 ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
670 ret.pushKV("isactive", pwallet->IsDestinationActive(dest));
671
672 if (provider) {
673 auto inferred = InferDescriptor(scriptPubKey, *provider);
674 bool solvable = inferred->IsSolvable();
675 ret.pushKV("solvable", solvable);
676 if (solvable) {
677 ret.pushKV("desc", inferred->ToString());
678 }
679 } else {
680 ret.pushKV("solvable", false);
681 }
682
683 const auto& spk_mans = pwallet->GetScriptPubKeyMans(scriptPubKey);
684 // In most cases there is only one matching ScriptPubKey manager and we can't resolve ambiguity in a better way
685 ScriptPubKeyMan* spk_man{nullptr};
686 if (spk_mans.size()) spk_man = *spk_mans.begin();
687
688 DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
689 if (desc_spk_man) {
690 std::string desc_str;
691 if (desc_spk_man->GetDescriptorString(desc_str, /*priv=*/false)) {
692 ret.pushKV("parent_desc", desc_str);
693 }
694 }
695
696 ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
697
698 UniValue detail = DescribeWalletAddress(*pwallet, dest);
699 ret.pushKVs(std::move(detail));
700
701 ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey));
702
703 if (spk_man) {
704 if (const std::unique_ptr<CKeyMetadata> meta = spk_man->GetMetadata(dest)) {
705 ret.pushKV("timestamp", meta->nCreateTime);
706 if (meta->has_key_origin) {
707 // In legacy wallets hdkeypath has always used an apostrophe for
708 // hardened derivation. Perhaps some external tool depends on that.
709 ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path, /*apostrophe=*/!desc_spk_man));
710 ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
711 ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint));
712 }
713 }
714 }
715
716 // Return a `labels` array containing the label associated with the address,
717 // equivalent to the `label` field above. Currently only one label can be
718 // associated with an address, but we return an array so the API remains
719 // stable if we allow multiple labels to be associated with an address in
720 // the future.
721 UniValue labels(UniValue::VARR);
722 const auto* address_book_entry = pwallet->FindAddressBookEntry(dest);
723 if (address_book_entry) {
724 labels.push_back(address_book_entry->GetLabel());
725 }
726 ret.pushKV("labels", std::move(labels));
727
728 // NOTE: Intentionally not special-casing a single txid: while addresses
729 // should never be reused, it's not unexpected to have RBF result in
730 // multiple txids for a single use.
731 UniValue use_txids(UniValue::VARR);
732 pwallet->FindScriptPubKeyUsed(std::set<CScript>{scriptPubKey}, [&use_txids](const CWalletTx&wtx) {
733 use_txids.push_back(wtx.GetHash().GetHex());
734 });
735 ret.pushKV("use_txids", std::move(use_txids));
736
737 return ret;
738 },
739 };
740 }
741
742 RPCHelpMan getaddressesbylabel()
743 {
744 return RPCHelpMan{"getaddressesbylabel",
745 "\nReturns the list of addresses assigned the specified label.\n",
746 {
747 {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label."},
748 },
749 RPCResult{
750 RPCResult::Type::OBJ_DYN, "", "json object with addresses as keys",
751 {
752 {RPCResult::Type::OBJ, "address", "json object with information about address",
753 {
754 {RPCResult::Type::STR, "purpose", "Purpose of address (\"send\" for sending address, \"receive\" for receiving address)"},
755 }},
756 }
757 },
758 RPCExamples{
759 HelpExampleCli("getaddressesbylabel", "\"tabby\"")
760 + HelpExampleRpc("getaddressesbylabel", "\"tabby\"")
761 },
762 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
763 {
764 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
765 if (!pwallet) return UniValue::VNULL;
766
767 LOCK(pwallet->cs_wallet);
768
769 const std::string label{LabelFromValue(request.params[0])};
770
771 // Find all addresses that have the given label
772 UniValue ret(UniValue::VOBJ);
773 std::set<std::string> addresses;
774 pwallet->ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label, bool _is_change, const std::optional<AddressPurpose>& _purpose) {
775 if (_is_change) return;
776 if (_label == label) {
777 std::string address = EncodeDestination(_dest);
778 // CWallet::m_address_book is not expected to contain duplicate
779 // address strings, but build a separate set as a precaution just in
780 // case it does.
781 bool unique = addresses.emplace(address).second;
782 CHECK_NONFATAL(unique);
783 // UniValue::pushKV checks if the key exists in O(N)
784 // and since duplicate addresses are unexpected (checked with
785 // std::set in O(log(N))), UniValue::pushKVEnd is used instead,
786 // which currently is O(1).
787 UniValue value(UniValue::VOBJ);
788 value.pushKV("purpose", _purpose ? PurposeToString(*_purpose) : "unknown");
789 ret.pushKVEnd(address, std::move(value));
790 }
791 });
792
793 if (ret.empty()) {
794 throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label));
795 }
796
797 return ret;
798 },
799 };
800 }
801
802 RPCHelpMan listlabels()
803 {
804 return RPCHelpMan{"listlabels",
805 "\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n",
806 {
807 {"purpose", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument."},
808 },
809 RPCResult{
810 RPCResult::Type::ARR, "", "",
811 {
812 {RPCResult::Type::STR, "label", "Label name"},
813 }
814 },
815 RPCExamples{
816 "\nList all labels\n"
817 + HelpExampleCli("listlabels", "") +
818 "\nList labels that have receiving addresses\n"
819 + HelpExampleCli("listlabels", "receive") +
820 "\nList labels that have sending addresses\n"
821 + HelpExampleCli("listlabels", "send") +
822 "\nAs a JSON-RPC call\n"
823 + HelpExampleRpc("listlabels", "receive")
824 },
825 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
826 {
827 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
828 if (!pwallet) return UniValue::VNULL;
829
830 LOCK(pwallet->cs_wallet);
831
832 std::optional<AddressPurpose> purpose;
833 if (!request.params[0].isNull()) {
834 std::string purpose_str = request.params[0].get_str();
835 if (!purpose_str.empty()) {
836 purpose = PurposeFromString(purpose_str);
837 if (!purpose) {
838 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid 'purpose' argument, must be a known purpose string, typically 'send', or 'receive'.");
839 }
840 }
841 }
842
843 // Add to a set to sort by label name, then insert into Univalue array
844 std::set<std::string> label_set = pwallet->ListAddrBookLabels(purpose);
845
846 UniValue ret(UniValue::VARR);
847 for (const std::string& name : label_set) {
848 ret.push_back(name);
849 }
850
851 return ret;
852 },
853 };
854 }
855
856
857 #ifdef ENABLE_EXTERNAL_SIGNER
858 RPCHelpMan walletdisplayaddress()
859 {
860 return RPCHelpMan{
861 "walletdisplayaddress",
862 "Display address on an external signer for verification.",
863 {
864 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "limenka address to display"},
865 },
866 RPCResult{
867 RPCResult::Type::OBJ,"","",
868 {
869 {RPCResult::Type::STR, "address", "The address as confirmed by the signer"},
870 }
871 },
872 RPCExamples{""},
873 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
874 {
875 std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
876 if (!wallet) return UniValue::VNULL;
877 CWallet* const pwallet = wallet.get();
878
879 LOCK(pwallet->cs_wallet);
880
881 CTxDestination dest = DecodeDestination(request.params[0].get_str());
882
883 // Make sure the destination is valid
884 if (!IsValidDestination(dest)) {
885 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
886 }
887
888 util::Result<void> res = pwallet->DisplayAddress(dest);
889 if (!res) throw JSONRPCError(RPC_MISC_ERROR, util::ErrorString(res).original);
890
891 UniValue result(UniValue::VOBJ);
892 result.pushKV("address", request.params[0].get_str());
893 return result;
894 }
895 };
896 }
897 #endif // ENABLE_EXTERNAL_SIGNER
898 } // namespace wallet
899