Lightning Contracts

Lightning contracts make a good example for showing how the DSL is useful. LN contracts use csv, cltv as well as anchor transactions. The transactions between channel participants transition through a number of states, and we can capture those using the DSL.

Setup Keys and Coinbases
# Generate keys for channel funding tx
@alice = key :new
@alice_revocation_key_for_bob = key :new
@alice_htlc_key = key :new

@bob = key :new
@bob_revocation_key_for_alice = key :new
@bob_htlc_key = key :new

# Delay used for local CLTV locks
@local_delay = 1008

# 32 byte preimage in hex
@payment_preimage = 'beef' * 16

# Seed alice with some coins
extend_chain to: @alice

# Seed bob with some coins and make coinbase spendable
extend_chain num_blocks: 101, to: @bob
Alice and Bob create funding transaction
run_script './setup.rb'

@alice_funding_input_tx = spendable_coinbase_for @alice
@bob_funding_input_tx = spendable_coinbase_for @bob

@channel_funding_tx = transaction inputs: [
                                    { tx: @alice_funding_input_tx,
                                      vout: 0,
                                      script_sig: 'sig:wpkh(@alice)' }, (1)
                                    { tx: @bob_funding_input_tx,
                                      vout: 0,
                                      script_sig: 'sig:wpkh(@bob)' } (1)
                                  ],
                                  outputs: [
                                    {
                                      descriptor: 'wsh(multi(2,@alice,@bob))', (2)
                                      amount: 99.999.sats
                                    }
                                  ]

broadcast @channel_funding_tx

confirm transaction: @channel_funding_tx, to: @alice

log 'Funding transaction confirmed'
1 Sign inputs using sig:wpkh construct
2 Create a multisig output using a descriptor

Branch Point 1 - Close Without HTLCs

Here we branch off to close the channel without adding any HTLCs

Alice and Bob cooperatively close without adding any HTLC
run_script './commitment_without_htlcs.rb' (1)

# Alice broadcasts commitment tx
broadcast @alice_commitment_tx
confirm transaction: @alice_commitment_tx, to: @alice

# Bob's commitment can no longer be broadcast
assert_not_mempool_accept @bob_commitment_tx
1 Run the setup and funding scripts first

Branch Point 1 - Close Unilaterally Without HTLCs

Here we branch off to close the channel without adding any HTLCs

Alice closes unilaterally
run_script './commitment_without_htlcs.rb'

# Alice broadcasts her commitment transaction unilaterally
broadcast @alice_commitment_tx
confirm transaction: @alice_commitment_tx, to: @alice

# Bob's commitment transaction can no longer be broadcast
assert_not_mempool_accept @bob_commitment_tx

# Alice sweeps her fund from the commitment output
@alice_sweep_tx = transaction inputs: [
                                { tx: @alice_commitment_tx,
                                  vout: 0,
                                  script_sig: 'sig:@alice ""', (1)
                                  csv: @local_delay }
                              ],
                              outputs: [
                                { descriptor: 'wpkh(@alice)', amount: 49.998.sats }
                              ]

# Alice can't sweep until local_delay blocks have been generated
assert_not_mempool_accept @alice_sweep_tx

extend_chain num_blocks: @local_delay, to: @alice (2)

# Now alice can sweep the output from commitment transaction
broadcast @alice_sweep_tx
confirm transaction: @alice_sweep_tx, to: @alice
1 We use bitcoin Script to provide the unlocking script. The witness program is automatically looked up by the DSL.
2 We need to extend the chain before Alice can broadcast the sweeping transaction.

Branch Point 1 - Add HTLCs

Here we branch off to close the channel without adding any HTLCs

Add HTLC
@alice_offered_htlc = %(OP_DUP OP_HASH160 hash160(@alice_revocation_key_for_bob) OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    @bob_htlc_key OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_NOTIF
        OP_DROP 2 OP_SWAP @alice_htlc_key 2 OP_CHECKMULTISIG
    OP_ELSE
        OP_HASH160 hash160(@payment_preimage) OP_EQUALVERIFY
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF) (1)

@cltv_expiry = get_height + 10

@bob_received_htlc = %(
OP_DUP OP_HASH160 hash160(@bob_htlc_key) OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    @alice_htlc_key OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_IF
        OP_HASH160 hash160(@payment_preimage) OP_EQUALVERIFY
        2 OP_SWAP @bob_htlc_key 2 OP_CHECKMULTISIG
    OP_ELSE
        OP_DROP @cltv_expiry OP_CHECKLOCKTIMEVERIFY OP_DROP
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF
) (1)

# New commitment transactions are created. With received and offered
# HTLCs.

@alice_commitment_tx =
  transaction inputs: [
                { tx: @channel_funding_tx, vout: 0, script_sig: 'sig:multi(_empty,@bob)' }
              ],
              outputs: [
                # local output for Alice
                { script: %(OP_IF @alice_revocation_key_for_bob
                            OP_ELSE @local_delay OP_CHECKSEQUENCEVERIFY OP_DROP @alice
                            OP_ENDIF OP_CHECKSIG),
                  amount: 49.899.sats },
                # HTLC offered by Alice
                { script: @alice_offered_htlc, amount: 0.1.sats }, (2)
                # remote output for Bob
                { descriptor: 'wpkh(@bob)', amount: 49.999.sats }
              ]

