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