bips icon indicating copy to clipboard operation
bips copied to clipboard

BIP Draft: unspendable() Descriptor Key Expression

Open andrewtoth opened this issue 10 months ago • 21 comments

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

andrewtoth avatar Jan 17 '25 14:01 andrewtoth

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?

pythcoiner avatar Jan 18 '25 14:01 pythcoiner

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

pythcoiner avatar Jan 18 '25 17:01 pythcoiner

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.

andrewtoth avatar Jan 18 '25 17:01 andrewtoth

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 avatar Jan 18 '25 18:01 pythcoiner

@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).

andrewtoth avatar Jan 18 '25 19:01 andrewtoth

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.

darosior avatar Jan 18 '25 19:01 darosior

@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.

darosior avatar Jan 18 '25 19:01 darosior

disingenuous

What I meant was incompatible with just the key vector and not the template. Apologies for the way that came across.

andrewtoth avatar Jan 18 '25 20:01 andrewtoth

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.

pythcoiner avatar Jan 19 '25 02:01 pythcoiner

You can have duplicate xpubs without having duplicate keys in the scripts. An (uninteresting) example is tr(_,{pk(xpubA/0/*),pk(xpubA/1/*)}).

darosior avatar Jan 19 '25 03:01 darosior

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"

pythcoiner avatar Jan 19 '25 03:01 pythcoiner

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?

pythcoiner avatar Jan 19 '25 03:01 pythcoiner

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.

andrewtoth avatar Jan 19 '25 04:01 andrewtoth

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

pythcoiner avatar Jan 19 '25 04:01 pythcoiner

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 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.

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)})?

andrewtoth avatar Jan 19 '25 19:01 andrewtoth

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.

darosior avatar Jan 19 '25 20:01 darosior

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 = H from 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

scgbckbone avatar Jan 20 '25 04:01 scgbckbone

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.

darosior avatar Jan 20 '25 17:01 darosior

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.

andrewtoth avatar Jan 20 '25 17:01 andrewtoth

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.

murchandamus avatar Feb 06 '25 20:02 murchandamus

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?

murchandamus avatar Jun 20 '25 22:06 murchandamus

Hi @andrewtoth, it looks like your last comment here was on Jan 20, six months ago. Are you still working on this BIP draft?

jonatack avatar Jul 23 '25 23:07 jonatack

Sorry for the delayed response. I am no longer working on this BIP draft.

andrewtoth avatar Sep 17 '25 16:09 andrewtoth