Why Formal Verification for PCN?
The stakes are high. Bitcoin locked in payment channels represents real money. A single bug in the protocol logic — not the cryptography — can be exploited by an adversary to drain all funds. Unlike a web app bug, there is no patch that recovers stolen satoshis.
What formal verification gives you
Safety
"Bad things never happen." Funds are never stolen; balances are always consistent; no honest party can be defrauded.
Liveness
"Good things eventually happen." Payments eventually complete or fail — they are never stuck in limbo forever, regardless of network conditions.
Security Proofs
"Even with a malicious adversary, the protocol achieves its goals." Cryptographic reduction to hard problems or ideal functionalities.
Bug taxonomy — what can go wrong, and what catches it
Race Condition
Two parties simultaneously try to resolve an HTLC. One message is processed before the other in an unexpected order, leaving funds in limbo or double-counted.
Deadlock / Liveness Violation
A valid payment path cannot complete because timeouts are ordered incorrectly across hops. Every participant waits for another to act first.
Invariant Violation
A sequence of valid-looking state transitions leads to a state where balances do not sum to the channel total, allowing one party to claim more than they own.
Authentication Failure
A routing node can inject or modify an HTLC without being detected, or a revocation secret can be replayed to steal funds from a channel update.
Compositionality Break
A protocol that is safe in isolation becomes unsafe when composed with concurrent payments across multiple channels — wormhole attack, balance correlation.
Spec Ambiguity
The BOLT specification does not fully define behaviour in an edge case; different implementations diverge, one of which has exploitable behaviour.
Three levels of formalization
Protocol-level
Model the state machine. Check all reachable states for safety invariants and liveness properties. Tools: TLA+, Spin.
Implementation-level
Verify actual code against a formal specification. Inductive proof that every code transition preserves invariants. Tools: Why3, Coq, Isabelle.
Cryptographic
Prove security against computationally bounded adversaries. Reduction to hard problems or UC ideal functionalities. Tools: EasyCrypt, Tamarin.
Real PCN bugs formal methods could have caught
• HTLC resolution race: If Alice broadcasts HTLC_FAIL and Bob simultaneously sends HTLC_FULFILL, commitment state can diverge — funds get stuck. TLA+/Spin enumerate this interleaving.
• Timeout ordering: In a 3-hop route, if T_BC ≥ T_AB, Bob can get the preimage from Carol without being able to claim from Alice. TLA+ liveness check catches this immediately.
• Revocation replay: An old commitment transaction published by a malicious party must always be claimable with the revocation key. Tamarin verifies this authentication invariant holds under all adversary strategies.
TLA+ for Lightning Network State Machines
What is TLA+?
Leslie Lamport's Temporal Logic of Actions — a specification language for concurrent and distributed systems. You write state variables, initial conditions, actions (transitions), and invariants. The TLC model checker exhaustively explores all reachable states and verifies that every invariant holds.
Why TLA+ for PCN?
Payment channels are concurrent state machines: two parties exchange messages asynchronously. TLA+ excels at finding unexpected interleavings — the exact scenario that causes a real bug. TLC can check millions of states automatically with no human proof effort.
Lightning Network as TLA+ — simplified pseudospec
(* State variables *)
VARIABLES alice_balance, bob_balance, htlc_in_flight, state,
preimage_revealed, timeout_expired
(* Initial condition: channel opened with TOTAL_FUNDS split equally *)
Init ==
/\ alice_balance = TOTAL_FUNDS \div 2
/\ bob_balance = TOTAL_FUNDS \div 2
/\ htlc_in_flight = 0
/\ state = "open"
/\ preimage_revealed = FALSE
/\ timeout_expired = FALSE
(* Action: Alice proposes an HTLC *)
HTLC_SETUP(amount) ==
/\ state = "open"
/\ alice_balance >= amount
/\ htlc_in_flight' = amount
/\ alice_balance' = alice_balance - amount
/\ state' = "htlc_pending"
/\ UNCHANGED <<bob_balance, preimage_revealed, timeout_expired>>
(* Action: Bob reveals preimage, receives funds *)
HTLC_FULFILL ==
/\ state = "htlc_pending"
/\ preimage_revealed = TRUE
/\ htlc_in_flight' = 0
/\ bob_balance' = bob_balance + htlc_in_flight
/\ state' = "settled"
/\ UNCHANGED <<alice_balance, preimage_revealed, timeout_expired>>
(* Action: HTLC expires, Alice reclaims funds *)
HTLC_FAIL ==
/\ state = "htlc_pending"
/\ timeout_expired = TRUE
/\ htlc_in_flight' = 0
/\ alice_balance' = alice_balance + htlc_in_flight
/\ state' = "failed"
/\ UNCHANGED <<bob_balance, preimage_revealed, timeout_expired>>
(* Safety invariant — checked after EVERY transition *)
BalanceInvariant ==
alice_balance + bob_balance + htlc_in_flight = TOTAL_FUNDS
(* Liveness: payment must eventually resolve *)
PaymentLiveness ==
state = "htlc_pending" ~> (state = "settled" \/ state = "failed")
Interactive state machine — click a state to explore
Pending
Select a state above
Click any state node to see transitions, invariant status, and what variables change.
What TLC finds automatically
TLC explores every possible interleaving of concurrent actions. For example: if Alice sends HTLC_FAIL and Bob simultaneously sends HTLC_FULFILL, TLC identifies the exact sequence — and which one executes first — that could violate the balance invariant. In practice this found 3 ambiguous cases in the BOLT spec (see §3).
Paper — Fabiański et al., NDSS 2025 NDSS 2025
What they verified — click each layer for details
Security Properties
Balance security · Payment atomicity · Conditional liveness
Payment atomicity: A payment either completes fully (preimage revealed and credited) or reverts fully (HTLC failed and funds returned). No partial states are reachable.
Liveness (conditional): If all parties are honest and the network is cooperative, a payment eventually succeeds. Requires that the Bitcoin mempool does not censor transactions — formally stated as a fairness assumption.
Protocol Model
update_add_htlc · commitment_signed · revoke_and_ack · update_fulfill_htlc
• The full commitment transaction signing ceremony (both directions)
• HTLC addition, fulfillment, and failure with proper state tracking
• Revocation key derivation and the penalty transaction mechanism
• Fee negotiation and dust threshold handling
The model has ~4,200 lines of Why3 specification.
Transaction Model
Funding tx · Commitment tx · HTLC-Success tx · HTLC-Timeout tx · Justice tx
• Funding transaction: 2-of-2 multisig output
• Commitment transactions: asymmetric (Alice's vs Bob's), to_local with CSV, to_remote with CLTV
• HTLC-Success and HTLC-Timeout: spending paths from commitment HTLC outputs
• Justice (penalty) transaction: spending revoked commitment output with revocation key
Bitcoin Script semantics are axiomatized — the model trusts that Bitcoin correctly validates scripts.
Bitcoin Script Semantics
Hash preimage · CSV · CLTV · Multisig · Revocation
•
OP_CHECKLOCKTIMEVERIFY (CLTV): output unspendable before absolute block height N•
OP_CHECKSEQUENCEVERIFY (CSV): output unspendable before N blocks after confirmation•
OP_SHA256 / OP_HASH160: hash preimage witness requirements• Multisig: both parties must sign; revocation key invalidates this requirement
These are not proven from Bitcoin source code — they are trusted axioms. Bridging this gap to Bitcoin Core's script interpreter is an open problem.
Proof structure — click any node to expand
Model Checking with Spin / Promela
What is Spin?
Explicit-state model checker by Gerard Holzmann. Uses the Promela language — more imperative than TLA+, with explicit message channels and concurrent processes. Spin exhaustively verifies LTL (Linear Temporal Logic) properties over all reachable states.
Promela vs TLA+
Spin/Promela: Imperative style, message-passing channels, natural for protocol simulations. Better for modelling concurrent processes exchanging typed messages.
TLA+: Mathematical/relational style, state transitions as logical formulas. Better for abstract state machine properties.
Lightning channel in Promela — conceptual model
/* Message types */
mtype = { UPDATE_ADD_HTLC, COMMITMENT_SIGNED, REVOKE_AND_ACK,
UPDATE_FULFILL_HTLC, UPDATE_FAIL_HTLC, ERROR }
/* Shared channel state */
int alice_balance = 500000; /* satoshis */
int bob_balance = 500000;
int htlc_amount = 0;
byte channel_state = 0; /* 0=open, 1=pending, 2=closed */
/* Bidirectional channel between Alice and Bob */
chan a2b = [4] of { mtype, int };
chan b2a = [4] of { mtype, int };
active proctype Alice() {
int amount;
do
:: /* Initiate payment */
amount = 10000;
atomic {
alice_balance >= amount;
alice_balance = alice_balance - amount;
htlc_amount = amount;
channel_state = 1;
}
a2b!UPDATE_ADD_HTLC, amount;
b2a?COMMITMENT_SIGNED, _;
a2b!REVOKE_AND_ACK, 0;
b2a?COMMITMENT_SIGNED, _;
a2b!REVOKE_AND_ACK, 0;
/* Await resolution */
if
:: b2a?UPDATE_FULFILL_HTLC, _ ->
skip /* payment succeeded */
:: b2a?UPDATE_FAIL_HTLC, _ ->
alice_balance = alice_balance + amount;
htlc_amount = 0;
channel_state = 0;
fi
:: break
od
}
active proctype Bob() {
int amount;
do
:: /* Respond to incoming HTLC */
a2b?UPDATE_ADD_HTLC, amount;
b2a!COMMITMENT_SIGNED, 0;
a2b?REVOKE_AND_ACK, _;
b2a!COMMITMENT_SIGNED, 0;
a2b?REVOKE_AND_ACK, _;
if
:: /* Preimage known — fulfill */
b2a!UPDATE_FULFILL_HTLC, 0;
bob_balance = bob_balance + amount;
htlc_amount = 0; channel_state = 0;
:: /* Cannot fulfill — fail */
b2a!UPDATE_FAIL_HTLC, 0;
fi
:: break
od
}
/* Never property: Spin verifies this never becomes false */
never { /* balance sum invariant */
do
:: skip
:: (alice_balance + bob_balance + htlc_amount != 1000000) -> break
od
}
LTL Properties — hover or click for explanation
What it catches: Any state transition that causes funds to appear or disappear. If the model checker finds a counterexample, there is a sequence of messages that results in money creation or destruction.
Status in LN: ✓ Verified — holds for all reachable states in the Fabiański et al. model.
What it catches: Payment deadlocks. If this formula is violated, Spin produces a counterexample trace showing the exact sequence of actions that leads to a state where neither
payment_complete nor payment_failed is ever reached.Status in LN: ⚠ Conditional — holds only under fairness assumption (Bitcoin transactions eventually confirmed). Violated if a counterparty can censor mempool indefinitely.
What it catches: Failures in the watchtower / revocation mechanism. If a malicious party publishes an old commitment, the honest party (or their watchtower) must respond within the CSV timelock window.
Status in LN: ✓ Verified by PISA paper (watchtower protocol). Requires the watchtower is online and has the revocation data.
What it catches: Channel griefing — a counterparty who neither cooperates with closing nor triggers unilateral close. In practice this is a griefing attack: attacker keeps the channel open to waste the honest party's capital.
Status in LN: ✓ Holds — any party can force-close unilaterally at any time by broadcasting their latest commitment transaction.
What it catches: HTLC accumulation attacks. If an attacker can keep HTLCs in-flight indefinitely without triggering a timeout, they can exhaust the channel's HTLC slot capacity (max 483 per BOLT spec), making the channel unusable.
Status in LN: ✓ Holds — HTLC timeouts are enforced on-chain if counterparty is unresponsive.
Cryptographic Security Proofs — UC Framework
Universal Composability (UC) (Canetti 2001): a protocol P is secure if any adversary attacking P can be "simulated" against an ideal functionality F — a trusted third party that does exactly what we want. Security is preserved even when P is composed with other protocols running concurrently.
Ideal functionality F_LN
/* The "God's-eye" ideal version of LN */
On receive (OPEN, Alice, Bob, amount):
channel[Alice,Bob] = {
bal_a: amount / 2,
bal_b: amount / 2,
open: true
}
On receive (PAY, from=Alice, to=Bob, amt):
if channel[Alice,Bob].bal_a >= amt:
channel[Alice,Bob].bal_a -= amt
channel[Alice,Bob].bal_b += amt
output (SUCCESS, Bob, amt)
else:
output (FAILURE, Alice)
On receive (CLOSE, Alice, Bob):
pay channel[Alice,Bob].bal_a to Alice on-chain
pay channel[Alice,Bob].bal_b to Bob on-chain
channel[Alice,Bob].open = false
/* Real LN ≡_UC F_LN iff:
for all env Z, adv A, ∃ sim S:
EXEC[LN, A, Z] ≈ EXEC[F_LN, S, Z] */
UC Proof components
Environment Z
Adaptive adversary that controls all inputs and observes outputs
Adversary A / Simulator S
Corrupts parties, controls the network, observes messages
Real Protocol LN
Actual HTLC messages, Bitcoin transactions, timeouts
Ideal Func F_LN
Trusted party: instant, atomic, correct balance updates
Malavolta et al. — Concurrency and Privacy in PCN
amount_alice ≠ amount_bob ≠ amount_carol from any single observer's view.Key security properties — click to expand
Balance Security
Honest party always gets at least what they are owed.
Value Atomicity
Either all HTLCs in a payment resolve, or none do.
Relationship Anonymity
Routing nodes cannot identify sender/receiver from HTLC data alone.
Griefing Resistance
Attacker cannot indefinitely delay an honest party.
Paper — Sprites (Miller et al., WWW 2019)
The HTLC timeout ordering constraint
Why timeouts must be staggered
In a 3-hop route Alice → Bob → Carol:
Constraint: TBC < TAB is mandatory for Bob's safety. If Carol reveals the preimage at block 149, Bob must be able to claim the Alice→Bob HTLC before block 200. Bob needs TAB - TBC ≥ δ blocks to get his transaction confirmed.
Consequence: With N hops and timeout T per hop, total collateral lockup = N × T blocks. For N=20 hops and T=144 blocks (1 day): 20 days of locked capital.
Sprites solution
Sprites Mechanism
Replace per-hop HTLC timeouts with a single global smart contract ("PreimageManager"). Each hop locks funds in the contract with a single global expiry time T.
Once the preimage is submitted to the contract, all hops can claim simultaneously — no cascade needed. Total lockup: O(T) regardless of N.
Formal model result
Sprites formally proves: under their model, collateral efficiency is optimal — no HTLC-based protocol can do better than O(T) lockup per payment.
Limitation: requires a smart contract platform (Ethereum), not directly applicable to Bitcoin's script-limited environment without modifications.
Collateral lockup calculator
Formal Verification Tools Comparison
Click any row to expand details about learning curve, strengths, limitations, and PCN papers that use each tool.
| Tool | Type | Language | What it verifies | PCN papers |
|---|---|---|---|---|
TLA+ / TLC
|
Model checking | TLA+ | State machine safety & liveness | LN state machine, multi-hop atomicity |
Spin
|
Model checking | Promela | Concurrent protocol LTL properties | Channel protocols, HTLC ordering |
Why3
|
Deductive verification | WhyML | Code correctness vs. spec | Fabiański NDSS 2025 |
Coq
|
Proof assistant | Gallina | Mathematical theorems about protocols | Perun framework, virtual channels |
Tamarin
|
Protocol verifier | Tamarin | Cryptographic protocol properties | Authentication, key agreement in LN |
EasyCrypt
|
Crypto proof assistant | EasyCrypt | UC/game-based security proofs | PCN privacy, AMHL security |
Specification Coverage Dashboard
Which LN protocol components have been formally verified? Hover any cell for details.
| Component | TLA+ | Why3 | UC Proof | Tamarin | Notes |
|---|
Open problems in formal verification
1. Full MPP atomicity proof under concurrency
Multi-Path Payments split a single payment across multiple channels. Proving that all sub-payments atomically succeed or fail — even under concurrent interference from other payments — remains an open problem. The interaction between payment shards and routing is complex.
2. Eltoo / LN-Symmetry formal security model
Eltoo (SIGHASH_ANYPREVOUT) removes the asymmetric revocation mechanism entirely. The security model fundamentally changes — instead of revocation keys, the protocol relies on update transactions superseding each other. No complete UC-security proof exists for Eltoo yet.
3. Route blinding privacy proof
Route blinding (BOLT 12) hides the recipient's node identity by encrypting the last hops. A formal privacy proof — showing the adversary cannot identify the recipient beyond a negligible advantage — has not been published. The interaction with onion routing makes this subtle.
4. Channel factory security under Byzantine participants
Channel factories allow N parties to share a single funding transaction. If any participant goes Byzantine (not just malicious but also offline), the safety and liveness of the remaining channels must be formally guaranteed. This requires a multi-party UC model that is significantly harder than 2-party channels.
5. Post-quantum PCN security proofs
Current PCN proofs assume hardness of ECDLP (for signatures) and collision-resistance of SHA-256 (for HTLC hashes). Post-quantum adaptor signatures change the security assumptions. No formal proof of a complete PQ-PCN exists.