Coverage Report

Created: 2025-06-10 13:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/bitcoin/src/wallet/rpc/encrypt.cpp
Line
Count
Source
1
// Copyright (c) 2011-2022 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 <rpc/util.h>
6
#include <wallet/rpc/util.h>
7
#include <wallet/wallet.h>
8
9
10
namespace wallet {
11
RPCHelpMan walletpassphrase()
12
22.1k
{
13
22.1k
    return RPCHelpMan{
14
22.1k
        "walletpassphrase",
15
22.1k
        "Stores the wallet decryption key in memory for 'timeout' seconds.\n"
16
22.1k
                "This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
17
22.1k
            "\nNote:\n"
18
22.1k
            "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
19
22.1k
            "time that overrides the old one.\n",
20
22.1k
                {
21
22.1k
                    {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"},
22
22.1k
                    {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."},
23
22.1k
                },
24
22.1k
                RPCResult{RPCResult::Type::NONE, "", ""},
25
22.1k
                RPCExamples{
26
22.1k
            "\nUnlock the wallet for 60 seconds\n"
27
22.1k
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
28
22.1k
            "\nLock the wallet again (before 60 seconds)\n"
29
22.1k
            + HelpExampleCli("walletlock", "") +
30
22.1k
            "\nAs a JSON-RPC call\n"
31
22.1k
            + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
32
22.1k
                },
33
22.1k
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
34
22.1k
{
35
0
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
36
0
    if (!wallet) return UniValue::VNULL;
  Branch (36:9): [True: 0, False: 0]
37
0
    CWallet* const pwallet = wallet.get();
38
39
0
    int64_t nSleepTime;
40
0
    int64_t relock_time;
41
    // Prevent concurrent calls to walletpassphrase with the same wallet.
42
0
    LOCK(pwallet->m_unlock_mutex);
43
0
    {
44
0
        LOCK(pwallet->cs_wallet);
45
46
0
        if (!pwallet->IsCrypted()) {
  Branch (46:13): [True: 0, False: 0]
47
0
            throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called.");
48
0
        }
49
50
        // Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed
51
0
        SecureString strWalletPass;
52
0
        strWalletPass.reserve(100);
53
0
        strWalletPass = std::string_view{request.params[0].get_str()};
54
55
        // Get the timeout
56
0
        nSleepTime = request.params[1].getInt<int64_t>();
57
        // Timeout cannot be negative, otherwise it will relock immediately
58
0
        if (nSleepTime < 0) {
  Branch (58:13): [True: 0, False: 0]
59
0
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
60
0
        }
61
        // Clamp timeout
62
0
        constexpr int64_t MAX_SLEEP_TIME = 100000000; // larger values trigger a macos/libevent bug?
63
0
        if (nSleepTime > MAX_SLEEP_TIME) {
  Branch (63:13): [True: 0, False: 0]
64
0
            nSleepTime = MAX_SLEEP_TIME;
65
0
        }
66
67
0
        if (strWalletPass.empty()) {
  Branch (67:13): [True: 0, False: 0]
68
0
            throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
69
0
        }
70
71
0
        if (!pwallet->Unlock(strWalletPass)) {
  Branch (71:13): [True: 0, False: 0]
72
            // Check if the passphrase has a null character (see #27067 for details)
73
0
            if (strWalletPass.find('\0') == std::string::npos) {
  Branch (73:17): [True: 0, False: 0]
74
0
                throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
75
0
            } else {
76
0
                throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered is incorrect. "
77
0
                                                                    "It contains a null character (ie - a zero byte). "
78
0
                                                                    "If the passphrase was set with a version of this software prior to 25.0, "
79
0
                                                                    "please try again with only the characters up to — but not including — "
80
0
                                                                    "the first null character. If this is successful, please set a new "
81
0
                                                                    "passphrase to avoid this issue in the future.");
82
0
            }
83
0
        }
84
85
0
        pwallet->TopUpKeyPool();
86
87
0
        pwallet->nRelockTime = GetTime() + nSleepTime;
88
0
        relock_time = pwallet->nRelockTime;
89
0
    }
90
91
    // rpcRunLater must be called without cs_wallet held otherwise a deadlock
92
    // can occur. The deadlock would happen when RPCRunLater removes the
93
    // previous timer (and waits for the callback to finish if already running)
94
    // and the callback locks cs_wallet.
95
0
    AssertLockNotHeld(wallet->cs_wallet);
96
    // Keep a weak pointer to the wallet so that it is possible to unload the
97
    // wallet before the following callback is called. If a valid shared pointer
98
    // is acquired in the callback then the wallet is still loaded.
99
0
    std::weak_ptr<CWallet> weak_wallet = wallet;
100
0
    pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] {
101
0
        if (auto shared_wallet = weak_wallet.lock()) {
  Branch (101:18): [True: 0, False: 0]
102
0
            LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet);
103
            // Skip if this is not the most recent rpcRunLater callback.
104
0
            if (shared_wallet->nRelockTime != relock_time) return;
  Branch (104:17): [True: 0, False: 0]
105
0
            shared_wallet->Lock();
106
0
            shared_wallet->nRelockTime = 0;
107
0
        }
108
0
    }, nSleepTime);
109
110
0
    return UniValue::VNULL;
111
0
},
112
22.1k
    };
