commit e56a951334d53d70c5f5b3059e6efb995456b757
parent e8e8396aa150a6f8607ab00a7379a744646f2b6c
Author: Jared Tobin <jared@jtobin.io>
Date: Sat, 10 Oct 2026 15:30:03 +0400
docs: readme and changelog for 0.1.0
Diffstat:
| M | CHANGELOG | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| M | README.md | | | 151 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
2 files changed, 166 insertions(+), 45 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG
@@ -1,4 +1,60 @@
# Changelog
-- 0.0.1 (UNRELEASED)
- * Initial release
+- 0.1.0 (UNRELEASED)
+ * The library's scope is being redesigned, and this version is not
+ yet ready for release (see the README for what it does and
+ doesn't cover).
+
+ * Ports to ppad-bolt1 0.1.0, ppad-bolt3 0.1.0 and ppad-tx 0.2.0, with
+ breaking changes:
+
+ * Amounts, points and hashes are bolt1's types (amounts are
+ bounded, with checked arithmetic), and ChannelFeatures is
+ replaced by bolt3's CommitmentFormat.
+
+ * The classifiers read each HTLC output's HTLC from bolt3's
+ OutputHTLC, and no longer take an HTLC list.
+ classify_remote_commit_outputs no longer takes the channel
+ features, and classify_revoked_commit_outputs takes only the
+ commitment and the revocation pubkey.
+
+ * spend_htlc_tx replaces spend_htlc_timeout and spend_htlc_success,
+ building the transaction with bolt3's build_htlc_tx, and fails
+ if bolt3 can't build it.
+
+ * The API is exported from Lightning.Protocol.BOLT5 alone, with
+ snake_case names (htlc_output_type, htlc_output_type_weight); the
+ Internal module is removed. Every public type has an NFData
+ instance.
+
+ * extract_preimage_offered and extract_preimage_htlc_success take
+ the HTLC's payment hash, and revoked_output_weight and
+ htlc_output_type_weight the commitment format.
+
+ * spending_fee saturates at 21 million BTC.
+
+ * Fixes preimage extraction, which never matched a real witness (one
+ ending in the witness script), and checks preimages against the
+ payment hash.
+
+ * Fixes identify_close, which compared an unsigned serialization with
+ the signed transaction found on chain; it compares txids.
+
+ * Fixes HTLC output classification, which matched outputs to HTLCs by
+ script alone, so that HTLCs sharing a payment hash were confused.
+
+ * Fixes the sighash type of the local signature on anchor-channel
+ HTLC transactions, which is SIGHASH_ALL.
+
+ * Sweeps no longer create outputs below the destination's dust
+ threshold, and anchor-channel HTLC penalty sweeps use the anchor
+ scripts' weights.
+
+ * Removes spend_revoked_batch and PenaltyContext, whose transaction
+ couldn't be signed.
+
+ * Tests check HTLC transactions and preimage extraction against the
+ BOLT #3 Appendix C and F vectors.
+
+- 0.0.1 (2026-04-18)
+ * Initial release.
diff --git a/README.md b/README.md
@@ -4,9 +4,50 @@

