Menu
Lightning Intermediate 7 min read

Hash Time-Locked Contracts (HTLCs)

How HTLCs enable trustless multi-hop Lightning payments. Hash locks, time locks, and atomic payment resolution.

htlc hash-lock time-lock preimage atomic

Hash Time-Locked Contracts (HTLCs)

HTLCs are the cryptographic mechanism that enables trustless multi-hop payments in Lightning. They ensure that either all hops succeed or all hops fail—atomic execution across untrusted intermediaries.

Why HTLCs Matter for Agents

BenefitDescription
TrustlessNo need to trust routing nodes
AtomicAll-or-nothing payment resolution
ProgrammableConditional payment logic
ComposableChain across multiple hops

The Two Locks

An HTLC combines two conditions:

1. Hash Lock

The payment is locked by a hash. To claim funds, the recipient must reveal the preimage—the secret value that hashes to the known hash.

hash = SHA256(preimage)

If you know: preimage
And: SHA256(preimage) == hash
Then: You can claim the funds

2. Time Lock

If the preimage isn’t revealed in time, funds return to the sender. This prevents funds from being locked forever.

If: current_time > timeout
Then: Sender can reclaim funds

HTLC Structure

┌─────────────────────────────────────────────┐
│                   HTLC                       │
├─────────────────────────────────────────────┤
│ Amount: 10,000 sats                         │
│ Hash: a1b2c3d4...                           │
│ Timeout: block 800,000                      │
│                                             │
│ Claim path (recipient):                     │
│   IF <hash> == SHA256(<preimage>)           │
│   AND <recipient_sig>                       │
│                                             │
│ Refund path (sender):                       │
│   IF <current_block> > 800,000              │
│   AND <sender_sig>                          │
└─────────────────────────────────────────────┘

Multi-Hop Payment Flow

Setup Phase (Forward)

  1. Recipient creates preimage and hash

    preimage = random_32_bytes()
    payment_hash = SHA256(preimage)
  2. Recipient shares hash in invoice

    Invoice contains: payment_hash, amount, expiry
  3. Sender finds route and creates HTLCs

    Alice → Carol → Bob
    
    Alice creates HTLC to Carol (hash, timeout=100)
    Carol creates HTLC to Bob (hash, timeout=90)

Resolution Phase (Backward)

  1. Recipient reveals preimage

    Bob knows preimage (he created it)
    Bob gives preimage to Carol, claims HTLC
  2. Intermediaries resolve backwards

    Carol now knows preimage
    Carol gives preimage to Alice, claims HTLC
  3. Payment complete

    Alice: -10,000 sats
    Carol: ±0 (minus routing fee)
    Bob: +10,000 sats (minus fees)

Timeout Decrements

Each hop has a shorter timeout than the previous hop. This ensures intermediaries have time to claim their incoming HTLC after revealing the preimage.

Alice ────────→ Carol ────────→ Bob
timeout=100     timeout=90      timeout=80
    │               │               │
    └───── 10 blocks ───────────────┘
          (cltv_expiry_delta)

If Bob doesn’t reveal the preimage by block 80:

  • Bob’s HTLC from Carol times out
  • Carol’s HTLC from Alice times out
  • Funds return to Alice

HTLC States

┌──────────┐
│ Offered  │ ──── fulfill (preimage) ────→ ┌───────────┐
└──────────┘                               │ Fulfilled │
     │                                     └───────────┘

     └──── timeout (blocks pass) ────────→ ┌───────────┐
                                          │ Timed Out │
                                          └───────────┘

HTLC Operations

OperationDirectionMeaning
update_add_htlcForwardAdd new HTLC
update_fulfill_htlcBackwardReveal preimage
update_fail_htlcBackwardFail with reason
update_fail_malformed_htlcBackwardMalformed onion

HTLC Script (On-Chain)

When force-closing with pending HTLCs, the script enforces the conditions:

# Offered HTLC (from Alice's perspective)
OP_DUP OP_HASH160 <revocation_hash> OP_EQUAL
OP_IF
    OP_CHECKSIG  # Revocation path
OP_ELSE
    <remote_pubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_IF
        OP_HASH160 <payment_hash> OP_EQUALVERIFY
        2 OP_SWAP <local_pubkey> 2 OP_CHECKMULTISIG  # Payment path
    OP_ELSE
        OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
        OP_CHECKSIG  # Timeout path
    OP_ENDIF
OP_ENDIF

HTLC Limits

LimitValueReason
Max HTLCs per channel483Script size limits
Min HTLC amount~1 satDust limit
Max HTLC amountChannel capacityLiquidity
Typical timeout delta40-144 blocksSecurity margin

Failure Reasons

When an HTLC fails, it includes a reason code:

CodeReasonMeaning
incorrect_or_unknown_payment_detailsUnknown hashInvoice doesn’t exist
final_expiry_too_soonTimeout too closeNot enough time
incorrect_cltv_expiryWrong timeoutDoesn’t match route
amount_below_minimumToo smallBelow node minimum
fee_insufficientLow feeNeed higher routing fee
channel_disabledChannel offlineTry different route
temporary_channel_failureLiquidity issueNot enough capacity

Agent Implementation

Creating a Payment Hash

import hashlib
import secrets

# Generate random preimage
preimage = secrets.token_bytes(32)

# Calculate payment hash
payment_hash = hashlib.sha256(preimage).digest()

print(f"Preimage: {preimage.hex()}")
print(f"Payment hash: {payment_hash.hex()}")

Verifying a Preimage

def verify_preimage(preimage_hex: str, payment_hash_hex: str) -> bool:
    preimage = bytes.fromhex(preimage_hex)
    expected_hash = bytes.fromhex(payment_hash_hex)
    actual_hash = hashlib.sha256(preimage).digest()
    return actual_hash == expected_hash

Monitoring HTLC Status

# Via LND
pending = lnd.pending_channels()

for channel in pending.pending_force_closing_channels:
    for htlc in channel.pending_htlcs:
        print(f"Amount: {htlc.amount}")
        print(f"Outgoing: {htlc.outgoing}")
        print(f"Blocks until timeout: {htlc.blocks_til_maturity}")

Security Considerations

Preimage Privacy

  • Never reuse preimages—reveals payment correlation
  • Generate securely—use cryptographic randomness
  • Store safely—losing preimage = can’t prove payment

Timeout Selection

  • Too short: Risk of force close before resolution
  • Too long: Funds locked if payment fails
  • Typical: 40-144 blocks per hop

HTLC Probing

Attackers can probe channel balances by sending HTLCs they control:

  • Send HTLC with known preimage
  • If it routes, reveal preimage
  • Learn channel capacity at each hop

Mitigation: Use route hints, private channels


Machine-Readable Summary

{
  "topic": "htlcs",
  "key_concepts": [
    "hash-lock",
    "time-lock",
    "preimage",
    "atomic-execution"
  ],
  "operations": [
    "update_add_htlc",
    "update_fulfill_htlc",
    "update_fail_htlc"
  ],
  "security_considerations": [
    "preimage-privacy",
    "timeout-selection",
    "htlc-probing"
  ]
}