This article will discuss the revocation used by the current update mechanism (as of July 2021) for balances in a single-funded channel of the Lightning Network. This article assumes you have some familiarity with Bitcoin transactions, Lighting Network concepts, and ECDSA cryptography.
Why is revocation necessary?
One of the primary purposes of payment channels is to avoid on-chain transactions for each payment between participants. Let's consider the construction for a channel where we have a funding transaction that locks funds into a 2-of-2 multisig output. This funding output gets spent by a second transaction, the commitment transaction (ctx).
The ctx is responsible for distributing the balance of the funding UTXO between the two participants, Alice and Bob naturally. Initially the commitment transaction can have two outputs, one paying Alice and one paying Bob.
Payment channels work by deferring broadcasting of the commitment transaction until one or both participants want to redeem their piece of the funding UTXO. This deferment is what enables Alice and Bob to adjust the balances off-chain. In short, we're allowing double spending of the funding UTXO.
Say Alice wants to transfer funds to Bob. She can lower the balance of her output and increase the balance of Bob's output by creating and signing a new commitment transaction reflecting this change. This can happen millions of times with the balance shifting in both directions.
However, the wrinkle is that the prior commitment transaction must be invalidated. Since the funding UTXO is double spent, we need some mechanism to enforce the last state as the final state. With revocation, a malicious actor could broadcast a prior and more favorable state.
Now that we've covered an overview of why revocation is necessary, buckle up because we're about to discuss how LN-Penalty handles revocation.
10,000ft View of LN-Penalty Revocation
LN-Penalty takes an aggressive approach to revocation. If a channel member attempts to broadcast a prior commitment transaction, all of the funds can be redeemed by the counterparty. This is why we call it LN-Penalty since the penalty transaction sweeps all funds as a penalty for cheating.
So from our example, the Alice and Bob channel has 1BTC in it. If Alice broadcasts a revoked commitment transaction, Bob can claim the entire 1BTC in the channel.
The complexity of LN-Penalty stems from enabling this type of revocation at the right time by the right party.
For example, if Alice broadcasts the commitment with the most current state, Bob shouldn't be able to perform a penalty.
But if Alice broadcasts the revoked commitment transaction, Bob should be able to perform a penalty.
In order for Bob to perform a penalty:
- The transaction must have been revoked
- Bob needs to detect that Alice broadcast a revoked commitment transaction
- Bob needs to broadcast his penalty transaction that sweeps all of the funds before Alice is able to access her funds
If we don't prevent Alice from accessing her funds for some period we end up with a race condition. Alice could broadcast a revoked commitment transaction, then spend her output immediately. If Bob broadcasts the penalty, he may or may not win depending on whose transaction is mined first. This means that we need a mechanism to ensure that the owner of the transaction can't access their funds for some delayed period of time.
This introduces some interesting requirements for the outputs of our commitment transaction. The net result is that we actually have two different versions of each commitment transaction, one that Alice can broadcast and one that Bob can broadcast.
Instead of thinking of the two outputs of the commitment transaction as one that pays Alice and one that pays Bob. Think of them as one that pays the local node, the to_local output, and one that pays the remote node, the to_remote output.
The to_remote output is nothing special. It is simply a P2WPKH output that pays the counterparty's public key hash. If the commitment transaction is broadcast, the counterparty can access these funds without delay or condition.
The to_local output is more interesting. It is a P2WSH output with two branches of execution. We call it a revocable sequence maturing contract (RSMC) and it looks like this:
OP_IF <remote_revocation_pubkey> OP_ELSE <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP <local_delayed_pubkey> OP_ENDIF OP_CHECKSIG
The two branches of script allow an output to be spent:
- Immediately by the remote node using the revocation key (if they have access to it)
- By the local node after a delayed number of blocks since the output was confirmed on-chain
The local delay uses OP_CSV. This opcode adds a relative timelock to an output. This means that the output can only be spent after a delay (blocks or time) has elapsed since the output was confirmed on chain.
The relative time lock is how the local node is delayed from accessing their funds after they broadcast a commitment transaction. If the commitment transaction hasn't been revoked, then the local node is minorly inconvenienced by having to wait for the delay to expire. If the commitment transaction has been revoked, then the remote node can use the revocation key to sweep all funds via a penalty transaction.
Back to our example with a slight nomenclature change. ctx_0a is the initial commitment transaction that Alice can broadcast and ctx_0b is the commitment transaction that Bob can broadcast.
For ctx_0a (the commitment transaction that Alice can broadcast):
- The to_remote output pays to Bob's pubkey via P2WPKH
- The to_local output pays to Alice's pubkey after some relative delay
- The to_local output pays to Bob when he has access to the revocation key
For ctx_0b, the commitment transaction that Bob can broadcast:
- The to_remote output pays to Alice's pubkey via P2WPKH
- The to_local output pays to Bob's pubkey after some relative delay
- The to_local output pays to Alice when she has access to the revocation key
Keys Enable Revocation
LN-Penalty enables revocation through key rotation. Each commitment transaction uses new keys. An old commitment transaction is revoked by revealing the private key to the counterparty. For Alice's first commitment transaction she generates a key. She does the same for her second, third, etc. When she wants to revoke the first commitment transaction she provides Bob with the secret to the key corresponding to her first commitment transaction.
In reality things are a tad more complicated. If Alice simply generated a new key for her commitment ctx_0a and shared the pubkey with Bob, there is nothing stopping Alice from using the secret from that key herself!
As a result we need to introduce a slightly more complicated construct and use key blinding. Key blinding allows Alice and Bob to create a public key together but neither of them can generate the secret alone. Only when one of them reveals their secret to the other can the full secret to be used by the party with both secrets.
So let us figure out how this works concretely.
When Alice initiates the channel, she sends Bob an ECDSA point referred to as the revocation_basepoint, which we'll dub revocation_basepoint_a.
Note, we call it a base point because a) we don't use it directly and use it to derive other keys b) ECDSA pubkeys are just points on the curve.
Alice also send along her first per_commitment_point. This is the point used for generated pub keys in Alice's commitment transaction ctx_0a. We'll refer to it is a per_commitment_point_0a.
When Bob accepts the channel he sends his own distinct revocation_basepoint, revocation_basepoint_b, to Alice.
Bob also sends along his first per_commitment_point that is used for his version of the commitment transaction ctx_0b, referred to as per_commitment_point_0b.
If you recall from our to_local output above we use a value called remote_revocation_pubkey.
OP_IF <remote_revocation_pubkey> <<<<<<<<<< HELLO! OP_ELSE <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP <local_delayed_pubkey> OP_ENDIF OP_CHECKSIG
You'll notice that we haven't defined that value anywhere yet. That's because we derive it for each and every commitment transaction. So we'll refer to remote_revocation_pubkey_0a, or simply revocation_pubkey_0a, as the value used in the to_local output of ctx_0a. Whew!
So how do we create the revocation_pubkey? Well there is a formula defined in BOLT 3. For this article, I'll simplify it a bit.
revocation_pubkey = u*revocation_basepoint + v*per_commitment_point
From this we see a revocation_pubkey is generated by adding together a revocation_basepoint and a per_commitment_point.
And for the private keys we use a similar formula except we add two secrets together:
revocation_privkey = u*revocation_basepoint_secret + v*per_commitment_secret
We can generate a revocation public key based on two public keys. But in order to generate the secret we need both of the corresponding secrets.
Now that we know the formula, and know that both Alice and Bob each have a revocation_basepoint and a per_commitment_point, we need to know which ones go together.
Recall that ctx_0a is the first commitment transaction that Alice can broadcast. If Alice broadcasts after it's revoke, Bob needs to be able to perform a penalty.
In ctx_0a, the remote node is Bob. Alice will ultimate reveal her per_commitment_secret to revoke this transaction. So we need to use Bob's revocation_basepoint_b and Alice's per_commitment_point_0a.
Both Alice and Bob have these pubkeys since they were exchanged during the channel open. So they both can generate revocation_pubkey_0a.
However, prior to revoking ctx_0a, neither Alice nor Bob have the the required information to generate the revocation private key. Bob clearly knows his revocation_basepoint_secret but he does not have Alice's per_commitment_secret_0a yet. Likewise, Alice does not have, nor will ever have, Bob's revocation_basepoint_secret. But Alice does have the per_commitment_secret_0a for ctx_0a.
Prior to revocation, if Alice drops ctx_0a on-chain neither Alice nor Bob have sufficient information to generate the revocation_privkey and spend via that path. This is perfect. In ctx_0a, Bob has immediate access to his funds via to_remote. Alice will need to wait until the sequence delay expires then she can access her funds.
This change once a ctx_0a has been revoked! To revoke ctx_0a, Alice sends Bob per_commitment_secret_0a. Bob now has both his revocation_basepoint_secret_b and per_commitment_secret_0a from Alice. If Alice broadcasts ctx_0a he can create the revocation_secret and sweep the funds! This is exactly the construct we need!
Hopefully this has helped you understand how keys are used to perform revocation with LN-Penalty.