Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add createwallet "disableprivatekeys" option: a sane mode for watchonly-wallets #9662

Merged
merged 6 commits into from Jul 20, 2018

Conversation

@jonasschnelli
Copy link
Member

commented Feb 1, 2017

This mode ('createwallet {"disableprivatekeys": true}') is intended for a sane pure watch-only mode, ideal for a use-case where one likes to use Bitcoin-Core in conjunction with a hardware-wallet or another solutions for cold-storage.

Since we have support for custom change addresses in fundrawtransaction, pure watch-only wallets including coin-selection are possible and do make sense for some use cases.

This new mode disables all forms of private key generation and ensure that no mix between hot and cold keys are possible.

@jonasschnelli jonasschnelli added the Wallet label Feb 1, 2017

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch Feb 1, 2017

@TheBlueMatt

This comment has been minimized.

Copy link
Contributor

commented Feb 1, 2017

It seems somewhat strange that this is set per-run and not per-wallet. Indeed, if it were set per-wallet, an easy way to get the intended behavior here is to simply encrypt your wallet with a garbage passphrase such that it can no longer be opened (not to say we shouldn't do this, better UX around that is good, but it might be easier to review/write if it re-used that infrastructure).

@laanwj

This comment has been minimized.

Copy link
Member

commented Feb 2, 2017

Concept ACK. I agree that this would be better as a mode on the wallet instead of yet another startup option.

@NicolasDorier

This comment has been minimized.

Copy link
Member

commented Feb 16, 2017

I would like to rebase on top of that for #9728, can you rebase?
I would switch that on if hdwatchonly is used.

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch Feb 17, 2017

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented Feb 17, 2017

Added two commit.

  • First commit adds a facility to store and check wallet flags (64bit). Made DISABLE_PRIVATE_KEYS the first flag.
  • Second commit makes the disablehot function per wallet (no longer a global state)

Disabling private keys on a wallet that already contains private keys is not possible (also the other way around).

qa/rpc-tests/disablehot.py Outdated
print("restarting node with disablehot=0 (try to re-enable private keys which must not be possible)")
stop_nodes(self.nodes)
try:
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [[], ['-disablehot=0']])

This comment has been minimized.

Copy link
@jonasschnelli

jonasschnelli Feb 17, 2017

Author Member

@MarcoFalke: any idea how I can make this pass? Right now, staring bitcoind in this case with this setup will cause bitcoind to write to stderr (and halt) which then result in pass=false in rpc-tests.py. But it's actually the correct behaviour.

This comment has been minimized.

Copy link
@MarcoFalke

MarcoFalke Feb 18, 2017

Member

We might create a assert_start_raises_init_error() method. I will take a look at this tomorrow.

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch Feb 17, 2017

src/wallet/wallet.cpp Outdated
@@ -1408,13 +1407,25 @@ bool CWallet::IsHDEnabled()
return !hdChain.masterKeyID.IsNull();
}

void CWallet::SetWalletFlag(uint64_t flag)
{
walletFlags |= flag;

This comment has been minimized.

Copy link
@NicolasDorier

NicolasDorier Feb 20, 2017

Member

Kind of confusing....
I thought this would be equivalent to CWallet::SetWalletFlags(uint64_t flags, false).

However while CWallet::SetWalletFlags(uint64_t flags, bool memonly) set the flags to the indicated value, CWallet::SetWalletFlag(uint64_t flag) appends to it.

Maybe you should rename to AddWalletFlags.

This comment has been minimized.

Copy link
@saleemrashid

saleemrashid Feb 20, 2017

CWallet::AddWalletFlags(uint64_t flags, book memonly) seems most consistent.

This comment has been minimized.

Copy link
@jonasschnelli

jonasschnelli Feb 21, 2017

Author Member

IMO set flags seems most appropriate here. Flags are not getting appended. Either you set or unset IMO. For consistency, we should have a UnsetWalletFlag().
But I agree its kind of confusing that we have a SetWalletFlags (plural) method that is capable or re-setting all 64 flags.

This comment has been minimized.

Copy link
@saleemrashid

saleemrashid Feb 21, 2017

I think this method should be plural, since there is no distinction between a single flag and multiple flags in its operation and it might be useful to be able to do the latter.

Then, of course, you have confusion. So, a verb change in the method name might be useful.

This comment has been minimized.

Copy link
@NicolasDorier

NicolasDorier Feb 22, 2017

Member

@jonasschnelli the strange thing is that SetWalletFlag switch a bit, when SetWalletFlags set the walletFlags to a specific value. I was expecting SetWalletFlags to switch several bits.

This comment has been minimized.

Copy link
@jonasschnelli

jonasschnelli Feb 22, 2017

Author Member

Maybe renaming SetWalletFlags (plural) to OverwriteWalletFlags?

This comment has been minimized.

Copy link
@ryanofsky

ryanofsky May 4, 2017

Contributor

Maybe renaming SetWalletFlags (plural) to OverwriteWalletFlags?

Since this is doing an | I'd call it AddFlags (which conceivably could be paired with a RemoveFlags doing & ~).

qa/rpc-tests/disablehot.py Outdated
self.num_nodes = 2

def setup_network(self, split=False):
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [[], ['-disablehot']])

