Coverage Report

Created: 2026-10-07 13:27

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
935
{
16
935
    return RPCMethod{
17
935
        "walletpassphrase",
18
935
        "Stores the wallet decryption key in memory for 'timeout' seconds.\n"
19
935
                "This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
20
935
            "\nNote:\n"
21
935
            "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
22
935
            "time that overrides the old one.\n",
23
935
                {
24
935
                    {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"},
25
935
                    {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."},
26
935
                },
27
935
                RPCResult{RPCResult::Type::NONE, "", ""},
28
935
                RPCExamples{
29
935
            "\nUnlock the wallet for 60 seconds\n"
30
935
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
31
935
            "\nLock the wallet again (before 60 seconds)\n"
32
935
            + HelpExampleCli("walletlock", "") +
33
935
            "\nAs a JSON-RPC call\n"
34
935
            + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
35
935
                },
36
935
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
37
935
{
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 (auto unlocked{pwallet->Unlock(strWalletPass)}; !unlocked) {
75
4
            throw JSONRPCError(HandleWalletErrorCode(unlocked.error().code), unlocked.error().message.original);
76
4
        }
77
78
50
        pwallet->TopUpKeyPool();
79
80
50
        pwallet->nRelockTime = GetTime() + nSleepTime;
81
50
        relock_time = pwallet->nRelockTime;
82
50
    }
83
84
    // Get wallet scheduler to queue up the relock callback in the future.
85
    // Scheduled events don't get destructed until they are executed,
86
    // and they are executed in series in a single scheduler thread so
87
    // no cs_wallet lock is needed.
88
0
    WalletContext& context = EnsureWalletContext(request.context);
89
    // Keep a weak pointer to the wallet so that it is possible to unload the
90
    // wallet before the following callback is called. If a valid shared pointer
91
    // is acquired in the callback then the wallet is still loaded.
92
50
    std::weak_ptr<CWallet> weak_wallet = wallet;
93
50
    context.scheduler->scheduleFromNow([weak_wallet, relock_time] {
94
5
        if (auto shared_wallet = weak_wallet.lock()) {
95
3
            LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet);
96
            // Skip if this is not the most recent relock callback.
97
3
            if (shared_wallet->nRelockTime != relock_time) return;
98
3
            shared_wallet->Lock();
99
3
            shared_wallet->nRelockTime = 0;
100
3
        }
101
5
    }, std::chrono::seconds(nSleepTime));
102
103
50
    return UniValue::VNULL;
104
54
},
105
935
    };
106
935
}
107
108
109
RPCMethod walletpassphrasechange()
110
880
{
111
880
    return RPCMethod{
112
880
        "walletpassphrasechange",
113
880
        "Changes the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n",
114
880
                {
115
880
                    {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"},
116
880
                    {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"},
117
880
                },
118
880
                RPCResult{RPCResult::Type::NONE, "", ""},
119
880
                RPCExamples{
120
880
                    HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"")
121
880
            + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")
122
880
                },
123
880
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
124
880
{
125
7
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
126
7
    if (!pwallet) return UniValue::VNULL;
127
128
7
    if (!pwallet->HasEncryptionKeys()) {
129
1
        throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
130
1
    }
131
132
6
    if (pwallet->Scanner().IsScanningWithPassphrase()) {
133
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.");
134
1
    }
135
136
5
    LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
137
138
5
    SecureString strOldWalletPass;
139
5
    strOldWalletPass.reserve(100);
140
5
    strOldWalletPass = std::string_view{request.params[0].get_str()};
141
142
5
    SecureString strNewWalletPass;
143
5
    strNewWalletPass.reserve(100);
144
5
    strNewWalletPass = std::string_view{request.params[1].get_str()};
145
146
5
    if (strOldWalletPass.empty() || strNewWalletPass.empty()) {
147
1
        throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
148
1
    }
149
150
4
    if (auto changed{pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)}; !changed) {
151
2
        throw JSONRPCError(HandleWalletErrorCode(changed.error().code), changed.error().message.original);
152
2
    }
153
154
2
    return UniValue::VNULL;
155
4
},
156
880
    };
157
880
}
158
159
160
RPCMethod walletlock()
161
901
{
162
901
    return RPCMethod{
163
901
        "walletlock",
164
901
        "Removes the wallet encryption key from memory, locking the wallet.\n"
165
901
                "After calling this method, you will need to call walletpassphrase again\n"
166
901
                "before being able to call any methods which require the wallet to be unlocked.\n",
167
901
                {},
168
901
                RPCResult{RPCResult::Type::NONE, "", ""},
169
901
                RPCExamples{
170
901
            "\nSet the passphrase for 2 minutes to perform a transaction\n"
171
901
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
172
901
            "\nPerform a send (requires passphrase set)\n"
173
901
            + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") +
174
901
            "\nClear the passphrase since we are done before 2 minutes is up\n"
175
901
            + HelpExampleCli("walletlock", "") +
176
901
            "\nAs a JSON-RPC call\n"
177
901
            + HelpExampleRpc("walletlock", "")
178
901
                },
179
901
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
180
901
{
181
28
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
182
28
    if (!pwallet) return UniValue::VNULL;
183
184
28
    if (!pwallet->HasEncryptionKeys()) {
185
0
        throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called.");
186
0
    }
187
188
28
    if (pwallet->Scanner().IsScanningWithPassphrase()) {
189
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.");
190
1
    }
191
192
27
    LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
193
194
27
    pwallet->Lock();
195
27
    pwallet->nRelockTime = 0;
196
197
27
    return UniValue::VNULL;
198
28
},
199
901
    };
200
901
}
201
202
203
RPCMethod encryptwallet()
204
895
{
205
895
    return RPCMethod{
206
895
        "encryptwallet",
207
895
        "Encrypts the wallet with 'passphrase'. This is for first time encryption.\n"
208
895
        "After this, any calls that interact with private keys such as sending or signing \n"
209
895
        "will require the passphrase to be set prior to making these calls.\n"
210
895
                "Use the walletpassphrase call for this, and then walletlock call.\n"
211
895
                "If the wallet is already encrypted, use the walletpassphrasechange call.\n"
212
895
                "** IMPORTANT **\n"
213
895
                "For security reasons, the encryption process will generate a new HD seed, resulting\n"
214
895
                "in the creation of a fresh set of active descriptors. Therefore, it is crucial to\n"
215
895
                "securely back up the newly generated wallet file using the backupwallet RPC.\n",
216
895
                {
217
895
                    {"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."},
218
895
                },
219
895
                RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
220
895
                RPCExamples{
221
895
            "\nEncrypt your wallet\n"
222
895
            + HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
223
895
            "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
224
895
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
225
895
            "\nNow we can do something like sign\n"
226
895
            + HelpExampleCli("signmessage", "\"address\" \"test message\"") +
227
895
            "\nNow lock the wallet again by removing the passphrase\n"
228
895
            + HelpExampleCli("walletlock", "") +
229
895
            "\nAs a JSON-RPC call\n"
230
895
            + HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
231
895
                },
232
895
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
233
895
{
234
22
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
235
22
    if (!pwallet) return UniValue::VNULL;
236
237
22
    if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
238
3
        throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt.");
239
3
    }
240
241
19
    if (pwallet->HasEncryptionKeys()) {
242
1
        throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
243
1
    }
244
245
18
    if (pwallet->Scanner().IsScanningWithPassphrase()) {
246
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.");
247
0
    }
248
249
18
    LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
250
251
18
    SecureString strWalletPass;
252
18
    strWalletPass.reserve(100);
253
18
    strWalletPass = std::string_view{request.params[0].get_str()};
254
255
18
    if (strWalletPass.empty()) {
256
1
        throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
257
1
    }
258
259
17
    if (!pwallet->EncryptWallet(strWalletPass)) {
260
0
        throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
261
0
    }
262
263
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.";
264
17
},
265
895
    };
266
895
}
267
} // namespace wallet