Coverage Report

Created: 2026-09-14 20:36

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