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