Skip to content

Commit 91fa827

Browse files
committed
Update trampoline payment to blinded path to match spec proposal
We update our trampoline payments to blinded paths to match the official specification from lightning/bolts#836. The blinded paths and recipient features are included in the trampoline onion, which potentially allows using multiple trampoline hops. That was already what we were doing with experimental TLVs, so we simply update the TLV values to match the spec values.
1 parent 56f7c61 commit 91fa827

File tree

3 files changed

+314
-57
lines changed

3 files changed

+314
-57
lines changed

modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/PaymentOnion.kt

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import fr.acinq.bitcoin.utils.Either
1111
import fr.acinq.bitcoin.utils.flatMap
1212
import fr.acinq.lightning.*
1313
import fr.acinq.lightning.payment.Bolt11Invoice
14-
import fr.acinq.lightning.payment.Bolt12Invoice
14+
import fr.acinq.lightning.payment.Bolt12Invoice.Companion.PaymentBlindedContactInfo
1515
import fr.acinq.lightning.utils.msat
1616
import fr.acinq.lightning.utils.toByteVector
1717

@@ -151,16 +151,43 @@ sealed class OnionPaymentPayloadTlv : Tlv {
151151
}
152152

153153
/**
154-
* Invoice feature bits. Only included for intermediate trampoline nodes when they should convert to a legacy payment
155-
* because the final recipient doesn't support trampoline.
154+
* Features that may be used to reach the recipient, provided by the payment sender (usually obtained them from an invoice).
155+
* Only included for a trampoline node when relaying to a non-trampoline recipient using [OutgoingBlindedPaths] or [InvoiceRoutingInfo].
156156
*/
157-
data class InvoiceFeatures(val features: ByteVector) : OnionPaymentPayloadTlv() {
158-
override val tag: Long get() = InvoiceFeatures.tag
157+
data class RecipientFeatures(val features: ByteVector) : OnionPaymentPayloadTlv() {
158+
override val tag: Long get() = RecipientFeatures.tag
159159
override fun write(out: Output) = LightningCodecs.writeBytes(features, out)
160160

161-
companion object : TlvValueReader<InvoiceFeatures> {
162-
const val tag: Long = 66097
163-
override fun read(input: Input): InvoiceFeatures = InvoiceFeatures(ByteVector(LightningCodecs.bytes(input, input.availableBytes)))
161+
companion object : TlvValueReader<RecipientFeatures> {
162+
const val tag: Long = 21
163+
override fun read(input: Input): RecipientFeatures = RecipientFeatures(ByteVector(LightningCodecs.bytes(input, input.availableBytes)))
164+
}
165+
}
166+
167+
/**
168+
* Blinded paths that can be used to reach the final recipient.
169+
* Only included for a trampoline node when paying a Bolt 12 invoice that doesn't support trampoline.
170+
*/
171+
data class OutgoingBlindedPaths(val paths: List<PaymentBlindedContactInfo>) : OnionPaymentPayloadTlv() {
172+
override val tag: Long get() = OutgoingBlindedPaths.tag
173+
override fun write(out: Output) {
174+
for (path in paths) {
175+
OfferTypes.writePath(path.route, out)
176+
OfferTypes.writePaymentInfo(path.paymentInfo, out)
177+
}
178+
}
179+
180+
companion object : TlvValueReader<OutgoingBlindedPaths> {
181+
const val tag: Long = 22
182+
override fun read(input: Input): OutgoingBlindedPaths {
183+
val paths = ArrayList<PaymentBlindedContactInfo>()
184+
while (input.availableBytes > 0) {
185+
val route = OfferTypes.readPath(input)
186+
val payInfo = OfferTypes.readPaymentInfo(input)
187+
paths.add(PaymentBlindedContactInfo(route, payInfo))
188+
}
189+
return OutgoingBlindedPaths(paths)
190+
}
164191
}
165192
}
166193

@@ -205,30 +232,6 @@ sealed class OnionPaymentPayloadTlv : Tlv {
205232
}
206233
}
207234

208-
/** Blinded paths to relay the payment to */
209-
data class OutgoingBlindedPaths(val paths: List<Bolt12Invoice.Companion.PaymentBlindedContactInfo>) : OnionPaymentPayloadTlv() {
210-
override val tag: Long get() = OutgoingBlindedPaths.tag
211-
override fun write(out: Output) {
212-
for (path in paths) {
213-
OfferTypes.writePath(path.route, out)
214-
OfferTypes.writePaymentInfo(path.paymentInfo, out)
215-
}
216-
}
217-
218-
companion object : TlvValueReader<OutgoingBlindedPaths> {
219-
const val tag: Long = 66102
220-
override fun read(input: Input): OutgoingBlindedPaths {
221-
val paths = ArrayList<Bolt12Invoice.Companion.PaymentBlindedContactInfo>()
222-
while (input.availableBytes > 0) {
223-
val route = OfferTypes.readPath(input)
224-
val payInfo = OfferTypes.readPaymentInfo(input)
225-
paths.add(Bolt12Invoice.Companion.PaymentBlindedContactInfo(route, payInfo))
226-
}
227-
return OutgoingBlindedPaths(paths)
228-
}
229-
}
230-
}
231-
232235
}
233236

