Skip to content

Commit

Permalink
Add support for official trampoline payments to blinded paths
Browse files Browse the repository at this point in the history
We update the blinded path TLVs to use the official spec values.
We also implement the spec test vector.
  • Loading branch information
t-bast committed Jul 16, 2024
1 parent 793cec9 commit c7a69c3
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 51 deletions.
81 changes: 42 additions & 39 deletions src/commonMain/kotlin/fr/acinq/lightning/wire/PaymentOnion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,43 @@ sealed class OnionPaymentPayloadTlv : Tlv {
}

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

companion object : TlvValueReader<InvoiceFeatures> {
const val tag: Long = 66097
override fun read(input: Input): InvoiceFeatures = InvoiceFeatures(ByteVector(LightningCodecs.bytes(input, input.availableBytes)))
companion object : TlvValueReader<RecipientFeatures> {
const val tag: Long = 21
override fun read(input: Input): RecipientFeatures = RecipientFeatures(ByteVector(LightningCodecs.bytes(input, input.availableBytes)))
}
}

/**
* Blinded paths that can be used to reach the final recipient.
* Only included for a trampoline node when paying a Bolt 12 invoice.
*/
data class OutgoingBlindedPaths(val paths: List<Bolt12Invoice.Companion.PaymentBlindedContactInfo>) : OnionPaymentPayloadTlv() {
override val tag: Long get() = OutgoingBlindedPaths.tag
override fun write(out: Output) {
for (path in paths) {
OfferTypes.writePath(path.route, out)
OfferTypes.writePaymentInfo(path.paymentInfo, out)
}
}

companion object : TlvValueReader<OutgoingBlindedPaths> {
const val tag: Long = 22
override fun read(input: Input): OutgoingBlindedPaths {
val paths = ArrayList<Bolt12Invoice.Companion.PaymentBlindedContactInfo>()
while (input.availableBytes > 0) {
val route = OfferTypes.readPath(input)
val payInfo = OfferTypes.readPaymentInfo(input)
paths.add(Bolt12Invoice.Companion.PaymentBlindedContactInfo(route, payInfo))
}
return OutgoingBlindedPaths(paths)
}
}
}

Expand Down Expand Up @@ -208,30 +235,6 @@ sealed class OnionPaymentPayloadTlv : Tlv {
}
}

/** Blinded paths to relay the payment to */
data class OutgoingBlindedPaths(val paths: List<Bolt12Invoice.Companion.PaymentBlindedContactInfo>) : OnionPaymentPayloadTlv() {
override val tag: Long get() = OutgoingBlindedPaths.tag
override fun write(out: Output) {
for (path in paths) {
OfferTypes.writePath(path.route, out)
OfferTypes.writePaymentInfo(path.paymentInfo, out)
}
}

companion object : TlvValueReader<OutgoingBlindedPaths> {
const val tag: Long = 66102
override fun read(input: Input): OutgoingBlindedPaths {
val paths = ArrayList<Bolt12Invoice.Companion.PaymentBlindedContactInfo>()
while (input.availableBytes > 0) {
val route = OfferTypes.readPath(input)
val payInfo = OfferTypes.readPaymentInfo(input)
paths.add(Bolt12Invoice.Companion.PaymentBlindedContactInfo(route, payInfo))
}
return OutgoingBlindedPaths(paths)
}
}
}

}

object PaymentOnion {
Expand Down Expand Up @@ -259,9 +262,9 @@ object PaymentOnion {
OnionPaymentPayloadTlv.PaymentMetadata.tag to OnionPaymentPayloadTlv.PaymentMetadata.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
OnionPaymentPayloadTlv.TotalAmount.tag to OnionPaymentPayloadTlv.TotalAmount.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
OnionPaymentPayloadTlv.TrampolineOnion.tag to OnionPaymentPayloadTlv.TrampolineOnion.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
OnionPaymentPayloadTlv.InvoiceFeatures.tag to OnionPaymentPayloadTlv.InvoiceFeatures.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag to OnionPaymentPayloadTlv.InvoiceRoutingInfo.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
OnionPaymentPayloadTlv.RecipientFeatures.tag to OnionPaymentPayloadTlv.RecipientFeatures.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
OnionPaymentPayloadTlv.OutgoingBlindedPaths.tag to OnionPaymentPayloadTlv.OutgoingBlindedPaths.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag to OnionPaymentPayloadTlv.InvoiceRoutingInfo.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
)
)

