Skip to content

Commit

Permalink
[backport#15006] Add option to create an encrypted wallet
Browse files Browse the repository at this point in the history
Summary:
bitcoin/bitcoin@662d117

---

Backport of Core [[bitcoin/bitcoin#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
  • Loading branch information
achow101 authored and majcosta committed Jun 7, 2020
1 parent 60df8b1 commit 8345f44
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 30 deletions.
5 changes: 5 additions & 0 deletions doc/release-notes.md
Expand Up @@ -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.
104 changes: 74 additions & 30 deletions src/wallet/rpcwallet.cpp
Expand Up @@ -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\" : <wallet_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\" : <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\" : <wallet_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\" : <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();
Expand All @@ -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;
}

Expand All @@ -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();
Expand Down Expand Up @@ -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"} },
Expand Down
51 changes: 51 additions & 0 deletions test/functional/wallet_createwallet.py
Expand Up @@ -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()

0 comments on commit 8345f44

Please sign in to comment.