Skip to content
ppad-techPublic

About

Lightning onion routing protocol.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ppad-bolt4

A pure Haskell implementation of Lightning Network onion routing, BOLT #4: onion construction and processing, returning errors, and route blinding.

Usage

A sample GHCi session:

  > :set -XOverloadedStrings
  > import qualified Data.ByteString as BS
  > import qualified Lightning.Protocol.BOLT1 as BOLT1
  > import Lightning.Protocol.BOLT4
  >
  > -- keys (use a fresh random session key for every onion)
  > let Just session = secret_key (BS.replicate 32 0x41)
  > let Just alice = secret_key (BS.replicate 32 0x42)
  > let Just bob = secret_key (BS.replicate 32 0x43)
  >
  > -- a two-hop route, paying bob through alice
  > let Just amt = BOLT1.milli_satoshi 100000
  > let Just scid = BOLT1.short_channel_id 800000 1 0
  > let pay = empty_hop_payload { hp_amt_to_forward = Just amt }
  > :{
  ghci| let to_alice = Hop (public_key alice) pay {
  ghci|         hp_outgoing_cltv_value = Just 800040
  ghci|       , hp_short_channel_id    = Just scid
  ghci|       }
  ghci|     to_bob = Hop (public_key bob) pay {
  ghci|         hp_outgoing_cltv_value = Just 800000
  ghci|       }
  ghci| :}
  > let hash = BS.replicate 32 0xff
  > let Right (onion, secrets) = construct session [to_alice, to_bob] hash
  > BS.length (encode_onion_packet onion)
  1366
  >
  > -- each hop peels off its layer
  > let Right (Forward fwd) = process alice onion hash Nothing
  > hp_short_channel_id (fwd_payload fwd)
  Just (ShortChannelId 879609302220865536)
  > let next = fwd_next_packet fwd
  > let Right (Receive rcv) = process bob next hash Nothing
  > hp_amt_to_forward (rcv_payload rcv)
  Just (MilliSatoshi 100000)
  >
  > -- bob fails the payment, alice wraps the error, and the sender
  > -- attributes it to bob
  > let failure = FailureMessage MppTimeout BOLT1.empty_tlv_stream
  > let Right err = construct_error (rcv_shared_secret rcv) failure
  > unwrap_error secrets (wrap_error (fwd_shared_secret fwd) err)
  Attributed 1 (FailureMessage {fm_failure = MppTimeout,
    fm_tlvs = TlvStream []})
  >
  > -- bob hands out a blinded route to himself
  > let Just seed = secret_key (BS.replicate 32 0x01)
  > let mine = empty_blinded_hop_data { bhd_path_id = Just "invoice 1" }
  > let Right path = create_blinded_path seed [(public_key bob, mine)]
  > let [hop] = bp_hops path
  >
  > -- the sender reaches the route's introduction node through alice
  > :{
  ghci| let blinded = Hop (bp_first_node_id path) pay {
  ghci|         hp_outgoing_cltv_value = Just 800000
  ghci|       , hp_total_amount_msat   = Just amt
  ghci|       , hp_encrypted_data      = Just (bh_encrypted_data hop)
  ghci|       , hp_current_path_key    = Just (bp_first_path_key path)
  ghci|       }
  ghci| :}
  > let Right (onion', _) = construct session [to_alice, blinded] hash
  > let Right (Forward fwd') = process alice onion' hash Nothing
  > let next' = fwd_next_packet fwd'
  > let Right (Receive rcv') = process bob next' hash Nothing
  > fmap (bhd_path_id . bi_data) (rcv_blinded rcv')
  Just (Just "invoice 1")

Nodes further along a blinded route pass the path_key they received (bi_next_path_key of the previous hop) as the last argument of process, which then derives their blinded private key.

Documentation

Haddocks (API documentation, etc.) are hosted at docs.ppad.tech/bolt4.

Specification

This library implements BOLT #4 as of lightning/bolts commit 1aadb719 (2026-09-21); a copy lives in etc/. It is tested against the onion-test.json, onion-error-test.json, route-blinding-test.json and blinded-payment-onion-test.json vectors, and the 'Returning Errors' trace.

Fundamental types and the TLV format come from ppad-bolt1; allowed_features is a ppad-bolt9 feature vector.

Not implemented:

  • Attributable failures (option_attribution_data): the attribution_data of update_fail_htlc and update_fulfill_htlc, and HTLC hold times.
  • Onion messages (onion_message_packet, onionmsg_tlv).
  • The 32 KiB limit on return packets: construct_error doesn't enforce it, and wrap_error doesn't truncate longer packets.
  • The origin node's dummy decryption rounds: unwrap_error stops at the hop whose HMAC matches.
  • The blinded_path wire encoding, and the computation of a blinded route's total fees and CLTV delta.

process enforces the onion decryption requirements and the path key rules of route blinding; the reader requirements that depend on the HTLC or the node's channels (required fields, amount and expiry checks, payment_relay and payment_constraints, allowed_features, replayed HMACs) are left to the caller.

Performance

The aim is best-in-class performance for pure Haskell code. Run the benchmarks with:

$ cabal bench

Security

This library has received no security audit. Secret keys, shared secrets and payment secrets are abstract and kept out of Show output; shared and payment secrets compare in constant time, and process checks onion HMACs in constant time. Scalar multiplications involving secrets use ppad-secp256k1's constant-time implementation.

Development

You'll require Nix with flake support enabled. Enter a development shell with:

$ nix develop

Then do e.g.:

$ cabal repl ppad-bolt4

to get a REPL for the main library.

About

Lightning onion routing protocol.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages