Skip to content

Commit b1a3048

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 d8344fb commit b1a3048

File tree

3 files changed

+321
-49
lines changed

3 files changed

+321
-49
lines changed

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

Lines changed: 74 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import fr.acinq.bitcoin.utils.flatMap
1212
import fr.acinq.lightning.*
1313
import fr.acinq.lightning.payment.Bolt11Invoice
1414
import fr.acinq.lightning.payment.Bolt12Invoice
15+
import fr.acinq.lightning.payment.Bolt12Invoice.Companion.PaymentBlindedContactInfo
1516
import fr.acinq.lightning.utils.msat
1617
import fr.acinq.lightning.utils.toByteVector
1718

@@ -151,16 +152,43 @@ sealed class OnionPaymentPayloadTlv : Tlv {
151152
}
152153

153154
/**
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.
155+
* Features that may be used to reach the recipient, provided by the payment sender (usually obtained them from an invoice).
156+
* Only included for a trampoline node when relaying to a non-trampoline recipient using [OutgoingBlindedPaths] or [InvoiceRoutingInfo].
156157
*/
157-
data class InvoiceFeatures(val features: ByteVector) : OnionPaymentPayloadTlv() {
158-
override val tag: Long get() = InvoiceFeatures.tag
158+
data class RecipientFeatures(val features: ByteVector) : OnionPaymentPayloadTlv() {
159+
override val tag: Long get() = RecipientFeatures.tag
159160
override fun write(out: Output) = LightningCodecs.writeBytes(features, out)
160161

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

@@ -205,30 +233,6 @@ sealed class OnionPaymentPayloadTlv : Tlv {
205233
}
206234
}
207235

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-
232236
}
233237

234238
object PaymentOnion {
@@ -256,9 +260,10 @@ object PaymentOnion {
256260
OnionPaymentPayloadTlv.PaymentMetadata.tag to OnionPaymentPayloadTlv.PaymentMetadata.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
257261
OnionPaymentPayloadTlv.TotalAmount.tag to OnionPaymentPayloadTlv.TotalAmount.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
258262
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>,
263+
OnionPaymentPayloadTlv.RecipientFeatures.tag to OnionPaymentPayloadTlv.RecipientFeatures.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
261264
OnionPaymentPayloadTlv.OutgoingBlindedPaths.tag to OnionPaymentPayloadTlv.OutgoingBlindedPaths.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
265+
// The following TLVs aren't official TLVs from the BOLTs.
266+
OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag to OnionPaymentPayloadTlv.InvoiceRoutingInfo.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
262267
)
263268
)
264269

@@ -423,6 +428,32 @@ object PaymentOnion {
423428
}
424429
}
425430

