/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 |