Coverage Report

Created: 2024-10-29 12:10

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