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