Allow specific private keys to be derived from descriptor #15024

pull meshcollider wants to merge 4 commits into bitcoin:master from meshcollider:201812_descriptor_keys changing 4 files +75 −15
  1. meshcollider commented at 10:30 pm on December 21, 2018: contributor

    This is based on #14491, review the last 3 commits only.

    Currently, descriptors have an Expand() function which returns public keys and scripts for a specific index of a ranged descriptor. But the private key for a specific index is not given. This allows private keys for specific indices to be derived. This also allows those keys to be imported through the importmulti RPC rather than having to provide them separately.

  2. meshcollider added the label Wallet on Dec 21, 2018
  3. DrahtBot commented at 12:50 pm on December 24, 2018: member

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #15764 (Native descriptor wallets by achow101)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. Sjors commented at 3:04 pm on January 7, 2019: member

    Concept ACK. Code changes look simple enough at first glance, will review more once upstream is merged.

    I suggest dropping the keys argument for importmulti when a descriptor is used. At least assuming this makes it into the same release as the previous PR.

  5. DrahtBot added the label Needs rebase on Feb 1, 2019
  6. DrahtBot commented at 1:44 pm on February 1, 2019: member
  7. meshcollider commented at 6:55 am on February 5, 2019: contributor
    I’ll rebase this after #14491 is merged
  8. Sjors commented at 9:00 am on February 15, 2019: member
    Needs rebase
  9. gwillen commented at 10:38 pm on April 8, 2019: contributor
    I am strongly interested in this for the usability of my offline-signing work, and will give it a look.
  10. gwillen commented at 10:41 pm on April 8, 2019: contributor
    Could you rebase now that 14491 is in? It will clean up the diff a lot. Then I’ll take a look.
  11. meshcollider commented at 11:35 am on April 9, 2019: contributor
    Rebased :)
  12. meshcollider removed the label Needs rebase on Apr 9, 2019
  13. gwillen commented at 10:17 pm on April 9, 2019: contributor

    ACK with some testing.

    I tested one ranged and one non-ranged descriptor, verified with deriveaddresses and getaddressinfo that I get the correct keypaths and such displayed everywhere, and is_mine is true where expected. I did not test actually spending.

    I get a slightly weird result from getaddressinfo which I assume is probably not exactly the fault of this PR: I get an “hdseedid” of “0000000000000000000000000000000000000000” for all keys derived this way. It seems like, if we don’t have an hdseedid, that key should instead be left out, as it is optional. I suspect before this change we may rarely or never have had derived private keys without an hdseedid available, so it never came up.

    I am interested in whether a key imported this way would be expected to be used by the wallet, in the case where we are trying to sign for some other descriptor where the key appeared, other than the one imported using importmulti. (In particular, a multisig descriptor where we only have one key and are creating a partial signature using walletprocesspsbt.)

  14. laanwj added this to the "Blockers" column in a project

  15. in test/functional/wallet_importmulti.py:587 in e00c3ddae4 outdated
    583@@ -584,12 +584,12 @@ def run_test(self):
    584         self.test_importmulti({"desc": descsum_create(desc),
    585                                "timestamp": "now",
    586                                "range": 1},
    587-                              success=True,
    588-                              warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
    589+                              success=True)
    


    jnewbery commented at 9:36 pm on May 2, 2019:
    Comment above (# Test importing of a ranged descriptor without keys) needs to be updated to something like # Test importing of a ranged descriptor with xpriv
  16. in test/functional/wallet_importmulti.py:588 in e00c3ddae4 outdated
    583@@ -584,12 +584,12 @@ def run_test(self):
    584         self.test_importmulti({"desc": descsum_create(desc),
    585                                "timestamp": "now",
    586                                "range": 1},
    587-                              success=True,
    588-                              warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."])
    589+                              success=True)
    590         for address in addresses:
    


    jnewbery commented at 9:36 pm on May 2, 2019:
    This isn’t testing anything (the address loop variable in not being used, and the address tested is left over from the test above)

    meshcollider commented at 7:30 am on June 6, 2019:

    @jnewbery not sure what you mean, it calls self.test_address(address, ...) with each address to ensure they imported correctly from the xpriv after failing to import in the test before?

    EDIT: sorry, I was looking at an older branch, will fix

  17. jnewbery commented at 9:57 pm on May 2, 2019: member

    Concept ACK.

    I’ve left a couple of comments on the test. The last two commits should be squashed (currently the penultimate commit fails because the RPC has changed but test hasn’t been updated).

  18. sipa commented at 11:36 pm on May 9, 2019: member
    ACK code changes, but I think the tests don’t cover much.
  19. in src/wallet/rpcdump.cpp:1169 in 4b630bb805 outdated
    1153@@ -1154,12 +1154,12 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
    1154 
    1155     const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
    1156 
    1157-    // Expand all descriptors to get public keys and scripts.
    1158-    // TODO: get private keys from descriptors too
    1159+    // Expand all descriptors to get public keys and scripts, and private keys if available.
    


    ariard commented at 1:05 am on May 10, 2019:
    nit: Maybe take opportunity to add importmulti as a supporting RPC in descriptors.md. You may also add a descriptor import example in importmulti
  20. ariard commented at 1:10 am on May 10, 2019: member

    Tested the following descriptors :

    • pkh(m/0h/0h/*h), range[1-3]
    • pkh(m/0h/0h/0h)
    • sh(wpkh(m/0h/0h/*h), range[1-3]
    • sh(wpkh(m/0h/0h/0h)
    • wpkh(m/0h/0h/0h)
    • wpkh(m/0h/0h/*h), range[1-3]
    • wsh(multi(2, m/0h/1h/0h, m/0h/1h/0h)))

    For each, I took master key from wallet dump, then created a blank wallet and importmulti descriptor then diff expanded private keys from blank wallet dump against master wallet dump ones.

    I had to turn require_checksum as false in Parse due to not straighforward way to calculate checksum for descriptors with private keys (and it’s not really clear from descriptor.cpp if bip32 encoded xpub or xpriv need to be also in form SCRIPT#CHECKSUM), maybe add checksum boolean in importmulti args ?

    Also while testing wsh(multi(2, …, …)) I got descriptor parsed as p2sh-segwit address instead of a bech32 one, is this normal (with addresstype=p2sh-segwit) ?

  21. sipa commented at 1:14 am on May 10, 2019: member

    Also while testing wsh(multi(2, …, …)) I got descriptor parsed as p2sh-segwit address instead of a bech32 one, is this normal (with addresstype=p2sh-segwit) ?

    I don’t know what you mean by this. What RPC/function calls did you make, and what did you observe?

  22. ariard commented at 2:49 am on May 10, 2019: member

    I did :

    bitcoin-cli importmulti '[{ "desc" : "wsh(multi(2,tprv8ZgxMBicQKsPebkWkTSP4F4mfbaSsriLFNWkJGKJxmq881mR8aEpCe8wH1V5ZGSWAj3H15TCfMWKdhG136ysuRfYRq5YuLDNYLxvPsaqzxf/0h/1h/0h, tprv8ZgxMBicQKsPebkWkTSP4F4mfbaSsriLFNWkJGKJxmq881mR8aEpCe8wH1V5ZGSWAj3H15TCfMWKdhG136ysuRfYRq5YuLDNYLxvPsaqzxf/0h/1h/1h))", "timestamp" : 0 }]'
    

    Got "success" : true Then, with address from wallet dump

    bitcoin-cli getaddressinfo 2N35sQ7r7C2nZT7r3qiZHMYPB8b2PKaNczv 
    

    Result :
    { “address”: “2N35sQ7r7C2nZT7r3qiZHMYPB8b2PKaNczv”, “scriptPubKey”: “a9146bec53ca4b8b49e6581aec6d741962595dc6954487”, “ismine”: true, “solvable”: true, “desc”: “sh(multi(2,[f330b5bd]02d1c3cd9ee8feede98f825120b806c515ac334358363678b9f4cde840ba4f2b83,[84f1c397]03e4382dd7a9cb5c77929504ed8c8dfd3527fa044c766a57c0001bfb222ab1ecad))#e34pekd5”, “iswatchonly”: false, “isscript”: true, “iswitness”: false, “script”: “multisig”, “hex”: “522102d1c3cd9ee8feede98f825120b806c515ac334358363678b9f4cde840ba4f2b832103e4382dd7a9cb5c77929504ed8c8dfd3527fa044c766a57c0001bfb222ab1ecad52ae”, “sigsrequired”: 2, “pubkeys”: [ “02d1c3cd9ee8feede98f825120b806c515ac334358363678b9f4cde840ba4f2b83”, “03e4382dd7a9cb5c77929504ed8c8dfd3527fa044c766a57c0001bfb222ab1ecad” ], “ischange”: true, “labels”: [ ] }

  23. sipa commented at 3:02 am on May 10, 2019: member

    That’s not surprising, but why are you calling getaddressinfo on a P2SH address rather than a P2WSH one? Is that the address you get from deriveaddresses?

    EDIT: Oh, because that’s the address you got from the dump? Don’t rely on those, it’s very hard for the current logic to guess what addresses you actually wanted imported (it inevitably imports other versions of the same policy along with it).

  24. ariard commented at 12:40 pm on May 10, 2019: member

    Yes that’s it I was relying on address from the dump, it’s fine with deriveaddresses I got a bech32 one

    So tested ACK e00c3dd minor the maybe-add-a-no-checksum option arg in RPC

  25. Sjors commented at 2:28 pm on May 10, 2019: member

    I’d like to see more tests. I tried a similar example as @ariard, but having some difficulty:

    0src/bitcoin-cli getdescriptorinfo "wsh(multi(2,tprv8ZgxMBicQKsPebkWkTSP4F4mfbaSsriLFNWkJGKJxmq881mR8aEpCe8wH1V5ZGSWAj3H15TCfMWKdhG136ysuRfYRq5YuLDNYLxvPsaqzxf,tprv8ZgxMBicQKsPebkWkTSP4F4mfbaSsriLFNWkJGKJxmq881mR8aEpCe8wH1V5ZGSWAj3H15TCfMWKdhG136ysuRfYRq5YuLDNYLxvPsaqzxf))"
    1{
    2  "descriptor": "wsh(multi(2,tpubD6NzVbkrYhZ4Y4nJe76yTeitEd6P3BuEpg7XanMcP3dWxW2Bky4QP8koTBc69APLZLNHRiJwnTqzumsjpUzDzBKpgVbQmFExramEMJRDyBA,tpubD6NzVbkrYhZ4Y4nJe76yTeitEd6P3BuEpg7XanMcP3dWxW2Bky4QP8koTBc69APLZLNHRiJwnTqzumsjpUzDzBKpgVbQmFExramEMJRDyBA))#458rdgch",
    3  "isrange": false,
    4  "issolvable": true,
    5  "hasprivatekeys": true
    6}
    

    I can derive addresses using the public key version with checksum returned above:

    0src/bitcoin-cli deriveaddresses "wsh(multi(2,tpubD6NzVbkrYhZ4Y4nJe76yTeitEd6P3BuEpg7XanMcP3dWxW2Bky4QP8koTBc69APLZLNHRiJwnTqzumsjpUzDzBKpgVbQmFExramEMJRDyBA,tpubD6NzVbkrYhZ4Y4nJe76yTeitEd6P3BuEpg7XanMcP3dWxW2Bky4QP8koTBc69APLZLNHRiJwnTqzumsjpUzDzBKpgVbQmFExramEMJRDyBA))#458rdgch"
    1[
    2  "tb1qxhuy9m6uczvc5l5m4dyx8kvplkf0hfwru8cd82yqrc74g6e8rlmsug2jdx"
    3]
    

    But I can’t import it (in a blank wallet):

    0src/bitcoin-cli importmulti  '[{ "desc" : "wsh(multi(2,tprv8ZgxMBicQKsPebkWkTSP4F4mfbaSsriLFNWkJGKJxmq881mR8aEpCe8wH1V5ZGSWAj3H15TCfMWKdhG136ysuRfYRq5YuLDNYLxvPsaqzxf,tprv8ZgxMBicQKsPebkWkTSP4F4mfbaSsriLFNWkJGKJxmq881mR8aEpCe8wH1V5ZGSWAj3H15TCfMWKdhG136ysuRfYRq5YuLDNYLxvPsaqzxf))", "timestamp" : 0, "keypool": false }]'
    1[
    2  {
    3    "success": false,
    4    "error": {
    5      "code": -5,
    6      "message": "Descriptor is invalid"
    7    }
    8  }
    9]
    

    My guess is that it needs a checksum. See related discussion in #15740.

  26. sipa commented at 5:29 pm on May 10, 2019: member
    @Sjors Yes, importmulti requires descriptors with a checksum. See #15986.
  27. Sjors commented at 12:14 pm on May 12, 2019: member
    @sipa in particular, it requires the checksum based on the private key (added in #15986), not the checksum based on public key currently returned by getdescriptorinfo.
  28. promag commented at 6:05 pm on May 13, 2019: member
    @meshcollider please update OP as the base PR is already merged.
  29. meshcollider commented at 8:11 am on June 6, 2019: contributor
    Fixed the test and amended the comment pointed out by John
  30. in src/script/descriptor.cpp:323 in ca039db077 outdated
    322@@ -312,6 +323,18 @@ class BIP32PubkeyProvider final : public PubkeyProvider
    323         }
    324         return true;
    325     }
    326+    bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
    327+    {
    328+        CExtKey extkey;
    


    ryanofsky commented at 9:14 am on June 6, 2019:

    In commit “Add private key derivation functions to descriptors” (ca039db0776b1626ce4d220a0da5ff985d93985d)

    This is duplicating code already in GetPubKey above. Since this has to exist separately, it’d be good to change GetPubKey to call this function. It’d simplify GetPubKey and remove a chunk of duplicate code.

  31. ryanofsky commented at 9:43 am on June 6, 2019: member
    utACK b6dc630019dce4a0852c5c02fd6154c773ca5ebe. Previous reviews asked for more tests: #15024 (comment) and #15024 (comment), but it’s unclear what other kinds of tests would be helpful here.
  32. Add private key derivation functions to descriptors a4d1bd1a29
  33. Import private keys from descriptor with importmulti if provided 81a884bbd0
  34. meshcollider commented at 10:05 am on June 6, 2019: contributor
    @ryanofsky done, thanks!
  35. jnewbery commented at 10:35 am on June 6, 2019: member

    it’s unclear what other kinds of tests would be helpful here.

    The added test in this PR checks that importing sh(wpkh(xpriv/path)) makes the sh(wpkh(pubkey)) spendable for paths /0 and /1.

    Suggested additional tests:

    • That the subscript wpkh(pubkey) is spendable for paths /0 and /1 (tests the if (m_script_arg) path in ExpandPrivate()).
    • Import a descriptor with a WIF private key and check that the address is spendable (see https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md#reference for all key types supported by descriptors). (tests GetPrivKey() in ConstPubkeyProvider).
    • import a ranged descriptor with key origin information and check that the paths are spendable. Tests the for (auto entry : m_path) path in BIP32PubkeyProvider:: GetPrivKey().
  36. meshcollider requested review from achow101 on Jun 6, 2019
  37. meshcollider commented at 8:11 am on June 7, 2019: contributor
    Thanks for the suggestions @jnewbery, I’ve added the first two tests you suggested, but I’m unsure about the third. The first test already tests the BIP32PubkeyProvider derivation. Did you mean OriginPubkeyProvider? OriginPubkeyProvider just calls the BIP32 derivation though so I don’t think it needs a separate test
  38. Extend importmulti descriptor tests 2857bc4a64
  39. achow101 approved
  40. achow101 commented at 1:28 pm on June 7, 2019: member

    Tested ACK 2857bc4a64cc8dc7914bc984ac878397ac64292d

    Checked it compiled and passed tests. Also tested an import manually and checked that private keys are actually imported and can be exported with dumpprivkey. I would feel better if you had a test that did dumpprivkey or signmessage on the imported keys to be sure that the private keys exist.

  41. Add test for dumping the private key imported from descriptor 53b7de629d
  42. meshcollider commented at 1:36 pm on June 7, 2019: contributor
    @achow101 done
  43. achow101 commented at 1:37 pm on June 7, 2019: member

    ACK 53b7de629d3d9281dc6f8fa10e32c4cdec59c140

    Squash yo commits

  44. laanwj merged this on Jun 7, 2019
  45. laanwj closed this on Jun 7, 2019

  46. laanwj referenced this in commit 5d2ccf0ce9 on Jun 7, 2019
  47. meshcollider deleted the branch on Jun 7, 2019
  48. meshcollider removed this from the "Blockers" column in a project

  49. sidhujag referenced this in commit ad3fa4d5f4 on Jun 7, 2019
  50. deadalnix referenced this in commit 5815006534 on Nov 12, 2020
  51. DrahtBot locked this on Feb 15, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-22 03:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me