234237
object PaymentOnion {
@@ -256,9 +259,10 @@ object PaymentOnion {
256259
OnionPaymentPayloadTlv.PaymentMetadata.tag to OnionPaymentPayloadTlv.PaymentMetadata.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
257260
OnionPaymentPayloadTlv.TotalAmount.tag to OnionPaymentPayloadTlv.TotalAmount.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
258261
OnionPaymentPayloadTlv.TrampolineOnion.tag to OnionPaymentPayloadTlv.TrampolineOnion.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
259-
OnionPaymentPayloadTlv.InvoiceFeatures.tag to OnionPaymentPayloadTlv.InvoiceFeatures.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
260-
OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag to OnionPaymentPayloadTlv.InvoiceRoutingInfo.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
262+
OnionPaymentPayloadTlv.RecipientFeatures.tag to OnionPaymentPayloadTlv.RecipientFeatures.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
261263
OnionPaymentPayloadTlv.OutgoingBlindedPaths.tag to OnionPaymentPayloadTlv.OutgoingBlindedPaths.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
264+
// The following TLVs aren't official TLVs from the BOLTs.
265+
OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag to OnionPaymentPayloadTlv.InvoiceRoutingInfo.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
262266
)
263267
)
264268

@@ -401,6 +405,7 @@ object PaymentOnion {
401405
data class ChannelRelayPayload(val records: TlvStream<OnionPaymentPayloadTlv>) : PerHopPayload() {
402406
val amountToForward = records.get<OnionPaymentPayloadTlv.AmountToForward>()!!.amount
403407
val outgoingCltv = records.get<OnionPaymentPayloadTlv.OutgoingCltv>()!!.cltv
408+
val outgoingChannelId = records.get<OnionPaymentPayloadTlv.OutgoingChannelId>()!!.shortChannelId
404409

405410
override fun write(out: Output) = tlvSerializer.write(records, out)
406411

@@ -427,7 +432,23 @@ object PaymentOnion {
427432

428433
companion object : PerHopPayloadReader<BlindedChannelRelayPayload> {
429434
override fun read(input: Input): Either<InvalidOnionPayload, BlindedChannelRelayPayload> {
430-
return PerHopPayload.read(input).map { BlindedChannelRelayPayload(it) }
435+
return PerHopPayload.read(input).flatMap { tlvs ->
436+
when {
437+
tlvs.get<OnionPaymentPayloadTlv.AmountToForward>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.AmountToForward.tag, 0))
438+
tlvs.get<OnionPaymentPayloadTlv.OutgoingCltv>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingCltv.tag, 0))
439+
tlvs.get<OnionPaymentPayloadTlv.OutgoingChannelId>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingChannelId.tag, 0))
440+
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
441+
else -> Either.Right(BlindedChannelRelayPayload(tlvs))
442+
}
443+
}
444+
}
445+
446+
fun create(encryptedData: ByteVector, pathKey: PublicKey?): BlindedChannelRelayPayload {
447+
val tlvs = buildSet {
448+
add(OnionPaymentPayloadTlv.EncryptedRecipientData(encryptedData))
449+
pathKey?.let { add(OnionPaymentPayloadTlv.PathKey(it)) }
450+
}
451+
return BlindedChannelRelayPayload(TlvStream(tlvs))
431452
}
432453
}
433454
}
@@ -477,7 +498,7 @@ object PaymentOnion {
477498
val outgoingNodeId = records.get<OnionPaymentPayloadTlv.OutgoingNodeId>()!!.nodeId
478499
val paymentSecret = records.get<OnionPaymentPayloadTlv.PaymentData>()!!.secret
479500
val paymentMetadata = records.get<OnionPaymentPayloadTlv.PaymentMetadata>()?.data
480-
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.InvoiceFeatures>()!!.features
501+
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.RecipientFeatures>()!!.features
481502
val invoiceRoutingInfo = records.get<OnionPaymentPayloadTlv.InvoiceRoutingInfo>()!!.extraHops
482503

483504
override fun write(out: Output) = tlvSerializer.write(records, out)
@@ -490,7 +511,7 @@ object PaymentOnion {
490511
tlvs.get<OnionPaymentPayloadTlv.OutgoingCltv>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingCltv.tag, 0))
491512
tlvs.get<OnionPaymentPayloadTlv.OutgoingNodeId>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingNodeId.tag, 0))
492513
tlvs.get<OnionPaymentPayloadTlv.PaymentData>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.PaymentData.tag, 0))
493-
tlvs.get<OnionPaymentPayloadTlv.InvoiceFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceFeatures.tag, 0))
514+
tlvs.get<OnionPaymentPayloadTlv.RecipientFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.RecipientFeatures.tag, 0))
494515
tlvs.get<OnionPaymentPayloadTlv.InvoiceRoutingInfo>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag, 0))
495516
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
496517
tlvs.get<OnionPaymentPayloadTlv.PathKey>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.PathKey.tag, 0))
@@ -508,19 +529,23 @@ object PaymentOnion {
508529
add(OnionPaymentPayloadTlv.OutgoingNodeId(targetNodeId))
509530
add(OnionPaymentPayloadTlv.PaymentData(invoice.paymentSecret, totalAmount))
510531
invoice.paymentMetadata?.let { add(OnionPaymentPayloadTlv.PaymentMetadata(it)) }
511-
add(OnionPaymentPayloadTlv.InvoiceFeatures(invoice.features.toByteArray().toByteVector()))
532+
add(OnionPaymentPayloadTlv.RecipientFeatures(invoice.features.toByteArray().toByteVector()))
512533
add(OnionPaymentPayloadTlv.InvoiceRoutingInfo(routingInfo.map { it.hints }))
513534
}
514535
)
515536
)
516537
}
517538
}
518539

