/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 | 935 | { |
16 | 935 | return RPCMethod{ |
17 | 935 | "walletpassphrase", |
18 | 935 | "Stores the wallet decryption key in memory for 'timeout' seconds.\n" |
19 | 935 | "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" |
20 | 935 | "\nNote:\n" |
21 | 935 | "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" |
22 | 935 | "time that overrides the old one.\n", |
23 | 935 | { |
24 | 935 | {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"}, |
25 | 935 | {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."}, |
26 | 935 | }, |
27 | 935 | RPCResult{RPCResult::Type::NONE, "", ""}, |
28 | 935 | RPCExamples{ |
29 | 935 | "\nUnlock the wallet for 60 seconds\n" |
30 | 935 | + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + |
31 | 935 | "\nLock the wallet again (before 60 seconds)\n" |
32 | 935 | + HelpExampleCli("walletlock", "") + |
33 | 935 | "\nAs a JSON-RPC call\n" |
34 | 935 | + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") |
35 | 935 | }, |
36 | 935 | [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue |
37 | 935 | { |
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 (auto unlocked{pwallet->Unlock(strWalletPass)}; !unlocked) { |
75 | 4 | throw JSONRPCError(HandleWalletErrorCode(unlocked.error().code), unlocked.error().message.original); |
76 | 4 | } |
77 | | |
78 | 50 | pwallet->TopUpKeyPool(); |
79 | | |
80 | 50 | pwallet->nRelockTime = GetTime() + nSleepTime; |
81 | 50 | relock_time = pwallet->nRelockTime; |
82 | 50 | } |
83 | | |
84 | | // Get wallet scheduler to queue up the relock callback in the future. |
85 | | // Scheduled events don't get destructed until they are executed, |
86 | | // and they are executed in series in a single scheduler thread so |
87 | | // no cs_wallet lock is needed. |
88 | 0 | WalletContext& context = EnsureWalletContext(request.context); |
89 | | // Keep a weak pointer to the wallet so that it is possible to unload the |
90 | | // wallet before the following callback is called. If a valid shared pointer |
91 | | // is acquired in the callback then the wallet is still loaded. |
92 | 50 | std::weak_ptr<CWallet> weak_wallet = wallet; |
93 | 50 | context.scheduler->scheduleFromNow([weak_wallet, relock_time] { |
94 | 5 | if (auto shared_wallet = weak_wallet.lock()) { |
95 | 3 | LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet); |
96 | | // Skip if this is not the most recent relock callback. |
97 | 3 | if (shared_wallet->nRelockTime != relock_time) return; |
98 | 3 | shared_wallet->Lock(); |
99 | 3 | shared_wallet->nRelockTime = 0; |
100 | 3 | } |
101 | 5 | }, std::chrono::seconds(nSleepTime)); |
102 | | |
103 | 50 | return UniValue::VNULL; |
104 | 54 | }, |
105 | 935 | }; |
106 | 935 | } |
107 | | |
108 | | |
109 | | RPCMethod walletpassphrasechange() |
110 | 880 | { |
111 | 880 | return RPCMethod{ |
112 | 880 | "walletpassphrasechange", |
113 | 880 | "Changes the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n", |
114 | 880 | { |
115 | 880 | {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"}, |
116 | 880 | {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"}, |
117 | 880 | }, |
118 | 880 | RPCResult{RPCResult::Type::NONE, "", ""}, |
119 | 880 | RPCExamples{ |
120 | 880 | HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") |
121 | 880 | + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") |
122 | 880 | }, |
123 | 880 | [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue |
124 | 880 | { |
125 | 7 | std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); |
126 | 7 | if (!pwallet) return UniValue::VNULL; |
127 | | |
128 | 7 | if (!pwallet->HasEncryptionKeys()) { |
129 | 1 | throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); |
130 | 1 | } |
131 | | |
132 | 6 | if (pwallet->Scanner().IsScanningWithPassphrase()) { |
133 | 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."); |
134 | 1 | } |
135 | | |
136 | 5 | LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); |
137 | | |
138 | 5 | SecureString strOldWalletPass; |
139 | 5 | strOldWalletPass.reserve(100); |
140 | 5 | strOldWalletPass = std::string_view{request.params[0].get_str()}; |
141 | | |
142 | 5 | SecureString strNewWalletPass; |
143 | 5 | strNewWalletPass.reserve(100); |
144 | 5 | strNewWalletPass = std::string_view{request.params[1].get_str()}; |
145 | | |
146 | 5 | if (strOldWalletPass.empty() || strNewWalletPass.empty()) { |
147 | 1 | throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty"); |
148 | 1 | } |
149 | | |
150 | 4 | if (auto changed{pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)}; !changed) { |
151 | 2 | throw JSONRPCError(HandleWalletErrorCode(changed.error().code), changed.error().message.original); |
152 | 2 | } |
153 | | |
154 | 2 | return UniValue::VNULL; |
155 | 4 | }, |
156 | 880 | }; |
157 | 880 | } |
158 | | |
159 | | |
160 | | RPCMethod walletlock() |
161 | 901 | { |
162 | 901 | return RPCMethod{ |
163 | 901 | "walletlock", |
164 | 901 | "Removes the wallet encryption key from memory, locking the wallet.\n" |
165 | 901 | "After calling this method, you will need to call walletpassphrase again\n" |
166 | 901 | "before being able to call any methods which require the wallet to be unlocked.\n", |
167 | 901 | {}, |
168 | 901 | RPCResult{RPCResult::Type::NONE, "", ""}, |
169 | 901 | RPCExamples{ |
170 | 901 | "\nSet the passphrase for 2 minutes to perform a transaction\n" |
171 | 901 | + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") + |
172 | 901 | "\nPerform a send (requires passphrase set)\n" |
173 | 901 | + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") + |
174 | 901 | "\nClear the passphrase since we are done before 2 minutes is up\n" |
175 | 901 | + HelpExampleCli("walletlock", "") + |
176 | 901 | "\nAs a JSON-RPC call\n" |
177 | 901 | + HelpExampleRpc("walletlock", "") |
178 | 901 | }, |
179 | 901 | [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue |
180 | 901 | { |
181 | 28 | std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); |
182 | 28 | if (!pwallet) return UniValue::VNULL; |
183 | | |
184 | 28 | if (!pwallet->HasEncryptionKeys()) { |
185 | 0 | throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); |
186 | 0 | } |
187 | | |
188 | 28 | if (pwallet->Scanner().IsScanningWithPassphrase()) { |
189 | 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."); |
190 | 1 | } |
191 | | |
192 | 27 | LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); |
193 | | |
194 | 27 | pwallet->Lock(); |
195 | 27 | pwallet->nRelockTime = 0; |
196 | | |
197 | 27 | return UniValue::VNULL; |
198 | 28 | }, |
199 | 901 | }; |
200 | 901 | } |
201 | | |
202 | | |
203 | | RPCMethod encryptwallet() |
204 | 895 | { |
205 | 895 | return RPCMethod{ |
206 | 895 | "encryptwallet", |
207 | 895 | "Encrypts the wallet with 'passphrase'. This is for first time encryption.\n" |
208 | 895 | "After this, any calls that interact with private keys such as sending or signing \n" |
209 | 895 | "will require the passphrase to be set prior to making these calls.\n" |
210 | 895 | "Use the walletpassphrase call for this, and then walletlock call.\n" |
211 | 895 | "If the wallet is already encrypted, use the walletpassphrasechange call.\n" |
212 | 895 | "** IMPORTANT **\n" |
213 | 895 | "For security reasons, the encryption process will generate a new HD seed, resulting\n" |
214 | 895 | "in the creation of a fresh set of active descriptors. Therefore, it is crucial to\n" |
215 | 895 | "securely back up the newly generated wallet file using the backupwallet RPC.\n", |
216 | 895 | { |
217 | 895 | {"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."}, |
218 | 895 | }, |
219 | 895 | RPCResult{RPCResult::Type::STR, "", "A string with further instructions"}, |
220 | 895 | RPCExamples{ |
221 | 895 | "\nEncrypt your wallet\n" |
222 | 895 | + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + |
223 | 895 | "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n" |
224 | 895 | + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") + |
225 | 895 | "\nNow we can do something like sign\n" |
226 | 895 | + HelpExampleCli("signmessage", "\"address\" \"test message\"") + |
227 | 895 | "\nNow lock the wallet again by removing the passphrase\n" |
228 | 895 | + HelpExampleCli("walletlock", "") + |
229 | 895 | "\nAs a JSON-RPC call\n" |
230 | 895 | + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") |
231 | 895 | }, |
232 | 895 | [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue |
233 | 895 | { |
234 | 22 | std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); |
235 | 22 | if (!pwallet) return UniValue::VNULL; |
236 | | |
237 | 22 | if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { |
238 | 3 | throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt."); |
239 | 3 | } |
240 | | |
241 | 19 | if (pwallet->HasEncryptionKeys()) { |
242 | 1 | throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); |
243 | 1 | } |
244 | | |
245 | 18 | if (pwallet->Scanner().IsScanningWithPassphrase()) { |
246 | 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."); |
247 | 0 | } |
248 | | |
249 | 18 | LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); |
250 | | |
251 | 18 | SecureString strWalletPass; |
252 | 18 | strWalletPass.reserve(100); |
253 | 18 | strWalletPass = std::string_view{request.params[0].get_str()}; |
254 | | |
255 | 18 | if (strWalletPass.empty()) { |
256 | 1 | throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty"); |
257 | 1 | } |
258 | | |
259 | 17 | if (!pwallet->EncryptWallet(strWalletPass)) { |
260 | 0 | throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); |
261 | 0 | } |
262 | | |
263 | 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."; |
264 | 17 | }, |
265 | 895 | }; |
266 | 895 | } |
267 | | } // namespace wallet |