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