Merge #15006: Add option to create an encrypted wallet

662d1171d9 Add option to create an encrypted wallet (Andrew Chow)

Pull request description:

  This PR adds a new `passphrase` argument to `createwallet` which will create a wallet that is encrypted with that passphrase.

  This is built on #15226 because it needs to first create an empty wallet, then encrypt the empty wallet and generate new keys that have only been stored in an encrypted state.

ACKs for commit 662d11:
  laanwj:
    utACK 662d1171d9
  jnewbery:
    Looks great. utACK 662d1171d9

Tree-SHA512: a53fc9a0f341eaec1614eb69abcf2d48eb4394bc89041ab69bfc05a63436ed37c65ad586c07fd37dc258ac7c7d5e4f7f93b4191407f5824bbf063b4c50894c4a
This commit is contained in:
Wladimir J. van der Laan 2019-05-16 18:20:05 +02:00
commit 1c719f78d3
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
3 changed files with 82 additions and 16 deletions

View file

@ -0,0 +1,4 @@
Miscellaneous RPC changes
------------
- `createwallet` can now create encrypted wallets if a non-empty passphrase is specified.

View file

@ -2641,26 +2641,29 @@ static UniValue loadwallet(const JSONRPCRequest& request)
static UniValue createwallet(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."},
},
RPCResult{
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\"")
},
RPCExamples{
HelpExampleCli("createwallet", "\"testwallet\"")
+ HelpExampleRpc("createwallet", "\"testwallet\"")
},
}.ToString());
},
};
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
throw std::runtime_error(help.ToString());
}
std::string error;
std::string warning;
@ -2670,7 +2673,20 @@ static UniValue createwallet(const JSONRPCRequest& request)
flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
}
bool create_blank = false; // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
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;
}
@ -2688,6 +2704,29 @@ static UniValue createwallet(const JSONRPCRequest& request)
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();
@ -4140,7 +4179,7 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank"} },
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },

View file

@ -96,5 +96,28 @@ class CreateWalletTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
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()