bolt3

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

commit bb138778c6409032943e2139d8274f5d08962a03
parent 0accd2e83523fd3c545c34624af4ef97ef1e1228
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 25 Jan 2026 11:20:31 +0400

Wire public API in Lightning.Protocol.BOLT3

Adds comprehensive re-exports from all submodules:

Types (from Types.hs):
- Monetary: Satoshi, MilliSatoshi, conversion functions
- Keys: Pubkey, Seckey, Point
- Hashes: PaymentHash, PaymentPreimage
- Transaction: TxId, Outpoint, Sequence, Locktime
- Channel: CommitmentNumber, ToSelfDelay, CltvExpiry, etc.
- HTLC: HTLC, HTLCDirection
- Basepoints and derived keys
- Script, Witness, ChannelFeatures
- Weight and dust constants

Key derivation (from Keys.hs):
- derive_per_commitment_point, derive_pubkey, derive_*
- generate_from_seed, derive_secret
- SecretStore, empty_store, insert_secret, derive_old_secret
- obscured_commitment_number

Scripts (from Scripts.hs):
- funding_script, funding_witness
- to_local_script, to_remote_script, anchor_script
- offered_htlc_script, received_htlc_script
- All witness construction functions
- to_p2wsh, witness_script_hash

Transaction assembly (from Tx.hs):
- CommitmentTx, HTLCTx, ClosingTx and contexts
- build_commitment_tx, build_htlc_*_tx, build_closing_tx
- TxOutput, OutputType
- Fee calculation and trimming functions
- sort_outputs

Serialization (from Encode.hs):
- encode_tx, encode_htlc_tx, encode_closing_tx
- encode_tx_for_signing
- Primitive encoders

Parsing (from Decode.hs):
- DecodeError, RawTx, RawInput, RawOutput
- decode_tx and primitive decoders

Validation (from Validate.hs):
- ValidationError
- All validation functions

Module-level Haddock documentation with overview, quick start
example, and links to submodules.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Diffstat:
Mlib/Lightning/Protocol/BOLT3.hs | 270++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mlib/Lightning/Protocol/BOLT3/Types.hs | 14+++++---------
Aplans/REVIEW1.md | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 320 insertions(+), 18 deletions(-)

diff --git a/lib/Lightning/Protocol/BOLT3.hs b/lib/Lightning/Protocol/BOLT3.hs @@ -9,19 +9,271 @@ -- Bitcoin transaction formats for the Lightning Network, per -- [BOLT #3](https://github.com/lightning/bolts/blob/master/03-transactions.md). -- --- This module re-exports the public API from submodules: +-- = Overview -- --- * "Lightning.Protocol.BOLT3.Types" - Core types --- * "Lightning.Protocol.BOLT3.Keys" - Key derivation --- * "Lightning.Protocol.BOLT3.Scripts" - Script templates +-- This library implements the transaction and script formats defined in +-- BOLT #3, including: +-- +-- * Commitment transactions with to_local, to_remote, anchor, and HTLC +-- outputs +-- * HTLC-timeout and HTLC-success second-stage transactions +-- * Closing transactions (legacy and option_simple_close) +-- * Per-commitment key derivation and secret storage +-- * Transaction serialization and parsing +-- * Stateless validation +-- +-- = Quick Start +-- +-- @ +-- import Lightning.Protocol.BOLT3 +-- +-- -- Build a commitment transaction +-- let ctx = CommitmentContext { ... } +-- tx = build_commitment_tx ctx +-- +-- -- Serialize for signing +-- let bytes = encode_tx tx +-- +-- -- Validate the transaction +-- case validate_commitment_tx dustLimit features tx of +-- Right () -> putStrLn "Valid" +-- Left err -> print err +-- @ +-- +-- = Modules +-- +-- * "Lightning.Protocol.BOLT3.Types" - Core types (Satoshi, Pubkey, HTLC, +-- etc.) +-- * "Lightning.Protocol.BOLT3.Keys" - Per-commitment key derivation and +-- secret storage +-- * "Lightning.Protocol.BOLT3.Scripts" - Witness script templates (funding, +-- to_local, HTLC, anchor) -- * "Lightning.Protocol.BOLT3.Tx" - Transaction assembly --- * "Lightning.Protocol.BOLT3.Encode" - Serialization --- * "Lightning.Protocol.BOLT3.Decode" - Parsing --- * "Lightning.Protocol.BOLT3.Validate" - Validation +-- * "Lightning.Protocol.BOLT3.Encode" - Transaction serialization +-- * "Lightning.Protocol.BOLT3.Decode" - Transaction parsing +-- * "Lightning.Protocol.BOLT3.Validate" - Stateless validation module Lightning.Protocol.BOLT3 ( - -- * Re-exports - module Lightning.Protocol.BOLT3.Types + -- * Types + -- ** Monetary amounts + Satoshi(..) + , MilliSatoshi(..) + , msat_to_sat + , sat_to_msat + + -- ** Keys and points + , Pubkey(..) + , Seckey(..) + , Point(..) + + -- ** Hashes + , PaymentHash(..) + , PaymentPreimage(..) + + -- ** Transaction primitives + , TxId(..) + , Outpoint(..) + , Sequence(..) + , Locktime(..) + + -- ** Channel parameters + , CommitmentNumber(..) + , ToSelfDelay(..) + , CltvExpiry(..) + , DustLimit(..) + , FeeratePerKw(..) + + -- ** HTLC types + , HTLC(..) + , HTLCDirection(..) + + -- ** Basepoints + , Basepoints(..) + , PerCommitmentPoint(..) + , PerCommitmentSecret(..) + , RevocationBasepoint(..) + , PaymentBasepoint(..) + , DelayedPaymentBasepoint(..) + , HtlcBasepoint(..) + + -- ** Derived keys + , LocalPubkey(..) + , RemotePubkey(..) + , LocalDelayedPubkey(..) + , RemoteDelayedPubkey(..) + , LocalHtlcPubkey(..) + , RemoteHtlcPubkey(..) + , RevocationPubkey(..) + , FundingPubkey(..) + + -- ** Script and witness + , Script(..) + , Witness(..) + + -- ** Channel features + , ChannelFeatures(..) + , has_anchors + + -- ** Constants + , commitment_weight_no_anchors + , commitment_weight_anchors + , htlc_timeout_weight_no_anchors + , htlc_timeout_weight_anchors + , htlc_success_weight_no_anchors + , htlc_success_weight_anchors + , htlc_output_weight + , dust_p2pkh + , dust_p2sh + , dust_p2wpkh + , dust_p2wsh + , anchor_output_value + + -- * Key derivation + , derive_per_commitment_point + , derive_pubkey + , derive_localpubkey + , derive_local_htlcpubkey + , derive_remote_htlcpubkey + , derive_local_delayedpubkey + , derive_remote_delayedpubkey + , derive_revocationpubkey + + -- ** Secret generation + , generate_from_seed + , derive_secret + + -- ** Secret storage + , SecretStore + , empty_store + , insert_secret + , derive_old_secret + + -- ** Commitment number + , obscured_commitment_number + + -- * Scripts + -- ** Funding output + , funding_script + , funding_witness + + -- ** to_local output + , to_local_script + , to_local_witness_spend + , to_local_witness_revoke + + -- ** to_remote output + , to_remote_script + , to_remote_witness + + -- ** Anchor outputs + , anchor_script + , anchor_witness_owner + , anchor_witness_anyone + + -- ** Offered HTLC + , offered_htlc_script + , offered_htlc_witness_preimage + , offered_htlc_witness_revoke + + -- ** Received HTLC + , received_htlc_script + , received_htlc_witness_timeout + , received_htlc_witness_revoke + + -- ** HTLC output (same as to_local) + , htlc_output_script + , htlc_output_witness_spend + , htlc_output_witness_revoke + + -- ** P2WSH helpers + , to_p2wsh + , witness_script_hash + + -- * Transaction assembly + -- ** Commitment transactions + , CommitmentTx(..) + , CommitmentContext(..) + , CommitmentKeys(..) + , build_commitment_tx + + -- ** HTLC transactions + , HTLCTx(..) + , HTLCContext(..) + , build_htlc_timeout_tx + , build_htlc_success_tx + + -- ** Closing transactions + , ClosingTx(..) + , ClosingContext(..) + , build_closing_tx + , build_legacy_closing_tx + + -- ** Transaction outputs + , TxOutput(..) + , OutputType(..) + + -- ** Fee calculation + , commitment_fee + , commitment_weight + , htlc_timeout_fee + , htlc_success_fee + + -- ** Trimming + , htlc_trim_threshold + , is_trimmed + , trimmed_htlcs + , untrimmed_htlcs + + -- ** Output ordering + , sort_outputs + + -- * Serialization + , encode_tx + , encode_htlc_tx + , encode_closing_tx + , encode_tx_for_signing + , encode_varint + , encode_le32 + , encode_le64 + , encode_outpoint + , encode_output + , encode_witness + , encode_funding_witness + + -- * Parsing + , DecodeError(..) + , RawTx(..) + , RawInput(..) + , RawOutput(..) + , decode_tx + , decode_varint + , decode_le32 + , decode_le64 + , decode_outpoint + , decode_output + , decode_witness + + -- * Validation + , ValidationError(..) + , validate_commitment_tx + , validate_commitment_locktime + , validate_commitment_sequence + , validate_htlc_tx + , validate_htlc_timeout_tx + , validate_htlc_success_tx + , validate_closing_tx + , validate_legacy_closing_tx + , validate_output_ordering + , validate_dust_limits + , validate_anchor_outputs + , validate_commitment_fee + , validate_htlc_fee ) where import Lightning.Protocol.BOLT3.Types +import Lightning.Protocol.BOLT3.Keys +import Lightning.Protocol.BOLT3.Scripts +import Lightning.Protocol.BOLT3.Tx +import Lightning.Protocol.BOLT3.Encode +import Lightning.Protocol.BOLT3.Decode +import Lightning.Protocol.BOLT3.Validate diff --git a/lib/Lightning/Protocol/BOLT3/Types.hs b/lib/Lightning/Protocol/BOLT3/Types.hs @@ -195,6 +195,11 @@ data HTLCDirection deriving (Eq, Ord, Show, Generic) -- | HTLC output details. +-- +-- NOTE: No Ord instance is provided. BOLT #3 requires output ordering by +-- amount then scriptPubKey, but scriptPubKey depends on derived keys which +-- are not available here. Use 'sort_outputs' in Tx module for proper BIP69 +-- output ordering. data HTLC = HTLC { htlc_direction :: !HTLCDirection , htlc_amount_msat :: {-# UNPACK #-} !MilliSatoshi @@ -202,15 +207,6 @@ data HTLC = HTLC , htlc_cltv_expiry :: {-# UNPACK #-} !CltvExpiry } deriving (Eq, Show, Generic) --- Define ordering per BOLT #3: by amount (sat), then scriptpubkey, then expiry -instance Ord HTLC where - compare h1 h2 = - let sat1 = msat_to_sat (htlc_amount_msat h1) - sat2 = msat_to_sat (htlc_amount_msat h2) - in case compare sat1 sat2 of - EQ -> compare (htlc_cltv_expiry h1) (htlc_cltv_expiry h2) - other -> other - -- basepoints ------------------------------------------------------------------ -- | Per-commitment point (used to derive keys). diff --git a/plans/REVIEW1.md b/plans/REVIEW1.md @@ -0,0 +1,54 @@ +# REVIEW1: PTAL findings (master) + +## Scope + +Review of recent master commits: +- 8af91e3 (Types) +- 583dea5 (Keys) +- 8ed369e (Scripts) +- 5c8e641 (Keys fix) + +## Findings + +### Critical + +1) SecretStore bucket indexing is inconsistent with shachain layout. + - `SecretStore` stores entries in a plain list without bucket + positions; `insert_secret` and `derive_old_secret` treat list index + as bucket index. + - `insertAt` appends when `length entries <= b`, so inserting bucket 10 + into an empty store yields position 0; `derive_old_secret` then uses + `b=0` for masking/derivation and produces incorrect results or + accepts invalid secrets. + - References: + - `lib/Lightning/Protocol/BOLT3/Keys.hs:304` + - `lib/Lightning/Protocol/BOLT3/Keys.hs:331` + - `lib/Lightning/Protocol/BOLT3/Keys.hs:374` + +### High + +2) HTLC ordering deviates from BOLT #3. + - `Ord HTLC` compares amount then `cltv_expiry`, but spec requires + ordering by amount then `scriptPubKey` lexicographic (with + `cltv_expiry` impacting script bytes for received HTLCs). + - This affects output ordering and thus signature preimages. + - Reference: `lib/Lightning/Protocol/BOLT3/Types.hs:205` + +3) `to_remote_witness` missing pubkey in non-anchors case. + - P2WPKH witness must be `<sig> <pubkey>`; helper returns only + `<sig>` and relies on caller to append pubkey (not implemented). + - Reference: `lib/Lightning/Protocol/BOLT3/Scripts.hs:365` + +4) `push_cltv` encodes script numbers with reversed endianness. + - `encode_scriptnum` builds little-endian then reverses twice, yielding + big-endian output. Values > 16 will be incorrectly encoded. + - Reference: `lib/Lightning/Protocol/BOLT3/Scripts.hs:191` + +## Notes / Open questions + +- Witness helpers for P2WSH outputs omit the witness script item; if the + Tx assembly is expected to append the script, document this explicitly + to avoid misuse. +- `to_remote_script` returns a witness script for anchors but a + scriptPubKey for non-anchors; consider splitting APIs or clarifying + naming.