Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
Can create Watch Only HD wallet with -hdwatchonly #9728
Conversation
| @@ -59,6 +63,10 @@ class CHDChain | ||
| READWRITE(this->nVersion); | ||
| READWRITE(nExternalChainCounter); | ||
| READWRITE(masterKeyID); | ||
| + if (this->nVersion <= SUPPORT_WATCHONLY_VERSION) { |
| + } | ||
| + else { | ||
| + if (!AddWatchOnly(GetScriptForDestination(pubkey.GetID()))) | ||
| + throw std::runtime_error(std::string(__func__) + ": AddKey failed"); |
| + // update the chain model in the database | ||
| + if (!CWalletDB(strWalletFile).WriteHDChain(hdChain)) | ||
| + throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); | ||
| + return false; |
jonasschnelli
Feb 9, 2017
Member
I think returning false in case of isWatchOnly is confusing.
IMO better set the hasSecret boolean with another check of hdChain.isWatchOnly.
NicolasDorier
Feb 9, 2017
•
Member
I can replace returns bool by void, and make the client responsible to test the validity of the returned Key.
But I fear that a new user would assume that DeriveNewKey always returns a valid CKey.
jonasschnelli
Feb 9, 2017
Member
Yes. I once did a PR where we separate the key/pubkey records: #9298
I guess this would be the cleaner solution... but maybe later.
|
Concept ACK (will review in detail and test soon). |
fanquake
added the
Wallet
label
Feb 9, 2017
Instead of removing the wallet, it would also be possible to specify an alternative one with |
|
How does this work, considering that Core is exclusively hardened derivation right now? Or can it only watch non-Core wallets? O.o |
For the hd-watch-only default keypath, we should probably use Bip44. |
|
Travis error not related to this PR. |
|
@NicolasDorier: I think in order to pass travis you need to add |
|
I added a commit to track P2PK as well. It would be the same behavior as normal wallet. However I am not sure it is the right approach as P2PK are obsolete. An attacker could disrupt service by sending a P2PK output, when the service does not expect it. Also, what for P2WPKH ? |
NicolasDorier
referenced
this pull request
Feb 16, 2017
Open
Add `-disablehot` mode: a sane mode for watchonly-wallets #9662
|
I am replicating the behavior of normal wallets, both p2pk and p2pkh are tracked. The test is failing because I am testing wrong parameter usage, and there is no way in the test framework to assert an exception. Ping @MarcoFalke , same problem as reported by jonas on #9662 |
| + assert('bitcoind exited' in str(e)) #node must have shutdown | ||
| + | ||
| + | ||
| +def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, ignorestderr=False): |
MarcoFalke
Feb 21, 2017
•
Member
Is this additional optional arg required? I don't think this will ever be used.
Edit: I see you are doing it for consistency, so might be fine...
|
I am at loss understanding why there is a permission denied on travis.... I thought I maybe did not had right to /dev/null, and tried to redirect stderr to stdout instead, but same problem. Any idea ? |
Try chmod +x qa/rpc-tests/hdwatchonly.py |
NicolasDorier
referenced
this pull request
Feb 23, 2017
Merged
[qa] assert_start_raises_init_error #9832
|
rebased, and cleaned up the commits for better review. |
saleemrashid
commented
Mar 13, 2017
|
As far as I understand, Bitcoin Core uses However, this means that you are doing one less derivation than with |
|
@saleemrashid very good point. Will make your change once #9294 is merged. |
| - static const int CURRENT_VERSION = 1; | ||
| + static const int FIRST_VERSION = 1; | ||
| + static const int SUPPORT_WATCHONLY_VERSION = 2; | ||
| + static const int CURRENT_VERSION = 2; |
| + } | ||
| + else if (IsHDWatchOnly()) { | ||
| + CExtPubKey& masterKey = hdChain.masterPubKey; //hd master key | ||
| + CExtPubKey accountKey; //key at m/0 |
jonasschnelli
Mar 17, 2017
Member
To make this compatible with BIP44, can we not just use m as the BIP44 account key. No hardened derivation would then be involved and users could use their BIP44 xpub's to generate watchonlys.
The externalChainChildKey would then be m/0 (instead of m/0/0)
jonasschnelli
Mar 20, 2017
Member
Ah.. Wasn't aware that @saleemrashid already pointed this out. Great.
| + CExtPubKey childKey; //key at m/0/0/<n> | ||
| + | ||
| + // derive m/0 | ||
| + masterKey.Derive(accountKey, BIP32_NONHARDENED_KEY_LIMIT); |
jonasschnelli
Mar 17, 2017
Member
IMO this constant (BIP32_NONHARDENED_KEY_LIMIT) is superfluous. Just use 0.
| + | ||
| + // derive child key at next index, skip keys already known to the wallet | ||
| + do { | ||
| + externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_NONHARDENED_KEY_LIMIT); |
jonasschnelli
Mar 17, 2017
Member
Also here, ... just use hdChain.nExternalChainCounter instead of hdChain.nExternalChainCounter | BIP32_NONHARDENED_KEY_LIMIT (no need to bitwise OR it with 0).
| - CPubKey pubkey = secret.GetPubKey(); | ||
| - assert(secret.VerifyPubKey(pubkey)); | ||
| + if (!IsHDWatchOnly()) | ||
| + assert(secret.VerifyPubKey(pubkey)); |
jonasschnelli
Mar 17, 2017
Member
I think there is no way to verify the correctness of the derived pubkey?
| + if (this->nVersion >= SUPPORT_WATCHONLY_VERSION) { | ||
| + READWRITE(isWatchOnly); | ||
| + if(isWatchOnly) | ||
| + READWRITE(masterPubKey); |
jonasschnelli
Mar 17, 2017
Member
Somehow this PR was crashing here because of
Assertion failed: (pubkey.size() == 33), function Encode, file pubkey.cpp, line 255.
It looks like that my tested tpub was non compressed? How is that even possible?
LLDB told me pubkey = (vch = unsigned char [65] @ 0x00007fd16736976c).
jonasschnelli
Mar 17, 2017
Member
Wait... I was using the extended private key instead. Though, I guess it should not crash in this case.
NicolasDorier
Mar 20, 2017
Member
How is it possible to get an extended private key here? if you provided a xpriv instead of xpub, it should have crashed at startup.
jonasschnelli
Mar 20, 2017
Member
I used a tpriv which made this PR proceeding the hdwatchonly initialisation but then crashed at this point. Haven't looked into the details why the tpriv was accepted during init.
| + CExtPubKey extPubKey; | ||
| + if (!hdWatchOnly.empty()) { | ||
| + CBitcoinExtPubKey bitcoinExtPubKey; | ||
| + auto extpubKeyPrefix = chainparams.Base58Prefix(CChainParams::Base58Type::EXT_PUBLIC_KEY); |
jonasschnelli
Mar 17, 2017
Member
Hmm.. I think this is incorrect. I could load a xpub (mainnet) on regtest.
Shouldn't CBitcoinExtPubKey("<x|tpub>") do the check for the correct encoding according to the chainparams?
| + extPubKey = bitcoinExtPubKey.GetKey(); | ||
| + } | ||
| + | ||
| + if (!fFirstRun && !hdWatchOnly.empty()) { |
jonasschnelli
Mar 17, 2017
Member
I got no warning when I started bitcoind again with a different -hdwatchonly=xpub.
Here there should be a check wether the wallet has already a watch-only xpub set, and if the user provides one, and it's different, it should warn or stop.
NicolasDorier
Mar 20, 2017
•
Member
this is normally checked. Actually I am testing it on https://github.com/bitcoin/bitcoin/pull/9728/files#diff-08fcd10ff4d7d4f9f0249ba978b3b4d4R49 this is strange... actual check done on https://github.com/bitcoin/bitcoin/pull/9728/files#diff-b2bb174788c7409b671c46ccc86034bdR3731 can you verify that you did not made another mistake ?
|
This is a great PR. I really would like this to be in 0.15. It makes HWW signing much simpler and could be a pre-step for HWW support in Core. |
|
I will rebase and pass on all your nits and better test/code the case where you pass a xpriv once #9294 get merged. |
|
fanquake
added this to the
0.15.0
milestone
Mar 22, 2017
|
Rebased:
|
NicolasDorier
referenced
this pull request
in NTumbleBit/NTumbleBit
May 24, 2017
Closed
Drop the forceful support of BIP44 and make it optional #52
|
Rebased @saleemrashid |
saleemrashid
commented
Jun 5, 2017
|
ACK |
|
needs rebase, will review |
|
rebased |
|
Is there a way exposed to the user that their wallet is HD watch-only? Might be useful in |
|
just added info in |
|
Ok my blinded push passed @instagibbs . Added some info in |
| @@ -111,9 +111,7 @@ CPubKey CWallet::GenerateNewKey(bool internal) | ||
| SetMinVersion(FEATURE_COMPRPUBKEY); | ||
| if (!IsHDWatchOnly()) | ||
| - { |
instagibbs
Jun 19, 2017
Member
Braces are the recommended code style, not sure why they're being removed.
| } | ||
| // Compressed public keys were introduced in version 0.6.0 | ||
| if (fCompressed) | ||
| SetMinVersion(FEATURE_COMPRPUBKEY); | ||
| - CPubKey pubkey = secret.GetPubKey(); | ||
| - assert(secret.VerifyPubKey(pubkey)); | ||
| + if (!IsHDWatchOnly()) |
| - CPubKey pubkey = secret.GetPubKey(); | ||
| - assert(secret.VerifyPubKey(pubkey)); | ||
| + if (!IsHDWatchOnly()) | ||
| + assert(secret.VerifyPubKey(pubkey)); | ||
| mapKeyMetadata[pubkey.GetID()] = metadata; |
| + if (!fFirstRun) { | ||
| + if (!walletInstance->IsHDWatchOnly() || | ||
| + walletInstance->GetHDChain().masterKeyID != extPubKey.pubkey.GetID()) { | ||
| + InitError(_("Cannot specify hdwatchonly on an already existing wallet")); |
instagibbs
Jun 19, 2017
Member
Message should be something like "Cannot specify new hdwatchonly on an already existing wallet"
| @@ -2400,7 +2400,8 @@ UniValue getwalletinfo(const JSONRPCRequest& request) | ||
| " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n" | ||
| " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" | ||
| " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" | ||
| - " \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n" | ||
| + " \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n" + | ||
| + " \"hdwatchonlykey\": \"<hdpubkey>\" (string) the HD pubkey used for key derivation in hdwatchonly mode\n" |
| @@ -57,6 +57,14 @@ bool CWalletDB::EraseTx(uint256 hash) | ||
| return EraseIC(std::make_pair(std::string("tx"), hash)); | ||
| } | ||
| +bool CWalletDB::WriteKeyMeta(const CPubKey& vchPubKey, const CKeyMetadata& keyMeta) | ||
| +{ | ||
| + if (!batch.Write(std::make_pair(std::string("keymeta"), vchPubKey), |
instagibbs
Jun 19, 2017
Member
Please add braces for readability
Should you be using WriteIC like everywhere else?
| + | ||
| + self.stop_nodes() | ||
| + | ||
| + # check fails gracefully if any mess up with hdwatchonly parameter |
instagibbs
Jun 19, 2017
Member
suggested rewording:
"check for graceful failure due to any invalid hdwatchonly parameters"
| + validated_address = self.nodes[0].validateaddress('mxKeRQP6gTdCW6jHhn9FW8bGXD8W1UpR6n') | ||
| + assert_equal(validated_address['hdkeypath'], 'm/0/1') | ||
| + | ||
| + # check the hd key was persisted |
|
Thanks for the review @instagibbs I addressed your nits, can you re tACK once ? |
|
re-tACK 067b117 |
|
We could actually also return the xpub in |
|
Included the return in Please feel free to take. Using this PR "in production" with no issues. |
laanwj
self-assigned this
Jun 27, 2017
| + const auto& meta = pwallet->mapKeyMetadata; | ||
| + auto it = meta.find(CScriptID(parentOut.scriptPubKey)); | ||
| + if (it == meta.end() || | ||
| + it->second.hdKeypath.empty()) |
instagibbs
Jun 27, 2017
•
Member
I think we may need to generalize this check a bit more. If the script here is a multisig in which the watch-only wallet owns all the keys, this will return false since there is no hdKeypath.
Practically this means that spending 0-conf p2sh funds is a dicey experience that results in "Insufficient funds" responses sometimes.
Perhaps something like pwalletMain->IsHDWatchOnly(CScriptID& scriptID) to make this check, which accounts for this situation?
instagibbs
Jun 27, 2017
Member
Since we're not even required to be backwards compatible, I think adding a new piece of key metadata fIsHDWatchOnly, and adding that during importaddress(or some new call) when all keys are hdwatchonly keys(or a mixture of watchonly and stored private keys?).
NicolasDorier
Jun 28, 2017
•
Member
Does IsTrusted() returns ISMINE_SPENDABLE when for a multi sig output where we know all the keys when not hdwatchonly?
instagibbs
Jun 28, 2017
Member
If you have the private keys, yes, because of check a few lines above.
NicolasDorier
Jun 29, 2017
Member
So your goal is that importaddress scriptPubKeys should be considered trusted if we are in hdwatchonly mode ?
instagibbs
Jun 29, 2017
Member
If we completely those addresses via hdwatchonly keys and/or privkeys, yes.
instagibbs
Jun 29, 2017
Member
Currently in this mode you can import a privkey and spend unconfirmed outputs. Likewise you can spend unconfirmed hdwatchonly outputs. You cannot however spend a mixture of the two in a multisig, or pure hdwatchonly p2sh.
NicolasDorier
and others
added some commits
Feb 9, 2017
|
Rebased and included @instagibbs commit. |
|
Not trying to completely sidetrack this PR, but this is for future PR consideration: @NicolasDorier instagibbs/bitcoin@bb72d12 This is the implementation that works for unconfirmed p2sh multisig where all keys are mixture of imported privkeys and hdwatchonly. Pretty ugly though and quite special-cased. Longer-term I'd look to extend ISMINE instead to have a value which covers this consideration and make this whole IsTrusted logic much simpler. |
|
I think making more general case with SPENDABLE_WATCHONLY is preferable. |
| - if (!GetKey(hdChain.masterKeyID, key)) | ||
| + if (GetKey(hdChain.masterKeyID, key)) | ||
| + { | ||
| + // for now we use a fixed keypath scheme of m/0'/0'/k |
jtimon
Jul 13, 2017
Member
it seems like you are repeating yourself a little bit here. Perhaps a common function can be extracted to avoid some code duplication between this if and the next one?
NicolasDorier
Jul 15, 2017
Member
I tried to think about this, but this was not as easy. I preferred keeping this way as it makes review easier: I did not touch what was already working.
NicolasDorier commentedFeb 9, 2017
•
edited
The user creates a new wallet by running
./bitcoind -hdwatchonly=[ExtPubKey base58].This make it possible to use methods like
getnewaddress,fundrawtransactionand all normal wallet operations on a HD pubkey.Software built on top of core which need to delegate signing operations to hardware wallet will have almost the same code as if signing was done by Core.
With the introduction of a standard for dealing with hardware wallet signing in the future, I expect that
signrawtransactionwill just delegate the signing to the hardware wallet.In this way, there will be no code difference between software using third party solution for signing, and those just using core for signing.
I will use it in my own projects. My HW is giving me the ExtPubKey, and I want to use bitcoin core just for coin selection and tracking. I also did not wanted to break bunch of old code. Ping @jonasschnelli
EDIT: @saleemrashid built HW support for Bitcoin Core on https://github.com/saleemrashid/bitcoin/tree/hardware-wallet based on this PR