113
22.1k
}
114
115
116
RPCHelpMan walletpassphrasechange()
117
22.1k
{
118
22.1k
    return RPCHelpMan{
119
22.1k
        "walletpassphrasechange",
120
22.1k
        "Changes the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n",
121
22.1k
                {
122
22.1k
                    {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"},
123
22.1k
                    {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"},
124
22.1k
                },
125
22.1k
                RPCResult{RPCResult::Type::NONE, "", ""},
126
22.1k
                RPCExamples{
127
22.1k
                    HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"")
128
22.1k
            + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")
129
22.1k
                },
130
22.1k
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
131
22.1k
{
132
0
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
133
0
    if (!pwallet) return UniValue::VNULL;
  Branch (133:9): [True: 0, False: 0]
134
135
0
    if (!pwallet->IsCrypted()) {
  Branch (135:9): [True: 0, False: 0]
136
0
        throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
137
0
    }
138
139
0
    if (pwallet->IsScanningWithPassphrase()) {
  Branch (139:9): [True: 0, False: 0]
140
0
        throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before changing the passphrase.");
141
0
    }
142
143
0
    LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
144
145
0
    SecureString strOldWalletPass;
146
0
    strOldWalletPass.reserve(100);
147
0
    strOldWalletPass = std::string_view{request.params[0].get_str()};
148
149
0
    SecureString strNewWalletPass;
150
0
    strNewWalletPass.reserve(100);
151
0
    strNewWalletPass = std::string_view{request.params[1].get_str()};
152
153
0
    if (strOldWalletPass.empty() || strNewWalletPass.empty()) {
  Branch (153:9): [True: 0, False: 0]
  Branch (153:37): [True: 0, False: 0]
154
0
        throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
155
0
    }
156
157
0
    if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) {
  Branch (157:9): [True: 0, False: 0]
158
        // Check if the old passphrase had a null character (see #27067 for details)
159
0
        if (strOldWalletPass.find('\0') == std::string::npos) {
  Branch (159:13): [True: 0, False: 0]
160
0
            throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
161
0
        } else {
162
0
            throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The old wallet passphrase entered is incorrect. "
163
0
                                                                "It contains a null character (ie - a zero byte). "
164
0
                                                                "If the old passphrase was set with a version of this software prior to 25.0, "
165
0
                                                                "please try again with only the characters up to — but not including — "
166
0
                                                                "the first null character.");
167
0
        }
168
0
    }
169
170
0
    return UniValue::VNULL;
171
0
},
172
22.1k
    };
