The #Bitcoin #Lightning Spec Part 4/8: Bitcoin Transaction and Script Formats
This is 18 pages of Segregated Witness goodness, in which we spell out the exact scripts used by each transaction, and their weights (important for fee calculations!). These were mainly written over email with Tadge Dryja fixing my mistakes, so despite review they’ll need real-world testing still before they’re finalized.
- The P2WSH output script for the funding transaction.
- The ordering of outputs for the commitment transaction (BIP 69)
- The P2WSH output scripts for accepted and offered HTLCs.
- The P2WSH output script for the ‘to-self’ commitment transaction output.
- The P2PKH output for the ‘to-other’ commitment transaction output.
- The input and output scripts for HTLC-success and HTLC-timeout transactions.
- Under what circumstances “dust” outputs get trimmed from the commitment transaction.
- The exact fee calculation method for commitment transaction and HTLC transactions, using their approximate weight (exact weight won’t be known until after they’re signed).
- The method for deriving the revocation key for each commitment transaction (generated from a single seed, using shachain (it was a toss-up between that and lnd’s elkrem, and Fabrice made the call). This means you only need to remember a maximum of 48 secrets, however many commitment revocations you’ve received.
- The method for deriving the ‘to-me’, ‘to-you’ and ‘delayed-to-me’ keys for each commitment transaction
We went with lnd’s encoding of the commitment number into the ‘nLocktime’ and input ‘nSequence’ of the transaction, obscured by a value known only to the two participants: this saves a lookup if an old transaction is seen onchain, but with a limit of 2⁴⁸ total commitments per channel.
The larger difference from my previous work is in the keys used, and I want to cover that in some detail. The exact method of key derivation used here may yet change: a proposal is taking shape for Schnorr signatures which we could use here. But it will be a drop-in replacement.
Tadge recommended we use revocation keys instead of revocation preimages: if we generate these correctly (eg. using blinding), only the other side knows the private key once an old commitment transaction is revoked.
This means the HTLC-success and HTLC-timeout transactions no longer
need “your key and the revocation preimage” to spend, but simply “the revocation key”. That saves around 22 bytes in the script, but since the HTLC-success and HTLC-timeout transactions are only used half the time, it’s only about 11 bytes per HTLC.
We take some care to ensure that the revocation key can’t be gamed by the side handing out the per-commitment-point; we don’t want it to be able to hand a magic value which will cause it to know the revocation key. If it tried such a trick it couldn’t complete the protocol for the next commitment transaction, but it could use the revocation key to close instead of waiting for the CSV delay, and the CSV delay provides mild incentive to negotiate a mutual close (esp. if the other side is paying all the fees!).
Continually Changing Keys
A commitment transaction contains HTLC outputs for payments in progress, and one output directly to each party. In my prototype, these paid to static addresses (keys) each side announced at startup.
Now the addresses will change every time, in a way predictable to the parties but not anyone else. In addition, the key used for the my own delayed “to-local” outputs is different from the key used for the HTLC transaction inputs. These are each generated from a fixed base point and a tweak: we already have a nice unguessable tweak available as our revocation key.
The reasoning for changing keys is subtle, and took much debate and refinement. It’s driven by Tadge Dryja’s work on implementing outsourcing of penalty transactions. In this setup, we want to hand off penalty transactions to a watcher who can then use them to punish our peers if they try to cheat, even if we’re offline ourselves.
But we want the watcher to know nothing unless they have to act: not the state of the channel, not the HTLCs, not even what funding transaction they’re watching. We want this to be true even if both sides of a channel are using the same watcher. We want this to hold even if we are forced to do a unilateral close so it sees the latest (unrevoked) commitment transaction. And even if they see a commitment transaction trying to cheat (which necessarily reveals the state of the channel at that point in time), they should learn nothing about the other channel states.
This seemed a ridiculously ambitious goal to me, and certainly not on the roadmap for early releases! But Tadge’s perseverance here has paid off, and the protocol has everything needed to support it; adding it later would either be suboptimal or have caused a protocol change.
It turns out that the two-stage HTLC design helps here: all the outputs the watcher needs to spend in a penalty transaction are of the same form. And those scripts only involve the revocation key and the “delayed-to-self” key, which can be generated by the watcher from the transaction number. In fact, after some initial setup, for each revoked commitment transaction you need only tell the watcher the first bits of the commitment transaction id (to save it some work checking every transaction) and the signatures for the penalty
transactions; one for each HTLC plus one for the non-HTLC “to-self” payment. You can pad with bogus signatures, too, and it can’t tell the difference, so you can obscure the number of HTLC outputs.
Now, consider a watcher who is trying to determine the contents of a
old commitment transaction it’s been told to watch but hasn’t seen
on-chain. It can’t even try to determine the contents of the commitment transaction it’s supposed to watch, since it doesn’t know the remote key used in the ‘to-other’ or local key used in the ‘to-self` outputs (and there’s a small channel reserve which guarantees there will be one). Even if it does see an invalid unilateral close, it can’t determine these for the previous commitment transactions it was told to watch. It could try to guess which HTLC an HTLC transaction referred to from the signature it was given, but it doesn’t know most of the commitment transaction ID which the HTLC transaction refers to.
Even if we’re forced to do a unilateral close, the watcher can’t correlate the commitment transaction we broadcast with the ones it was told to watch, since all the keys change every time in a way it can’t predict.
Tadge’s impressive work here goes a long way to relieving anxiety about the risk of being cheated if a node is offline for a long time, and trying to keep up with him was a great deal of fun!