Expand Down Expand Up @@ -476,7 +479,7 @@ object PaymentOnion {
// NB: the following fields are only included in the trampoline-to-legacy case.
val paymentSecret = records.get<OnionPaymentPayloadTlv.PaymentData>()!!.secret
val paymentMetadata = records.get<OnionPaymentPayloadTlv.PaymentMetadata>()?.data
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.InvoiceFeatures>()!!.features
val recipientFeatures = records.get<OnionPaymentPayloadTlv.RecipientFeatures>()!!.features
val invoiceRoutingInfo = records.get<OnionPaymentPayloadTlv.InvoiceRoutingInfo>()!!.extraHops

override fun write(out: Output) = tlvSerializer.write(records, out)
Expand All @@ -489,7 +492,7 @@ object PaymentOnion {
tlvs.get<OnionPaymentPayloadTlv.OutgoingCltv>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingCltv.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.OutgoingNodeId>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingNodeId.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.PaymentData>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.PaymentData.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.InvoiceFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceFeatures.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.RecipientFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.RecipientFeatures.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.InvoiceRoutingInfo>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.BlindingPoint>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.BlindingPoint.tag, 0))
Expand All @@ -507,7 +510,7 @@ object PaymentOnion {
add(OnionPaymentPayloadTlv.OutgoingNodeId(targetNodeId))
add(OnionPaymentPayloadTlv.PaymentData(invoice.paymentSecret, totalAmount))
invoice.paymentMetadata?.let { add(OnionPaymentPayloadTlv.PaymentMetadata(it)) }
add(OnionPaymentPayloadTlv.InvoiceFeatures(invoice.features.toByteArray().toByteVector()))
add(OnionPaymentPayloadTlv.RecipientFeatures(invoice.features.toByteArray().toByteVector()))
add(OnionPaymentPayloadTlv.InvoiceRoutingInfo(invoice.routingInfo.map { it.hints }))
}
)
Expand All @@ -519,7 +522,7 @@ object PaymentOnion {
val amountToForward = records.get<OnionPaymentPayloadTlv.AmountToForward>()!!.amount
val outgoingCltv = records.get<OnionPaymentPayloadTlv.OutgoingCltv>()!!.cltv
val outgoingBlindedPaths = records.get<OnionPaymentPayloadTlv.OutgoingBlindedPaths>()!!.paths
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.InvoiceFeatures>()!!.features
val recipientFeatures = records.get<OnionPaymentPayloadTlv.RecipientFeatures>()!!.features

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

Expand All @@ -529,7 +532,7 @@ object PaymentOnion {
when {
tlvs.get<OnionPaymentPayloadTlv.AmountToForward>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.AmountToForward.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.OutgoingCltv>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingCltv.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.InvoiceFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.InvoiceFeatures.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.RecipientFeatures>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.RecipientFeatures.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.OutgoingBlindedPaths>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingBlindedPaths.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.BlindingPoint>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.BlindingPoint.tag, 0))
Expand All @@ -545,7 +548,7 @@ object PaymentOnion {
OnionPaymentPayloadTlv.AmountToForward(amount),
OnionPaymentPayloadTlv.OutgoingCltv(expiry),
OnionPaymentPayloadTlv.OutgoingBlindedPaths(invoice.blindedPaths),
OnionPaymentPayloadTlv.InvoiceFeatures(invoice.features.toByteArray().toByteVector())
OnionPaymentPayloadTlv.RecipientFeatures(invoice.features.toByteArray().toByteVector())
)
)
)
Expand Down
Loading

0 comments on commit c7a69c3

Please sign in to comment.