Hi all, TL;DR: It is easy to convert from wallet policy to descriptors and back; imho aliases are better left out of descriptors in real world usage; some more examples given.
I received some very useful feedback on the wallet policy proposal (in this list and outside); that also led me to realize that my initial post lacked some clarity and more practical examples. This post wants to: - clarify that extracting descriptors from the wallet policy is trivial; - argue that figuring out the wallet policy (template and list of keys information) from the descriptor is reasonably easy − automatable for sane descriptors currently in use, and much more general ones as well; - give an idea of what the information shown on a hardware wallet screen would look like (emphasizing compactness); - explain my point of view on "descriptors with aliases". This gist demoes conversions from wallet policies to descriptors, and back: https://gist.github.com/bigspider/10df51401be3aa6120217c03c2836ffa Note that I would expect/hope software wallets to prefer working directly with wallet policies − but it might help to have automated tools for the conversion, for interoperability with tools that do not adopt wallet policies. (All the following examples use the `/**` notation as a shortcut for `/<0,1>/*`; this notation might be dropped without consequences on the rest of the proposal.) All the keys in the example I'm proposing are followed by /**. It is unclear to me if hardware wallets should allow *registration* of wallet policies with static keys (that is, without any range operator), as that would incentivize key reuse. The specs still support it as there might be other use cases. The policy for miniscript examples not using taproot was generated with the online compiler: https://bitcoin.sipa.be/miniscript. Many examples are also borrowed from there. (To the best of my knowledge, there is no publicly released compiler for miniscript on taproot, yet) Note on aliases: it has been pointed out that many miniscript implementations internally use aliases to refer to the keys. In my opinion, aliases: - should be external to the descriptor language, as they bear no significance for the actual script(s) that the descriptor can produce - fail to distinguish which part of the KEY expression is part of the "wallet description", and which part is not By clearly separating the key information in the vector (typically, an xpub with key origin information) from the key placeholder expression (which typically will have the `/**` or `/<0,1>/*` derivation step), wallet policies semantically represent keys in a way that should be convenient to both software wallets and hardware signers. Associating recognizable names to the xpubs (and registering them on the device) is a good idea for future developments and can greatly improve the UX, both during wallet setup, or in recognizing outputs for repeated payments; it should be easy to build this feature on top of wallet policies. == Examples == All the examples show: - Miniscript policy: semantic spending rules, and optimization hints (can be compiled to miniscript automatically) - Miniscript: the actual miniscript descriptor, compiles 1-to-1 to Bitcoin Script - Wallet template: the "wallet descriptor template" - Vector of keys: the list of key information (with key origin information) Together, the wallet template and the vector of keys are the complet "wallet policy". === Example 1: Either of two keys (equally likely) === Miniscript policy: or(pk(key_0),pk(key_1)) Miniscript: wsh(or_b([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/<0;1>/*),s:pk([12345678/44'/0'/0']xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/<0;1>/*))) Descriptor template: wsh(or_b(pk(@0/**),s:pk(@1/**))) Vector of keys: [ "[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", "[12345678/44'/0'/0']xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB" ] In all the following examples, I will replace the xpubs with aliases in the miniscript for brevity, and omit the corresponding vector of keys in the wallet policy. Of course, in comparing the "information density" (especially for UX purposes), it is important to take the full descriptor into account. It is always to be assumed that the keys are xpubs, complete with key origin information if internal (that is, controlled by the software or hardware signer that the wallet policy is being with). === Example 2: Either of two keys, but one is more likely === Miniscript policy: or(99@pk(key_likely),pk(key_unlikely)) Miniscript: wsh(or_d(pk(key_likely),pkh(key_unlikely))) Descriptor template: wsh(or_d(pk(@0/**),pkh(@1/**))) Vector of keys: <omitted> === Example 3: A 3-of-3 that turns into a 2-of-3 after 90 days === Miniscript policy: thresh(3,pk(key_0),pk(key_1),pk(key_2),older(12960)) Miniscript: wsh(thresh(3,pk(key_0),s:pk(key_1),s:pk(key_2),sln:older(12960))) Descriptor template: wsh(thresh(3,pk(@0/**),s:pk(@1/**),s:pk(@2/**),sln:older(12960))))) Vector of keys: <omitted> === Example 4: The BOLT #3 received HTLC policy === Miniscript policy: andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(395e368b267d64945f30e4b71de1054f364c9473)),older(1008)),pk(key_revocation)) Miniscript: wsh(andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(395e368b267d64945f30e4b71de1054f364c9473)),older(1008)),pk(key_revocation))) Descriptor template: wsh(andor(pk(@0/**),or_i(and_v(v:pkh(@1/**),hash160(395e368b267d64945f30e4b71de1054f364c9473)),older(1008)),pk(@2/**))) Vector of keys: <omitted> === Example 5: Taproot complex script (2-of-2 with cold backup and timelocked inheritance) === The likely path is a 2-of-2 of a hot_key and a cosigner_key (2FA-like service). At any time, a cold_key can be used for signing, and after about a year, a separate timelocked_key becomes active (for example, to a notary for inheritance purposes). The timelock is reset every time UTXOs are spent. Miniscript policy: or(99@thresh(2,pk(hot_key),pk(cosigner_key)),1@or(99@pk (cold_key),1@and(pk(timelocked_key),older(52596)))) Miniscript: tr(cold_key,{and_v(v:pk(timelocked_key),older(52596)),multi_a(2,hot_key,cosigner_key)}) Descriptor template: tr(@0/**,{and_v(v:pk(@1/**),older(52596)),multi_a(2,@2/**,@3/**)}) Vector of keys: <omitted> === Example 6: Taproot complex script with MuSig2 === The same policy as above, but we assume that the hot wallet and the cosigner are able to engage in the MuSig2 protocol. This greatly exemplifies the practical advantage of MuSig2 with taproot in terms of both transaction cost and privacy. Miniscript policy: or(99@musig2(hot_key,cosigner_key),1@or(99@pk (cold_key),1@and(pk(timelocked_key),older(52596)))) Miniscript: tr(musig2(hot_key,cosigner_key),{and_v(v:pk(timelocked_key),older(52596)),pk(cold_key)}) Descriptor template: tr(musig2(@0,@1)/**,{and_v(v:pk(@2/**),older(52596)),pk(@3/**)}) Vector of keys: <omitted>. Note: the order of keys differs from the previous example. Salvatore Ingala
_______________________________________________ bitcoin-dev mailing list bitcoin-dev@lists.linuxfoundation.org https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev