Branch data Line data Source code
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 : 10 : RPCHelpMan walletpassphrase()
14 : : {
15 : 10 : return RPCHelpMan{
16 : : "walletpassphrase",
17 : : "Stores the wallet decryption key in memory for 'timeout' seconds.\n"
18 : : "This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
19 : : "\nNote:\n"
20 : : "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
21 : : "time that overrides the old one.\n",
22 : : {
23 [ + - ]: 10 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"},
24 [ + - ]: 10 : {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."},
25 : : },
26 [ + - + - : 20 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - ]
27 : 10 : RPCExamples{
28 : : "\nUnlock the wallet for 60 seconds\n"
29 [ + - + - : 20 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
+ - + - ]
30 : 10 : "\nLock the wallet again (before 60 seconds)\n"
31 [ + - + - : 40 : + HelpExampleCli("walletlock", "") +
+ - + - ]
32 : 10 : "\nAs a JSON-RPC call\n"
33 [ + - + - : 40 : + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
+ - + - ]
34 [ + - ]: 10 : },
35 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
36 : : {
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);
45 : 0 : {
46 [ # # ]: 0 : LOCK(pwallet->cs_wallet);
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 : : }
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 : : }
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 : : }
68 : :
69 [ # # ]: 0 : if (strWalletPass.empty()) {
70 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
71 : : }
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 : : } else {
78 [ # # ]: 0 : throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered is incorrect. "
79 : : "It contains a null character (ie - a zero byte). "
80 : : "If the passphrase was set with a version of this software prior to 25.0, "
81 : : "please try again with only the characters up to — but not including — "
82 : : "the first null character. If this is successful, please set a new "
83 [ # # # # ]: 0 : "passphrase to avoid this issue in the future.");
84 : : }
85 : : }
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);
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 [ + - + - : 120 : };
+ - + - +
- + - + -
+ - + + -
- ]
115 [ + - + - : 50 : }
+ - - - ]
116 : :
117 : :
118 : 10 : RPCHelpMan walletpassphrasechange()
119 : : {
120 : 10 : return RPCHelpMan{
121 : : "walletpassphrasechange",
122 : : "Changes the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n",
123 : : {
124 [ + - ]: 10 : {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"},
125 [ + - ]: 10 : {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"},
126 : : },
127 [ + - + - : 20 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - ]
128 : 10 : RPCExamples{
129 [ + - + - : 20 : HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"")
+ - ]
130 [ + - + - : 40 : + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")
+ - + - ]
131 [ + - ]: 10 : },
132 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
133 : : {
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 : : }
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 : : }
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()) {
156 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
157 : : }
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 : : } else {
164 [ # # ]: 0 : throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The old wallet passphrase entered is incorrect. "
165 : : "It contains a null character (ie - a zero byte). "
166 : : "If the old passphrase was set with a version of this software prior to 25.0, "
167 : : "please try again with only the characters up to — but not including — "
168 [ # # # # ]: 0 : "the first null character.");
169 : : }
170 : : }
171 : :
172 : 0 : return UniValue::VNULL;
173 [ # # # # ]: 0 : },
174 [ + - + - : 120 : };
+ - + - +
- + - + -
+ - + + -
- ]
175 [ + - + - : 50 : }
+ - - - ]
176 : :
177 : :
178 : 10 : RPCHelpMan walletlock()
179 : : {
180 : 10 : return RPCHelpMan{
181 : : "walletlock",
182 : : "Removes the wallet encryption key from memory, locking the wallet.\n"
183 : : "After calling this method, you will need to call walletpassphrase again\n"
184 : : "before being able to call any methods which require the wallet to be unlocked.\n",
185 : : {},
186 [ + - + - : 20 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - ]
187 : 10 : RPCExamples{
188 : : "\nSet the passphrase for 2 minutes to perform a transaction\n"
189 [ + - + - : 20 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
+ - + - ]
190 : 10 : "\nPerform a send (requires passphrase set)\n"
191 [ + - + - : 50 : + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") +
+ - + - ]
192 : 10 : "\nClear the passphrase since we are done before 2 minutes is up\n"
193 [ + - + - : 40 : + HelpExampleCli("walletlock", "") +
+ - + - ]
194 : 10 : "\nAs a JSON-RPC call\n"
195 [ + - + - : 40 : + HelpExampleRpc("walletlock", "")
+ - + - ]
196 [ + - ]: 10 : },
197 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
198 : : {
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 : : }
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 : : }
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 [ + - + - : 60 : };
+ - + - ]
218 : : }
219 : :
220 : :
221 : 10 : RPCHelpMan encryptwallet()
222 : : {
223 : 10 : return RPCHelpMan{
224 : : "encryptwallet",
225 : : "Encrypts the wallet with 'passphrase'. This is for first time encryption.\n"
226 : : "After this, any calls that interact with private keys such as sending or signing \n"
227 : : "will require the passphrase to be set prior to making these calls.\n"
228 : : "Use the walletpassphrase call for this, and then walletlock call.\n"
229 : : "If the wallet is already encrypted, use the walletpassphrasechange call.\n"
230 : : "** IMPORTANT **\n"
231 : : "For security reasons, the encryption process will generate a new HD seed, resulting\n"
232 : : "in the creation of a fresh set of active descriptors. Therefore, it is crucial to\n"
233 : : "securely back up the newly generated wallet file using the backupwallet RPC.\n",
234 : : {
235 [ + - ]: 10 : {"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 : : },
237 [ + - + - : 20 : RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
+ - ]
238 : 10 : RPCExamples{
239 : : "\nEncrypt your wallet\n"
240 [ + - + - : 20 : + HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
+ - + - ]
241 : 10 : "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
242 [ + - + - : 40 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
+ - + - ]
243 : 10 : "\nNow we can do something like sign\n"
244 [ + - + - : 40 : + HelpExampleCli("signmessage", "\"address\" \"test message\"") +
+ - + - ]
245 : 10 : "\nNow lock the wallet again by removing the passphrase\n"
246 [ + - + - : 40 : + HelpExampleCli("walletlock", "") +
+ - + - ]
247 : 10 : "\nAs a JSON-RPC call\n"
248 [ + - + - : 40 : + HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
+ - + - ]
249 [ + - ]: 10 : },
250 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
251 : : {
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 : : }
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 : : }
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 : : }
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()) {
274 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
275 : : }
276 : :
277 [ # # # # ]: 0 : if (!pwallet->EncryptWallet(strWalletPass)) {
278 [ # # # # ]: 0 : throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
279 : : }
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 [ + - + - : 90 : };
+ - + - +
- + - + +
- - ]
284 [ + - + - ]: 30 : }
285 : : } // namespace wallet
|