Open darosior opened 2 years ago
Is there any plan to implement to implement Output Script Descriptors (with Miniscript) for Trezor?
This is something we are looking into, but it's not on an immediate roadmap since there are so many other things we need to do first.
Also read the thread in here to learn more about the challenges: https://github.com/trezor/trezor-firmware/issues/416#issuecomment-1119350991
I am marking this as block until this proposal is discussed and widely accepted by the Bitcoin community: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2022-May/020423.html
Thanks. To address https://github.com/trezor/trezor-firmware/issues/416#issuecomment-1119350991. Miniscript can be compiled from, and lifted to, a humanly-readable policy language. The compiler obviously has nothing to do on the hardware signing device, but the policy lifter definitely makes sense.
The policy language describes the semantics of the contract in a humanly-readable way, short of all the types annotations of Miniscript (which is then more engineer-readable). Further, the Rust-Miniscript library introduces a difference between the compiled policy (Concrete Policy), and the lifted policy (Semantic policy). Using this wording, here is an example for a contract where Alice pays Bob if he shows knowledge of a secret, or can claim her coins back after a timelock if he didn't (assuming it is 9 times more likely that Bob does know the secret):
or(1@and(pk(Alice),older(1024)),9@and(pk(Bob),hash160(H)))
andor(pk(Bob),hash160(H),and_v(v:pkh(Alice),older(1024)))
.
Which corresponds to the Script
<Bob> OP_CHECKSIG OP_NOTIF
OP_DUP OP_HASH160 <HASH160(Alice)> OP_EQUALVERIFY OP_CHECKSIGVERIFY <0004>
OP_CHECKSEQUENCEVERIFY
OP_ELSE
OP_SIZE <20> OP_EQUALVERIFY OP_HASH160 <h> OP_EQUAL
OP_ENDIF
or(
and(pkh(Alice), older(1024)),
and(pkh(Bob), hash160(H))
)
This gives a reasonable way of doing user verification of the Script. For instance, let's say that Alice's device has the following aliases for extended keys:
hot_key = xpub...
cold_key xpub...
cold_key_backup xpub...
bob_key = xpub...
It is then shown a transaction with one input and two outputs along with the witness Script for each of the 3 outputs scriptPubKey involved in the tx (by using a PSBT or anything). Its signing device can parse each of the Script and show her:
This transaction is spending X sats from your "cold walet" descriptor with policy
thresh(2, hot_key, cold_key, cold_key_backup)
, paying Y sats to the contract with policyor(and(pkh(Alice), older(1024)), and(pkh(Bob), hash160(H)))
and Z sats back to your aforementioned "cold wallet" descriptor.
Of course that's handwavy but the point is that the Miniscript framework allows for this "lifting" to a reasonably-human-readable description of the spending conditions of a contract.
I have yet to re-read and give more thoughts to @bigspider's mail who obviously has much more context than me on applying these cute ideas to the real world of embedded systems. :)
Thanks @darosior for a shoutout, it's great to discuss these things also from the point of view of the UX, that my proposal is still only partially addressing.
In the model that we are using (register and verify the policy once, then trust the previous registration), unfortunately I think showing the "lifted policy" might not be safe for some applications: for complicated scripts (especially taptrees), the attacker could find a different valid miniscript that compiles to the same policy. Even if for a single miniscript expression you might find ways of limiting the number of "source miniscripts", this still blows for taptrees: a tree with n leafs, where each leaf has 2 valid miniscript for the same policy, still has 2^n candidates for the whole tree; that would make the registration flow gameable (attack: malware changes the miniscript without altering the semantic policy, then ransoms you once you make a transaction, as your change is locked in one of those 2^n policy). Probably hard to pull off in practice, and I think there are practical ways to address this concern for most real-world scripts - but still concerning until more thoughts can be put into the right mitigations.
Therefore, in the initial version I plan to show the wallet policy as is. Showing aliases for registered xpubs is indeed another nice feature for a future version.
For complex taptrees, I think lifted semantic policies could be very useful when signing using a specific leaf, to show "what" the leaf allows. I think there are scenarios where a user only wants to sign using the key that controls a specific condition, rather than all the possible ones (which is likely the default behavior in many cases, e.g. when you sign a multisig without knowing all the cosigners in advance). But this only matters for certain advanced use-cases.
I think it's unavoidable (especially on taptrees) that certain common miniscript patterns should be "whitelisted" so that the HW can recognize them, and users can safely skip the wallet policy validation - focusing instead on checking the cosigners.
In the model that we are using (register and verify the policy once, then trust the previous registration), unfortunately I think showing the "lifted policy" might not be safe for some applications
The attack you are describing is for change output(s) right? In this case i agree that a device may not consider as change what does not pay to one of the descriptors it was registered with (it's why in my example it's referring to a pre-registered descriptor with label "cold wallet"). Showing the policy is more interesting in order to check from the device where your funds are going. Today people check an address on their signing device that is shown on the laptop, which makes no sense since if the laptop is compromised it'll send a rogue address to the HW and display the same one on the screen. Miniscript makes it so the Script may be transmitted to the device and its semantics analyzed. This makes the human verification much stronger. Although in the end you still need a source of trust for the public keys, hmm..
I think there are scenarious where a user only wants to sign using the key that controls a specific condition, rather than all the possible ones
I think i agree with you, but i know some others don't. Argument being "signing a transaction is binary, either you authorize it or you don't, the spending path used does not matter". I think similar is the question: "is there a usecase for displaying the policy of the Script your are spending from in order to analyze it?".
EDIT: here is an example where it is important to known which leaf you are signing:
tr(NUMS,{
multi(2,Alice,Bob),
and(older(52560),and(Notary,Alice)),
and(older(52560),and(Notary,Bob))
})
Here the notary may want to sign only for Bob's or Alice's key. That's how it works anyways, but that's a counter-example to the above argument.
The attack you are describing is for change output(s) right? In this case i agree that a device may not consider as change what does not pay to one of the descriptors it was registered with (it's why in my example it's referring to a pre-registered descriptor with label "cold wallet").
With the wallet registration flow (where the "policy" is only shown during wallet registration), the policy registration is indeed the only point of attack. If you convince the user tho register a miniscript A while they thing they are registering miniscript B, they are going to not have a backup for A on their client side. Therefore, the attacker is the only person who knows A and might be able to ransom the user (even before they try to spend funds − just wait for them to receive to A!).
Showing the policy is more interesting in order to check from the device where your funds are going. Today people check an address on their signing device that is shown on the laptop, which makes no sense since if the laptop is compromised it'll send a rogue address to the HW and display the same one on the screen. Miniscript makes it so the Script may be transmitted to the device and its semantics analyzed. This makes the human verification much stronger. Although in the end you still need a source of trust for the public keys, hmm..
Checking the (external) outputs is a whole different problem. I agree that currently the outputs are basically blindly trusted (exchange said "send to X", only check that "X" matches on the device ==> malware just needs to replace the address shown on the website!).
For simple usecases, that could be solved more easily with signatures, though: e.g. Kraken signs addresses (better: the scriptPubKey
), and the pubkey is registered on the device.
Indeed, it's a good point that the wallet registration flow could be used for repeated receivers, and maybe miniscript could help with unknown scripts where the identities of the participants are known (skipping registration altogether).
Interesting ideas for the future!
I think i agree with you, but i know some others don't. Argument being "signing a transaction is binary, either you authorize it or you don't, the spending path used does not matter". I think similar is the question: "is there a usecase for displaying the policy of the Script your are spending from in order to analyze it?".
I'd say that you certainly want to know where you are spending from! The same hardware wallets hold the keys for arbitrarily many accounts (including accounts that involve other participants), it would certainly be bad if you could be tricked into spending from the wrong place.
It is not so much a concern for standard legacy/segwit/taproot accounts, so I think it makes sense to keep things simple there and hide the account origin.
EDIT: here is an example where it is important to known which leaf you are signing:
tr(NUMS,{ multi(2,Alice,Bob), and(older(52560),and(Notary,Alice)), and(older(52560),and(Notary,Bob)) })
Here the notary may want to sign only for Bob's or Alice's key. That's how it works anyways, but that's a counter-example to the above argument.
Not sure the notary is a good example, as he is likely the last signer, not the first.
A better example imho is delegation: or(and(Alice1,Bob),and(Alice2,Charles))
. Here Alice can presign, but chose who to delegate the final decision on the transaction to. Don't ask me why a business might want to do that − probably Alice knows.
A lot has happened in the last year since this issue was opened.
Would you consider this issue no longer blocked?
Output Script Descriptor are a sane standard for wallet backups. They express all the information necessary to locate owned (or partially owned) outputs in an engineer-readable format, avoiding the pitfalls of previously used implicit hacks to recover only from a mnemonic.
Miniscript is an extension to Output Script Descriptors, which defines a language to reason about a subset of Script. Miniscript allows to:
Miniscript also guarantees consensus soundness (unless the conditions of the Miniscript are met, no witness can be created for the Script) and standardness completeness (for any Miniscript that was analysed as sane, a witness can be constructed in the bounds of the consensus and standardness rules).
Output Script Descriptors along with Miniscript are particularly appealing to signing devices, as it permits the user of this device to safely take part in more complex contracts. It also gives a framework to add support for new Scripts, instead of relying on hard-coded Script templates and footgun-y backup solutions.
Output descriptors have been in Bitcoin Core for a while now, and Miniscript was merged into Bitcoin Core mainline this year. The Specter signing device added support for Output descriptors and Miniscript more than a year ago. Ledger added support for (a subset of) output descriptors last year and is currently working toward Miniscript support. Is there any plan to implement to implement Output Script Descriptors (with Miniscript) for Trezor?