Eclair/Lightning-kmp incorrectly parses BOLT12 offer with invalid Bech32 encoding
Eclair and lightning-kmp is successfully parsing and extracting data from a BOLT12 offer string that contains invalid Bech32 encoding, while rust-lightning rejects it with a parsing error. bitcoin-kmp's Bech32 validation is insufficient during offer deserialization.
Offer deserialization failed for lno1zcss88lll8vlpqqqqqqclllllllvwvcqpq8qllllgqrqqgqq8s(q8888
Module: rust-lightning
Result: Bech32(Parse(Char(InvalidChar('('))))
Module: Eclair
Result: CHAINS=6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000;METADATA=;DESCRIPTION=;FEATURES=;ABSOLUTE_EXPIRY=;ISSUER=;QUANTITY=;ISSUER_ID=039ffff9d9f0800000018ffffffffec73300080e0fffff40060020003c3e039ce7
Offer deserialization failed for lno1zcss88lll8vlpqqqqqqclllllllvwvcqpq8qllllgqrqqgqq8s(q8888
Module: rust-lightning
Result: Bech32(Parse(Char(InvalidChar('('))))
Module: LightningKmp
Result: CHAINS=6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000;METADATA=;DESCRIPTION=;FEATURES=;ABSOLUTE_EXPIRY=null;ISSUER=null;QUANTITY=null;ISSUER_ID=039ffff9d9f0800000018ffffffffec73300080e0fffff40060020003c3e039ce7
public fun decode(bech32: String, noChecksum: Boolean = false): Triple<String, Array<Int5>, Encoding> {
require(bech32.lowercase() == bech32 || bech32.uppercase() == bech32) { "mixed case strings are not valid bech32" }
bech32.forEach { require(it.code in 33..126) { "invalid character " } } <-- `)` is ASCII (40)
val input = bech32.lowercase()
val pos = input.lastIndexOf('1')
val hrp = input.take(pos)
require(hrp.length in 1..83) { "hrp must contain 1 to 83 characters" }
val data = Array<Int5>(input.length - pos - 1) { 0 }
for (i in 0..data.lastIndex) data[i] = map[input[pos + 1 + i].code] <-- `)` will return -1
return if (noChecksum) {
Triple(hrp, data, Encoding.Beck32WithoutChecksum)
} else {
val encoding = when (polymod(expand(hrp), data)) {
Encoding.Bech32.constant -> Encoding.Bech32
Encoding.Bech32m.constant -> Encoding.Bech32m
else -> throw IllegalArgumentException("invalid checksum for $bech32")
}
Triple(hrp, data.dropLast(6).toTypedArray(), encoding)
}
}
Thanks! There is indeed a bug as we do not check for invalid characters properly, but decode() still fails later when it tries to verify checksums, with an ugly Index 5 out of bounds for length 5 error. Are you sure that you're properly checking the result ? (bitcoin-kmp would throw an exception, which lighning-kmp wraps inside a Failure object).
Thanks! There is indeed a bug as we do not check for invalid characters properly, but
decode()still fails later when it tries to verify checksums, with an uglyIndex 5 out of bounds for length 5error. Are you sure that you're properly checking the result ? (bitcoin-kmpwould throw an exception, whichlighning-kmpwraps inside aFailureobject).
I didn't get the error because I'm using it to decode offers which don't have checksums.
You're right, Offer decoding bypasses checksum verification. I've checked that this change will fix offer decoding in lightning-kmp (using Offer.decode()). Thanks!