173
22.1k
}
174
175
176
RPCHelpMan walletlock()
177
22.1k
{
178
22.1k
    return RPCHelpMan{
179
22.1k
        "walletlock",
180
22.1k
        "Removes the wallet encryption key from memory, locking the wallet.\n"
181
22.1k
                "After calling this method, you will need to call walletpassphrase again\n"
182
22.1k
                "before being able to call any methods which require the wallet to be unlocked.\n",
183
22.1k
                {},
184
22.1k
                RPCResult{RPCResult::Type::NONE, "", ""},
185
22.1k
                RPCExamples{
186
22.1k
            "\nSet the passphrase for 2 minutes to perform a transaction\n"
187
22.1k
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
188
22.1k
            "\nPerform a send (requires passphrase set)\n"
189
22.1k
            + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") +
190
22.1k
            "\nClear the passphrase since we are done before 2 minutes is up\n"
191
22.1k
            + HelpExampleCli("walletlock", "") +
192
22.1k
            "\nAs a JSON-RPC call\n"
193
22.1k
            + HelpExampleRpc("walletlock", "")
194
22.1k
                },
195
22.1k
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
196
22.1k
{
197
0
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
198
0
    if (!pwallet) return UniValue::VNULL;
  Branch (198:9): [True: 0, False: 0]
199
200
0
    if (!pwallet->IsCrypted()) {
  Branch (200:9): [True: 0, False: 0]
201
0
        throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called.");
202
0
    }
203
204
0
    if (pwallet->IsScanningWithPassphrase()) {
  Branch (204:9): [True: 0, False: 0]
205
0
        throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before locking the wallet.");
206
0
    }
207
208
0
    LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
209
210
0
    pwallet->Lock();
211
0
    pwallet->nRelockTime = 0;
212
213
0
    return UniValue::VNULL;
214
0
},
215
22.1k
    };
216
22.1k
}
217
218
219
RPCHelpMan encryptwallet()
220
22.1k
{
221
22.1k
    return RPCHelpMan{
222
22.1k
        "encryptwallet",
223
22.1k
        "Encrypts the wallet with 'passphrase'. This is for first time encryption.\n"
224
22.1k
        "After this, any calls that interact with private keys such as sending or signing \n"
225
22.1k
        "will require the passphrase to be set prior to making these calls.\n"
226
22.1k
                "Use the walletpassphrase call for this, and then walletlock call.\n"
227
22.1k
                "If the wallet is already encrypted, use the walletpassphrasechange call.\n"
228
22.1k
                "** IMPORTANT **\n"
229
22.1k
                "For security reasons, the encryption process will generate a new HD seed, resulting\n"
230
22.1k
                "in the creation of a fresh set of active descriptors. Therefore, it is crucial to\n"
231
22.1k
                "securely back up the newly generated wallet file using the backupwallet RPC.\n",
232
22.1k
                {
233
22.1k
                    {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long."},
234
22.1k
                },
235
22.1k
                RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
236
22.1k
                RPCExamples{
237
22.1k
            "\nEncrypt your wallet\n"
238
22.1k
            + HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
239
22.1k
            "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
240
22.1k
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
241
22.1k
            "\nNow we can do something like sign\n"
242
22.1k
            + HelpExampleCli("signmessage", "\"address\" \"test message\"") +
243
22.1k
            "\nNow lock the wallet again by removing the passphrase\n"
244
22.1k
            + HelpExampleCli("walletlock", "") +
245
22.1k
            "\nAs a JSON-RPC call\n"
246
22.1k
            + HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
247
22.1k
                },
248
22.1k
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
249
22.1k
{
250
0
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
251
0
    if (!pwallet) return UniValue::VNULL;
  Branch (251:9): [True: 0, False: 0]
252
253
0
    if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
  Branch (253:9): [True: 0, False: 0]
254
0
        throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt.");
255
0
    }
256
257
0
    if (pwallet->IsCrypted()) {
  Branch (257:9): [True: 0, False: 0]
258
0
        throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
259
0
    }
260
261
0
    if (pwallet->IsScanningWithPassphrase()) {
  Branch (261:9): [True: 0, False: 0]
262
0
        throw JSONRPCError(RPC_WALLET_ERROR, "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before encrypting the wallet.");
263
0
    }
264
265
0
    LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
266
267
0
    SecureString strWalletPass;
268
0
    strWalletPass.reserve(100);
269
0
    strWalletPass = std::string_view{request.params[0].get_str()};
270
271
0
    if (strWalletPass.empty()) {
  Branch (271:9): [True: 0, False: 0]
272
0
        throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
273
0
    }
274
275
0
    if (!pwallet->EncryptWallet(strWalletPass)) {
  Branch (275:9): [True: 0, False: 0]
276
0
        throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
277
0
    }
278
279
0
    return "wallet encrypted; The keypool has been flushed and a new HD seed was generated. You need to make a new backup with the backupwallet RPC.";
280
0
},
281
22.1k
    };
282
22.1k
}
283
} // namespace wallet