In this commit, we cap each decompressed short channel ID set at 100,000
entries, matching the aggregate range reply budget. The old zlib reader
bounded compressed input rather than decoded output, so the two working-set
limits could drift apart.
We retain compatibility with protocol-valid compressed replies, reject
truncated or corrupt zlib streams, and close the reader on every exit.
Boundary, compatibility, corruption, and property tests cover the
decoder.
BOLT 4 requires the final node to ignore an onion message whose
onionmsg_tlv contains more than one payload field, where payload fields
are the tlv types reserved for the final hop (type 64 and above). Decode
previously accumulated every such field it found, so a payload bundling
invoice_request, invoice, and invoice_error together was accepted.
Reject the payload when more than one final hop field is present. Every
entry collected in FinalHopTLVs is in the final hop range, so its count
is the number of payload fields. The round-trip test for multiple fields
becomes a rejection test, and the property test now draws at most one
payload field.
BOLT 4 requires the final node to ignore an onion message whose
onionmsg_tlv contains an unknown even type, since even types are
"must understand". The TLV stream decoder does not enforce this on its
own: its parsed-type map collects unknown types of either parity, so an
even type such as 70 would otherwise be accepted as a final hop payload.
Reject any unknown even type during decode, regardless of its range. The
check runs before the final hop range skip so unknown even types below
type 64 are rejected as well.
When decoding an onion message payload, the loop that forwards
unrecognized final hop TLVs to higher layers skipped any entry with a
zero-length value. DecodeWithParsedTypesP2P marks a recognized type with
a nil map entry but records the raw bytes for an unknown type, and an
unknown odd TLV with an empty value is valid. Keying the skip off a
length check therefore dropped such a TLV instead of passing it through.
Test the recognized-type skip against a nil entry so a valid unknown odd
zero-length TLV is preserved.
Migrate all btcd dependencies to the new per-package v2 modules (wire/v2,
txscript/v2, chaincfg/v2, chainhash/v2, btcutil/v2, psbt/v2, btcec/v2)
introduced by btcd v0.26.0, and pin the tagged ecosystem versions:
btcwallet v0.17.0, neutrino v0.18.0 and lightning-onion v1.4.0.
The bulk of the import rewrite was produced by the scripted diff from
https://github.com/btcsuite/btcd/pull/2547 (followed by 'make rpc'). The
address symbols that moved out of btcutil into the new address package
are imported as btcaddr where a local "address" variable would otherwise
shadow them. The go.mod/go.sum updates and the remaining manual
compilation fixes are folded into this single commit so it builds on its
own (the migration was previously split into a reproducible scripted-diff
plus follow-ups, intended to be squashed on merge).
Since Go 1.22 loop variables are scoped per-iteration, so the
`x := x` / `a, b := a, b` copies inside range/for loops are no longer
needed. This removes the existing redundant copies in test files.
Switch OnionMessagePayload.ReplyPath from *sphinx.BlindedPath to
*lnwire.BlindedPath. The reply-path TLV is now produced and consumed by
(*lnwire.BlindedPath).Record(), which honours the BOLT 4 sciddir_or_pubkey
introduction-node form. The legacy decoder gated on a 67-byte minimum
length and silently rejected reply paths whose introduction node used
the 9-byte sciddir variant.
The legacy replyPathRecord / replyPathSize / encodeReplyPath /
decodeReplyPath / blindedHopSize / encodeBlindedHop / decodeBlindedHop
helpers and the unused ErrNoHops sentinel are deleted.
Consumers update mechanically: routing/route's
OnionMessageBlindedPathToSphinxPath replyPath parameter, the
onionmessage.OnionMessageUpdate field, the rpcserver onion-message
subscription bridge, and the lnwire test utilities now use the lnwire
type directly. The new TestOnionMessagePayloadRoundTrip "sciddir intro
reply path" subtest pins the BOLT 4 spec fix.
Introduce the canonical lnwire.BlindedPath / BlindedPaths codec with a
sealed IntroductionNode sum-type covering both the BOLT 4 pubkey and
sciddir variants. The codec gates every variable-length subfield against
an io.LimitedReader. It fails closed on the encoder side so invalid
input never hits the wire.
This commit is a pure addition: no existing caller changes. Subsequent
commits migrate OnionMessagePayload and the bolt12 message structs to
consume the new codec.
Add UnsignedRangeFunc and the SerialiseFieldsToSignFn /
ExtraSignedFieldsFromTypeMapFn variants so callers with non-BOLT 7 v2
signed ranges (e.g. BOLT 12, which reserves only 240-1000) can plug in
their own predicate. The existing SerialiseFieldsToSign and
ExtraSignedFieldsFromTypeMap entry points keep their behaviour by
delegating to the Fn variants with InUnsignedRange.
Tor stopped serving v2 onion services in October 2021; lnd should not
produce v2 addresses anymore, but it must still verify signatures on
and re-broadcast peer NodeAnnouncement messages that carry v2 entries.
Stop accepting v2 as configuration input (lncfg), strip the legacy
`--tor.v2` flag from the sample config, and remove the
`tor.OnionHostToFakeIP` helper. Operator entry points (`--externalip`,
`--listen`, `lncli connect`, `lncli wtclient towers add`) fail fast on
a v2 `.onion` string, so upgrading nodes must remove any v2 entry from
`lnd.conf` before lnd will start.
Filter persisted v2 state before use without rewriting on-disk records:
the self-announcement builder strips any v2 entry inherited from the
stored self-node; the watchtower client drops v2 entries from each
persisted tower's address list (skipping the tower entirely if no
non-v2 address remains); the autopilot connector, graph bootstrapper,
and static-channel backup restore paths skip v2 entries before
attempting outbound dials. Restrict the Tor controller's ADD_ONION
path to v3 keys, including the encrypted on-disk legacy-key fallback.
For inbound announcements, keep the wire codec wire-faithful:
`lnwire.WriteOnionAddr`, `graph/db.encodeOnionAddr`, and the matching
decoders round-trip v2 bytes so `DataToSign` reproduces the bytes the
remote peer signed, signature validation succeeds, and the announcement
is persisted to the graph DB and re-broadcast across restarts byte-for-
byte. RPC surfaces continue to expose the full address set so external
tools can independently reproduce and verify the signed bytes.
Add a netann regression test that signs a [v3, v2, ipv4] announcement,
round-trips it through Encode/Decode, verifies the signature, and
confirms the resulting models.Node preserves the v2 entry. Add a
graph bootstrapper test asserting v2 entries are skipped while v3 and
plain TCP entries on the same node still surface as bootstrap
candidates.
In this commit, we added createValidTLVExtraData which creates a
valid TLV data, and use it in place of createExtraData for messages
that their Encode or Decode requires validating the TLV data, which
were failing initially.
This commit plumbs the combined IngressLimiter (per-peer + global)
through peer.Config and consults it from the readHandler's
*lnwire.OnionMessage case. The decision is factored into a small
allowOnionMessage helper so that the ingress policy is directly
unit-testable without standing up a full Brontide harness. Per-peer is
checked first inside the IngressLimiter: if we consulted the global
limiter first, a peer whose own bucket was already empty would still
get to burn a global token on each attempt, letting a single hostile
peer drain the shared budget and starve legitimate peers.
peer.Config carries a single OnionLimiter field of IngressLimiter type;
the brontide readHandler calls a single AllowN per incoming onion
message and dispatches on sentinel errors via errors.Is for the
first-drop log path. Nil limiter values are treated as "disabled"
throughout, which both preserves the pre-change behavior when onion
messaging is entirely turned off and keeps the brontide test harness
from needing to construct real limiters. Per-peer bucket state is
retained across disconnect at the IngressLimiter layer so a peer
cannot cycle the connection to reset its per-peer allowance.
OnionMessage also gains a WireSize method that computes the
on-the-wire size directly from the in-memory fields (no round-trip
through Encode) so the hot ingress path can charge the right number of
byte tokens without paying for a full serialization.
The accompanying unit tests cover the nil/disabled path, the
per-peer-rejects-first ordering invariant (asserting the global
limiter is not consulted when the per-peer bucket is empty), the
global rejection path, per-peer isolation across distinct pubkeys, and
a small concurrent stress test that asserts every attempt is accounted
for as either accepted or dropped and that the total accepted count
equals the configured burst under -race. A property-based rapid test
on WireSize guards against silent divergence from WriteMessage if the
OnionMessage wire format ever gains a TLV extension.
Before this commit, we'd _always_ set both nonces fields, for both the
staging and the final taproot channels type.
With this commit, we've switched to only setting the new nonce map field
for the final taproot feature bit type.
Fix all lint issues across the taproot RBF coop close changes:
- Fix line length violations (ll) by wrapping long lines and adding
nolint:ll where wrapping would hurt readability.
- Fix nlreturn: add blank lines before return statements.
- Fix misspell: correct "siganture" typos.
- Fix forcetypeassert: add checked type assertions.
- Fix nonamedreturns: remove named returns from function signatures.
- Fix usetesting: replace context.Background() with t.Context().
- Fix unused: remove unused remoteSchnorrSig variable.
- Fix whitespace: add newlines after multi-line func signatures.
- Fix gocritic appendAssign warning.
- Fix gci: correct import ordering.
Also address PR review comments from @erickcestari:
- Remove nonce cache in ClosingNonce() to prevent future footguns.
- Rename extractSigAndNonce to extractSigAndNonceFromClosingSig.
- Rename extractSigAndNonceFromComplete to
extractSigAndNonceFromClosingComplete with channel type validation.
- Replace env.RemoteMusigSession \!= nil with env.IsTaproot().
- Swap manual mocks to mock.Mock in musig_nonce_order_test.go.
MusigChanCloser.ClosingNonce() cached the local nonce and returned
the same one on subsequent calls. Since each RBF round creates a new
MuSig2 session via ProposalClosingOpts() but passes the same SecNonce,
the btcd library's per-session nonce reuse guard was bypassed (fresh
Session each round). Signing different closing transactions (different
fees/sighashes) with the same secret nonce enables private key
extraction via simple linear algebra on the partial signatures.
Fix by adding ClearNonce() to the MusigSession interface and calling
it after each signing round completes, forcing fresh nonce generation
on every RBF iteration. Also fix a rebase issue where
updateAndValidateCloseTerms was not extracting NextCloseeNonce from
ClosingSig messages for subsequent RBF rounds.
lnwallet/chancloser: fix MuSig2 nonce reuse across RBF rounds
MusigChanCloser.ClosingNonce() cached the secret nonce and returned
the same one across RBF rounds. Since each round creates a new
MuSig2 session via ProposalClosingOpts() but passes the same
SecNonce, signing different closing transactions with different
sighashes enables private key extraction.
Fix this by:
1. Storing the full MusigPartialSig from LocalCloseStart in the
LocalOfferSent state, eliminating the second CreateCloseProposal
call in prepareClosingSignatures. This was also flagged in PR
review as wasteful.
2. Adding InvalidateNonce() to the MusigSession interface. After
the closer round completes (CompleteCooperativeClose), the closer
nonce is invalidated so the next RBF round generates fresh. For
the closee, the nonce is invalidated before generating the next
closee nonce in createClosingSigMessage.
Add decode-time validation to closing_complete and closing_sig that
rejects messages containing both regular ECDSA and taproot partial
signatures. This provides defense-in-depth rather than relying solely
on state machine validation.
In this commit, we revise the sig type parsing to make the control flow
clearer, and also to be spec compliant. Before we would error out if
_both_ the CloserNoClosee and the CloserAndClosee fields were set.
lnwallet/chancloser: fix priority ordering for rbf sig parsing
We need to parse the sigs in a strict order, as it's possible for a
party to send more than one siganture.
In this commit we, add support for taproot partial signatures with
nonces to the ClosingComplete message. This is the foundation for
taproot RBF cooperative channel closing, implementing the JIT nonce
pattern required for the modern taproot closing flow.
The changes include a new TaprootClosingSigs struct that mirrors the
existing ClosingSigs but uses PartialSigWithNonce for taproot channels.
The decoding and encoding functions are updated to handle both regular
ECDSA signatures and taproot partial signatures. For taproot channels,
the TaprootClosingSigs field is populated while ClosingSigs remains
empty, maintaining backward compatibility.
We also fix a minor typo in the comment for CloserNoClosee field
(clsoee -> closee).
lnwire: add taproot partial signatures support to closing_sig message
In this commit we, extend the ClosingSig message to support taproot
partial signatures for the RBF cooperative close flow. The ClosingSig
message is sent by the closee in response to a ClosingComplete message.
For taproot channels, we add TaprootPartialSigs which contains partial
signatures without nonces since the remote party already knows our
nonce from the previous ClosingComplete message. We also add a
NextCloseeNonce field for RBF iterations, allowing the closee to
provide a new nonce for the next potential RBF round.
The decoding and encoding functions are updated to handle both regular
signatures and taproot partial signatures, maintaining backward
compatibility with existing non-taproot channels while enabling the
advanced taproot RBF flow.
lnwire: add shutdown nonce support for taproot channels
In this commit we, add support for closee nonces in the Shutdown
message to enable taproot RBF cooperative channel closing. The
ShutdownNonce field allows taproot channels to exchange the initial
nonces required for the MuSig2 signing process during cooperative
closure.
This nonce represents the closee nonce that the remote party will use
when they act as the closer in the RBF flow. The nonce is transmitted
as part of the shutdown flow and is essential for the JIT nonce pattern
used in the modern taproot closing protocol.
The changes maintain backward compatibility as the ShutdownNonce field
is optional and only used for taproot channels that support the
enhanced RBF flow.
lnwire: update test message with taproot signature fields
In this commit we, update the test message utilities to include the new
taproot signature fields added to ClosingComplete and ClosingSig
messages. This ensures the wire protocol tests properly exercise the
new taproot partial signature functionality.
chancloser: add taproot channel infrastructure and nonce state management
In this commit we, add the fundamental infrastructure for taproot RBF
cooperative channel closing. This includes adding taproot channel
detection, MuSig2 session management, and nonce state tracking
throughout the closing state machine.
Key additions include the IsTaproot method on Environment to detect
taproot channels based on the presence of MuSig sessions, and
LocalMusigSession/RemoteMusigSession fields for managing the different
signing contexts. We add NonceState tracking to maintain closee nonces
exchanged during the shutdown phase.
The SendShutdown and ShutdownReceived events are extended to carry
closee nonces for taproot channels, and we add proper error handling
for missing nonces in taproot shutdown messages. These changes provide
the foundation for the taproot-specific state transitions while
maintaining compatibility with existing non-taproot channels.
chancloser: implement taproot cooperative close state transitions
In this commit we, implement the complete taproot RBF cooperative close
state machine transitions. This is a comprehensive change that adds all
the necessary components for taproot channel closing support.
The implementation includes several key areas:
First, we add nonce management helpers including initLocalMusigCloseeNonce
and initRemoteMusigCloseeNonce for properly initializing MuSig2 sessions
with the appropriate closee nonces during the RBF flow.
Second, we implement signature extraction and validation helpers including
partialSigToWireSig for converting partial signatures to wire format, and
extractTaprootSigAndNonce, extractSigAndNonce, and validateAndExtractSigAndNonce
for handling both taproot and regular signatures with proper validation.
Third, we add comprehensive signature encoding logic with encodeClosingSignatures
that creates appropriate signature structures for both channel types, and helper
functions like processRemoteTaprootSig, createLocalCloseeSignature, and
createClosingSigMessage for managing the complex taproot signing flow.
Fourth, we extend the shutdown validation logic to require nonces for
taproot channels and update all state transitions to properly handle
nonce exchange, MuSig2 session initialization, and the dual signature
paths for taproot vs non-taproot channels.
Finally, we add signature preparation logic with prepareClosingSignatures
and extraction helpers like extractSigAndNonceFromComplete that handle
the complex musig signature combination required for taproot channels
while maintaining compatibility with existing ECDSA signatures.
The changes maintain backward compatibility with existing non-taproot
channels while enabling the full taproot RBF cooperative close flow
with proper nonce rotation and signature handling.
chancloser: add taproot test infrastructure and test cases
In this commit we, extend the RBF cooperative close test suite to
support taproot channels. This includes adding schnorr signature
test constants, taproot channel test helpers, and comprehensive
test coverage for the taproot RBF flow.
The changes add localSchnorrSig and remoteSchnorrSig test constants
to mirror the existing ECDSA signatures, and include proper imports
for musig2, chainhash, and lnwallet to support the taproot testing
infrastructure.
The test modifications ensure that both taproot and non-taproot
channels are properly tested throughout the RBF cooperative close
state machine, validating the dual signature handling paths and
nonce management logic introduced in the main implementation.
chancloser: update test utilities and message mapping for taproot
In this commit we, update the chancloser test utilities and message
mapping functions to properly handle the new taproot-specific fields
in the RBF cooperative close flow.
The changes ensure that test harnesses and message mapping functions
are aware of the taproot signature fields and nonce handling required
for the extended wire protocol support. This maintains test coverage
for both existing non-taproot functionality and the new taproot
capabilities.
Drop the unused pong limit decode error now that ping deserialization
accepts the full uint16 wire range. Update the randomized ping generator
to cover the full range so the property tests exercise the no-reply
sentinel values too.
Allow pings in the BOLT 1 no-reply range to decode and be ignored
instead of disconnecting peers. This keeps reconnects compatible with
peers that pad channel_reestablish with no-reply pings.
Extends the ChannelReestablish message to include a new optional
LocalNonces field alongside the existing LocalNonce field. This enables
backwards-compatible transmission of multiple nonces for different
purposes during channel reestablishment.
Changes include:
- Add LocalNonces field to ChannelReestablish struct
- Update Encode/Decode methods to handle the new TLV field
- Extend property-based testing to randomly include LocalNonces
- Maintain full backwards compatibility with existing LocalNonce field
This commit introduces a new TLV structure LocalNoncesData that contains
a map of transaction IDs to MuSig2 nonces. This structure enables
coordinating multiple nonces for different purposes (e.g., channel
commits, splice operations) within a single wire message.
This is a prep for upcoming spec changes to allow a party that has
in-prorgess splices to tell the remote party which nonces to use for
which splice.
Add point-on-curve validation for MuSig2 public nonces at the TLV
decode layer. A MuSig2 nonce is 66 bytes (two 33-byte compressed
secp256k1 public keys). Previously, nonce bytes were accepted without
validation, with invalid points only failing later during MuSig2
session creation deep in the signing flow. Now, malformed nonces from
a peer are rejected immediately at decode time with clear errors.
This hardens all nonce-carrying messages: ClosingComplete (JIT closer
nonces in PartialSigWithNonce), ClosingSig (NextCloseeNonce), Shutdown,
ChannelReestablish, CommitSig, and others.
Add a Timestamp interface for channel and node update ordering values
that abstracts over the two freshness semantics used by the gossip
versions:
- UnixTimestamp (uint64): seconds-since-epoch, used by v1 gossip
channels and nodes.
- BlockHeightTimestamp (uint32): block height, used by v2 gossip
channels and nodes.
Both concrete types implement IsZero() and Cmp(). Cmp returns an error
if the two operands are of different concrete types, preventing
accidental cross-version comparisons.
Add onion message forwarding capability using the OnionPeerActor for
communication. Messages are routed through a receptionist pattern where
each peer has a dedicated OnionPeerActor for handling message sends.
The OnionEndpoint uses the sphinx router for decoding and decrypting the
onion message packet and the encrypted recipient data in the payload of
the onion messages.
Introduce a fat OnionPeerActor that handles the full onion message
processing pipeline for each peer connection. The actor decodes incoming
onion messages, determines the routing action (forward or deliver),
executes the action via PeerMessageSender, and dispatches updates to
subscribers via OnionMessageUpdateDispatcher.
Key components:
- OnionRouter interface abstracting sphinx router operations
- PeerMessageSender interface for forwarding to other peers
- OnionMessageUpdateDispatcher interface for subscriber notifications
- OnionActorFactory for spawning per-peer actors with shared deps
- Full test suite calling Receive() directly with NoOpReplayLog
Extend ChannelEdgePolicy to support v2 channel updates by adding:
- Version field to track gossip protocol version (v1 or v2).
- LastBlockHeight for v2's block-height-based timestamps.
- SecondPeer flag to indicate which peer announced the policy in v2.
- DisableFlags for v2-specific channel disable signaling.
- ExtraSignedFields map for v2 extra signed TLV data.
Add version-aware methods:
- IsNode1() determines if the policy was announced by node_1, handling
both v1 (via ChannelFlags direction bit) and v2 (via SecondPeer).
- IsDisabled() checks disable status using ChannelFlags for v1 and
DisableFlags for v2.
- String() provides version-appropriate string representations.
The new fields use zero values for v1 compatibility (Version defaults
to GossipVersion1, LastBlockHeight to 0, SecondPeer to false). This
lays the groundwork for v2 policy support; a subsequent commit will
handle reading and writing these fields from/to the database.
This commit improves TLV decoding safety and consistency across multiple
packages by enforcing fixed-length requirements and adding unit tests to
prevent malformed TLV records from being accepted.
Changes include:
- lnwire:
* Enforce 8-byte length in Fee TLV decoder.
* Enforce PubNonceSize in Musig2Nonce TLV decoder.
* Enforce 8-byte length in ShortChannelID TLV decoder.
* Added roundtrip and invalid length tests for Fee, Musig2Nonce,
and ShortChannelID records.
- routing/route:
* Enforce Vertex TLV length (33 bytes).
* Added encode/decode and invalid length tests for Vertex.
- tlv:
* Enforce correct length in DBytes33 decoder (33 bytes).
* Added tests ensuring all fixed-size primitive decoders reject
incorrect TLV lengths.
By strictly validating TLV lengths, we prevent malformed or corrupted
TLV records from being silently accepted, improving protocol safety.
Define a GossipVersion enum along with a GossipMessage interface to be
satisfied by all gossip related messages. This will be useful later on
when we want to make decisions based on the protocol version that a
message is part of.
In this commit, the lnwire.NodeAnnouncement2 type is defined. This will
be used to represent the `node_announcement_2` message used in the
Gossip 2 (1.75) protocol.
We leave a TODO that should be addressed after a discussion at the spec
meeting. For now, having the incorrect TLV type is not a problem since
this ChannelUpdate2 type is not used in production.
In preparation for adding a NodeAnnouncement2 struct along with a
NodeAnnouncement interface, this commit renames the existing
NodeAnnouncment struct to NodeAnnouncement1.