Commit message · pm47More AI fixes and defense-in-depth (#3376)
* loupe: add min-to-self-delay check
See: https://github.com/project-loupe/audit-eclair/issues/205
* loupe: use distinct nonces for initial commitments
See https://github.com/project-loupe/audit-eclair/issues/83
* loupe: check signed psbt
See https://github.com/project-loupe/audit-eclair/issues/44
* loupe: prevent overflow on relay-fee calculation
See https://github.com/project-loupe/audit-eclair/issues/31
* Validate relay parameters before on-the-fly funding
When relaying a blinded payment to a wallet node we don't have a channel
with, we cannot validate the relay parameters against a channel_update,
so we skipped that validation entirely before attempting on-the-fly
funding. A malicious wallet node could thus create a blinded path with
a null cltv_expiry_delta for our node, and we would fund a channel and
relay an HTLC that expires at the same block as the upstream HTLC. That
HTLC could then be fulfilled on-chain by the wallet node while the
upstream HTLC times out, making us lose the HTLC amount.
We now validate the relay parameters against the ones the channel we
would create would use (our expiry delta, htlc minimum and our default
relay fees) and fail early instead of funding a
channel for a payment we would have rejected anyway.
See: https://github.com/project-loupe/audit-eclair/issues/162
* Force-close with a publishable commitment
When we sign a splice transaction first (which always happens when we
don't contribute any input) and our peer withholds their tx_signatures,
we cannot publish that splice transaction. Our latest commitment builds
on it, so its commit tx cannot confirm unless our peer publishes the
splice transaction. We still force-closed with that commitment, and the
publisher then retried forever waiting for the parent.
A malicious peer could exploit this to lock our funds indefinitely, or
worse, to steal pending HTLCs: they wait for the upstream HTLC to time
out, then publish the previous commitment and claim the HTLC with its
preimage, since we were unable to time it out on-chain.
When force-closing, we now skip commitments whose funding transaction
hasn't been fully signed and use the latest one that can confirm. The
commitments themselves are left untouched: the closing state now
resolves the commitment matching each published commit txid (ours or
our peer's) wherever it needs commitment-specific data (HTLC signatures,
keys, format), including when restoring after a restart and when
reporting the closed channel. Revoked and future commit txs cannot be
matched and keep using the latest commitment, as before. If our peer
later publishes that splice transaction and it confirms, our commit tx
is double-spent and we force-close again with the latest commitment.
* Bound closing fees proposed by our peer
When we pay the closing fees and our peer sends a closing_signed with a
fee outside of our fee range, we try to converge towards their fee, for
compatibility with implementations that ignore fee ranges. But nothing
bounded that fee: a malicious peer could propose a fee larger than our
balance, we would drop our output and sign a closing transaction that
sends our whole balance to miners, in a single round-trip.
We now refuse fees that exceed what our maximum closing feerate allows
when we pay the fees, and reply with a warning: our peer must propose a
reasonable fee, or we can force-close.
See: https://github.com/project-loupe/audit-eclair/issues/63
* Extract preimage from taproot HTLC-success with annex
When our peer publishes their commit tx and claims an HTLC with an
HTLC-success transaction, we extract the preimage from its witness to
relay it upstream. For taproot channels, we only handled the witness
without an annex, whereas we already handled both cases for
Claim-HTLC-success transactions. If an HTLC-success transaction with an
annex was confirmed, we would fail to extract the preimage and treat
the HTLC as timed out, failing it upstream even though it was fulfilled
downstream.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Use pending signed commitment in canSendFee
When checking that we can afford a new feerate, we were using the spec
of the remote commitment we last received a revocation for, ignoring
the changes we had already signed in a pending commit_sig. Every other
balance check uses the pending commitment when there is one. We could
thus send an update_fee that our peer cannot accept, since they check
it against the commitment we signed, which would make them force-close.
* Bound hidden blinded path fees per payment part
When we hide part of the blinded path fees from the payer, we accept
HTLCs whose amount is smaller than the onion amount by at most those
fees. That bound was computed on the whole invoice amount for every
HTLC, so a payer splitting the payment in several parts could underpay
us by the proportional fee on each part.
The hidden fees are paid on each part, so we now compute the bound on
the part's onion amount. We add a small tolerance since LDK doesn't use
the BOLT 4 formula to compute amt_to_forward inside blinded paths and
rounds down the fee it pays as a sender, which can leave parts short by
a few msat.
* Only accept derivation paths tracked by our wallet
When using the eclair signer, Bitcoin Core only holds a watch-only
wallet built from our BIP84 and BIP86 account xpubs. We verified that
addresses and change outputs it provides belong to us by re-deriving
the public key along the derivation path it gives us, but we accepted
any path. Since Bitcoin Core knows our account xpubs, a malicious node
could provide addresses for deeper or non-standard branches: we would
consider them ours and use them for channel closing scripts, splice
outputs and change, but our wallet would never track them and we would
be unable to find those funds.
We now only accept paths of the form purpose'/coin_type'/account'/{0,1}/i
matching the descriptors we import, with the purpose matching the
address type.
* Check mining fees of wallet-funded interactive txs
When funding an interactive transaction (dual funding or splice), we
let bitcoind select wallet inputs and add a change output. We verified
that the funding output and our outputs were preserved, but never that
the resulting mining fee was sane: interactive-tx validation only
enforces a lower bound on the feerate, and splices don't provide a fee
budget. A malicious bitcoind could thus select inputs worth much more
than needed and skip the change output: all checks would pass and the
difference would be paid to miners once we sign.
We now verify that the mining fee we pay stays close to the target
feerate before announcing our inputs to our peer, like we already do
for single-funded channels.
* Add max-funding-feerate configuration parameter
The feerate we use for funding and splice transactions comes from our
fee estimator, which relies on external data. Nothing bounded that
feerate, so inaccurate or malicious data could make us pay arbitrarily
high mining fees on every channel open or splice. Closing transactions
were already protected by max-closing-feerate.
We add a max-funding-feerate parameter (50 sat/byte by default) that
caps the feerate we use for funding and splice transactions. Operators
can RBF their transactions or increase this value and restart their
node if their transactions don't confirm when the mempool is congested.
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
91/100 · StrongMessage clarity
✓ Descriptive subject✓ Names a concrete action or component✓ Provides detailed explanatory context✓ Mentions testing or verification✓ Links an issue, advisory, or supporting reference✓ Names security-relevant behavior explicitly
Why it was queuedexplicit security languagesigning boundarymemory safetydefensive validationcryptography-sensitive pathsigning or wallet path
AI analysis · Critical 89/100This is a large security patch for the Eclair Lightning node that fixes multiple ways an attacker could steal funds, burn money to miners, or lock funds forever. The fixes include: preventing force-closes with un-publishable splice transactions; capping funding/closing miner fees; validating relay fees before opening channels on-the-fly; stopping overflow in fee math; rejecting fake Bitcoin Core change addresses; checking signed PSBTs haven't been altered; extracting payment secrets from newer taproot transactions; and ensuring distinct cryptographic nonces are used when signing commitments.