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
descriptors: Add a KEY expression representing a list of individual keys #26626
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
3cc02e4
to
7b504c9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So when expanding the descriptor containing func({A1,A2,A3},{B1,B2,B3})
at index 0
, 1
and 2
you'd get func(A1,B1)
, func(A2,B2)
and func(A3,B3)
. This can be expressed more concisely with a ranged xpub: func(A/*,B/*)
will give you a similar behaviour when expanded at index 0
, 1
and 2
. The only differences in behaviour being:
- This new syntax may be used with non-xpub keys
- This new syntax would fail to be expanded at any index >2
I assume that 2) is never desirable for descriptors, the limitation can always be enforced by the application ("never expand at an index superior to 2"). What is a common situation where you wouldn't be able to swap a "raw" key for an xpub?
concept ACK from me. I understand that the use is primarily in expressing pre-descriptor wallet patterns in descriptors. This concept feels like it could be used (or is related to) multipath descriptors, like if it were possible to use multiple wildcards, one for "set indexing" and the other for "range indexing". |
It does, but they're slightly different. I had a discussion with @sipa about that and we concluded that it wasn't useful at least for the purpose that this PR targets. Multipath descriptors expands into multiple descriptors. In #22838, this is implemented as |
That makes sense. We may want to revisit, from a conceptual point of view (vs the concrete needs of the Core wallet), later on. |
This seems a rather ad-hoc extension that doesn't match the usage of descriptors outside of bitcoin-core. Perhaps core could consider having some "extensions" to descriptors for these special use cases, while keeping the standardized descriptor language as lean as possible? |
@bigspider I think that's how we've been thinking about descriptors in the first place, a base language, with a set of extensions. There is no requirement that everyone implements all of them, as long as supported keywords have the same meaning across implementations. For example, new software might not care about pre-segwit Things like BIP384 ( |
Would this scale well to migrating large pre-HD wallets? |
I believe it will. It's the intended use of this syntax. |
7b504c9
to
70189d9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK, mostly happy with the code.
m_pubkeys.begin(), m_pubkeys.end(), | ||
0, | ||
[](const size_t acc, const std::unique_ptr<PubkeyProvider>& pubkey) { | ||
return std::max(acc, pubkey->GetSize()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8f4ee67: Is this going to cause problems if compressed and uncompressed keys end up in a single descriptor? Maybe we should enforce keeping those separate (including when migrating from legacy)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are mixed compressed and uncompressed keys in the list, then this will always use the uncompressed size, which may result in overestimating the size. I think this will only be used by miniscript though.
src/test/descriptor_tests.cpp
Outdated
@@ -450,6 +458,11 @@ BOOST_AUTO_TEST_CASE(descriptor_test) | |||
CheckUnparsable("sh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Can only have sh() at top level"); // Cannot embed P2SH inside P2SH | |||
CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Can only have wsh() at top level or inside sh()"); // Cannot embed P2WSH inside P2WSH | |||
|
|||
// Using list of keys expression | |||
Check("pkh({L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr})", "pkh({03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600})", "pkh({03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600})", RANGE | DERIVE_HARDENED | SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}, {"76a914a48e80ddd68b1a9ac7b3bdf028b00a1dbf5e450388ac"}}, OutputType::LEGACY, {{}, {}}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
70189d9 Why are RANGE | DERIVE_HARDENED
set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RANGE
to do the range tests, DERIVE_HARDENED
to avoid the xpub cache checks.
e8a4225
to
78c2e41
Compare
78c2e41
to
36e3b3c
Compare
36e3b3c
to
e831fd9
Compare
Needs rebase if still relevant |
Maybe mark as draft for as long as CI is red? |
ListPubkeyProvider is a ranged PubkeyProvider that returns individual public keys that it was constructed with.
A new KEY expression is introduced which takes a list of non-ranged public keys. This expression itself is ranged and can provide the key at a particular index. It can take any KEY as long as it is not ranged.
e831fd9
to
1488496
Compare
There are some cases where several individual keys are being used in the same descriptor functions. It can be inefficient to treat them all as separate descriptors. This PR adds a new syntax where multiple keys are provided in an array. This list of keys is a ranged KEY expression (like xpubs) with position in the range being explicitly provided. As the list of descriptors is a range, only non-ranged key expressions are allowed. This is to avoid issues with expanding nested ranges. xpubs can be used as key expressions so long as they do not specify a
*
.The descriptors will be of the form
func({KEY, KEY, KEY,...})
.