This comment has been minimized.

Copy link
@NicolasDorier

NicolasDorier Feb 20, 2017

Member

does the [] arguments is on purpose? because I think it makes -disablehot ignored

This comment has been minimized.

Copy link
@jonasschnelli

jonasschnelli Feb 21, 2017

Author Member

We start two nodes here, the first node without startup parameters (thats why there is the []). Only second node has -disablehot.

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch Mar 3, 2017

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented Mar 3, 2017

Needed rebase (#8775).
Travis will fail because of the missing assert_start_raises_init_error (see #9832).

@laanwj

This comment has been minimized.

Copy link
Member

commented Mar 6, 2017

ideal for a use-case where one likes to use Bitcoin-Core in conjunction with a hardware-wallet or another solutions for cold-stogare

Nice, so this guarantees that bitcoind process will contain no private key data at all?
Seems like s a good step towards supporting hardware wallets too.
Concept ACK.

Travis should pass now that #9832 is merged.

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch to 9549e2c Mar 6, 2017

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented Mar 6, 2017

The disablehot.py now makes use of the new (#9832) assert_start_raises_init_error call.

Nice, so this guarantees that bitcoind process will contain no private key data at all?

Yes. Event the default key is disabled (while still detecting the first start correctly).

else if (IsArgSet("-disablehot")) {
bool disableHot = GetBoolArg("-disablehot", DEFAULT_DISABLE_HOT_WALLET);
LOCK(walletInstance->cs_wallet);
if (walletInstance->vchDefaultKey.IsValid() && disableHot) {

This comment has been minimized.

Copy link
@NicolasDorier

NicolasDorier Mar 13, 2017

Member

I think you should use fFirstRun here instead of walletInstance->vchDefaultKey.IsValid()

This comment has been minimized.

Copy link
@jonasschnelli

jonasschnelli Mar 13, 2017

Author Member

At this point fFirstRun is always false. And I think this also works in conjunction with salvagewallet.

This comment has been minimized.

Copy link
@NicolasDorier

NicolasDorier Mar 13, 2017

Member

Indeed. Might be out of this PR matter, in the case of hdwatchonly, walletInstance->vchDefaultKey.IsValid() will be true. So this would mean disabledHot is incompatible with hdwatchonly mode ?

This comment has been minimized.

Copy link
@NicolasDorier

NicolasDorier Mar 13, 2017

Member

OK forget what I said, there is no reason to have disabledHot with hdwatchonly at same time.

This comment has been minimized.

Copy link
@luke-jr

luke-jr Oct 30, 2017

Member

This should at least be abstracted somehow. vchDefaultKey's existence is not exactly identical to "does this wallet have keys?"

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch from 9549e2c Mar 29, 2017

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented Mar 29, 2017

Rebased.

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch Apr 2, 2017

@laanwj

This comment has been minimized.

Copy link
Member

commented May 2, 2017

Needs rebase again (sorry)
utACK otherwise.

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch May 4, 2017

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented May 4, 2017

Rebased.

@ryanofsky
Copy link
Contributor

left a comment

utACK 9b39bf94f168ab82707df91f21cffea0b286f919

The commit history of this PR is messy though. I would suggest moving the "Add facility to store wallet flags (64 bits)" commit to be first, then then squashing all the other changes down into single commit following that.

src/wallet/wallet.cpp Outdated
@@ -3915,6 +3948,7 @@ bool CWallet::ParameterInteraction()
nTxConfirmTarget = GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
bSpendZeroConfChange = GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
fWalletRbf = GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
fDisableHotKeys = GetBoolArg("-disablehot", DEFAULT_DISABLE_HOT_WALLET);

This comment has been minimized.

Copy link
@ryanofsky

ryanofsky May 4, 2017

Contributor

In commit "Add -disablehot option to ensure pure watchonly-wallets"

I don't understand why we want to call these "hot keys" and not "private keys," but I guess I'm out of the loop on terminology not knowing precisely what a "hot key" is.

qa/rpc-tests/disablehot.py Outdated
self.nodes[1].getnewaddress()
raise AssertionError("Wallet can derive private keys while -disablehot is enabled")
except JSONRPCException as e:
assert('Hot key' in e.error['message'])

This comment has been minimized.

Copy link
@ryanofsky

ryanofsky May 4, 2017

Contributor

In commit "Add -disablehot option to ensure pure watchonly-wallets"

Consider using assert_raises_jsonrpc. (It's less verbose)

src/wallet/wallet.cpp Outdated
@@ -2497,6 +2500,11 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// post-backup change.

// Reserve a new key pair from key pool
if (fDisableHotKeys) {

This comment has been minimized.

Copy link
@ryanofsky

ryanofsky May 4, 2017

Contributor

In commit "Add -disablehot option to ensure pure watchonly-wallets"

Do you think it might be better to fail earlier in CreateTransaction, regardless of the nChange > 0 condition? I'm thinking it might be, just so calls would fail consistently instead of sometimes randomly succeeding. But I don't have much understanding of the real-world use-cases for this, so this is just a thought.

Considering adding a code comment here explaining more.

This comment has been minimized.

Copy link
@jonasschnelli

jonasschnelli May 5, 2017

Author Member

Falling earlier would disallow creating watch-only transactions (over fundrawtransaction).

src/qt/walletmodel.cpp Outdated
@@ -705,7 +705,8 @@ bool WalletModel::hdEnabled() const

bool WalletModel::hotKeysDisabled() const
{
return fDisableHotKeys;
return (pwalletMain->IsWalletFlagSet(WALLET_FLAG_DISABLE_HOT_KEYS));

This comment has been minimized.

Copy link
@ryanofsky

ryanofsky May 4, 2017

Contributor

In commit "Make -disablehot per wallet (remove global state)"

Extra newline here

src/wallet/wallet.cpp Outdated
@@ -1408,13 +1407,25 @@ bool CWallet::IsHDEnabled()
return !hdChain.masterKeyID.IsNull();
}

void CWallet::SetWalletFlag(uint64_t flag)
{
walletFlags |= flag;

This comment has been minimized.

Copy link
@ryanofsky

ryanofsky May 4, 2017

Contributor

Maybe renaming SetWalletFlags (plural) to OverwriteWalletFlags?

Since this is doing an | I'd call it AddFlags (which conceivably could be paired with a RemoveFlags doing & ~).

src/wallet/wallet.cpp Outdated
}
bool hotKeys = walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_HOT_KEYS);
if (hotKeys && !disableHot) {
InitError(strprintf(_("Error loading %s: You can't enable hot keys once you have initialized a wallet with disabled hot keys"), walletFile));

This comment has been minimized.

Copy link
@ryanofsky

ryanofsky May 4, 2017

Contributor

Add -disablehot mode: a sane mode for watchonly-wallets

Maybe the error message could suggest passing -disablehot to avoid the problem. I think it would make the context of the error easier to understand for someone not expecting it.

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch 2 times, most recently May 5, 2017

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented May 5, 2017

  • Completely rewrote the commit history (as reported by @ryanofsky it was messy).
  • Fixed @ryanofsky points

No strong opinion if this should be called -disableprivatekeys (which somehow nails it but may confuse people who just want cold key storage). Other opinions?�

@ryanofsky

This comment has been minimized.

Copy link
Contributor

commented May 5, 2017

utACK fcea55a6ec6be57ee96458bde6fb24656269d3ee

Changes from previous review: pwalletMain removal, SetWalletFlag rename, error message tweaks, assert_raises_jsonrpc usage, and history cleanup.

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented Jun 28, 2018

Fixed @jnewbery's nits.

@jnewbery

This comment has been minimized.

Copy link
Member

commented Jul 3, 2018

utACK 5caf38f42f34b2aec7440dec752c10856e5d85a7

1 similar comment
@laanwj

This comment has been minimized.

Copy link
Member

commented Jul 4, 2018

utACK 5caf38f42f34b2aec7440dec752c10856e5d85a7

src/wallet/wallet.cpp Outdated
{
LOCK(cs_wallet);
m_wallet_flags = overwriteFlags;
if ( (overwriteFlags >> 32 | g_known_wallet_flags >> 32) != (g_known_wallet_flags >> 32) ) {

This comment has been minimized.

Copy link
@laanwj

laanwj Jul 4, 2018

Member

nit: Code style is a bit funky here.

This comment has been minimized.

Copy link
@kallewoof

kallewoof Jul 4, 2018

Member

Personally think this definitely needs parens to clarify priority, but maybe I'm just easily confused.

This comment has been minimized.

Copy link
@jonasschnelli

jonasschnelli Jul 4, 2018

Author Member

Fixed

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch Jul 4, 2018

@laanwj

This comment has been minimized.

Copy link
Member

commented Jul 5, 2018

@Sjors

This comment has been minimized.

Copy link
Member

commented Jul 10, 2018

Lightly tested by creating a wallet via RPC, seeing it appear in the GUI, importing a watch-only address and sending some coins to it from a normal wallet. That money doesn't show up, even after confirmation, until I restart.

After restart, importing a watch-only address and sending money to it, that transaction shows up immediately.

@jnewbery said somewhere above:

Named arguments are much preferable. Using named arguments provides type- and error- checking for free, and simplify/flatten the interface

For example, if I typo the an option disableprivtaekeys here, this implementation will silently ignore the option. That's not possible with named arguments.

disable_private_keys is still positional, which would be painful to change later...

This GUI behavior is confusing - because it suggests the wallet isn't HD, even though it may or may not be - but I'm fine with addressing it later:

gui

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch Jul 12, 2018

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented Jul 12, 2018

Rebased and fixed the upper-bits restriction (for the wallet flags) found by @laanwj (#9662 (comment)).

@Sjors

This comment has been minimized.

Copy link
Member

commented Jul 12, 2018

Unhappy Travis for the Darwin machine:
schermafbeelding 2018-07-12 om 13 13 49

I these warnings too when locally on macOS:
schermafbeelding 2018-07-12 om 13 20 39

@DrahtBot DrahtBot removed the Needs rebase label Jul 12, 2018

jonasschnelli added some commits May 5, 2017

@jonasschnelli jonasschnelli force-pushed the jonasschnelli:2017/02/disable_hot branch to a3fa4d6 Jul 12, 2018

@jonasschnelli

This comment has been minimized.

Copy link
Member Author

commented Jul 12, 2018

Fixed the rebase issue (the warning; requires cs_KeyStore instead of cs_wallet now).

@laanwj laanwj added this to the 0.17.0 milestone Jul 19, 2018

@laanwj laanwj merged commit a3fa4d6 into bitcoin:master Jul 20, 2018

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details

laanwj added a commit that referenced this pull request Jul 20, 2018

Merge #9662: Add createwallet "disableprivatekeys" option: a sane mod…
…e for watchonly-wallets

a3fa4d6 QA: Fix bug in -usecli logic that converts booleans to non-lowercase strings (Jonas Schnelli)
4704e5f [QA] add createwallet disableprivatekey test (Jonas Schnelli)
c7b8f34 [Qt] Disable creating receive addresses when private keys are disabled (Jonas Schnelli)
2f15c2b Add disable privatekeys option to createwallet (Jonas Schnelli)
cebefba Add option to disable private keys during internal wallet creation (Jonas Schnelli)
9995a60 Add facility to store wallet flags (64 bits) (Jonas Schnelli)

Pull request description:

  This mode ('createwallet {"disableprivatekeys": true}') is intended for a sane pure watch-only mode, ideal for a use-case where one likes to use Bitcoin-Core in conjunction with a hardware-wallet or another solutions for cold-storage.

  Since we have support for custom change addresses in `fundrawtransaction`, pure watch-only wallets including coin-selection are possible and do make sense for some use cases.

  This new mode disables all forms of private key generation and ensure that no mix between hot and cold keys are possible.

Tree-SHA512: 3ebe7e8d54c4d4e5f790c348d4c292d456f573960a5b04d69ca5ef43a9217c7e7671761c6968cdc56f9a8bc235f3badd358576651af9f10855a0eb731f3fc508
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.