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. |