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