I’ve been thinking about how to expand support for smart contracts on Stellar, looking at use cases to learn what we need to make them viable. One of the most compelling things I’ve found is my friends Joseph Poon and Thaddeus Dryja’s smart contract-based proposal for bitcoin. It’s called Lightning Networks.

Lightning allows you to set up a channel to send transactions securely between two parties, off blockchain. Neither party needs to trust the other—they only go to the blockchain to settle a dispute or to close the channel. Theoretically, any given channel could stay open for years and have millions of transactions flow across it without ever settling to the public ledger.

The two parties involved in the channel exchange pre-signed transactions without introducing them to the network. These transactions vary the fraction of the escrow amount sent to each end of the channel, a setup that is effectively equivalent to sending money between the two ends. Only the latest signed transaction should be valid between the two parties, but the network doesn’t know that—every transaction seems valid to the network. The trick is to set the channel up so that if either party introduces an old transaction, the malfeasor is penalized. Read the Lightning paper for a more thorough description.

A Lightning-like system is possible on Stellar today. Here’s one way you could set it up:

What’s involved

Setting up the channel will involve some of the more advanced features of the Stellar protocol: time bounds on transactions, accounts with multiple signers, and batched operations.

A : Account of one participant in the channel, (Alice, Adrian, Arkon, …)

B : Account of one participant in the channel, (Bob, Brody, Bevis, …)

Escrow : Account that holds the funds moved between A and B and the malfeasor insurance posted by each participant.

Helper: Account controlled by A and B that issues the assets needed by the channel. The helper account holds no funds.

SetupTx : Created and submitted to the network once to establish the channel.

CloseTxA & CloseTxB : These txs close the channel and return the correct balance to each participant. Creating a new closeTx is also how the balance between the two participants is changed while the channel is open. An A and B version of the closeTx make it clear who closed the channel.

RefundTx : This transaction finishes closing the channel and returns the insurance to the participants if there hasn’t been any bad acting. It can only be introduced a few days after a closeTx to give people time to submit a penaltyTx if there has been cheating.

PenaltyTxA & PenaltyTxB: For each pair of closeTx’s, there is a corresponding pair of penaltyTx’s. These transactions are only valid if one participant introduces an old closeTx. This action allows the other participant to introduce the penaltyTx and claim the entire insurance amount held in escrow.

Information to track

Each participant must individually keep track of the following information outside of the Stellar ledger:

refundCounter : How many times the refundTx has been bumped.

txCounter : How many off-ledger transactions have occurred over this channel.

maxCloseTxTime : All closeTxs must be limited to execution before this time, allowing enough time for a penaltyTx to be introduced before the refundTx.

current balance between the two parties

latest refundTx

latest closeTx

latest penaltyTx

How it works

To make a Lightning-like system work, we use assets in a novel way. They act as a counter, relying on the fact that payment operations (and thus the entire transaction) will fail if the account doesn’t have the necessary funds.

The channel needs 3 assets: `AEND`, `BEND`, and `TIME`. What these assets are used for should become clear below.

To close a channel, either A or B must introduce a closeTx. These closeTx’s send a decreasing amount of either `AEND` or `BEND` to the escrow account. The penaltyTx that corresponds to a given closeTx requires escrow to hold more of the `AEND` or `BEND` then that closeTx sends to escrow. Thus the penaltyTx will only be valid if an old closeTx was introduced.

For example, if A closes the channel correctly:

A introduces the current closeTxA.

Escrow now holds X ` AEND `, where X=(MAX_INT-txCounter).

But penaltyTxA needs to send X + 1 ` AEND ` so it fails and B cannot submit it.

But if A cheats:

A introduces an old closeTxA.

Escrow now holds Y ` AEND `, where Y=(MAX_INT-old_lower_txCounter).

penaltyTxA is now valid since Y>X. In other words, escrow does hold enough ` AEND ` to send back to the helper account.

This logic should become clearer when you look at the actual transactions below.

The `TIME` asset works in a similar way for the refundTx’s.

Setup

Assume you have two parties, A and B. They want to create a lightning channel between themselves. A and B agree that the maximum either wants to pay the other is $100. They will each need to escrow $200: $50 for float to pay the other and $150 to post to ensure that neither can cheat.

A and B create, sign, and exchange the first closeTxB, closeTxA, and refundTx defined below. Then they create, sign, and submit the setupTx to the network.

SetupTx Source account A Operations A creates escrow account B creates helper account A sends escrow $200 ($50 for float to pay B. $150 to provide malfeasor insurance.) B sends escrow $200 ($50 for float to pay A. $150 to provide malfeasor insurance.) Escrow trusts helper for ` AEND ` (asset we are using to know when A sends a closeTx out of order) Escrow trusts helper for ` BEND ` (asset we are using to know when B sends a closeTx out of order) Escrow trusts helper for ` TIME ` (asset we are using to make sure old refundTxs aren’t valid) Escrow account sets A and B as the only signers Helper account sets A and B as the only signers Signers A,B,Escrow,Helper

