Coverage Report

Created: 2026-07-14 18:13

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