encrypt.cpp
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 RPCHelpMan walletpassphrase() 12 { 13 return RPCHelpMan{"walletpassphrase", 14 "\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 "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" 18 "time that overrides the old one.\n", 19 { 20 {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"}, 21 {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."}, 22 }, 23 RPCResult{RPCResult::Type::NONE, "", ""}, 24 RPCExamples{ 25 "\nUnlock the wallet for 60 seconds\n" 26 + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + 27 "\nLock the wallet again (before 60 seconds)\n" 28 + HelpExampleCli("walletlock", "") + 29 "\nAs a JSON-RPC call\n" 30 + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") 31 }, 32 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 33 { 34 std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); 35 if (!wallet) return UniValue::VNULL; 36 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 LOCK(pwallet->m_unlock_mutex); 42 { 43 LOCK(pwallet->cs_wallet); 44 45 if (!pwallet->IsCrypted()) { 46 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 SecureString strWalletPass; 51 strWalletPass.reserve(100); 52 strWalletPass = std::string_view{request.params[0].get_str()}; 53 54 // Get the timeout 55 nSleepTime = request.params[1].getInt<int64_t>(); 56 // Timeout cannot be negative, otherwise it will relock immediately 57 if (nSleepTime < 0) { 58 throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative."); 59 } 60 // Clamp timeout 61 constexpr int64_t MAX_SLEEP_TIME = 100000000; // larger values trigger a macos/libevent bug? 62 if (nSleepTime > MAX_SLEEP_TIME) { 63 nSleepTime = MAX_SLEEP_TIME; 64 } 65 66 if (strWalletPass.empty()) { 67 throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty"); 68 } 69 70 if (!pwallet->Unlock(strWalletPass)) { 71 // Check if the passphrase has a null character (see #27067 for details) 72 if (strWalletPass.find('\0') == std::string::npos) { 73 throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); 74 } else { 75 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 pwallet->TopUpKeyPool(); 85 86 pwallet->nRelockTime = GetTime() + nSleepTime; 87 relock_time = pwallet->nRelockTime; 88 } 89 90 // rpcRunLater must be called without cs_wallet held otherwise a deadlock 91 // 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 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 std::weak_ptr<CWallet> weak_wallet = wallet; 99 pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet, relock_time] { 100 if (auto shared_wallet = weak_wallet.lock()) { 101 LOCK2(shared_wallet->m_relock_mutex, shared_wallet->cs_wallet); 102 // Skip if this is not the most recent rpcRunLater callback. 103 if (shared_wallet->nRelockTime != relock_time) return; 104 shared_wallet->Lock(); 105 shared_wallet->nRelockTime = 0; 106 } 107 }, nSleepTime); 108 109 return UniValue::VNULL; 110 }, 111 }; 112 } 113 114 115 RPCHelpMan walletpassphrasechange() 116 { 117 return RPCHelpMan{"walletpassphrasechange", 118 "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n", 119 { 120 {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"}, 121 {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"}, 122 }, 123 RPCResult{RPCResult::Type::NONE, "", ""}, 124 RPCExamples{ 125 HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") 126 + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") 127 }, 128 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 129 { 130 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 131 if (!pwallet) return UniValue::VNULL; 132 133 if (!pwallet->IsCrypted()) { 134 throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); 135 } 136 137 if (pwallet->IsScanningWithPassphrase()) { 138 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 LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); 142 143 SecureString strOldWalletPass; 144 strOldWalletPass.reserve(100); 145 strOldWalletPass = std::string_view{request.params[0].get_str()}; 146 147 SecureString strNewWalletPass; 148 strNewWalletPass.reserve(100); 149 strNewWalletPass = std::string_view{request.params[1].get_str()}; 150 151 if (strOldWalletPass.empty() || strNewWalletPass.empty()) { 152 throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty"); 153 } 154 155 if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { 156 // Check if the old passphrase had a null character (see #27067 for details) 157 if (strOldWalletPass.find('\0') == std::string::npos) { 158 throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); 159 } else { 160 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 "please try again with only the characters up to — but not including — " 164 "the first null character."); 165 } 166 } 167 168 return UniValue::VNULL; 169 }, 170 }; 171 } 172 173 174 RPCHelpMan walletlock() 175 { 176 return RPCHelpMan{"walletlock", 177 "\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 {}, 181 RPCResult{RPCResult::Type::NONE, "", ""}, 182 RPCExamples{ 183 "\nSet the passphrase for 2 minutes to perform a transaction\n" 184 + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") + 185 "\nPerform a send (requires passphrase set)\n" 186 + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 1.0") + 187 "\nClear the passphrase since we are done before 2 minutes is up\n" 188 + HelpExampleCli("walletlock", "") + 189 "\nAs a JSON-RPC call\n" 190 + HelpExampleRpc("walletlock", "") 191 }, 192 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 193 { 194 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 195 if (!pwallet) return UniValue::VNULL; 196 197 if (!pwallet->IsCrypted()) { 198 throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); 199 } 200 201 if (pwallet->IsScanningWithPassphrase()) { 202 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 LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); 206 207 pwallet->Lock(); 208 pwallet->nRelockTime = 0; 209 210 return UniValue::VNULL; 211 }, 212 }; 213 } 214 215 216 RPCHelpMan encryptwallet() 217 { 218 return RPCHelpMan{"encryptwallet", 219 "\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 "** IMPORTANT **\n" 225 "For security reasons, the encryption process will generate a new HD seed, resulting\n" 226 "in the creation of a fresh set of active descriptors. Therefore, it is crucial to\n" 227 "securely back up the newly generated wallet file using the backupwallet RPC.\n", 228 { 229 {"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."}, 230 }, 231 RPCResult{RPCResult::Type::STR, "", "A string with further instructions"}, 232 RPCExamples{ 233 "\nEncrypt your wallet\n" 234 + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + 235 "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n" 236 + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") + 237 "\nNow we can do something like sign\n" 238 + HelpExampleCli("signmessage", "\"address\" \"test message\"") + 239 "\nNow lock the wallet again by removing the passphrase\n" 240 + HelpExampleCli("walletlock", "") + 241 "\nAs a JSON-RPC call\n" 242 + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") 243 }, 244 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 245 { 246 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 247 if (!pwallet) return UniValue::VNULL; 248 249 if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { 250 throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet does not contain private keys, nothing to encrypt."); 251 } 252 253 if (pwallet->IsCrypted()) { 254 throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); 255 } 256 257 if (pwallet->IsScanningWithPassphrase()) { 258 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."); 259 } 260 261 LOCK2(pwallet->m_relock_mutex, pwallet->cs_wallet); 262 263 SecureString strWalletPass; 264 strWalletPass.reserve(100); 265 strWalletPass = std::string_view{request.params[0].get_str()}; 266 267 if (strWalletPass.empty()) { 268 throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase cannot be empty"); 269 } 270 271 if (!pwallet->EncryptWallet(strWalletPass)) { 272 throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); 273 } 274 275 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."; 276 }, 277 }; 278 } 279 } // namespace wallet