Discovered via differential fuzzing using bitcoinfuzz against rust-lightning, lightning-kmp, and CLN implementations.
The BOLT12 offer parser incorrectly accepts invalid offers by creating empty collections for TLV fields instead of rejecting malformed input, causing validation to pass when it should fail. This creates a compatibility issue where lightning-kmp accepts offers that other implementations correctly reject.
Offer to Reproduce
Expected: Validation failure (matches rust-lightning and CLN behavior)
Actual: Creates OfferChains(chains=[]) and OfferPaths(paths=[]), bypassing validation
Root Cause
The offer string lno1qgqpqqq decodes to TLV bytes [02 00 10 00]:
- Type 2 (OfferChains) with length 0
- Type 16 (OfferPaths) with length 0
TLV readers create empty collections instead of failing:
override fun read(input: Input): OfferPaths {
val paths = ArrayList<ContactInfo.BlindedPath>()
while (input.availableBytes > 0) { // Never executes when length=0
paths.add(readPath(input))
}
return OfferPaths(paths) // Returns empty list instead of failing
}
This bypasses the validation check:
if (records.get<OfferIssuerId>() == null && records.get<OfferPaths>() == null)
return Left(MissingRequiredTlv(22))
Discovered via differential fuzzing using bitcoinfuzz against rust-lightning, lightning-kmp, and CLN implementations.
The BOLT12 offer parser incorrectly accepts invalid offers by creating empty collections for TLV fields instead of rejecting malformed input, causing validation to pass when it should fail. This creates a compatibility issue where lightning-kmp accepts offers that other implementations correctly reject.
Offer to Reproduce
Expected: Validation failure (matches rust-lightning and CLN behavior)
Actual: Creates OfferChains(chains=[]) and OfferPaths(paths=[]), bypassing validation
Root Cause
The offer string
lno1qgqpqqqdecodes to TLV bytes[02 00 10 00]:TLV readers create empty collections instead of failing:
This bypasses the validation check: