We have opened and closed the first mainnet Lightning channel embedding a Discrete Log Contract (DLC).
At Crypto Garage we have been working on getting DLC working over Lightning channels for some time, and we have finally reached an important milestone in having successfully opened a Lightning channel, set up a DLC inside it, and closed the entire channel. In this article we’ll explain the technical details of how the Lightning channel and DLC were set up and closed.
What is a DLC on Lightning (and what it is not)
For the purpose of this article, we refer to opening a DLC on Lightning meaning having a direct channel established with a node, and setting up a DLC within the channel with (part of) the funds that were used to open it. The DLC can be closed (reverting back to a regular Lightning channel), renewed or updated, and the channel can of course be closed on chain in case of a non cooperative counter party (this is what we did as it shows the full transaction structure of the Lightning channel and DLC contract on-chain).
We emphasize that it is not currently possible to have routed DLC over Lightning (meaning opening a DLC between nodes that don’t have a direct channel). See this mailing list post for more information.
In order to try and make this article self contained, we’ll go through the different components that are used to set up a DLC on Lightning: Lightning channels, adaptor signatures, DLCs, and DLC channels. You can skip the sections related to concepts you’re already familiar with.
As it is difficult to understand how to set up a DLC within a Lightning channel without first having a rough understanding of the transaction structure of regular Lightning channels, let’s recall that quickly (to get a deeper understanding this Bitcoin Magazine series is pretty great). We’ll only focus on what is relevant for the context and so we will skip anything related to routing.
When two Lightning nodes open a channel with each other, they cooperatively build and sign two types of transactions, the funding transaction and the commitment transactions.
The funding transaction takes a number of UTXOs from one of the parties as inputs (or both in the case of dual funding), and locks the funds to be used for the channel in a 2 of 2 output that requires a signature from both parties to be spent, referred to as the funding output.
A commitment transaction takes as input the funding output and contains two outputs, with values corresponding to the balances of each party. Note that these balances will change during the lifetime of the channel, and so the commitment transaction needs to be updatable to reflect these changes. This is done by making the commitment transaction revocable. In practice, this means that each party holds a different version of the commitment transaction, with the output paying to themselves locked in a script that can be unlocked either by themselves after a certain amount of time, or by their counter party with knowledge of a secret. When the parties agree to update the channel balance (e.g. one party wishes to send some sats to the other), they build and exchange signatures for the new commitment transactions, and revoke the previous ones by revealing to each other the secrets they previously used. If a party tries to cheat by broadcasting an old commitment transaction, the other party has some time to react and use the secret to take all the channel funds.
The transaction structure is illustrated in the following figure.
An adaptor signature is a signature that has been encrypted (for the case that is of interest with a public key), and for which we can prove that after decryption (with the corresponding secret key) we obtain a valid signature for a given message. It also has the property that knowledge of the adaptor signature, the encryption public key and the decrypted signature makes it possible to retrieve the secret that was used for encryption. Check here for a quick overview of the formulas involved.
The following figure illustrates the different operations involved in encrypting, verifying, and decrypting adaptor signatures, as well as recovering an encryption secret.
Discreet Log Contract
A DLC enables two parties to set up a contract over some event (sport game, price of an asset…), and settle it directly on the Bitcoin blockchain. The parties need to choose an oracle beforehand, an entity that will attest to the event outcome by releasing a signature over the outcome value.
A DLC looks rather similar to a Lightning channel at first sight, but instead of having commitment transactions spending from the funding output, we have Contract Execution Transactions (CET), one for each of the possible contract payout. The parties each encrypt their signatures (creating adaptor signatures) for these transactions using the pre-image of the signature that the oracle would produce for the corresponding event outcome (check here for more details), and exchange and verify them. Once the oracle attests to the event outcome, any of the two party will be able to decrypt a single signature from their counter party, and close the contract by broadcasting the corresponding CET. Note that thanks to using adaptor signatures, the parties hold exactly the same set of CETs. The transaction structure of a DLC is illustrated in the figure below.
What if two parties wish to renew a contract at expiry? They can of course close the first contract and reopen another one. This however incurs fees for each of the broadcasted transactions. Instead they can use a DLC channel (the folks at ItchySats have written in greater details about this here). A DLC channel enables parties to renew a contract, settle it ahead of its maturity date, and of course to close it on-chain once they want to recover their funds. A simple way would be to make CETs revocable. Unfortunately, this causes an issue when renewing a contract. Once one of the parties has revoked the previous CETs, they cannot prevent their counter party from still being able to use them. This means the counter party has the opportunity to close either the previous contract or the new one, while the party that first revoked the CETs has no choice but to wait for the new contract maturity to close (as they need to wait for the oracle to release the attestation to be able to broadcast a CET). This gives an unfair advantage to one of the parties. To solve this problem, we introduce a buffer transaction, which spends the funding output and outputs the same value (minus fee), which is used as input by the CETs. Then when renewing a contract, if one of the parties doesn’t revoke the previous contract in a timely manner, the other party can broadcast the buffer transaction for the new contract and prevent the malicious (or malfunctioning) party from closing the channel with the previous contract. With this construction, it is enough to make the buffer transaction revocable for revoking a contract.
So how do we revoke the buffer transaction? One way would be to use the same approach used in Lightning, with each party holding a different version of the buffer transaction. Fortunately, a better solution exists (that was originally proposed in this paper). It once again relies on adaptor signatures, but in a different way than how a regular DLC makes use of them. Each party uses three sets of key pairs. The first one is the funding key, used by each party in the 2 of 2 script of the funding output. The second one is the revocation key, which gets revealed when a contract is revoked. Finally, the third one is the publishing key. These three keys are used in the buffer transaction output scripts, which can be spent in three different ways (we use Alice and Bob to make things easier):
- With signatures from Alice and Bob funding keys (using one of the CET transaction, which as before can only be used with knowledge of the oracle attestation for the event outcome),
- With a signature from Alice funding key, and signatures from Bob publish and revocation keys,
- With a signature from Bob funding key, and signatures from Alice publish and revocation keys.
When a contract is set up in the channel, Alice and Bob give each other an adaptor signature for the buffer transaction input, encrypted with their counterparty publish (public) key. If one of them wants to broadcast the buffer transaction, they decrypt the adaptor signature that they obtained from their counterparty using their publish (secret) key. But by broadcasting the buffer transaction (which contains the decrypted signature), they will reveal their publish secret key to their counterparty (who already knows the adaptor signature and encryption key). If the buffer transaction has not been revoked, this is not an issue, they will be able to legitimately spend the buffer transaction output using one of the CET. But if the buffer transaction was revoked, their counterparty must also know the revocation secret and will thus be able to get the entire output funds. Finally, note that the CETs have a relative timelock to give time to a cheated party to spend a revoked buffer transaction output.
The following figure illustrates the transaction structure and their spending conditions.
DLC on Lightning
Now that we have explained all the building blocks, let’s see how we can embed a DLC channel in a Lightning channel. A first obvious way to do it would be to add an output to the commitment transaction of a Lightning channel to fund a DLC channel. Unfortunately, this would mean that every time the commitment transaction is updated, all the adaptor signatures of the DLC would need to be recomputed (and re-verified), which can be costly for contracts with a large number of possible outcomes (this issue would be fixed with something like SIGHASH_NOINPUT). To avoid this, we introduce a split transaction, which spends the funding input and contains two outputs, one for the Lightning channel, and one for the DLC one (it could potentially contain more inputs to fund multiple DLC channels and have multiple open contracts at the same time). In order to be able to restore the channel to a “typical” Lightning one later on, we make the split transaction revocable using the same adaptor signature based mechanism used for DLC channels. Unfortunately, one issue arises with the Lightning channel. In order for one of the parties to react to the broadcast of a revoked split transaction, there needs to be some timelock on the transactions spending from it. But the Lightning network specifications use both the nLockTime and nSequence fields of the commitment transaction to specify the commitment number. To circumvent this, we insert a glue transaction between the split transaction and the Lightning channel commitment transaction to enforce a relative timelock on it (h/t to Matt Corallo for suggesting this simple workaround). Note that this is really an engineering issue and could be solved by adopting a different specification for the commitment transaction (but this would also require much more changes to a Lightning implementation to support it).
To summarize, if two parties who have a direct Lightning network channel between them wish to set up a DLC within it they would collaboratively create and sign the split transaction, the glue transaction, the updated commitment transactions (reflecting the funds kept inside Lightning for each party), the buffer transaction and the CETs for the DLC. They can then keep routing through the Lightning channel, and update or settle the DLC contract in the DLC channel. If they wish to close the DLC channel, they can revoke the split transaction and collaboratively create and sign updated commitment transactions.
The following figure illustrates the transaction structure. Note that different publish and revocation keys are used for the split transaction and buffer transaction.
The construction detailed above was implemented in the rust-dlc library, using a fork of the Lightning Development Kit (LDK) that adds support for splitting a Lightning channel. A small CLI tool based on the LDK sample was also implemented that in addition to regular Lightning functionalities also enables opening and management of DLC channels. Note that the code is very unstable and we would like to emphasize that using it with mainnet coins is very likely to lead to the loss of the funds.
We first opened a channel between our two DLC on Lightning enabled nodes on November 21st, with this transaction. We then made a keysend payment from the node that opened the channel to the other one (once dual funding is implemented in LDK this step would not be necessary). We defined a DLC contract based on the price of bitcoin on the 21st November at 4pm (JST) and used it to open a DLC channel alongside the Lightning channel. At contract maturity, we made one of the node force close the channel. The node first broadcasted the split transaction that can be seen here. 288 blocks after the inclusion of the split transaction in a block (the nSequence value set on the buffer and glue transactions), we made the node broadcast the buffer and glue transactions (visible here and here), as well its local commitment transaction (visible here). 288 blocks after the inclusion of the buffer transactions, the node fetched the oracle signature for the outcome event, decrypted the other node adaptor signature matching the outcome, and was able to broadcast a CET to close the DLC (visible here).
Being able to open DLC within Lightning opens up to some nice applications such as pegging part of the channel value to some other currency, using some of the funds in a channel to trade some derivatives contracts without having to give away the custody of the fund, or simply bet on the outcome of a football game with a friend. By implementing support for this in the rust-dlc library and demonstrating its use through the execution and closing of a DLC within a Lightning channel, we have shown the feasibility of our proposed approach. Quite some work remains to be done both to analyze and improve the proposal, and to strengthen its implementation. So if you are interested in joining the effort, in using this technology or want to know more about it feel free to contact us!
Thanks to the LDK team for reviewing and accepting a couple of pull requests that made it possible to implement things discussed in this article, as well as for answering multiple questions regarding LDK, Lightning and Rust!