Coverage Report

Created: 2025-02-21 14:37

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