bips
bips copied to clipboard
BIP Draft: unspendable() Descriptor Key Expression
This is a BIP Draft for an unspendable key expression that can be used as the taproot internal key. The expression creates a provably unspendable key that is deterministically created with only the descriptor. This allows all participants to verify that the keypath is unspendable, while also hiding that fact from outside observers.
Previous discussion on delving https://delvingbitcoin.org/t/unspendable-keys-in-descriptors/304.
Mailing list post: https://groups.google.com/g/bitcoindev/c/xWxy8DtW6m8
cc @benma i remember your comment on BIP-0352, can't some signing devices have some issue also here about sorting keys with complexes taptrees?
Note: @darosior had implemented a similar approach in Liana, the main difference is there is no duplicate removing & sorting. Some signing devices already implemented this approach btw:
- Ledger @bigspider
- Bitbox @benma
- Coldcard @scgbckbone
- Krux @odudex
cc @NicolasDorier i think i already shared w/ you about this part of the Liana code
edit: I personally have no strong opinion, just wanted to ping interested ppl
Note: @darosior had implemented a similar approach in Liana, the main difference is there is no duplicate removing & sorting.
Unfortunately Liana's approach introduces malleability when using sortedmulti since the keys aren't sorted, and is incompatible with BIP388 wallet policies since the public keys aren't deduplicated. This is mentioned in the rationale.
and is incompatible with BIP388 wallet policies since the public keys aren't deduplicated. This is mentioned in the rationale.
I'm not sure to understand this point as iirc at least 2 of mentioned signing devices are using BIP388 wallet policies in their interface w/ liana (i should overlook something)
@pythcoiner the standard proposed in this BIP should work for any general TREE expression. Using Liana's scheme will work for any wallet policy that does not have duplicate public keys. However, if choosing a policy that contains duplicate public keys in the template, it will not be compatible.
For instance, the descriptor tr(unspendable(),{key1,key1}) translates to wallet policy tr(_,{@0,@0}). Liana's scheme using the descriptor will produce an unspendable key of SHA256(key1 + key1), whereas the wallet policy will have a key vector of just key1 so will produce an unspendable key of SHA256(key1).
Thanks for working on standardizing this. Let me state in advance that i think this is already an improvement over the current situation with a non-standardized scheme used by multiple implementations but not entirely usable by others. Therefore feel free to discard my suggestions.
That said it's really unfortunate we'd have to break the compatibility with the scheme already deployed everywhere just to accommodate sortedmulti, which is in my opinion a descriptor fragment that runs contrary to the spirit of the descriptor language in the first place.
My feeling is that this really applies to a layer on top of descriptor, as hinted by all the restrictions using this fragment imposes on the global descriptor. It might be interesting to design the fragment such as it can be used by both a lower level logic of descriptors and a higher level logic of wallet policy. For instance the unspendable fragment could contain the chaincode as its parameter such as a "dumb" descriptor-only parser would be able to generating the script. And a smarter parser could check that this chaincode does correspond to a hash of the wallet policy's xpubs.
Unfortunately Liana's approach [...] is incompatible with BIP388 wallet policies since the public keys aren't deduplicated.
Obviously not true. I think what you meant, which is a more reasonable claim, is that it does not have the property that you can compute the unspendable xpub's chaincode only from the BIP388 list of keys. That is, simply what Salvatore points out here.
I don't remember if i had a particular rationale for not de-duplicating at the time. One advantage it prevents internal key reuse for a larger set of descriptors. It may be preferable to get the property that you can compute the chaincode only from the BIP388 list of keys and not push complexity on signing devices (which are usually more constrained), but stating the already deployed scheme is completely incompatible with wallet policy is disingenuous.
@pythcoiner for what it's worth Ledger only check that the key part of the internal xpub is the BIP341 NUMS, it does not derive the chaincode. But you are right that you can use the already deployed scheme with BIP388 wallet policies. And Andrew and Salvatore are right to point out when using wallet policies it's desirable to be able to derive the chaincode only from the BIP388 list of xpubs, without the wallet policy template.
disingenuous
What I meant was incompatible with just the key vector and not the template. Apologies for the way that came across.
Using Liana's scheme will work for any wallet policy that does not have duplicate public keys. However, if choosing a policy that contains duplicate public keys in the template, it will not be compatible.
Note: I think using Liana's scheme will work for any descriptor generated by current miniscript "compilers" implementations, afaik both the c++ & rust implem fails if the policy contains duplicates keys.
You can have duplicate xpubs without having duplicate keys in the scripts. An (uninteresting) example is tr(_,{pk(xpubA/0/*),pk(xpubA/1/*)}).
What i mainly wondering is: signing devices that have already followed Liana's approach will now have to mantain both.
The only way i can imagine for having this standard being an "upgrade" of actual implementations is by:
- ruling out a policy that have duplicates keys (is it any valuable reason to have duplicate keys?)
- sorting only the keys contained in
sortedmulti()fragments
note: I don't say i'd push in this direction for the standard, but i'm never comfortable with breaking "user space"
when using wallet policies it's desirable to be able to derive the chaincode only from the BIP388 list of xpubs, without the wallet policy template.
what about the case my mom policy is:
or(and(MOM,SON),and(DAD,older(tl)))
my father one is:
or(and(DAD,SON),and(MOM,older(tl)))
iiuc w/ the actual proposal, they will have the same unspendable key?
they will have the same unspendable key
Having the same key in some scenarios is acceptable. What is unacceptable is a descriptor or policy that produces the same merkle root but produces a different unspendable key.
Having the same key in some scenarios is acceptable.
I think it's still better to avoid if we can
What is unacceptable is a descriptor or policy that produces the same merkle root but produces a different unspendable key.
agree, but if it's an issue only w/ sortedmulti(), maybe only sorting keys in sortedmulti() is a viable solution? with the advantage of not creating issue w/ any other schemes
Thank you @pythcoiner and @darosior for the valuable feedback.
but if it's an issue only w/
sortedmulti()
Looking back at the discussion, it is also an issue with using only the wallet policy key vector without the template to construct the chaincode, as pointed out by Salvatore here.
the
unspendablefragment could contain the chaincode as its parameter such as a "dumb" descriptor-only parser would be able to generating the script. And a smarter parser could check that this chaincode does correspond to a hash of the wallet policy's xpubs.
What would be the benefit of such a fragment for the "dumb" parser, vs just using the computed unspendable xpub with the NUMS public key as currently done?
// - The given descriptor does not contain a Taptree with at least a key in each leaf.
I see this constraint in the Liana code as well. So we can't do tr(unspendable, {pk(key),sha256(h)})?
I see this constraint in the Liana code as well. So we can't do
tr(unspendable, {pk(key),sha256(h)})?
This is a Liana-specific constraint which i don't think should apply to this standard. (We have a bunch of constraints on the descriptors accepted in Liana to make it easier to reason about what action can be performed by the user.)
Note also the sha256(h) leaf is a Miniscript, which here would be considered malleable as it does not have a key check required on all spending paths. For instance the Bitcoin Core wallet would refuse to import it as malleable Miniscript descriptors are considered unsafe (or not "sane") to use.
Some signing devices already implemented this approach btw
we haven't implemented any of this tbh, COLDCARD only allows explicit unspendable keys in descriptors https://github.com/Coldcard/firmware/blob/new_edge/docs/taproot.md#provably-unspendable-internal-key
- or you provide xpub & we detect pubkey =
Hfrom bip341 - or
unspend(chain_code)from https://gist.github.com/sipa/06c5c844df155d4e5044c2c8cac9c05e#unspendable-keys
For instance the unspendable fragment could contain the chaincode as its parameter such as a "dumb" descriptor-only parser would be able to generating the script. And a smarter parser could check that this chaincode does correspond to a hash of the wallet policy's xpubs.
If this proposal intends to operate on descriptor level - it needs to have chain code parameter as mentioned above so that signing devices do not need to care
What would be the benefit of such a fragment for the "dumb" parser, vs just using the computed unspendable xpub with the NUMS public key as currently done?
Compatibility with all descriptors, not only the small subset that corresponds to the restrictions here.
it needs to have chain code parameter as mentioned above so that signing devices do not need to care
Compatibility with all descriptors, not only the small subset that corresponds to the restrictions here.
The point of this proposal is so that signing devices do not need to care about the chaincode at all and infer it from the rest of the descriptor. I don't see much benefit for an unspendable(r) expression. Parsers in both Ledger and Coldcard already parse the xpub and determine it is unspendable by inspecting the public key, and show the user that it is indeed unspendable. However, the xpub still needs to be backed up because of the chaincode. An unspendable(r) does not change that.
Let me know if there are benefits I haven't considered, but it seems the only benefit to this type of expression is so that it can be human readable that it is unspendable. But, as soon as you put an xpub in a parser today it can tell you whether it is unspendable or not, and this new expression will require parsers to upgrade to be able to parse it.
An unspendable(r) expression will fail to parse for unupgraded parsers, while the xpub is backwards compatible and the parser can upgrade to detect the public key at their convenience. If we are going to have parsers upgrade for a new expression anyways, we might as well remove the need for the chaincode.
For what it's worth, BIPS 380, 386, and 390 are Informational.
That they are labeled informational is a consequence of the quirky interpretation of BIP 2 in the past. All three of those BIPs have specifications and reference implementations and are clearly defining exchange formats intended to be interoperable across various implementations, so they should all be Standards Track BIPs.
Hey @andrewtoth, I was just skimming this PR. There seems to be some unresolved review here. Could you take a look and provide an update what the state of this PR is? Is this waiting for updates from you or for editor review?
Hi @andrewtoth, it looks like your last comment here was on Jan 20, six months ago. Are you still working on this BIP draft?
Sorry for the delayed response. I am no longer working on this BIP draft.