bolt3

Lightning transaction and script formats, per BOLT #3 (docs.ppad.tech/bolt3).
git clone git://git.ppad.tech/bolt3.git
Log | Files | Refs | README | LICENSE

commit f535438b3f8df596b234e36ac29a75e875f13e53
parent 8abc27c8bbdffa9ff72b3d6ac3471992b50e08e8
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 10 Oct 2026 14:53:34 +0400

docs: readme and changelog for 0.1.0

Diffstat:
MCHANGELOG | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MREADME.md | 128++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
2 files changed, 167 insertions(+), 30 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -0,0 +1,69 @@ +# Changelog + +- 0.1.0 (UNRELEASED) + * A substantial rewrite, with breaking changes throughout: + + * Ports to ppad-bolt1 0.1.0 and ppad-tx 0.2.0. Amounts, points, + hashes and per-commitment secrets are bolt1's types (amounts are + bounded, with checked arithmetic); bolt3's Pubkey is merged into + bolt1's Point, wrapped in role newtypes (RevocationPubkey, etc.). + + * The API is exported from Lightning.Protocol.BOLT3 alone, with + snake_case names. Types with invariants (CommitmentNumber, + SecretIndex, Seed, Seckey, SecretStore) are abstract, and every + type has an NFData instance. + + * ChannelFeatures is replaced by CommitmentFormat (StaticRemotekey + or Anchors). + + * CommitmentContext is owner-relative throughout: the payment + basepoints are ordered for the obscured commitment number by + cc_is_funder. build_commitment_tx fails rather than produce a + transaction without outputs. + + * OutputType carries the HTLC an output pays. build_htlc_tx replaces + build_htlc_timeout_tx and build_htlc_success_tx, choosing by HTLC + direction, and fails if the fee exceeds the amount. + + * Closing transactions have separate builders with spec-shaped + inputs: build_legacy_closing_tx applies the signer's dust limit to + both outputs and can omit the signer's output; build_closing_tx + (option_simple_close) charges the closer, takes an explicit choice + of outputs, and gives OP_RETURN outputs amount zero. + + * to_remote_script is split into to_remote_witness_script and + to_remote_script_pubkey. Witness functions append the witness + script and take typed keys; funding_witness orders signatures by + pubkey. Adds htlc_success_witness, htlc_timeout_witness and + remote_htlc_sighash. + + * Per-commitment secrets use Seed, SecretIndex and bolt1's + PerCommitmentSecret. SecretStore is abstract, with a redacted + Show instance and secret_store/un_secret_store for persistence. + Adds commitment_secret_index. + + * Adds derive_commitment_keys, derive_privkey and + derive_revocationprivkey. + + * Renames encode_tx to encode_commitment_tx, and documents the + encoders as producing unsigned, witness-free serializations. + + * Removes the Validate, Decode, Encode and Internal modules, the + role-specific key derivation functions, encode_tx_for_signing, + encode_witness, encode_funding_witness, has_anchors, sort_outputs, + trimmed_htlcs, untrimmed_htlcs, derive_secret, the HTLC output + script aliases, the weight constants and unused record fields. + + * Fixes insert_secret, which discarded the entries needed to derive + older secrets and accepted invalid secret sequences. + + * Fixes closing transactions, which used each party's dust limit in + legacy closes, and charged the funder and paid OP_RETURN outputs in + option_simple_close. + + * Rejects key derivations yielding the point at infinity. + + * Tests cover every BOLT #3 Appendix B-F vector. + +- 0.0.1 (2026-04-18) + * Initial release. diff --git a/README.md b/README.md @@ -4,10 +4,15 @@ ![](https://img.shields.io/badge/license-MIT-brightgreen) [![](https://img.shields.io/badge/haddock-bolt3-lightblue)](https://docs.ppad.tech/bolt3) -A pure Haskell implementation of [BOLT #3][bolt3] (Lightning Network -Bitcoin transaction and script formats), including commitment -transactions, HTLC transactions, closing transactions, per-commitment -key derivation, and secret storage. +A pure Haskell implementation of [BOLT #3][bolt3], the Lightning +Network's Bitcoin transaction and script formats: commitment, HTLC and +closing transactions, their output scripts and witnesses, +per-commitment key derivation, and per-commitment secret generation and +storage. + +Amounts, points and secrets use the types of [ppad-bolt1][bolt1], and +transactions convert to [ppad-tx][tx] for txids, sighashes and +serialization. ## Usage @@ -15,42 +20,102 @@ A sample GHCi session: ``` > :set -XOverloadedStrings - > + > import qualified Bitcoin.Prim.Tx as BT > import qualified Data.ByteString as BS - > import qualified Data.ByteString.Base16 as B16 + > import qualified Lightning.Protocol.BOLT1 as BOLT1 > import Lightning.Protocol.BOLT3 > - > -- key derivation (BOLT #3 Appendix E test vector) - > let basepoint = Point (B16.decodeLenient "036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2") - > let perCommitment = PerCommitmentPoint (Point (B16.decodeLenient "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486")) - > let Just (Pubkey derived) = derive_pubkey basepoint perCommitment - > B16.encode derived - "0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5" + > -- per-commitment secrets come from a seed; secrets aren't shown + > let Just sd = seed (BS.replicate 32 0xff) + > let Just cn = commitment_number 0 + > let i = commitment_secret_index cn + > let secret = generate_from_seed sd i + > secret + PerCommitmentSecret <redacted> > - > -- secret generation (BOLT #3 Appendix D test vector) - > let seed = BS.replicate 32 0xFF - > B16.encode (generate_from_seed seed 281474976710655) - "7cc854b54e3e0dcdb010d7a3fee464a9687be6e8db3be6854c475621e007a5dc" + > -- the peer keeps revealed secrets in compact storage + > let Just store = insert_secret secret i empty_secret_store + > fmap (== secret) (derive_old_secret i store) + Just True > - > -- fee calculation - > let feerate = FeeratePerKw 5000 - > let features = ChannelFeatures { cf_option_anchors = False } - > commitment_fee feerate features 2 - Satoshi 5340 + > -- the commitment's per-commitment point, and a private key for it + > let Just pcp = derive_per_commitment_point secret + > let Just base = seckey (BS.pack [1 .. 32]) + > derive_privkey base pcp + Just (Seckey <redacted>) > - > -- HTLC trimming - > let dust = DustLimit (Satoshi 546) - > htlc_trim_threshold dust feerate features HTLCOffered + > -- fees and HTLC trimming + > commitment_fee (FeeratePerKw 5000) StaticRemotekey 2 + Satoshi 5340 + > let Just dust = fmap DustLimit (BOLT1.satoshi 546) + > htlc_trim_threshold dust (FeeratePerKw 5000) StaticRemotekey HTLCOffered Satoshi 3861 + > + > -- an option_simple_close transaction: the closer pays the fee, and + > -- its OP_RETURN output has amount zero + > let Just txid = BT.mkTxId (BS.replicate 32 0x01) + > let Just [closer, closee] = traverse BOLT1.milli_satoshi [6000000, 4000000] + > let Just fee = BOLT1.satoshi 500 + > let p2wpkh = Script . BS.append "\x00\x14" . BS.replicate 20 + :{ + ghci| let close = ClosingContext { + ghci| clc_funding_outpoint = BT.OutPoint txid 0 + ghci| , clc_closer_msat = closer + ghci| , clc_closee_msat = closee + ghci| , clc_closer_script = Script "\x6a" + ghci| , clc_closee_script = p2wpkh 0x22 + ghci| , clc_fee = fee + ghci| , clc_locktime = Locktime 0 + ghci| , clc_outputs = CloserAndCloseeOutputs + ghci| } + ghci| :} + > let Just tx = build_closing_tx close + > fmap clo_value (cltx_outputs tx) + Satoshi 0 :| [Satoshi 4000] + > BS.length (encode_closing_tx tx) + 92 ``` ## Documentation -Haddocks are hosted at [docs.ppad.tech/bolt3][hadoc]. +Haddocks (API documentation, etc.) are hosted at +[docs.ppad.tech/bolt3](https://docs.ppad.tech/bolt3). + +## Specification + +This library implements BOLT #3 as of lightning/bolts commit +[1aadb719][spec] (2026-09-21); a copy lives in `etc/`. Every test +vector in Appendices B through F is checked byte for byte. + +Not implemented: + +* `zero_fee_commitments` (version 3 commitment and HTLC transactions, + the `shared_anchor` P2A output, and Appendix H). Only the + `option_static_remotekey` and `option_anchors` commitment formats are + supported. +* Funding transaction construction (including Appendix G's dual-funded + vectors): the library provides the funding output's script and + witness only. + +The library builds unsigned transactions; signatures are made with +[ppad-secp256k1][secp] over sighashes from ppad-tx. Spending +transactions for on-chain resolution belong to BOLT #5. + +## Performance + +The aim is best-in-class performance for pure Haskell code. Run the +benchmarks with: + +``` +$ cabal bench +``` ## Security -This is a pre-release version and makes no security guarantees whatsoever. +This library has received no security audit. Secret material (seeds, +secret keys, per-commitment secrets and their storage) is kept out of +`Show` output, and private-key derivation uses constant-time +arithmetic. ## Development @@ -64,12 +129,15 @@ $ nix develop Then do e.g.: ``` -$ cabal build -$ cabal test -$ cabal bench +$ cabal repl ppad-bolt3 ``` +to get a REPL for the main library. + [bolt3]: https://github.com/lightning/bolts/blob/master/03-transactions.md +[bolt1]: https://git.ppad.tech/bolt1 +[tx]: https://git.ppad.tech/tx +[secp]: https://git.ppad.tech/secp256k1 +[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/bolt3