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:
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.