Line data Source code
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 0 : RPCHelpMan walletpassphrase()
12 : {
13 0 : return RPCHelpMan{"walletpassphrase",
14 0 : "\nStores the wallet decryption key in memory for 'timeout' seconds.\n"
15 : "This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
16 : "\nNote:\n"
17 173 : "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
18 173 : "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 : },
23 0 : RPCResult{RPCResult::Type::NONE, "", ""},
24 0 : RPCExamples{
25 : "\nUnlock the wallet for 60 seconds\n"
26 0 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
27 173 : "\nLock the wallet again (before 60 seconds)\n"
28 519 : + HelpExampleCli("walletlock", "") +
29 173 : "\nAs a JSON-RPC call\n"
30 173 : + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
31 : },
32 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
33 : {
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 : int64_t nSleepTime;
39 : int64_t relock_time;
40 : // Prevent concurrent calls to walletpassphrase with the same wallet.
41 0 : LOCK(pwallet->m_unlock_mutex);
42 : {
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 : }
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 0 :
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 : }
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 : }
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 173 : } else {
75 0 : throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered is incorrect. "
76 : "It contains a null character (ie - a zero byte). "
77 : "If the passphrase was set with a version of this software prior to 25.0, "
78 : "please try again with only the characters up to — but not including — "
79 : "the first null character. If this is successful, please set a new "
80 : "passphrase to avoid this issue in the future.");
81 : }
82 : }
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 173 : // 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 173 : 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 : };
112 0 : }
113 :
114 :
115 0 : RPCHelpMan walletpassphrasechange()
116 : {
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 : },
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 : },
128 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
129 : {
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 : }
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 : }
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 : }
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 : } else {
160 0 : throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The old wallet passphrase entered is incorrect. "
161 : "It contains a null character (ie - a zero byte). "
162 : "If the old passphrase was set with a version of this software prior to 25.0, "
163 173 : "please try again with only the characters up to — but not including — "
164 173 : "the first null character.");
165 173 : }
166 173 : }
167 173 :
168 173 : return UniValue::VNULL;
169 173 : },
170 173 : };
171 0 : }
172 :
173 :
174 0 : RPCHelpMan walletlock()
175 : {
176 0 : return RPCHelpMan{"walletlock",
177 0 : "\nRemoves the wallet encryption key from memory, locking the wallet.\n"
178 : "After calling this method, you will need to call walletpassphrase again\n"
179 : "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 : "\nSet the passphrase for 2 minutes to perform a transaction\n"
184 0 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
185 : "\nPerform a send (requires passphrase set)\n"
186 0 : + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") +
187 : "\nClear the passphrase since we are done before 2 minutes is up\n"
188 0 : + HelpExampleCli("walletlock", "") +
189 : "\nAs a JSON-RPC call\n"
190 0 : + HelpExampleRpc("walletlock", "")
191 : },
192 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
193 : {
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 : }
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 : }
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 : };
213 0 : }
214 :
215 :
216 0 : RPCHelpMan encryptwallet()
217 : {
218 0 : return RPCHelpMan{"encryptwallet",
219 0 : "\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n"
220 : "After this, any calls that interact with private keys such as sending or signing \n"
221 : "will require the passphrase to be set prior the making these calls.\n"
222 : "Use the walletpassphrase call for this, and then walletlock call.\n"
223 : "If the wallet is already encrypted, use the walletpassphrasechange call.\n",
224 0 : {
225 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."},
226 : },
227 0 : RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
228 0 : RPCExamples{
229 : "\nEncrypt your wallet\n"
230 0 : + HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
231 : "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
232 0 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
233 : "\nNow we can do something like sign\n"
234 0 : + HelpExampleCli("signmessage", "\"address\" \"test message\"") +
235 : "\nNow lock the wallet again by removing the passphrase\n"
236 0 : + HelpExampleCli("walletlock", "") +
237 : "\nAs a JSON-RPC call\n"
238 0 : + HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
239 : },
240 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
241 : {
242 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
243 0 : if (!pwallet) return UniValue::VNULL;
244 :
245 0 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
246 0 : throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt.");
247 : }
248 :
249 0 : if (pwallet->IsCrypted()) {
250 0 : throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
251 : }
252 :
253 0 : if (pwallet->IsScanningWithPassphrase()) {
254 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.");
255 : }
256 :
257 0 : LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
258 :
259 0 : SecureString strWalletPass;
260 0 : strWalletPass.reserve(100);
261 0 : strWalletPass = std::string_view{request.params[0].get_str()};
262 :
263 0 : if (strWalletPass.empty()) {
264 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
265 : }
266 :
267 0 : if (!pwallet->EncryptWallet(strWalletPass)) {
268 0 : throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
269 : }
270 173 :
271 0 : return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
272 0 : },
273 : };
274 0 : }
275 : } // namespace wallet
|