bolt5

On-chain transaction handling for Lightning (docs.ppad.tech/bolt5).
git clone git://git.ppad.tech/bolt5.git
Log | Files | Refs | README | LICENSE

commit 5e2409b2cd44b7a62ff47ba9a4fd76e7d43537fe
parent 897e5397e0818a9014ba6f4ba2e29df8788a0295
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 18 Apr 2026 21:16:11 +0800

meta: additional docs

Diffstat:
AREADME.md | 40++++++++++++++++++++++++++++++++++++++++
Aplans/ARCH1.md | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplans/IMPL1.md | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplans/PPAD-TX.md | 247+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 536 insertions(+), 0 deletions(-)

diff --git a/README.md b/README.md @@ -0,0 +1,40 @@ +# ppad-bolt5 + +![](https://img.shields.io/badge/license-MIT-brightgreen) + +A pure Haskell implementation of [BOLT #5][bolt5] (Lightning Network +on-chain transaction handling), including logic for mutual close, +unilateral close, and revoked transaction close scenarios. + +## Documentation + +Haddocks are hosted at [docs.ppad.tech/bolt5][hadoc]. + +## Security + +This library aims at the maximum security achievable in a +garbage-collected language under an optimizing compiler such as GHC. +If you discover any vulnerabilities, please disclose them via +security@ppad.tech. + +## Development + +You'll require [Nix][nixos] with [flake][flake] support enabled. Enter a +development shell with: + +``` +$ nix develop +``` + +Then do e.g.: + +``` +$ cabal build +$ cabal test +$ cabal bench +``` + +[bolt5]: https://github.com/lightning/bolts/blob/master/05-onchain.md +[nixos]: https://nixos.org/ +[flake]: https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html +[hadoc]: https://docs.ppad.tech/bolt5 diff --git a/plans/ARCH1.md b/plans/ARCH1.md @@ -0,0 +1,125 @@ +# ARCH plan v1 + +## Goals + +- Implement BOLT #5 on-chain handling for mutual, unilateral, revoked + closes. +- Encode invariants in types; total, safe API. +- Provide pure resolution engine that consumes chain events and returns + actions to take (spend, watch, wait). +- Keep dependencies minimal; prefer ppad libraries and GHC boot libs. +- Implement tx primitives locally (Bitcoin.Prim.Tx) for later extraction + into ppad-tx. + +## Non-goals (v1) + +- Wallet integration, mempool publishing, or networking. +- Full transaction construction for every policy variant unless + needed by spec examples. +- Persistent storage or chain sync. + +## Public API shape (lib/Lightning/Protocol/BOLT5) + +- Re-export primary types and functions from internal modules. +- Keep a small surface: + - Channel parameters and state + - On-chain events (funding spent, commitment seen, spend seen) + - Resolution actions (spend tx, watch, wait, fail channel) + - HTLC tx generation helpers + +## Module layout + +### Tx primitives (Bitcoin.Prim.*) + +These modules follow ppad-script conventions and will later be extracted +to ppad-tx: + +- Bitcoin.Prim.Tx + - Tx, TxIn, TxOut, OutPoint, Witness types + - Serialisation (to/from ByteArray, base16) + - TxId computation (double SHA256 of serialised tx) +- Bitcoin.Prim.Tx.Sighash + - SIGHASH flags, sighash computation for legacy/segwit + +### BOLT #5 modules (Lightning.Protocol.BOLT5.*) + +- Lightning.Protocol.BOLT5 + - public re-exports, minimal glue +- Lightning.Protocol.BOLT5.Types + - newtypes, ADTs, smart constructors +- Lightning.Protocol.BOLT5.Channel + - channel parameters/state, validation +- Lightning.Protocol.BOLT5.OnChain + - resolution engine per BOLT #5 +- Lightning.Protocol.BOLT5.HTLC + - HTLC tx generation, offered/received logic +- Lightning.Protocol.BOLT5.Weight + - weight calculations, constants + +(Keep clear separation between tx primitives and Lightning-specific +logic; tx modules should have no Lightning dependencies.) + +## Core types and invariants + +- newtype Satoshi, BlockHeight, BlockDepth, CSVDelay, Weight. +- newtype TxId, OutPoint, TxIndex for on-chain references. +- data Role = Local | Remote. +- data CommitmentKind = LocalCommit | RemoteCommit | RevokedCommit. +- data OutputKind = ToLocal | ToRemote | AnchorLocal | AnchorRemote + | HTLCOffered | HTLCReceived. +- data HTLC with amount, payment hash, cltv expiry, offerer. +- data CommitmentView summarizes a commitment tx: + - outputs list with kind, amount, scripts, htlc id +- data OnChainEvent: + - FundingSpent ClosingTx + - CommitmentSeen CommitmentKind CommitmentView + - SpendSeen OutPoint SpendingTx +- data ResolutionAction: + - Publish Tx + - Watch OutPoint + - WaitUntil BlockHeight + - FailChannel Reason + +Smart constructors enforce: +- Amounts >= 0 and within dust rules. +- HTLC CLTV within bounds. +- CSV delays non-negative. + +## Resolution engine responsibilities + +- Given channel params + current state + event, derive required + actions per BOLT #5. +- Track unresolved outputs and allow re-entry on reorgs. +- Local/remote symmetry: logic should be parametric on Role. +- Separate "detection" (what tx is this) from "resolution" (what to do). + +## Dependency strategy + +- Use ppad-script for Script representation. +- Use ppad-sha256 for txid computation (double SHA256). +- Implement tx primitives (Bitcoin.Prim.Tx) locally; extract to ppad-tx + later. This will unify duplicated types across bolt2/3/7 (TxId, + Outpoint, Satoshi, etc.). +- Avoid new deps; ask before adding any non-boot libraries. +- Use ByteString for hashes; keep MagicHash for hot paths only. + +## Testing strategy + +- Unit tests with vectors from BOLT #5 (HTLC outputs, delays). +- Property tests for invariants: + - All unresolved outputs produce actions + - Symmetry between local/remote roles + - Idempotent handling across reorg replay + +## Benchmarks + +- Criterion benchmarks for resolution engine on varied HTLC counts. +- Weigh benchmarks for allocation hot spots in HTLC generation. + +## Concurrency notes (for agent delegation) + +- Tx primitives (Bitcoin.Prim.Tx) are fully independent; start first. +- Types + Channel params can be built once tx primitives exist. +- Weight calculations and HTLC tx generation are independent. +- On-chain resolution can be built once types are fixed. +- Tests and benches can proceed after public API stabilizes. diff --git a/plans/IMPL1.md b/plans/IMPL1.md @@ -0,0 +1,124 @@ +# IMPL plan v1 + +## Step 0: Recon (independent) + +- Inventory ppad-* libraries for tx/script/crypto helpers. +- Review existing BOLT implementations for duplicated types: + - bolt2/Types.hs: TxId, Outpoint, Satoshis, MilliSatoshis, Point, + Signature, ScriptPubKey, ChainHash, ShortChannelId. + - bolt3/Types.hs: TxId, Outpoint, Sequence, Locktime, Script, Witness, + Satoshi, MilliSatoshi, Point, Pubkey, PaymentHash. + - bolt3/Tx.hs: Lightning-specific tx builders (CommitmentTx, HTLCTx, + ClosingTx) but NOT raw Bitcoin tx serialisation. + - bolt7/Types.hs: ChainHash, ShortChannelId, Signature, Point. + - Common gap: no raw Tx structure, no serialisation, no txid computation. +- Review ppad-script (../script) for module conventions. +- Confirm which variants to support: anchors, no-anchors, etc. + +NOTE: Significant type duplication across bolt impls. Future ppad-tx +extraction should unify these; for now, implement fresh in Bitcoin.Prim.Tx +with eye toward becoming the canonical source. + +## Step 1: Tx primitives (independent) + +- Create `lib/Bitcoin/Prim/Tx.hs` following ppad-script conventions. +- Implement core types (filling gaps from bolt3/Types.hs): + - Tx (version, inputs, outputs, locktime, witnesses) + - TxIn (outpoint, scriptSig, sequence) + - TxOut (value, scriptPubKey) +- Reuse where sensible: + - TxId, Outpoint, Sequence, Locktime patterns from bolt3/Types.hs + - Or define fresh and unify later during ppad-tx extraction. +- Serialisation (the key missing piece): + - to_bytes / from_bytes (raw tx format) + - to_base16 / from_base16 + - Segwit marker/flag handling (0x00 0x01 prefix) +- TxId computation (double SHA256 of non-witness serialisation). +- Keep this module independent of Lightning; will later become ppad-tx. + +## Step 1b: Sighash (depends on Step 1) + +- Create `lib/Bitcoin/Prim/Tx/Sighash.hs`. +- SIGHASH flags (ALL, NONE, SINGLE, ANYONECANPAY). +- Legacy sighash computation. +- BIP143 segwit sighash computation (needed for commitment tx signing). + +## Step 2: BOLT5 types and invariants (depends on Step 1) + +- Create `lib/Lightning/Protocol/BOLT5/Types.hs`. +- Implement newtypes and smart constructors: + - Satoshi, Weight, BlockHeight, CSVDelay. +- Re-export or newtype-wrap TxId, OutPoint from Bitcoin.Prim.Tx. +- Define Role, CommitmentKind, OutputKind, HTLC. +- Provide total helpers; no partials. + +## Step 3: Channel parameters + state (depends on Step 2) + +- Create `lib/Lightning/Protocol/BOLT5/Channel.hs`. +- Define ChannelParams (dust limits, delays, feature flags). +- Define ChannelState (known commitments, unresolved outputs). +- Encode invariants (e.g., anchor vs non-anchor rules). + +## Step 4: HTLC tx generation (depends on Steps 1-2) + +- Create `lib/Lightning/Protocol/BOLT5/HTLC.hs`. +- Implement offered/received HTLC tx templates. +- Use Bitcoin.Prim.Tx for tx construction. +- Add weight helpers and witness size constants. + +## Step 5: Weight calculations (depends on Step 2) + +- Create `lib/Lightning/Protocol/BOLT5/Weight.hs`. +- Implement expected weights from BOLT #5 Appendix A. +- Provide functions to compute fee based on feerate. + +## Step 6: On-chain resolution engine (depends on Steps 2-3) + +- Create `lib/Lightning/Protocol/BOLT5/OnChain.hs`. +- Implement resolution rules for: + - Mutual close + - Local commitment + - Remote commitment + - Revoked commitment +- Track unresolved outputs and required actions. +- Handle reorg safety by making actions idempotent. + +## Step 7: Public module + cabal glue (depends on Steps 1-6) + +- Update `lib/Lightning/Protocol/BOLT5.hs` to re-export API. +- Update `ppad-bolt5.cabal` for new modules (including Bitcoin.Prim.*). +- Add ppad-script, ppad-sha256 dependencies. + +## Step 8: Tests (depends on Steps 1-7) + +- Replace placeholder tests in `test/Main.hs`. +- Add unit tests for tx serialisation (known vectors). +- Add tasty-hunit vectors from BOLT #5 text. +- Add tasty-quickcheck properties for invariants. + +## Step 9: Benchmarks (depends on Steps 1-7) + +- Update `bench/Main.hs` and `bench/Weight.hs`. +- Provide NFData instances for benchmarked types. +- Benchmark tx serialisation/deserialisation. + +## Step 10: Docs and examples (parallel with Steps 7-9) + +- Add Haddock examples for exported functions. +- Ensure module headers, line length, OPTIONS_HADDOCK prune. + +## Delegation map + +- Agent A: Steps 1-1b (Tx primitives + Sighash). +- Agent B: Steps 2-3 (BOLT5 Types + Channel). +- Agent C: Steps 4-5 (HTLC + Weight). +- Agent D: Step 6 (OnChain engine). +- Agent E: Steps 8-9 (Tests + Bench). +- Integrator: Step 7 + Step 10, resolve API mismatches. + +## Future: ppad-tx extraction + +Once BOLT5 is stable, extract Bitcoin.Prim.Tx* into standalone ppad-tx: +- Move lib/Bitcoin/Prim/Tx.hs and Sighash.hs to new repo. +- Update ppad-bolt5 to depend on ppad-tx. +- No API changes needed if module names are preserved. diff --git a/plans/PPAD-TX.md b/plans/PPAD-TX.md @@ -0,0 +1,247 @@ +# ppad-tx + +Minimal Bitcoin transaction primitives for ppad libraries. + +## Motivation + +Multiple ppad-bolt implementations duplicate core tx-related types: + +| Type | bolt2 | bolt3 | bolt7 | +|------------------|-------|-------|-------| +| TxId | yes | yes | - | +| Outpoint | yes | yes | - | +| Sequence | - | yes | - | +| Locktime | - | yes | - | +| Script | - | yes | - | +| Witness | - | yes | - | +| Satoshi(s) | yes | yes | - | +| MilliSatoshi(s) | yes | yes | - | +| Point/Pubkey | yes | yes | yes | +| Signature | yes | - | yes | +| ChainHash | yes | - | yes | +| ShortChannelId | yes | - | yes | + +Common gap across all: no raw Tx structure, no serialisation, no txid +computation from raw bytes. + +ppad-tx will provide canonical definitions and allow bolt impls to depend +on a single source. + +## Scope + +### In scope (v1) + +- Raw transaction types (Tx, TxIn, TxOut) +- Outpoint, Sequence, Locktime +- Serialisation to/from bytes (legacy and segwit formats) +- TxId computation (double SHA256 of non-witness serialisation) +- Sighash computation (legacy and BIP143 segwit) +- Basic amount types (Satoshi) + +### Out of scope (v1) + +- Script execution or validation +- Signature creation/verification (use ppad-secp256k1) +- Transaction building DSL +- PSBT support +- Taproot/BIP341 sighash (defer to v2) + +## Module layout + +``` +lib/ + Bitcoin/ + Prim/ + Tx.hs -- core types, serialisation, txid + Tx/ + Sighash.hs -- sighash computation +``` + +Follow ppad-script conventions: +- `Bitcoin.Prim.*` namespace +- ByteArray for internal representation where appropriate +- base16 conversion utilities +- OPTIONS_HADDOCK prune + +## Core types + +```haskell +-- | Transaction ID (32 bytes, little-endian double-SHA256). +newtype TxId = TxId BS.ByteString + +-- | Transaction outpoint. +data OutPoint = OutPoint + { op_txid :: {-# UNPACK #-} !TxId + , op_vout :: {-# UNPACK #-} !Word32 + } + +-- | Transaction input. +data TxIn = TxIn + { txin_prevout :: {-# UNPACK #-} !OutPoint + , txin_script_sig :: !BS.ByteString + , txin_sequence :: {-# UNPACK #-} !Word32 + } + +-- | Transaction output. +data TxOut = TxOut + { txout_value :: {-# UNPACK #-} !Word64 -- satoshis + , txout_script_pubkey :: !BS.ByteString + } + +-- | Witness stack for a single input. +newtype Witness = Witness [BS.ByteString] + +-- | Complete transaction. +data Tx = Tx + { tx_version :: {-# UNPACK #-} !Word32 + , tx_inputs :: ![TxIn] + , tx_outputs :: ![TxOut] + , tx_witnesses :: ![Witness] -- empty list for legacy tx + , tx_locktime :: {-# UNPACK #-} !Word32 + } +``` + +## Serialisation + +### Format detection + +- Legacy: version || inputs || outputs || locktime +- Segwit: version || 0x00 || 0x01 || inputs || outputs || witnesses || locktime + +Detect segwit by marker byte (0x00) after version. If present and followed +by flag (0x01), parse as segwit. + +### Public API + +```haskell +-- Serialisation +to_bytes :: Tx -> BS.ByteString -- segwit format if witnesses present +from_bytes :: BS.ByteString -> Maybe Tx + +to_base16 :: Tx -> BS.ByteString +from_base16 :: BS.ByteString -> Maybe Tx + +-- Legacy serialisation (for txid computation) +to_bytes_legacy :: Tx -> BS.ByteString + +-- TxId +txid :: Tx -> TxId -- double SHA256 of legacy serialisation +``` + +### Encoding details + +All integers little-endian. Variable-length integers (compactSize): +- 0x00-0xfc: 1 byte +- 0xfd: 0xfd || 2 bytes (little-endian) +- 0xfe: 0xfe || 4 bytes +- 0xff: 0xff || 8 bytes + +## Sighash + +### Flags + +```haskell +data SighashType + = SIGHASH_ALL + | SIGHASH_NONE + | SIGHASH_SINGLE + | SIGHASH_ALL_ANYONECANPAY + | SIGHASH_NONE_ANYONECANPAY + | SIGHASH_SINGLE_ANYONECANPAY +``` + +### Legacy sighash + +Per BIP-143 predecessor. Modify tx copy based on flags, append sighash +type as 4-byte LE, double SHA256. + +### BIP143 segwit sighash + +Required for signing segwit inputs. Precomputed: +- hashPrevouts: SHA256(SHA256(all input outpoints)) +- hashSequence: SHA256(SHA256(all input sequences)) +- hashOutputs: SHA256(SHA256(all outputs)) + +Then: +``` +version || hashPrevouts || hashSequence || outpoint || scriptCode || +value || sequence || hashOutputs || locktime || sighashType +``` + +### Public API + +```haskell +-- Legacy +sighash_legacy + :: Tx + -> Int -- input index + -> BS.ByteString -- scriptPubKey being spent + -> SighashType + -> BS.ByteString -- 32-byte hash + +-- BIP143 segwit +sighash_segwit + :: Tx + -> Int -- input index + -> BS.ByteString -- scriptCode + -> Word64 -- value being spent + -> SighashType + -> BS.ByteString -- 32-byte hash +``` + +## Dependencies + +Minimal: +- base +- bytestring +- primitive (for ByteArray if needed) +- ppad-sha256 (for txid and sighash) +- ppad-base16 (for hex conversion) + +## Testing + +- Known tx vectors from Bitcoin Core / BIPs +- Round-trip: from_bytes . to_bytes == id +- TxId computation against known txids +- Sighash vectors from BIP143 + +Sources: +- BIP143 test vectors +- Bitcoin Core's tx_valid.json / tx_invalid.json +- Manually constructed edge cases (empty witness, max inputs, etc.) + +## Implementation steps + +### Step 1: Core types + serialisation (independent) + +- Define Tx, TxIn, TxOut, OutPoint, Witness +- Implement compactSize encoding/decoding +- Implement to_bytes / from_bytes (both formats) +- Implement txid computation + +### Step 2: Sighash (depends on Step 1) + +- Define SighashType +- Implement legacy sighash +- Implement BIP143 segwit sighash + +### Step 3: Tests + benchmarks + +- Add tx serialisation round-trip tests +- Add known vector tests for txid +- Add BIP143 sighash vectors +- Criterion benchmarks for serialisation +- Weigh benchmarks for allocations + +### Step 4: Polish + +- Haddock documentation with examples +- Ensure line length < 80 +- Module headers, OPTIONS_HADDOCK prune + +## Future (v2) + +- BIP341 taproot sighash +- BIP340 schnorr signature integration +- Witness program version detection +- Transaction weight/vsize calculation