Skip to content

Commit 662d117

Browse files
committed
Add option to create an encrypted wallet
1 parent 667a861 commit 662d117

File tree

3 files changed

+82
-16
lines changed

3 files changed

+82
-16
lines changed

doc/release-notes-15006.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Miscellaneous RPC changes
2+
------------
3+
4+
- `createwallet` can now create encrypted wallets if a non-empty passphrase is specified.

src/wallet/rpcwallet.cpp

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2641,26 +2641,29 @@ static UniValue loadwallet(const JSONRPCRequest& request)
26412641

26422642
static UniValue createwallet(const JSONRPCRequest& request)
26432643
{
2644-
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) {
2645-
throw std::runtime_error(
2646-
RPCHelpMan{"createwallet",
2647-
"\nCreates and loads a new wallet.\n",
2648-
{
2649-
{"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."},
2650-
{"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
2651-
{"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."},
2652-
},
2653-
RPCResult{
2644+
const RPCHelpMan help{
2645+
"createwallet",
2646+
"\nCreates and loads a new wallet.\n",
2647+
{
2648+
{"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."},
2649+
{"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
2650+
{"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."},
2651+
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
2652+
},
2653+
RPCResult{
26542654
"{\n"
26552655
" \"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"
26562656
" \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n"
26572657
"}\n"
2658-
},
2659-
RPCExamples{
2660-
HelpExampleCli("createwallet", "\"testwallet\"")
2658+
},
2659+
RPCExamples{
2660+
HelpExampleCli("createwallet", "\"testwallet\"")
26612661
+ HelpExampleRpc("createwallet", "\"testwallet\"")
2662-
},
2663-
}.ToString());
2662+
},
2663+
};
2664+
2665+
if (request.fHelp || !help.IsValidNumArgs(request.params.size())) {
2666+
throw std::runtime_error(help.ToString());
26642667
}
26652668
std::string error;
26662669
std::string warning;
@@ -2670,7 +2673,20 @@ static UniValue createwallet(const JSONRPCRequest& request)
26702673
flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
26712674
}
26722675

2676+
bool create_blank = false; // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted
26732677
if (!request.params[2].isNull() && request.params[2].get_bool()) {
2678+
create_blank = true;
2679+
flags |= WALLET_FLAG_BLANK_WALLET;
2680+
}
2681+
SecureString passphrase;
2682+
passphrase.reserve(100);
2683+
if (!request.params[3].isNull()) {
2684+
passphrase = request.params[3].get_str().c_str();
2685+
if (passphrase.empty()) {
2686+
// Empty string is invalid
2687+
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Cannot encrypt a wallet with a blank password");
2688+
}
2689+
// Born encrypted wallets need to be blank first so that wallet creation doesn't make any unencrypted keys
26742690
flags |= WALLET_FLAG_BLANK_WALLET;
26752691
}
26762692

@@ -2688,6 +2704,29 @@ static UniValue createwallet(const JSONRPCRequest& request)
26882704
if (!wallet) {
26892705
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
26902706
}
2707+
2708+
// Encrypt the wallet if there's a passphrase
2709+
if (!passphrase.empty() && !(flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
2710+
if (!wallet->EncryptWallet(passphrase)) {
2711+
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Wallet created but failed to encrypt.");
2712+
}
2713+
2714+
if (!create_blank) {
2715+
// Unlock the wallet
2716+
if (!wallet->Unlock(passphrase)) {
2717+
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Wallet was encrypted but could not be unlocked");
2718+
}
2719+
2720+
// Set a seed for the wallet
2721+
CPubKey master_pub_key = wallet->GenerateNewSeed();
2722+
wallet->SetHDSeed(master_pub_key);
2723+
wallet->NewKeyPool();
2724+
2725+
// Relock the wallet
2726+
wallet->Lock();
2727+
}
2728+
}
2729+
26912730
AddWallet(wallet);
26922731

26932732
wallet->postInitProcess();
@@ -4140,7 +4179,7 @@ static const CRPCCommand commands[] =
41404179
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
41414180
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
41424181
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
4143-
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank"} },
4182+
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase"} },
41444183
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
41454184
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
41464185
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },

test/functional/wallet_createwallet.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,28 @@ def run_test(self):
9696
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
9797
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
9898

99+
self.log.info('New blank and encrypted wallets can be created')
100+
self.nodes[0].createwallet(wallet_name='wblank', disable_private_keys=False, blank=True, passphrase='thisisapassphrase')
101+
wblank = node.get_wallet_rpc('wblank')
102+
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", wblank.signmessage, "needanargument", "test")
103+
wblank.walletpassphrase('thisisapassphrase', 10)
104+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", wblank.getnewaddress)
105+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", wblank.getrawchangeaddress)
106+
107+
self.log.info('Test creating a new encrypted wallet.')
108+
# Born encrypted wallet is created (has keys)
109+
self.nodes[0].createwallet(wallet_name='w6', disable_private_keys=False, blank=False, passphrase='thisisapassphrase')
110+
w6 = node.get_wallet_rpc('w6')
111+
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", w6.signmessage, "needanargument", "test")
112+
w6.walletpassphrase('thisisapassphrase', 10)
113+
w6.signmessage(w6.getnewaddress('', 'legacy'), "test")
114+
w6.keypoolrefill(1)
115+
# There should only be 1 key
116+
walletinfo = w6.getwalletinfo()
117+
assert_equal(walletinfo['keypoolsize'], 1)
118+
assert_equal(walletinfo['keypoolsize_hd_internal'], 1)
119+
# Empty passphrase, error
120+
assert_raises_rpc_error(-16, 'Cannot encrypt a wallet with a blank password', self.nodes[0].createwallet, 'w7', False, False, '')
121+
99122
if __name__ == '__main__':
100123
CreateWalletTest().main()

0 commit comments

Comments
 (0)