This transaction sets up the Escrow and Helper accounts and makes it so that any transaction from either requires A and B to sign.

RefundTx Source account Escrow Sequence number current+1 (so isn’t valid until after some closeTx is submitted) Time bounds valid between: { now + 1 month, now + 2 months} Operations Escrow sends {asset: ` TIME ` amount: MAX_INT-refundCounter} to Helper Escrow sends $150 to A (*refunding the malfeasor insurance*) Escrow sends $150 to B (*refunding the malfeasor insurance*) Signers A,B

A makes, signs, and sends the refundTx to B. B signs it and returns it to A. Both A and B hold on to the latest refundTx but don’t submit it to the network until after a closeTx is submitted.

Whenever a new refundTx is created, maxCloseTxTime is set to (now + 1 month – 1 day).

CloseTxB Source account Escrow Sequence number current Time bounds valid between: { dawn of time, maxCloseTxTime } Operations Escrow sends A As_fraction_of_$100 Escrow sends B Bs_fraction_of_$100 Helper sends {asset: ` BEND ` amount: MAX_INT-txCounter} to Escrow Helper sends {asset: ` TIME ` amount: MAX_INT-refundCounter} to Escrow Signers A

A creates, signs, and sends the closeTxB to B.

CloseTxA Source account Escrow Sequence number current Time bounds valid between: { dawn of time, maxCloseTxTime } Operations Escrow sends A As_fraction_of_$100 Escrow sends B Bs_fraction_of_$100 Helper sends {asset: ` AEND ` amount: MAX_INT-txCounter} to Escrow Helper sends {asset: ` TIME ` amount: MAX_INT-refundCounter} to Escrow Signers B

B creates, signs, and sends the closeTxA to A.

PenaltyTxA Source account Escrow Sequence number current + 1 (so isn’t valid until after some closeTx is submitted) Operations Escrow sends {asset: `A END ` amount: (MAX_INT-txCounter)+1 } to Helper Escrow sends $300 to B Signers A

A creates, signs, and sends the penaltyTxA to B.

PenaltyTxB Source account Escrow Sequence number current + 1 (so isn’t valid until after some closeTx is submitted) Operations Escrow sends {asset: `B END ` amount: (MAX_INT-txCounter)+1 } to Helper Escrow sends $300 to A Signers B

B creates, signs, and sends the penaltyTxB to A.

Process

Anytime A wants to send a transaction, they create and sign a new closeTxB with the updated balance between the two parties. B replies with the corresponding closeTxA and the penaltyTxB. A then sends penaltyTxA and the off-chain transaction is considered complete.

As time inevitably marches forward and gets close to maxCloseTxTime, the parties can increment the refundCounter and generate a new RefundTx. Future closeTx’s will be created with the updated refundCounter and maxCloseTxTime.

Scenarios

Happy path

A and B merrily exchange closeTx’s until they no longer want to maintain the channel. At that point, one of them introduces the latest closeTx and then both sign a transaction dividing up the insurance.

A cheats and introduces an old closeTxA

Let’s say that at some point there was a closeTx that sent $70 to A and $30 to B, but then A sent $45 to B over the channel. As a result, the current closeTx sends $25 to A and $75 to B.

A might be tempted to introduce the old closeTx. However, if A does that, B can submit the latest PenaltyTxA, which will succeed: Because of the old closeTxA, the escrow account now holds too much `AEND`. A can’t submit the refundTx before B has a chance to submit the penaltyTx. The current refundTx isn’t valid until sometime in the future. Since closeTxA is time-bound, it must have sent the current amount of `TIME` asset to escrow. Thus an old refundTx will fail because escrow doesn’t have enough `TIME` asset to send to helper.

Channel closes but B is a jerk

If the channel was closed correctly but B decides not to sign a transaction dividing up the insurance, A must wait for the pre-signed refund transaction to be valid and then submit it to the network.

Note: The channel in this example is for USD, but it could work with any currency or asset. There’s a bit of extra work I left off to ensure that A or B don’t remove their trust of the channel asset, which would prevent the close and refund transactions from working. But it’s possible to set up in a safe way, which I’ll leave as an exercise for the reader. (If the channel is for lumens you don’t have this issue.)

Going Further

Setting up a payment channel between two parties is just the first step in creating a full-fledged lightning network. The network becomes even more powerful when you can chain parties together and chain channels between totally different ledgers or blockchains. But more on how to do that later…