Skip to content

Commit

Permalink
Update TLV ranges for Offers (#716)
Browse files Browse the repository at this point in the history
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].
  • Loading branch information
thomash-acinq authored Oct 16, 2024
1 parent e6c1974 commit d19bc3e
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 6 deletions.
20 changes: 14 additions & 6 deletions src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -648,19 +648,27 @@ object OfferTypes {
}
}

private fun isOfferTlv(tlv: GenericTlv): Boolean {
// Offer TLVs are in the range [1, 79] or [1000000000, 1999999999].
return tlv.tag in 1..79 || tlv.tag in 1000000000..1999999999
}

private fun isInvoiceRequestTlv(tlv: GenericTlv): Boolean {
// Invoice request TLVs are in the range [0, 159] or [1000000000, 2999999999].
return tlv.tag in 0..159 || tlv.tag in 1000000000..2999999999
}

fun filterOfferFields(tlvs: TlvStream<InvoiceRequestTlv>): TlvStream<OfferTlv> {
// Offer TLVs are in the range (0, 80).
return TlvStream(
tlvs.records.filterIsInstance<OfferTlv>().toSet(),
tlvs.unknown.filter { it.tag < 80 }.toSet()
tlvs.unknown.filter { isOfferTlv(it) }.toSet()
)
}

fun filterInvoiceRequestFields(tlvs: TlvStream<InvoiceTlv>): TlvStream<InvoiceRequestTlv> {
// 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).
return TlvStream(
tlvs.records.filterIsInstance<InvoiceRequestTlv>().toSet(),
tlvs.unknown.filter { it.tag < 160 }.toSet()
tlvs.unknown.filter { isInvoiceRequestTlv(it) }.toSet()
)
}

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

Expand Down Expand Up @@ -932,7 +940,7 @@ object OfferTypes {
if (records.get<InvoiceRequestMetadata>() == null) return Left(MissingRequiredTlv(0L))
if (records.get<InvoiceRequestPayerId>() == null) return Left(MissingRequiredTlv(88))
if (records.get<Signature>() == null) return Left(MissingRequiredTlv(240))
if (records.unknown.any { it.tag >= 160 }) return Left(ForbiddenTlv(records.unknown.find { it.tag >= 160 }!!.tag))
if (records.unknown.any { !isInvoiceRequestTlv(it) }) return Left(ForbiddenTlv(records.unknown.find { !isInvoiceRequestTlv(it) }!!.tag))
return Right(InvoiceRequest(records))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import fr.acinq.lightning.Lightning.randomBytes32
import fr.acinq.lightning.Lightning.randomKey
import fr.acinq.lightning.crypto.RouteBlinding
import fr.acinq.lightning.logging.MDCLogger
import fr.acinq.lightning.payment.Bolt12Invoice
import fr.acinq.lightning.tests.TestConstants
import fr.acinq.lightning.tests.utils.LightningTestSuite
import fr.acinq.lightning.tests.utils.testLoggerFactory
Expand Down Expand Up @@ -522,4 +523,21 @@ class OfferTypesTestsCommon : LightningTestSuite() {
val expectedOffer = Offer.decode("lno1zrxq8pjw7qjlm68mtp7e3yvxee4y5xrgjhhyf2fxhlphpckrvevh50u0qf70a6j2x2akrhazctejaaqr8y4qtzjtjzmfesay6mzr3s789uryuqsr8dpgfgxuk56vh7cl89769zdpdrkqwtypzhu2t8ehp73dqeeq65lsqvlx5pj8mw2kz54p4f6ct66stdfxz0df8nqq7svjjdjn2dv8sz28y7z07yg3vqyfyy8ywevqc8kzp36lhd5cqwlpkg8vdcqsfvz89axkmv5sgdysmwn95tpsct6mdercmz8jh2r82qqscrf6uc3tse5gw5sv5xjdfw8f6c").get()
assertEquals(expectedOffer, offer)
}

@Test
fun `experimental TLVs range`() {
val trampolineNode = PublicKey.fromHex("03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f")
val nodeParams = TestConstants.Alice.nodeParams.copy(chain = Chain.Mainnet)
val (defaultOffer, key) = nodeParams.defaultOffer(trampolineNode)
val offerWithUnknownTlvs = Offer.validate(TlvStream(defaultOffer.records.records, setOf(GenericTlv(53, ByteVector.fromHex("b46af6")), GenericTlv(1000759647, ByteVector.fromHex("41dec6"))))).right!!
assertTrue(Offer.validate(TlvStream(defaultOffer.records.records, setOf(GenericTlv(127, ByteVector.fromHex("cd58"))))).isLeft)
assertTrue(Offer.validate(TlvStream(defaultOffer.records.records, setOf(GenericTlv(2045259641, ByteVector.fromHex("e84ad9"))))).isLeft)
val request = InvoiceRequest(offerWithUnknownTlvs, 5500.msat, 1, Features.empty, randomKey(), null, Block.LivenetGenesisBlock.hash)
assertEquals(request.offer, offerWithUnknownTlvs)
val requestWithUnknownTlvs = InvoiceRequest.validate(TlvStream(request.records.records, setOf(GenericTlv(127, ByteVector.fromHex("cd58")), GenericTlv(2045259645, ByteVector.fromHex("e84ad9"))))).right!!
assertTrue(InvoiceRequest.validate(TlvStream(request.records.records, setOf(GenericTlv(197, ByteVector.fromHex("cd58"))))).isLeft)
assertTrue(InvoiceRequest.validate(TlvStream(request.records.records, setOf(GenericTlv(3975455643, ByteVector.fromHex("e84ad9"))))).isLeft)
val invoice = Bolt12Invoice(requestWithUnknownTlvs, randomBytes32(), key, 300, Features.empty, listOf())
assertEquals(removeSignature(invoice.invoiceRequest.records), removeSignature(requestWithUnknownTlvs.records))
}
}

0 comments on commit d19bc3e

Please sign in to comment.