-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Background
During differential fuzzing tests on different Lightning implementations using Bitcoinfuzz, we've identified an inconsistency in how LND handles the expiry field in BOLT11 invoices compared to other implementations like C-Lightning and LDK.
When deserializing an invoice with a large expiry value, LND produces a negative expiry value due to overflow, while other implementations handle the same value correctly. This is caused by LND's use of time.Duration (int64) for storing the expiry field and the additional conversion to nanoseconds (multiplying by 1,000,000,000), which significantly reduces the effective range compared to other implementations that use uint64.
Steps to reproduce
https://github.com/bitcoinfuzz/bitcoinfuzz/actions/runs/15021579593/job/42211806963#step:33:55
Invoice that demonstrates the issue:
lnbc1qqygh9qpp5s7zqqcqpjpqqlqqqqphqqqqqqqqcqpjqqqqqqqqqqqqqqqqqqqqqxqgsqqqqqqqdqqqqqqqqqqqqqpjpqqlqqqqqqqqqqqqqqcqpjqqqqqsqqqqqqqqqqqqqqqqqnqqnqqpqqnqqqqqqqqqqqqqqqqqlqqqqqqqqqqqqqqqqqqdfrf47
Results:
LND:
HASH=878400600190400f8000006e0000000001800640000000000000000000000000;AMOUNT=0;DESCRIPTION=;RECIPIENT=02f36b27dd1d95740c66c076fb53b54991a19a525d997be3a868ff6f542242b6bd;EXPIRY=-3646508323;TIMESTAMP=4480160;ROUTING_HINTS=0;MIN_CLTV=18
C-Lightning:
HASH=878400600190400f8000006e0000000001800640000000000000000000000000;AMOUNT=0;DESCRIPTION=;RECIPIENT=02f36b27dd1d95740c66c076fb53b54991a19a525d997be3a868ff6f542242b6bd;EXPIRY=549755813888;TIMESTAMP=4480160;ROUTING_HINTS=0;MIN_CLTV=18
Note the significant difference in the EXPIRY values: LND shows a negative value (-3646508323) while C-Lightning shows 549755813888.
The underlying issue is that LND stores the expiry as a time.Duration, which is an int64, and then multiplies the decoded expiry value by 1,000,000,000 to store it as nanoseconds. This conversion significantly reduces the effective range of valid expiry values that LND can handle without overflow.
While the BOLT11 specification doesn't specify a maximum for the expiration field (it could technically be up to 639 bytes long), most implementations assume it's at most a uint64.
Proposed Solution
Consider changing LND to use uint64 for expiry fields rather than time.Duration for better compatibility with other implementations.