Branch data Line data Source code
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 : 955 : RPCMethod walletpassphrase()
15 : : {
16 : 955 : return RPCMethod{
17 : 955 : "walletpassphrase",
18 [ + - ]: 1910 : "Stores the wallet decryption key in memory for 'timeout' seconds.\n"
19 : : "This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
20 : : "\nNote:\n"
21 : : "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
22 : : "time that overrides the old one.\n",
23 : : {
24 [ + - + - ]: 1910 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"},
25 [ + - + - ]: 1910 : {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."},
26 : : },
27 [ + - + - : 1910 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - + - ]
28 : 955 : RPCExamples{
29 : : "\nUnlock the wallet for 60 seconds\n"
30 [ + - + - : 1910 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
+ - + - ]
31 : 955 : "\nLock the wallet again (before 60 seconds)\n"
32 [ + - + - : 3820 : + HelpExampleCli("walletlock", "") +
+ - + - ]
33 : 955 : "\nAs a JSON-RPC call\n"
34 [ + - + - : 3820 : + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")
+ - + - ]
35 [ + - ]: 955 : },
36 : 955 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
37 : : {
38 : 60 : std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
39 [ - + ]: 60 : if (!wallet) return UniValue::VNULL;
40 : 60 : CWallet* const pwallet = wallet.get();
41 : :
42 : 60 : int64_t nSleepTime;
43 : 60 : int64_t relock_time;
44 : : // Prevent concurrent calls to walletpassphrase with the same wallet.
45 [ + - ]: 60 : LOCK(pwallet->m_unlock_mutex);
46 : 60 : {
47 [ + - ]: 60 : LOCK(pwallet->cs_wallet);
48 : :
49 [ + - + + ]: 60 : if (!pwallet->HasEncryptionKeys()) {
50 [ + - + - ]: 12 : throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called.");
51 : : }
52 : :
53 : : // Note that the walletpassphrase is stored in request.params[0] which is not mlock()ed
54 [ + - ]: 54 : SecureString strWalletPass;
55 [ + - ]: 54 : strWalletPass.reserve(100);
56 [ + - + - : 54 : strWalletPass = std::string_view{request.params[0].get_str()};
- + + - ]
57 : :
58 : : // Get the timeout
59 [ + - + - ]: 54 : nSleepTime = request.params[1].getInt<int64_t>();
60 : : // Timeout cannot be negative, otherwise it will relock immediately
61 [ + + ]: 54 : if (nSleepTime < 0) {
62 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
63 : : }
64 : : // Clamp timeout to ~3 years to avoid overflow when computing the relock time
65 : 53 : constexpr int64_t MAX_SLEEP_TIME = 100000000;
66 [ + + ]: 53 : if (nSleepTime > MAX_SLEEP_TIME) {
67 : 1 : nSleepTime = MAX_SLEEP_TIME;
68 : : }
69 : :
70 [ + + ]: 53 : if (strWalletPass.empty()) {
71 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
72 : : }
73 : :
74 [ + - + + ]: 52 : if (auto unlocked{pwallet->Unlock(strWalletPass)}; !unlocked) {
75 [ + - + - ]: 4 : throw JSONRPCError(HandleWalletErrorCode(unlocked.error().code), unlocked.error().message.original);
76 : 4 : }
77 : :
78 [ + - ]: 48 : pwallet->TopUpKeyPool();
79 : :
80 [ + - ]: 48 : pwallet->nRelockTime = GetTime() + nSleepTime;
81 : 48 : relock_time = pwallet->nRelockTime;
82 [ + - ]: 66 : }
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 [ + - ]: 48 : 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 [ + - ]: 48 : std::weak_ptr<CWallet> weak_wallet = wallet;
93 [ + - + - : 346 : 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 [ - - - - : 11 : }
+ - ]
101 [ + - ]: 48 : }, std::chrono::seconds(nSleepTime));
102 : :
103 [ + - ]: 48 : return UniValue::VNULL;
104 [ + - ]: 156 : },
105 [ + - + - : 5730 : };
+ + - - ]
106 [ + - + - : 3820 : }
- - ]
107 : :
108 : :
109 : 902 : RPCMethod walletpassphrasechange()
110 : : {
111 : 902 : return RPCMethod{
112 : 902 : "walletpassphrasechange",
113 [ + - ]: 1804 : "Changes the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n",
114 : : {
115 [ + - + - ]: 1804 : {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"},
116 [ + - + - ]: 1804 : {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"},
117 : : },
118 [ + - + - : 1804 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - + - ]
119 : 902 : RPCExamples{
120 [ + - + - : 1804 : HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"")
+ - ]
121 [ + - + - : 3608 : + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")
+ - + - ]
122 [ + - ]: 902 : },
123 : 902 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
124 : : {
125 : 7 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
126 [ - + ]: 7 : if (!pwallet) return UniValue::VNULL;
127 : :
128 [ + - + + ]: 7 : if (!pwallet->HasEncryptionKeys()) {
129 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.");
130 : : }
131 : :
132 [ + - + + ]: 6 : if (pwallet->Scanner().IsScanningWithPassphrase()) {
133 [ + - + - ]: 2 : 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 : : }
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 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
148 : : }
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 [ + - + - ]: 20 : },
156 [ + - + - : 5412 : };
+ + - - ]
157 [ + - + - : 3608 : }
- - ]
158 : :
159 : :
160 : 923 : RPCMethod walletlock()
161 : : {
162 : 923 : return RPCMethod{
163 : 923 : "walletlock",
164 [ + - ]: 1846 : "Removes the wallet encryption key from memory, locking the wallet.\n"
165 : : "After calling this method, you will need to call walletpassphrase again\n"
166 : : "before being able to call any methods which require the wallet to be unlocked.\n",
167 : : {},
168 [ + - + - : 1846 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - ]
169 : 923 : RPCExamples{
170 : : "\nSet the passphrase for 2 minutes to perform a transaction\n"
171 [ + - + - : 1846 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
+ - + - ]
172 : 923 : "\nPerform a send (requires passphrase set)\n"
173 [ + - + - : 4615 : + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") +
+ - + - ]
174 : 923 : "\nClear the passphrase since we are done before 2 minutes is up\n"
175 [ + - + - : 3692 : + HelpExampleCli("walletlock", "") +
+ - + - ]
176 : 923 : "\nAs a JSON-RPC call\n"
177 [ + - + - : 3692 : + HelpExampleRpc("walletlock", "")
+ - + - ]
178 [ + - ]: 923 : },
179 : 923 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
180 : : {
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 : : }
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 : : }
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 [ + - ]: 81 : },
199 [ + - + - ]: 3692 : };
200 : : }
201 : :
202 : :
203 : 915 : RPCMethod encryptwallet()
204 : : {
205 : 915 : return RPCMethod{
206 : 915 : "encryptwallet",
207 [ + - ]: 1830 : "Encrypts the wallet with 'passphrase'. This is for first time encryption.\n"
208 : : "After this, any calls that interact with private keys such as sending or signing \n"
209 : : "will require the passphrase to be set prior to making these calls.\n"
210 : : "Use the walletpassphrase call for this, and then walletlock call.\n"
211 : : "If the wallet is already encrypted, use the walletpassphrasechange call.\n"
212 : : "** IMPORTANT **\n"
213 : : "For security reasons, the encryption process will generate a new HD seed, resulting\n"
214 : : "in the creation of a fresh set of active descriptors. Therefore, it is crucial to\n"
215 : : "securely back up the newly generated wallet file using the backupwallet RPC.\n",
216 : : {
217 [ + - + - ]: 1830 : {"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 : : },
219 [ + - + - : 1830 : RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
+ - + - ]
220 : 915 : RPCExamples{
221 : : "\nEncrypt your wallet\n"
222 [ + - + - : 1830 : + HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
+ - + - ]
223 : 915 : "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n"
224 [ + - + - : 3660 : + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
+ - + - ]
225 : 915 : "\nNow we can do something like sign\n"
226 [ + - + - : 3660 : + HelpExampleCli("signmessage", "\"address\" \"test message\"") +
+ - + - ]
227 : 915 : "\nNow lock the wallet again by removing the passphrase\n"
228 [ + - + - : 3660 : + HelpExampleCli("walletlock", "") +
+ - + - ]
229 : 915 : "\nAs a JSON-RPC call\n"
230 [ + - + - : 3660 : + HelpExampleRpc("encryptwallet", "\"my pass phrase\"")
+ - + - ]
231 [ + - ]: 915 : },
232 : 915 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
233 : : {
234 : 20 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
235 [ - + ]: 20 : if (!pwallet) return UniValue::VNULL;
236 : :
237 [ + - + + ]: 20 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
238 [ + - + - ]: 6 : throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt.");
239 : : }
240 : :
241 [ + - + + ]: 17 : if (pwallet->HasEncryptionKeys()) {
242 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called.");
243 : : }
244 : :
245 [ + - - + ]: 16 : 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 : : }
248 : :
249 [ + - + - ]: 16 : LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet);
250 : :
251 [ + - ]: 16 : SecureString strWalletPass;
252 [ + - ]: 16 : strWalletPass.reserve(100);
253 [ + - + - : 16 : strWalletPass = std::string_view{request.params[0].get_str()};
- + + - ]
254 : :
255 [ + + ]: 16 : if (strWalletPass.empty()) {
256 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty");
257 : : }
258 : :
259 [ + - - + ]: 15 : if (!pwallet->EncryptWallet(strWalletPass)) {
260 [ # # # # ]: 0 : throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
261 : : }
262 : :
263 [ + - ]: 15 : 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 [ + - + - ]: 63 : },
265 [ + - + - : 4575 : };
+ + - - ]
266 [ + - ]: 1830 : }
267 : : } // namespace wallet
|