commit 5e2409b2cd44b7a62ff47ba9a4fd76e7d43537fe
parent 897e5397e0818a9014ba6f4ba2e29df8788a0295
Author: Jared Tobin <jared@jtobin.io>
Date: Sat, 18 Apr 2026 21:16:11 +0800
meta: additional docs
Diffstat:
| A | README.md | | | 40 | ++++++++++++++++++++++++++++++++++++++++ |
| A | plans/ARCH1.md | | | 125 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | plans/IMPL1.md | | | 124 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | plans/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
+
+
+
+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