Skip to content

Commit d19bc3e

Browse files
Update TLV ranges for Offers (#716)
The spec now allows the ranges [1000000000, 1999999999] and [2000000000, 2999999999] for offer and invoice request TLVs in addition to the previous [1, 79] and [80, 159].
1 parent e6c1974 commit d19bc3e

File tree

2 files changed

+32
-6
lines changed

2 files changed

+32
-6
lines changed

src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -648,19 +648,27 @@ object OfferTypes {
648648
}
649649
}
650650

651+
private fun isOfferTlv(tlv: GenericTlv): Boolean {
652+
// Offer TLVs are in the range [1, 79] or [1000000000, 1999999999].
653+
return tlv.tag in 1..79 || tlv.tag in 1000000000..1999999999
654+
}
655+
656+
private fun isInvoiceRequestTlv(tlv: GenericTlv): Boolean {
657+
// Invoice request TLVs are in the range [0, 159] or [1000000000, 2999999999].
658+
return tlv.tag in 0..159 || tlv.tag in 1000000000..2999999999
659+
}
660+
651661
fun filterOfferFields(tlvs: TlvStream<InvoiceRequestTlv>): TlvStream<OfferTlv> {
652-
// Offer TLVs are in the range (0, 80).
653662
return TlvStream(
654663
tlvs.records.filterIsInstance<OfferTlv>().toSet(),
655-
tlvs.unknown.filter { it.tag < 80 }.toSet()
664+
tlvs.unknown.filter { isOfferTlv(it) }.toSet()
656665
)
657666
}
658667

659668
fun filterInvoiceRequestFields(tlvs: TlvStream<InvoiceTlv>): TlvStream<InvoiceRequestTlv> {
660-
// Invoice request TLVs are in the range [0, 160): invoice request metadata (tag 0), offer TLVs, and additional invoice request TLVs in the range [80, 160).
661669
return TlvStream(
662670
tlvs.records.filterIsInstance<InvoiceRequestTlv>().toSet(),
663-
tlvs.unknown.filter { it.tag < 160 }.toSet()
671+
tlvs.unknown.filter { isInvoiceRequestTlv(it) }.toSet()
664672
)
665673
}
666674

@@ -806,7 +814,7 @@ object OfferTypes {
806814
fun validate(records: TlvStream<OfferTlv>): Either<InvalidTlvPayload, Offer> {
807815
if (records.get<OfferDescription>() == null && records.get<OfferAmount>() != null) return Left(MissingRequiredTlv(10))
808816
if (records.get<OfferNodeId>() == null && records.get<OfferPaths>() == null) return Left(MissingRequiredTlv(22))
809-
if (records.unknown.any { it.tag >= 80 }) return Left(ForbiddenTlv(records.unknown.find { it.tag >= 80 }!!.tag))
817+
if (records.unknown.any { !isOfferTlv(it) }) return Left(ForbiddenTlv(records.unknown.find { !isOfferTlv(it) }!!.tag))
810818
return Right(Offer(records))
811819
}
812820

@@ -932,7 +940,7 @@ object OfferTypes {
932940
if (records.get<InvoiceRequestMetadata>() == null) return Left(MissingRequiredTlv(0L))
933941
if (records.get<InvoiceRequestPayerId>() == null) return Left(MissingRequiredTlv(88))
934942
if (records.get<Signature>() == null) return Left(MissingRequiredTlv(240))
935-
if (records.unknown.any { it.tag >= 160 }) return Left(ForbiddenTlv(records.unknown.find { it.tag >= 160 }!!.tag))
943+
if (records.unknown.any { !isInvoiceRequestTlv(it) }) return Left(ForbiddenTlv(records.unknown.find { !isInvoiceRequestTlv(it) }!!.tag))
936944
return Right(InvoiceRequest(records))
937945
}
938946

src/commonTest/kotlin/fr/acinq/lightning/wire/OfferTypesTestsCommon.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import fr.acinq.lightning.Lightning.randomBytes32
1010
import fr.acinq.lightning.Lightning.randomKey
1111
import fr.acinq.lightning.crypto.RouteBlinding
1212
import fr.acinq.lightning.logging.MDCLogger
13+
import fr.acinq.lightning.payment.Bolt12Invoice
1314
import fr.acinq.lightning.tests.TestConstants
1415
import fr.acinq.lightning.tests.utils.LightningTestSuite
1516
import fr.acinq.lightning.tests.utils.testLoggerFactory
@@ -522,4 +523,21 @@ class OfferTypesTestsCommon : LightningTestSuite() {
522523
val expectedOffer = Offer.decode("lno1zrxq8pjw7qjlm68mtp7e3yvxee4y5xrgjhhyf2fxhlphpckrvevh50u0qf70a6j2x2akrhazctejaaqr8y4qtzjtjzmfesay6mzr3s789uryuqsr8dpgfgxuk56vh7cl89769zdpdrkqwtypzhu2t8ehp73dqeeq65lsqvlx5pj8mw2kz54p4f6ct66stdfxz0df8nqq7svjjdjn2dv8sz28y7z07yg3vqyfyy8ywevqc8kzp36lhd5cqwlpkg8vdcqsfvz89axkmv5sgdysmwn95tpsct6mdercmz8jh2r82qqscrf6uc3tse5gw5sv5xjdfw8f6c").get()
523524
assertEquals(expectedOffer, offer)
524525
}
526+
527+
@Test
528+
fun `experimental TLVs range`() {
529+
val trampolineNode = PublicKey.fromHex("03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")
530+
val nodeParams = TestConstants.Alice.nodeParams.copy(chain = Chain.Mainnet)
531+
val (defaultOffer, key) = nodeParams.defaultOffer(trampolineNode)
532+
val offerWithUnknownTlvs = Offer.validate(TlvStream(defaultOffer.records.records, setOf(GenericTlv(53, ByteVector.fromHex("b46af6")), GenericTlv(1000759647, ByteVector.fromHex("41dec6"))))).right!!
533+
assertTrue(Offer.validate(TlvStream(defaultOffer.records.records, setOf(GenericTlv(127, ByteVector.fromHex("cd58"))))).isLeft)
534+
assertTrue(Offer.validate(TlvStream(defaultOffer.records.records, setOf(GenericTlv(2045259641, ByteVector.fromHex("e84ad9"))))).isLeft)
535+
val request = InvoiceRequest(offerWithUnknownTlvs, 5500.msat, 1, Features.empty, randomKey(), null, Block.LivenetGenesisBlock.hash)
536+
assertEquals(request.offer, offerWithUnknownTlvs)
537+
val requestWithUnknownTlvs = InvoiceRequest.validate(TlvStream(request.records.records, setOf(GenericTlv(127, ByteVector.fromHex("cd58")), GenericTlv(2045259645, ByteVector.fromHex("e84ad9"))))).right!!
538+
assertTrue(InvoiceRequest.validate(TlvStream(request.records.records, setOf(GenericTlv(197, ByteVector.fromHex("cd58"))))).isLeft)
539+
assertTrue(InvoiceRequest.validate(TlvStream(request.records.records, setOf(GenericTlv(3975455643, ByteVector.fromHex("e84ad9"))))).isLeft)
540+
val invoice = Bolt12Invoice(requestWithUnknownTlvs, randomBytes32(), key, 300, Features.empty, listOf())
541+
assertEquals(removeSignature(invoice.invoiceRequest.records), removeSignature(requestWithUnknownTlvs.records))
542+
}
525543
}

0 commit comments

Comments
 (0)