An upcoming version of the Ledger Nano Bitcoin application will have native support for a long-awaited feature: miniscript.
This brings the promise of unleashing a zoo of flexible solutions for self-custody that can make it easier and safer to hold your own keys. Remember: not your keys, not your coins! An exchange is like a bank, and keeping your coins in a bank defeats the whole purpose.
Hardware wallets are foundational infrastructure for self-custody. By supporting innovations right at the core, we want to make it accessible to the entire ecosystem.
But what is miniscript, and why are we working to support it?
Let’s start with some context.
Most transactions on the bitcoin blockchain are very simple: the owner of some coins provides a cryptographic signature that authorizes the transaction, and all that bitcoin nodes have to do is to verify that that signature is, indeed, valid.
A smaller number of transactions uses more complex conditions, like multisignature. A bitcoin locked using an m-of-n multisignature wallet requires that at least m of the n possible keys approve a transaction, by signing it.
Some examples are as follows:
- A 1-of-2 wallet could be a joint account where either of the two partners can approve spending; a 2-of-2 could be their savings account, where both must approve spending from it.
- A company with 3 directors could use a 2-of-3 multisignature wallet to allow any 2 of them to authorize a spend from the company’s treasury (especially useful for a company that has already moved to a bitcoin standard).
- A single individual could use a 2-of-3 wallet to avoid a single point of failure for its seed backup, by keeping one seed at home, one seed in the office, and one seed in a bank safe. The loss of any single key will still allow recovery, and it will be a lot harder for an intruder to coerce him to send the coins − as that is not possible from a single location.
Bigger and more complex scripts are also possible: from the beginning, bitcoin came equipped with a language called Script, that allows to programmatically specify the conditions that must be satisfied in order to unlock the funds.
For example, Script enables timelocks, which allow for spending conditions to only be valid after a given amount of time: a father of three could set up a wallet where he is the sole owner, but in case he is no longer able to move the coins, two of his three sons or daughters can spend the coins, but only after 1 year. Inheritance planning without a notary!
Script is a very rudimentary language similar to assembly, which makes it easy for machines to execute and verify, but difficult for humans to program with, and to check that there are no bugs!
Because of this complexity, a software wallet that wants to use a specific locking condition needs to know how to represent it using bitcoin’s Script language and encode these rules in the software wallet, together with the rules on how to actually satisfy this script when locking the coins (to build what is called the witness in SegWit transactions). This is time consuming and expensive (Script engineers are scarce…), and moreover it’s not flexible: each script will require specific support in software wallets − but the number of possible scripts is potentially infinite. No way they can all be encoded in a catalog!
That’s probably the main reason why support in the industry has been generally limited to relatively simple scripts like multisignature.
A more general solution was needed – and that’s miniscript.
Miniscript and policies
Miniscript was announced by Pieter Wuille and Andrew Poelstra of Blockstream, later joined by Sanket Kanjalkar. With more than 4 years in the works (and contributions from many external contributors), it is now getting ready for prime time. It is in progress of being merged to bitcoin-core, and we already designed the new Ledger Bitcoin app version 2 with miniscript at the back of our mind.
There are three steps to go from the locking conditions of a wallet to the actual bitcoin Script. First, we write those locking conditions in a Policy, which is simply their formal representation, without taking into account the constraints and caveats of Script. A policy is basically what conditions we want to express, without caring exactly about how we can express them. As a simple example, look at the following policy (where actual keys are replaced with the placeholders
This represents the following locking condition:
A can sign immediately, while key B is only active after about a week (1008 blocks). Moreover, the policy specifies that the first spending condition is 99 times more likely than the second one (this does not have an effect on the semantics of the locking conditions itself, but it affects which of the different possible ways of expressing them in Script is more efficient in practice). The policy compiler produces the following miniscript:
which in turns compiles directly to the following Script:
<A> OP_CHECKSIG OP_IFDUP OP_NOTIF OP_DUP OP_HASH160 <HASH160(B)> OP_EQUALVERIFY OP_CHECKSIGVERIFY <f003> OP_CHECKSEQUENCEVERIFY OP_ENDIF
Nothing too exciting about this Script (bitcoin could do this for a long time!), except that:
- The policy compiler could find the best possible miniscript automatically
- The policy itself is a lot more readable for a human (or at least an engineer).
- The compiler guarantees that the final resulting Script is correct, and can apply certain sanity checks.
- The miniscript language, being structured, can be analyzed by a miniscript-aware wallet.
- Everything we just said would equally apply to any other policy!
With Policy and miniscript, every step after writing the policy is completely automated; this removes any room for human error and makes it possible to build wallets that are interoperable even when using complex script − as long as they can speak miniscript.
In this section we show how the miniscript policy registration could look like on a Ledger Nano device. This is based on very early unreleased code from app-bitcoin-new, and tested using a yet-unreleased bitcoin-core with miniscript support. Nevertheless, we could produce a real testnet transaction spending from address
tb1qlmtlptrgr8v03leledzss43jgcfcc23u2qu43zg6ka4zh8dgwlas58hqav, making it the first spend from a miniscript wallet secured with keys on a Ledger Nano hardware wallet. Neat!
Warning: nothing shown in this section is final. Exact specifications and UX can and will be different on the actual release.
In this example, we will work with the following policy:
In other words, there are two keys: our key (
key_user) and the key of an external 2-factor authentication service (
key_service). Usually, the second service will countersign all our transactions (possibly according to some rules that we had set in advance, for example “no more than 1 million satoshis per day”). What happens if the service disappears? Unlike a more simple 2-of-2 multisig, coins are not burnt with this policy! After waiting 1008 blocks (about 1 week), our own key alone is enough to sign a transaction, and therefore retrieve our funds.
Compiling this policy produces the following miniscript:
and here is how the corresponding Script will look like:
<key_user> OP_CHECKSIGVERIFY <key_service> OP_CHECKSIG OP_IFDUP OP_NOTIF <f003> OP_CHECKSEQUENCEVERIFY OP_ENDIF
Policy registration on the Nano
In order to use a miniscript wallet account with the device, it is first necessary to register its descriptor template into the device. This teaches your Ledger everything about the miniscript wallet, and it is exactly the same process described in an earlier post in the context of multisignature wallets.
This is what it will look like, on a simulated Nano X (we chose the name “2FA wallet” for the wallet we are using):
Registering a miniscript policy
Note that Ledger’s descriptor templates use
@1, etc for the placeholders, and the full extended public keys (the so-called “xpub” on bitcoin mainnet, “tpub” on testnet), are inspected separately when registering the wallet policy. Moreover, the miniscript is contained in a
wsh descriptor. Only P2WSH (native segwit script) and P2SH-WSH (wrapped segwit script) will be supported in the very first release.
Receiving and spending
Receiving into a multisignature wallet has an interface which is familiar to our users, with the only difference that an additional screen explicitly mentions the name of the (previously registered) wallet account − in this example “2FA wallet”.
Spending from a “miniscript account” is similar: after an initial screen notifying you that you are spending from a registered wallet named “2FA wallet”, the UX is the same as usual.
Receiving/spending from a registered policy
We successfully spent from our first testnet miniscript wallet account with this transaction.
If you’ve been waiting for wallets to support advanced bitcoin scripts, the time to get coding is now.
Miniscript is coming!
But how much code is needed to make all of this happen on the hardware wallet? Not much:
from ledger_bitcoin import createClient, Chain, PolicyMapWallet from ledger_bitcoin.psbt import PSBT with createClient(chain=Chain.TEST) as client: policy_map = PolicyMapWallet( name="2FA wallet", policy_map="wsh(and_v(v:pk(@0),or_d(pk(@1),older(1008))))", keys_info=[ "[f5acc2fd/48'/1'/0'/2']tpubDFAq…4dK/**", "[67d0577f/44'/1'/0']tpubDC2r…Lar/**" ]) # Register the wallet wallet_id, wallet_hmac = client.register_wallet(policy_map) # Get the first receive address addr = client.get_wallet_address(policy_map, wallet_hmac, 0, 0, True) psbt_base64 = None # TODO: load the PSBT from somewhere psbt = PSBT() psbt.deserialize(psbt_base64) res = client.sign_psbt(psbt, policy_map, wallet_hmac) print(res) # print the signatures
Of course, the complexity of handling the user’s key, and to generate and correctly fill the PSBT, are out of the scope of this example.
Building the future of bitcoin self-custody
The roadmap of the Ledger Nano Bitcoin application is exciting.
Supporting PSBT and miniscript, then extending support to Taproot scripts, and taking full advantage of taproot with solutions like MuSig2 will usher in a new era for bitcoin self-custody. Easier, safer, more flexible, more interoperable, more private.
Time to build!
Deprecation of the legacy application
At the time of this post, the Ledger application still fully contains the old protocol inside; therefore, software written for a version of the app below 2.0.0 can interact with the current application and everything will work as expected.
Unfortunately, this has a substantial impact on the application size, making it unsustainable in the long term. Therefore, we target dropping support for the legacy protocol by October 2022 from the default distribution channel within Ledger Live.
We will make sure that the legacy application is available for download, in order to make sure that no user is locked out of their funds in case they need to use unupgraded software that does not support the new protocol.
Thanks to the work of Andrew Chow, the release 2.1.0 of the HWI library natively supports the version 2.0.0 and above of the Ledger Bitcoin application, while staying compatible with the legacy application for simple transactions. Currently, support for the wallet policy registration features is incomplete (but upgrade should be seamless for most use cases).
We would like to prompt any company that integrated Ledger devices in their workflow to plan accordingly, and please reach out to us if you need any assistance with the transition, or if you encounter any difficulties while integrating with the new application.
We can build the most awesome bitcoin application for hardware wallets because we sit on the shoulder of giants.
We would like to thank Antoine Poinsot and Sanket Kanjalkar for the numerous discussions, and for pushing the state-of-the-art forward on miniscript.
Similarly, our appreciation goes to Andrew Chow for the PSBT format and the output descriptors that we are building upon.
- github.com/LedgerHQ/app-bitcoin-new: the Ledger Nano application for bitcoin
- bitcoin.sipa.be/miniscript: the miniscript website and playground
- miniscript.fun: a graphical playground for miniscript
- bitcoinops.org/en/topics/miniscript: more links and additional information on miniscript
- en.bitcoin.it/wiki/Script: Script reference