Skip to content

Commit 1c719f7

Browse files
committed
Merge #15006: Add option to create an encrypted wallet
662d117 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 662d117 jnewbery: Looks great. utACK 662d117 Tree-SHA512: a53fc9a0f341eaec1614eb69abcf2d48eb4394bc89041ab69bfc05a63436ed37c65ad586c07fd37dc258ac7c7d5e4f7f93b4191407f5824bbf063b4c50894c4a
2 parents d5931f3 + 662d117 commit 1c719f7

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)