fuzz coverage

Coverage Report

Created: 2025-09-17 22:41

/Users/eugenesiegel/btc/bitcoin/src/wallet/rpc/encrypt.cpp
Line
Count
Source (jump to first uncovered line)
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 <scheduler.h>
7
#include <wallet/context.h>
8
#include <wallet/rpc/util.h>
9
#include <wallet/wallet.h>
10
11
12
namespace wallet {
13
RPCHelpMan walletpassphrase()
14
0
{
15
0
    return RPCHelpMan{
16
0
        "walletpassphrase",
17
0
        "Stores the wallet decryption key in memory for 'timeout' seconds.\n"
18
0
                "This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
19
0
            "\nNote:\n"
20
0
            "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
21
0
            "time that overrides the old one.\n",
22
0
                {
23
0
                    {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"},
24
0
                    {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."},
25
0
                },
26
0
                RPCResult{RPCResult::Type::NONE, "", ""},
27
0
                RPCExamples{
28
0
            "\nUnlock the wallet for 60 seconds\n"
29
0
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
30
0
            "\nLock the wallet again (before 60 seconds)\n"
31
0
            + HelpExampleCli("walletlock", "") +
32
0
            "\nAs a JSON-RPC call\n"
33
0
            + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
34
0
                },
35
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
36
0
{
37
0
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
38
0
    if (!wallet) return UniValue::VNULL;
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);
Line
Count
Source
259
0
#define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
Line
Count
Source
11
0
#define UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
Line
Count
Source
9
0
#define PASTE2(x, y) PASTE(x, y)
Line
Count
Source
8
0
#define PASTE(x, y) x ## y
45
0
    {
46
0
        LOCK(pwallet->cs_wallet);
Line
Count
Source
259
0
#define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__)
Line
Count
Source
11
0
#define UNIQUE_NAME(name) PASTE2(name, __COUNTER__)
Line
Count
Source
9
0
#define PASTE2(x, y) PASTE(x, y)
Line
Count
Source
8
0
#define PASTE(x, y) x ## y
47
48
0
        if (!pwallet->IsCrypted()) {
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) {
61
0
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
62
0
        }
63
        // Clamp timeout
64
0
        constexpr int64_t MAX_SLEEP_TIME = 100000000; // larger values trigger a macos/libevent bug?
65
0
        if (nSleepTime > MAX_SLEEP_TIME) {
66
0
            nSleepTime = MAX_SLEEP_TIME;
67
0
        }
68
69
0
        if (strWalletPass.empty()) {
70
0
            throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
71
0
        }
72
73
0
        if (!pwallet->Unlock(strWalletPass)) {
74
            // Check if the passphrase has a null character (see #27067 for details)
75
0
            if (strWalletPass.find('\0') == std::string::npos) {
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()) {
104
0
            LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet);
Line
Count
Source
261
0
    UniqueLock criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
262
0
    UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__)
105
            // Skip if this is not the most recent relock callback.
106
0
            if (shared_wallet->nRelockTime != relock_time) return;
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
0
    };
115
0
}
116
117
118
RPCHelpMan walletpassphrasechange()
119
0
{
120
0
    return RPCHelpMan{
121
0
        "walletpassphrasechange",
122
0
        "Changes the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n",
123
0
                {
124
0
                    {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"},
125
0
                    {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"},
126
0
                },
127
0
                RPCResult{RPCResult::Type::NONE, "", ""},
128
0
                RPCExamples{
129
0
                    HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"")
130
0
            + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")
131
0
                },
132
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
133
0
{
134
0
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
135
0
    if (!pwallet) return UniValue::VNULL;
136
137
0
    if (!pwallet->IsCrypted()) {
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()) {
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);
Line
Count
Source
261
0
    UniqueLock criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
262
0
    UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__)
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()) {
156
0
        throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
157
0
    }
158
159
0
    if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) {
160
        // Check if the old passphrase had a null character (see #27067 for details)
161
0
        if (strOldWalletPass.find('\0') == std::string::npos) {
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
0
    };
175
0
}
176
177
178
RPCHelpMan walletlock()
179
0
{
180
0
    return RPCHelpMan{
181
0
        "walletlock",
182
0
        "Removes the wallet encryption key from memory, locking the wallet.\n"
183
0
                "After calling this method, you will need to call walletpassphrase again\n"
184
0
                "before being able to call any methods which require the wallet to be unlocked.\n",
185
0
                {},
186
0
                RPCResult{RPCResult::Type::NONE, "", ""},
187
0
                RPCExamples{
188
0
            "\nSet the passphrase for 2 minutes to perform a transaction\n"
189
0
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
190
0
            "\nPerform a send (requires passphrase set)\n"
191
0
            + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") +
192
0
            "\nClear the passphrase since we are done before 2 minutes is up\n"
193
0
            + HelpExampleCli("walletlock", "") +
194
0
            "\nAs a JSON-RPC call\n"
195
0
            + HelpExampleRpc("walletlock", "")
196
0
                },
197
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
198
0
{
199
0
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
200
0
    if (!pwallet) return UniValue::VNULL;
201
202
0
    if (!pwallet->IsCrypted()) {
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()) {
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);
Line
Count
Source
261
0
    UniqueLock criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
262
0
    UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__)
211
212
0
    pwallet->Lock();
213
0
    pwallet->nRelockTime = 0;
214
215
0
    return UniValue::VNULL;
216
0
},
217
0
    };
218
0
}
219
220
221
RPCHelpMan encryptwallet()
222
0
{
223
0
    return RPCHelpMan{
224
0
        "encryptwallet",
225
0
        "Encrypts the wallet with 'passphrase'. This is for first time encryption.\n"
226
0
        "After this, any calls that interact with private keys such as sending or signing \n"
227
0
        "will require the passphrase to be set prior to making these calls.\n"
228
0
                "Use the walletpassphrase call for this, and then walletlock call.\n"
229
0
                "If the wallet is already encrypted, use the walletpassphrasechange call.\n"
230
0
                "** IMPORTANT **\n"
231
0
                "For security reasons, the encryption process will generate a new HD seed, resulting\n"
232
0
                "in the creation of a fresh set of active descriptors. Therefore, it is crucial to\n"
233
0
                "securely back up the newly generated wallet file using the backupwallet RPC.\n",
234
0
                {
235
0
                    {"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
0
                },
237
0
                RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
238
0
                RPCExamples{
239
0
            "\nEncrypt your wallet\n"
240
0
            + HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
241
0
            "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
242
0
            + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
243
0
            "\nNow we can do something like sign\n"
244
0
            + HelpExampleCli("signmessage", "\"address\" \"test message\"") +
245
0
            "\nNow lock the wallet again by removing the passphrase\n"
246
0
            + HelpExampleCli("walletlock", "") +
247
0
            "\nAs a JSON-RPC call\n"
248
0
            + HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
249
0
                },
250
0
        [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
251
0
{
252
0
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
253
0
    if (!pwallet) return UniValue::VNULL;
254
255
0
    if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
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->IsCrypted()) {
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()) {
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);
Line
Count
Source
261
0
    UniqueLock criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \
262
0
    UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__)
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()) {
274
0
        throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
275
0
    }
276
277
0
    if (!pwallet->EncryptWallet(strWalletPass)) {
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
0
    };
284
0
}
285
} // namespace wallet