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