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