431+
data class BlindedChannelRelayPayload(val records: TlvStream<OnionPaymentPayloadTlv>) : PerHopPayload() {
432+
override fun write(out: Output) = tlvSerializer.write(records, out)
433+
434+
companion object : PerHopPayloadReader<BlindedChannelRelayPayload> {
435+
override fun read(input: Input): Either<InvalidOnionPayload, BlindedChannelRelayPayload> {
436+
return PerHopPayload.read(input).flatMap { tlvs ->
437+
when {
438+
tlvs.get<OnionPaymentPayloadTlv.AmountToForward>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.AmountToForward.tag, 0))
439+
tlvs.get<OnionPaymentPayloadTlv.OutgoingCltv>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingCltv.tag, 0))
440+
tlvs.get<OnionPaymentPayloadTlv.OutgoingChannelId>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingChannelId.tag, 0))
441+
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
442+
else -> Either.Right(BlindedChannelRelayPayload(tlvs))
443+
}
444+
}
445+
}
446+
447+
fun create(encryptedData: ByteVector, blinding: PublicKey?): BlindedChannelRelayPayload {
448+
val tlvs = buildSet {
449+
add(OnionPaymentPayloadTlv.EncryptedRecipientData(encryptedData))
450+
blinding?.let { add(OnionPaymentPayloadTlv.BlindingPoint(it)) }
451+
}
452+
return BlindedChannelRelayPayload(TlvStream(tlvs))
453+
}
454+
}
455+
}
456+
426457
data class NodeRelayPayload(val records: TlvStream<OnionPaymentPayloadTlv>) : PerHopPayload() {
427458
val amountToForward = records.get<OnionPaymentPayloadTlv.AmountToForward>()!!.amount
428459
val outgoingCltv = records.get<OnionPaymentPayloadTlv.OutgoingCltv>()!!.cltv
@@ -468,7 +499,7 @@ object PaymentOnion {
468499
val outgoingNodeId = records.get<OnionPaymentPayloadTlv.OutgoingNodeId>()!!.nodeId
469500
val paymentSecret = records.get<OnionPaymentPayloadTlv.PaymentData>()!!.secret
470501
val paymentMetadata = records.get<OnionPaymentPayloadTlv.PaymentMetadata>()?.data
471-
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.InvoiceFeatures>()!!.features
502+
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.RecipientFeatures>()!!.features
472503
val invoiceRoutingInfo = records.get<OnionPaymentPayloadTlv.InvoiceRoutingInfo>()!!.extraHops
473504

474505
override fun write(out: Output) = tlvSerializer.write(records, out)
@@ -481,7 +512,7 @@ object PaymentOnion {
481512
tlvs.get<OnionPaymentPayloadTlv.OutgoingCltv>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingCltv.tag, 0))
482513
tlvs.get<OnionPaymentPayloadTlv.OutgoingNodeId>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingNodeId.tag, 0))
483514
tlvs.get<OnionPaymentPayloadTlv.PaymentData>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.PaymentData.tag, 0))
484-
tlvs.get<OnionPaymentPayloadTlv.InvoiceFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceFeatures.tag, 0))
515+
tlvs.get<OnionPaymentPayloadTlv.RecipientFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.RecipientFeatures.tag, 0))
485516
tlvs.get<OnionPaymentPayloadTlv.InvoiceRoutingInfo>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag, 0))
486517
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
487518
tlvs.get<OnionPaymentPayloadTlv.BlindingPoint>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.BlindingPoint.tag, 0))
@@ -499,19 +530,23 @@ object PaymentOnion {
499530
add(OnionPaymentPayloadTlv.OutgoingNodeId(targetNodeId))
500531
add(OnionPaymentPayloadTlv.PaymentData(invoice.paymentSecret, totalAmount))
501532
invoice.paymentMetadata?.let { add(OnionPaymentPayloadTlv.PaymentMetadata(it)) }
502-
add(OnionPaymentPayloadTlv.InvoiceFeatures(invoice.features.toByteArray().toByteVector()))
533+
add(OnionPaymentPayloadTlv.RecipientFeatures(invoice.features.toByteArray().toByteVector()))
503534
add(OnionPaymentPayloadTlv.InvoiceRoutingInfo(routingInfo.map { it.hints }))
504535
}
505536
)
506537
)
507538
}
508539
}
509540

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

516551
override fun write(out: Output) = tlvSerializer.write(records, out)
517552

@@ -521,7 +556,6 @@ object PaymentOnion {
521556
when {
522557
tlvs.get<OnionPaymentPayloadTlv.AmountToForward>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.AmountToForward.tag, 0))
523558
tlvs.get<OnionPaymentPayloadTlv.OutgoingCltv>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingCltv.tag, 0))
524-
tlvs.get<OnionPaymentPayloadTlv.InvoiceFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceFeatures.tag, 0))
525559
tlvs.get<OnionPaymentPayloadTlv.OutgoingBlindedPaths>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingBlindedPaths.tag, 0))
526560
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
527561
tlvs.get<OnionPaymentPayloadTlv.BlindingPoint>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.BlindingPoint.tag, 0))
@@ -530,14 +564,14 @@ object PaymentOnion {
530564
}
531565
}
532566

533-
fun create(amount: MilliSatoshi, expiry: CltvExpiry, features: Features, blindedPaths: List<Bolt12Invoice.Companion.PaymentBlindedContactInfo>): RelayToBlindedPayload =
567+
fun create(amount: MilliSatoshi, expiry: CltvExpiry, features: Features, blindedPaths: List<PaymentBlindedContactInfo>): RelayToBlindedPayload =
534568
RelayToBlindedPayload(
535569
TlvStream(
536570
setOf(
537571
OnionPaymentPayloadTlv.AmountToForward(amount),
538572
OnionPaymentPayloadTlv.OutgoingCltv(expiry),
539573
OnionPaymentPayloadTlv.OutgoingBlindedPaths(blindedPaths),
540-
OnionPaymentPayloadTlv.InvoiceFeatures(features.toByteArray().toByteVector())
574+
OnionPaymentPayloadTlv.RecipientFeatures(features.toByteArray().toByteVector())
541575
)
542576
)
543577
)

0 commit comments

Comments
 (0)