540+
/**
541+
* Create a trampoline payload to tell our trampoline node to relay to a blinded path, where the recipient doesn't support trampoline.
542+
* This only reveals the blinded path to our trampoline node, which doesn't reveal the recipient's identity.
543+
*/
519544
data class RelayToBlindedPayload(val records: TlvStream<OnionPaymentPayloadTlv>) : PerHopPayload() {
520545
val amountToForward = records.get<OnionPaymentPayloadTlv.AmountToForward>()!!.amount
521546
val outgoingCltv = records.get<OnionPaymentPayloadTlv.OutgoingCltv>()!!.cltv
522547
val outgoingBlindedPaths = records.get<OnionPaymentPayloadTlv.OutgoingBlindedPaths>()!!.paths
523-
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.InvoiceFeatures>()!!.features
548+
val recipientFeatures = records.get<OnionPaymentPayloadTlv.RecipientFeatures>()?.features ?: Features.empty
524549

525550
override fun write(out: Output) = tlvSerializer.write(records, out)
526551

@@ -530,7 +555,6 @@ object PaymentOnion {
530555
when {
531556
tlvs.get<OnionPaymentPayloadTlv.AmountToForward>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.AmountToForward.tag, 0))
532557
tlvs.get<OnionPaymentPayloadTlv.OutgoingCltv>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingCltv.tag, 0))
533-
tlvs.get<OnionPaymentPayloadTlv.InvoiceFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceFeatures.tag, 0))
534558
tlvs.get<OnionPaymentPayloadTlv.OutgoingBlindedPaths>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingBlindedPaths.tag, 0))
535559
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
536560
tlvs.get<OnionPaymentPayloadTlv.PathKey>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.PathKey.tag, 0))
@@ -539,14 +563,14 @@ object PaymentOnion {
539563
}
540564
}
541565

542-
fun create(amount: MilliSatoshi, expiry: CltvExpiry, features: Features, blindedPaths: List<Bolt12Invoice.Companion.PaymentBlindedContactInfo>): RelayToBlindedPayload =
566+
fun create(amount: MilliSatoshi, expiry: CltvExpiry, features: Features, blindedPaths: List<PaymentBlindedContactInfo>): RelayToBlindedPayload =
543567
RelayToBlindedPayload(
544568
TlvStream(
545569
setOf(
546570
OnionPaymentPayloadTlv.AmountToForward(amount),
547571
OnionPaymentPayloadTlv.OutgoingCltv(expiry),
548572
OnionPaymentPayloadTlv.OutgoingBlindedPaths(blindedPaths),
549-
OnionPaymentPayloadTlv.InvoiceFeatures(features.toByteArray().toByteVector())
573+
OnionPaymentPayloadTlv.RecipientFeatures(features.toByteArray().toByteVector())
550574
)
551575
)
552576
)

0 commit comments

Comments
 (0)