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: utACK662d1171d9
jnewbery: Looks great. utACK662d1171d9
Tree-SHA512: a53fc9a0f341eaec1614eb69abcf2d48eb4394bc89041ab69bfc05a63436ed37c65ad586c07fd37dc258ac7c7d5e4f7f93b4191407f5824bbf063b4c50894c4a
This commit is contained in:
commit
1c719f78d3
3 changed files with 82 additions and 16 deletions
4
doc/release-notes-15006.md
Normal file
4
doc/release-notes-15006.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
Miscellaneous RPC changes
|
||||
------------
|
||||
|
||||
- `createwallet` can now create encrypted wallets if a non-empty passphrase is specified.
|
|
@ -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"} },
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue