eclair icon indicating copy to clipboard operation
eclair copied to clipboard

BOLT 11 Compliance: Invoice `r` field don't reject empty routing hints

Open erickcestari opened this issue 7 months ago • 0 comments

Background

After doing some differential fuzzing between CLN, rust-lightning, LND and Eclair using bitcoinfuzz I noticed that Eclair (LND too) currently accepts bolt11 invoices with empty routing hints in the r field, while CLN and rust-lightning rejects. This violates BOLT 11 specification requirements.

BOLT 11 Requirements:

  • r field "MUST contain one or more ordered entries, indicating the forward route from a public node to the final destination"
  • Each hop must contain exactly 51 bytes (408 bits) (pubkey: 33B, short_channel_id: 8B, fee_base_msat: 4B, fee_proportional_millionths: 4B, cltv_expiry_delta: 2B)

Current Behavior: Eclair currently accepts BOLT 11 invoices with empty routing hints (r fields) due to how the codec is implemented

  1. Length Calculation Logic The extraHopsLengthCodec uses this logic:
val extraHopsLengthCodec = Codec[Int](
  (_: Int) => Attempt.successful(BitVector.empty),
  (wire: BitVector) => Attempt.successful(DecodeResult(wire.size.toInt / 408, wire)) // decoding: infers count from size
)

Key Issue: wire.size.toInt / 408 when wire.size = 0 results in 0, which means zero hops.

  1. List Decoding The listOfN(extraHopsLengthCodec, extraHopCodec) creates a list with the calculated length

  2. RoutingInfo Construction

case class RoutingInfo(path: List[ExtraHop]) extends TaggedField

An empty List[ExtraHop] creates a valid RoutingInfo object with zero routing hints.

Expected Behavior: Reject invoices with empty r fields.

Impact:

  • Spec non-compliance
  • Inconsistent behavior between Lightning implementations

Example invoice:

lnbc1p5q54jjpp5fe0dhqdt4m97psq0fv3wjlk95cclnatvuvq49xtnc8rzrp0dysusdqqcqzzsxqrrs0fppqy6uew5229e67r9xzzm9mjyfwseclstdgsp5rnanj9x5rnanj9xnq28hhgd6c7yxlmh6lta047h6lqqqqqqqqqqqrqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6qqqqqqqqqqqqqqqqqqq9kvnknh7ug5mttnqqqqqqqqq8849gwfhvnp9rqpe0cy97

Proposed Fix: Add validation in the RoutingInfo case class:

case class RoutingInfo(path: List[ExtraHop]) extends TaggedField {
  require(path.nonEmpty, "routing info must contain one or more entries")
}

erickcestari avatar Jun 09 '25 19:06 erickcestari