@bob_commitment_tx =
  transaction inputs: [
                { tx: @channel_funding_tx, vout: 0, script_sig: 'sig:multi(@alice,_empty)' }
              ],
              outputs: [
                # local output for Bob
                { script: %(OP_IF @bob_revocation_key_for_alice
                            OP_ELSE @local_delay OP_CHECKSEQUENCEVERIFY OP_DROP @bob
                            OP_ENDIF OP_CHECKSIG),
                  amount: 49.899.sats },
                # HTLC received by Bob
                { script: @bob_received_htlc, amount: 0.1.sats },
                # remote output for Alice
                { descriptor: 'wpkh(@alice)', amount: 49.999.sats }
              ]

# Can't broadcast the commitment transactions until fully signed
assert_not_mempool_accept @alice_commitment_tx
assert_not_mempool_accept @bob_commitment_tx

# Alice and bob arrange to sign the commitment transactions
update_script_sig for_tx: @alice_commitment_tx, at_index: 0,
                  with_script_sig: 'sig:multi(@alice,@bob)' (3)


update_script_sig for_tx: @bob_commitment_tx, at_index: 0,
                  with_script_sig: 'sig:multi(@alice,@bob)' (3)

assert_mempool_accept @alice_commitment_tx (4)
assert_mempool_accept @bob_commitment_tx (4)
1 Capture the offered and received HTLC contracts using bitcoin Script.
2 The commitment transaction now has HTLC outputs.
3 Use update_script_sig to update signatures for existing transactions.
4 Updated transactions are ready to broadcast now.

Branch Point 2 - Alice Closes Unilaterally and Bob Sweeps Alice's Offered HTLC

Bob sweeps Alice’s offered HTLC
run_script './add_htlc.rb'

# Alice broadcasts her commitment transaction unilaterally
broadcast @alice_commitment_tx (1)
confirm transaction: @alice_commitment_tx, to: @alice

# Bob sweeps the local output for Alice using revocation key
@bob_sweep_alice_funds =
  transaction inputs: [
                { tx: @alice_commitment_tx,
                  vout: 0,
                  script_sig: 'sig:@alice_revocation_key_for_bob 0x01' } (2)
              ],
              outputs: [
                { descriptor: 'wpkh(@bob)', amount: 49.898.sats }
              ]

# Bob sweeps the not yet settled HTLC, again using the revocation key
@bob_sweep_htlc_output =
  transaction inputs: [
                { tx: @alice_commitment_tx,
                  vout: 1,
                  script_sig: 'sig:wpkh(@alice_revocation_key_for_bob)' } (3)
              ],
              outputs: [
                { descriptor: 'wpkh(@bob)', amount: 0.09.sats }
              ]

broadcast @bob_sweep_alice_funds
confirm transaction: @bob_sweep_alice_funds, to: @bob

broadcast @bob_sweep_htlc_output
confirm transaction: @bob_sweep_htlc_output, to: @bob
1 Alice unilaterally broadcasts a revoked commitment
2 Bob has revocation key so he can sweep funds before Alice.
3 Bob can access his own funds from Alice’s commitment transaction.

Branch Point 2 - Alice Closes Unilaterally and Bob Sweeps Using Preimage Key

Bob sweeps Alice’s offered HTLC
run_script './add_htlc.rb'

# Alice broadcasts her commitment transaction unilaterally
broadcast @alice_commitment_tx (1)
confirm transaction: @alice_commitment_tx, to: @alice

# Bob sweeps the local output for Alice using revocation key
@bob_sweep_alice_funds =
  transaction inputs: [
                { tx: @alice_commitment_tx,
                  vout: 0,
                  script_sig: 'sig:@alice_revocation_key_for_bob 0x01' } (2)
              ],
              outputs: [
                { descriptor: 'wpkh(@bob)', amount: 49.898.sats }
              ]

# Bob sweeps the not yet settled HTLC, again using the revocation key
@bob_sweep_htlc_output =
  transaction inputs: [
                { tx: @alice_commitment_tx,
                  vout: 1,
                  script_sig: 'sig:wpkh(@alice_revocation_key_for_bob)' } (3)
              ],
              outputs: [
                { descriptor: 'wpkh(@bob)', amount: 0.09.sats }
              ]

broadcast @bob_sweep_alice_funds
confirm transaction: @bob_sweep_alice_funds, to: @bob

broadcast @bob_sweep_htlc_output
confirm transaction: @bob_sweep_htlc_output, to: @bob
1 Alice unilaterally broadcasts a revoked commitment
2 Bob has revocation key so he can sweep funds before Alice.
3 Bob can access his own funds from Alice’s commitment transaction.