[](https://docs.ppad.tech/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.
+A pure Haskell implementation of parts of [BOLT #5][bolt5], the
+Lightning Network's rules for handling channel closes on chain.
+
+## Status
+
+**This library's scope is being redesigned, and it is not yet ready for
+release.** Its API will change.
+
+What it does:
+
+* Identifies our local or the remote's current commitment on chain, by
+ txid.
+* Classifies the outputs of local, remote and revoked commitment
+ transactions built by [ppad-bolt3][bolt3], reading each HTLC output's
+ HTLC from bolt3.
+* Extracts payment preimages from the witnesses of remote preimage
+ claims and HTLC-success transactions, checking them against the
+ payment hash.
+* Builds unsigned, single-input spending transactions: to_local sweeps
+ (delayed and revoked), HTLC-timeout and HTLC-success transactions
+ (via bolt3), direct HTLC spends from the remote commitment, revoked
+ HTLC sweeps, second-stage output sweeps and anchor spends. Sweeps
+ refuse to create outputs below the destination's dust threshold.
+* Provides the BOLT #5 Appendix A weights and fee calculation.
+
+What it doesn't do (known gaps):
+
+* Per-input signing data. A spending transaction describes one input
+ and the local signature only; there is no batched penalty
+ transaction.
+* Witness assembly. The caller signs and builds every witness with
+ bolt3's witness functions.
+* Classification of raw on-chain transactions. The classifiers take the
+ bolt3 commitment transaction for the state in question, which the
+ caller must rebuild (often impossible for a revoked state).
+* Revoked and mutual close detection. Only the current local and
+ remote commitments are recognised, and the resolutions for a revoked
+ commitment lack what's needed to build its spends (to_self_delay,
+ delayed pubkey, payment hashes).
+* Fee bumping. Anchor spends and anchor-channel HTLC transactions pay
+ no fee and can't take fee inputs, and penalty sweeps don't signal
+ replace-by-fee.
+* `zero_fee_commitments` (version 3 transactions and the shared P2A
+ anchor), and spending the anchor-channel to_remote output.
## Usage
@@ -14,56 +55,79 @@ A sample GHCi session:
```
> :set -XOverloadedStrings
- >
+ > import qualified Bitcoin.Prim.Tx as BT
+ > import qualified Crypto.Hash.SHA256 as SHA256
> import qualified Data.ByteString as BS
- > import qualified Lightning.Protocol.BOLT3 as BOLT3
- > import qualified Lightning.Protocol.BOLT5 as BOLT5
+ > import qualified Lightning.Protocol.BOLT1 as BOLT1
+ > import Lightning.Protocol.BOLT3
+ > import Lightning.Protocol.BOLT5
>
- > -- fee calculation using BOLT #5 Appendix A weight constants
- > let feerate = BOLT3.FeeratePerKw 5000
- > BOLT5.spending_fee feerate BOLT5.to_local_penalty_input_weight
- Satoshi 1620
- > BOLT5.spending_fee feerate BOLT5.penalty_tx_base_weight
- Satoshi 1070
+ > -- fees from the BOLT #5 Appendix A weights
+ > let feerate = FeeratePerKw 5000
+ > let weight = to_local_penalty_input_weight + penalty_tx_base_weight
+ > spending_fee feerate weight
+ Satoshi 2690
>
- > -- check if an HTLC has timed out at the current block height
- > let htlc = BOLT3.HTLC BOLT3.HTLCOffered (BOLT3.MilliSatoshi 50000000)
- > (BOLT3.PaymentHash (BS.replicate 32 0xAA))
- > (BOLT3.CltvExpiry 800000)
- > BOLT5.htlc_timed_out 799999 htlc
+ > -- whether an HTLC has timed out at a given block height
+ > let Just amt = BOLT1.milli_satoshi 50000000
+ > let Just hash = BOLT1.payment_hash (BS.replicate 32 0xaa)
+ > let htlc = HTLC HTLCOffered amt hash (CltvExpiry 800000)
+ > htlc_timed_out 799999 htlc
False
- > BOLT5.htlc_timed_out 800000 htlc
+ > htlc_timed_out 800000 htlc
True
>
- > -- extract a payment preimage from an offered HTLC witness
- > let preimage = BS.replicate 32 0xBB
- > let sig = BS.replicate 71 0xCC
- > let wit = BOLT3.Witness [sig, preimage]
- > BOLT5.extract_preimage_offered wit
- Just PaymentPreimage <redacted>
- >
- > -- construct an unsigned spending tx for a to_local output
- > let Just txid = BOLT3.mkTxId (BS.replicate 32 0x01)
- > let outpoint = BOLT3.OutPoint txid 0
- > let value = BOLT3.Satoshi 1000000
- > let revpk = BOLT3.RevocationPubkey (BOLT3.Pubkey (BS.replicate 33 0x02))
- > let delay = BOLT3.ToSelfDelay 144
- > let delayedpk = BOLT3.LocalDelayedPubkey (BOLT3.Pubkey (BS.replicate 33 0x03))
- > let dest = BOLT3.Script (BS.replicate 34 0x00)
- >
- > let stx = BOLT5.spend_to_local outpoint value revpk delay
- > delayedpk dest feerate
- > BOLT5.stx_sighash_type stx
+ > -- an unsigned sweep of our to_local output, valid after the delay
+ > let key b = BOLT1.point (BS.cons 0x02 (BS.replicate 32 b))
+ > let Just revpk = RevocationPubkey <$> key 0x01
+ > let Just delayedpk = LocalDelayedPubkey <$> key 0x02
+ > let Just txid = BT.mkTxId (BS.replicate 32 0x01)
+ > let Just value = BOLT1.satoshi 1000000
+ > let dest = Script ("\x00\x14" <> BS.replicate 20 0xcc)
+ > let delay = ToSelfDelay 144
+ > let op = BT.OutPoint txid 0
+ > let Just stx = spend_to_local op value revpk delay delayedpk dest feerate
+ > fmap BT.txout_value (BT.tx_outputs (stx_tx stx))
+ 997310 :| []
+ > fmap BT.txin_sequence (BT.tx_inputs (stx_tx stx))
+ 144 :| []
+ > stx_sighash_type stx
SIGHASH_ALL
+ > stx_input_script stx == to_local_script revpk delay delayedpk
+ True
+ >
+ > -- extract the preimage from the remote's claim of an offered HTLC
+ > let Just preimage = BOLT1.payment_preimage (BS.replicate 32 0xbb)
+ > let Just ph = BOLT1.payment_hash (SHA256.hash (BS.replicate 32 0xbb))
+ > let sig = BS.replicate 71 0xcc
+ > let wit = offered_htlc_witness_preimage sig preimage (Script "")
+ > extract_preimage_offered ph wit
+ Just (PaymentPreimage <redacted>)
+ > fmap (== preimage) (extract_preimage_offered ph wit)
+ Just True
+ > extract_preimage_offered hash wit
+ Nothing
```
## Documentation
-Haddocks are hosted at [docs.ppad.tech/bolt5][hadoc].
+Haddocks (API documentation, etc.) are hosted at
+[docs.ppad.tech/bolt5](https://docs.ppad.tech/bolt5).
+
+## Specification
+
+This library tracks BOLT #5 as of lightning/bolts commit
+[1aadb719][spec] (2026-09-21); a copy lives in `etc/`. BOLT #5
+publishes no test vectors beyond Appendix A's weights, which are
+checked; the tests also check HTLC transactions and preimage extraction
+against BOLT #3's Appendix C and F vectors.
+
+See [Status](#status) for what isn't implemented.
## Security
-This is a pre-release and makes no claims about security whatsoever.
+This library has received no security audit. It handles no secret
+material: it builds unsigned transactions and reads public witnesses.
## Development
@@ -77,12 +141,13 @@ $ nix develop
Then do e.g.:
```
-$ cabal build
-$ cabal test
-$ cabal bench
+$ cabal repl ppad-bolt5
```
+to get a REPL for the main library.
+
[bolt5]: https://github.com/lightning/bolts/blob/master/05-onchain.md
+[bolt3]: https://git.ppad.tech/bolt3
+[spec]: https://github.com/lightning/bolts/tree/1aadb719b4007c4cea0ba6e36b08c4fb53788dee
[nixos]: https://nixos.org/
[flake]: https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html
-[hadoc]: https://docs.ppad.tech/bolt5