BOLT-03: Bitcoin Transactions
Lightning transaction format specification. Funding transactions, commitment transactions, and HTLC outputs.
| Type | Basis of Lightning Technology |
| Number | bolt-03 |
| Status | Final |
| Authors | Lightning Network Developers |
| Original | https://github.com/lightning/bolts/blob/master/03-bitcoin-transaction-and-script-formats.md |
| Requires |
BOLT-03: Bitcoin Transaction Formats
BOLT-03 specifies the exact Bitcoin transaction formats used in Lightning channels. This includes funding transactions, commitment transactions, and HTLC output scripts.
Specification Summary
| Aspect | Value |
|---|---|
| Status | Final |
| Layer | Bitcoin |
| Purpose | Transaction formats |
| Dependencies | BOLT-02 |
Transaction Types
| Transaction | Purpose | When Created |
|---|---|---|
| Funding | Creates channel | Channel open |
| Commitment | Current state | Each update |
| HTLC-Success | Claim HTLC | Preimage reveal |
| HTLC-Timeout | Refund HTLC | After timeout |
| Closing | Cooperative close | Mutual agreement |
Funding Transaction
Creates the channel by locking funds in a 2-of-2 multisig.
Output Script
OP_2 <pubkey1> <pubkey2> OP_2 OP_CHECKMULTISIG
Where pubkey1 < pubkey2 (lexicographically sorted).
P2WSH Address
def funding_script(pubkey1: bytes, pubkey2: bytes) -> bytes:
"""Generate funding output script."""
keys = sorted([pubkey1, pubkey2])
return bytes([
0x52, # OP_2
0x21, *keys[0], # 33-byte pubkey
0x21, *keys[1], # 33-byte pubkey
0x52, # OP_2
0xae # OP_CHECKMULTISIG
])
def funding_address(script: bytes) -> str:
"""Generate P2WSH address from script."""
script_hash = sha256(script)
return bech32_encode("bc", 0, script_hash)
Commitment Transaction
Represents current channel state. Each party holds their own asymmetric version.
Structure
Input:
- Funding transaction output
Outputs (example):
- to_local: Delayed output to holder
- to_remote: Immediate output to peer
- HTLCs: Offered and received HTLCs
- Anchors: Fee bumping outputs (if enabled)
to_local Output
Holder’s funds with revocation and CSV delay:
OP_IF
<revocationpubkey>
OP_ELSE
<to_self_delay>
OP_CHECKSEQUENCEVERIFY
OP_DROP
<local_delayedpubkey>
OP_ENDIF
OP_CHECKSIG
Spending paths:
- Revocation: Peer can claim if revoked state broadcast
- Local: Holder can claim after CSV delay
to_remote Output
Peer’s funds, immediately spendable:
# With static_remotekey feature
<remotepubkey> OP_CHECKSIGVERIFY 1 OP_CHECKSEQUENCEVERIFY
# Or simple P2WPKH
<remotepubkey>
Anchor Outputs
Small outputs for CPFP fee bumping:
<local_funding_pubkey> OP_CHECKSIG
OP_IFDUP
OP_NOTIF
16 OP_CHECKSEQUENCEVERIFY
OP_ENDIF
Value: 330 satoshis each (anchor dust limit).
HTLC Outputs
Offered HTLC (Outgoing)
You’re offering payment, peer can claim with preimage:
# To remote node with preimage:
OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
OP_CHECKSIG
OP_ELSE
<remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
OP_IF
# Payment preimage provided
OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
OP_ELSE
# Timeout path
OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
OP_CHECKSIG
OP_ENDIF
OP_ENDIF
Received HTLC (Incoming)
You’re receiving payment, can claim with preimage:
# Similar structure but inverted roles
OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
OP_CHECKSIG
OP_ELSE
<remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
OP_NOTIF
# Timeout path for remote
OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
OP_ELSE
# Payment path with preimage
OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
OP_ENDIF
<cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
OP_ENDIF
HTLC-Success Transaction
Claims received HTLC with preimage:
Version: 2
Inputs:
- HTLC output from commitment
- Witness: 0 <remotehtlcsig> <localhtlcsig> <payment_preimage>
Outputs:
- to_local script (with CSV delay)
Locktime: 0
HTLC-Timeout Transaction
Reclaims offered HTLC after timeout:
Version: 2
Inputs:
- HTLC output from commitment
- Witness: 0 <remotehtlcsig> <localhtlcsig> <>
Outputs:
- to_local script (with CSV delay)
Locktime: cltv_expiry
Key Derivation
Per-Commitment Keys
Each commitment uses unique derived keys:
def derive_pubkey(base_point: bytes, per_commitment_point: bytes) -> bytes:
"""Derive pubkey for this commitment."""
tweak = sha256(per_commitment_point + base_point)
return point_add(base_point, G * tweak)
def derive_revocation_key(
revocation_basepoint: bytes,
per_commitment_point: bytes
) -> bytes:
"""Derive revocation key."""
# Complex derivation for security
pass
Basepoints
Each party provides in open_channel/accept_channel:
funding_pubkey: For funding outputrevocation_basepoint: Derive revocation keyspayment_basepoint: to_remote outputsdelayed_payment_basepoint: to_local outputshtlc_basepoint: HTLC outputs
Fee Calculation
Weight Estimation
| Component | Weight |
|---|---|
| Base commitment | 724 |
| Per-HTLC (no anchors) | 172 |
| Per-HTLC (with anchors) | 172 |
| Anchor output | 330 |
def commitment_weight(num_htlcs: int, has_anchors: bool) -> int:
base = 724
htlc_weight = 172 * num_htlcs
anchor_weight = 660 if has_anchors else 0
return base + htlc_weight + anchor_weight
def calculate_fee(weight: int, feerate_per_kw: int) -> int:
"""Calculate fee in satoshis."""
return (weight * feerate_per_kw + 999) // 1000
Dust Limits
Outputs below dust limit are omitted:
DUST_LIMIT_P2WPKH = 294
DUST_LIMIT_P2WSH = 330
def is_dust(value: int, output_type: str) -> bool:
if output_type == "p2wpkh":
return value < DUST_LIMIT_P2WPKH
return value < DUST_LIMIT_P2WSH
Closing Transaction
Cooperative close with negotiated fee:
Input:
- Funding output
Outputs:
- to_local_scriptpubkey (immediate)
- to_remote_scriptpubkey (immediate)
No timelocks, immediately spendable by both parties.
Related BOLTs
Machine-Readable Summary
{
"bolt": "03",
"title": "Bitcoin Transaction Formats",
"status": "final",
"transaction_types": [
"funding",
"commitment",
"htlc_success",
"htlc_timeout",
"closing"
],
"key_concepts": [
"2-of-2-multisig",
"csv-delays",
"revocation",
"htlc-scripts"
]
}