From 8345f442a7e6f1ee489a4213440f169ecda8dfe6 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 19 Dec 2018 16:30:49 -0500 Subject: [PATCH] [backport#15006] Add option to create an encrypted wallet Summary: https://github.com/bitcoin/bitcoin/pull/15006/commits/662d1171d9e29964b039ba4c5bc8a2304426c003 --- Backport of Core [[https://github.com/bitcoin/bitcoin/pull/15006 | PR15006]] Test Plan: ninja check ./test/functional/test_runner.py wallet_createwallet.py Reviewers: #bitcoin_abc, deadalnix Reviewed By: #bitcoin_abc, deadalnix Subscribers: deadalnix Differential Revision: https://reviews.bitcoinabc.org/D6423 --- doc/release-notes.md | 5 ++ src/wallet/rpcwallet.cpp | 104 ++++++++++++++++++------- test/functional/wallet_createwallet.py | 51 ++++++++++++ 3 files changed, 130 insertions(+), 30 deletions(-) diff --git a/doc/release-notes.md b/doc/release-notes.md index 3be26adadc..c826821260 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -15,3 +15,8 @@ in BCH per kilobyte, not satoshi per byte. RPC changes ----------- The `getblockstats` RPC is faster for fee calculation by using BlockUndo data. Also, `-txindex` is no longer required and `getblockstats` works for all non-pruned blocks. + +Miscellaneous RPC changes +------------ + +- `createwallet` can now create encrypted wallets if a non-empty passphrase is specified. diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 2c9f5b9609..daa048b75d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3110,35 +3110,35 @@ static UniValue loadwallet(const Config &config, static UniValue createwallet(const Config &config, const JSONRPCRequest &request) { - if (request.fHelp || request.params.size() < 1 || - request.params.size() > 3) { - throw std::runtime_error(RPCHelpMan{ - "createwallet", - "\nCreates and loads a new wallet.\n", - { - {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, - "The name for the new wallet. If this is a path, " - "the wallet will be created at the path location."}, - {"disable_private_keys", RPCArg::Type::BOOL, - /* default */ "false", - "Disable the possibility of private keys (only " - "watchonlys are possible in this mode)."}, - {"blank", RPCArg::Type::BOOL, /* default */ "false", - "Create a blank wallet. A blank wallet has no keys " - "or HD seed. One can be set using sethdseed.\n"}, - }, - RPCResult{ - "{\n" - " \"name\" : , (string) The wallet " - "name if created successfully. If the wallet was created using " - "a full path, the wallet_name will be the full path.\n" - " \"warning\" : , (string) Warning " - "message if wallet was not loaded cleanly.\n" - "}\n"}, - RPCExamples{HelpExampleCli("createwallet", "\"testwallet\"") + - HelpExampleRpc("createwallet", "\"testwallet\"")}, - } - .ToString()); + const RPCHelpMan help{ + "createwallet", + "\nCreates and loads a new wallet.\n", + { + {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, + "The name for the new wallet. If this is a path, the wallet will " + "be created at the path location."}, + {"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", + "Disable the possibility of private keys (only watchonlys are " + "possible in this mode)."}, + {"blank", RPCArg::Type::BOOL, /* default */ "false", + "Create a blank wallet. A blank wallet has no keys or HD seed. " + "One can be set using sethdseed."}, + {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, + "Encrypt the wallet with this passphrase."}, + }, + RPCResult{"{\n" + " \"name\" : , (string) The wallet " + "name if created successfully. If the wallet was created " + "using a full path, the wallet_name will be the full path.\n" + " \"warning\" : , (string) Warning " + "message if wallet was not loaded cleanly.\n" + "}\n"}, + RPCExamples{HelpExampleCli("createwallet", "\"testwallet\"") + + HelpExampleRpc("createwallet", "\"testwallet\"")}, + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); } const CChainParams &chainParams = config.GetChainParams(); @@ -3151,7 +3151,25 @@ static UniValue createwallet(const Config &config, flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } + // Indicate that the wallet is actually supposed to be blank and not just + // blank to make it encrypted + bool create_blank = false; + if (!request.params[2].isNull() && request.params[2].get_bool()) { + create_blank = true; + flags |= WALLET_FLAG_BLANK_WALLET; + } + SecureString passphrase; + passphrase.reserve(100); + if (!request.params[3].isNull()) { + passphrase = request.params[3].get_str().c_str(); + if (passphrase.empty()) { + // Empty string is invalid + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, + "Cannot encrypt a wallet with a blank password"); + } + // Born encrypted wallets need to be blank first so that wallet creation + // doesn't make any unencrypted keys flags |= WALLET_FLAG_BLANK_WALLET; } @@ -3174,6 +3192,32 @@ static UniValue createwallet(const Config &config, if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } + + // Encrypt the wallet if there's a passphrase + if (!passphrase.empty() && !(flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!wallet->EncryptWallet(passphrase)) { + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, + "Error: Wallet created but failed to encrypt."); + } + + if (!create_blank) { + // Unlock the wallet + if (!wallet->Unlock(passphrase)) { + throw JSONRPCError( + RPC_WALLET_ENCRYPTION_FAILED, + "Error: Wallet was encrypted but could not be unlocked"); + } + + // Set a seed for the wallet + CPubKey master_pub_key = wallet->GenerateNewSeed(); + wallet->SetHDSeed(master_pub_key); + wallet->NewKeyPool(); + + // Relock the wallet + wallet->Lock(); + } + } + AddWallet(wallet); wallet->postInitProcess(); @@ -4755,7 +4799,7 @@ static const CRPCCommand commands[] = { { "wallet", "abandontransaction", abandontransaction, {"txid"} }, { "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label"} }, { "wallet", "backupwallet", backupwallet, {"destination"} }, - { "wallet", "createwallet", createwallet, {"wallet_name", "disable_private_keys", "blank"} }, + { "wallet", "createwallet", createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase"} }, { "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, { "wallet", "getaddressesbylabel", getaddressesbylabel, {"label"} }, { "wallet", "getaddressinfo", getaddressinfo, {"address"} }, diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index a7b09dae0f..810ac21620 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -123,6 +123,57 @@ def run_test(self): assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress) + self.log.info('New blank and encrypted wallets can be created') + self.nodes[0].createwallet( + wallet_name='wblank', + disable_private_keys=False, + blank=True, + passphrase='thisisapassphrase') + wblank = node.get_wallet_rpc('wblank') + assert_raises_rpc_error( + -13, + "Error: Please enter the wallet passphrase with walletpassphrase first.", + wblank.signmessage, + "needanargument", + "test") + wblank.walletpassphrase('thisisapassphrase', 10) + assert_raises_rpc_error(-4, + "Error: This wallet has no available keys", + wblank.getnewaddress) + assert_raises_rpc_error(-4, + "Error: This wallet has no available keys", + wblank.getrawchangeaddress) + + self.log.info('Test creating a new encrypted wallet.') + # Born encrypted wallet is created (has keys) + self.nodes[0].createwallet( + wallet_name='w6', + disable_private_keys=False, + blank=False, + passphrase='thisisapassphrase') + w6 = node.get_wallet_rpc('w6') + assert_raises_rpc_error( + -13, + "Error: Please enter the wallet passphrase with walletpassphrase first.", + w6.signmessage, + "needanargument", + "test") + w6.walletpassphrase('thisisapassphrase', 10) + w6.signmessage(w6.getnewaddress('', 'legacy'), "test") + w6.keypoolrefill(1) + # There should only be 1 key + walletinfo = w6.getwalletinfo() + assert_equal(walletinfo['keypoolsize'], 1) + assert_equal(walletinfo['keypoolsize_hd_internal'], 1) + # Empty passphrase, error + assert_raises_rpc_error(-16, + 'Cannot encrypt a wallet with a blank password', + self.nodes[0].createwallet, + 'w7', + False, + False, + '') + if __name__ == '__main__': CreateWalletTest().main()