Skip to content

Commit

Permalink
Update trampoline payment to blinded path to match spec proposal
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
t-bast committed Sep 16, 2024
1 parent 635864f commit a375c43
Show file tree
Hide file tree
Showing 3 changed files with 321 additions and 49 deletions.
114 changes: 74 additions & 40 deletions src/commonMain/kotlin/fr/acinq/lightning/wire/PaymentOnion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import fr.acinq.bitcoin.utils.flatMap
import fr.acinq.lightning.*
import fr.acinq.lightning.payment.Bolt11Invoice
import fr.acinq.lightning.payment.Bolt12Invoice
import fr.acinq.lightning.payment.Bolt12Invoice.Companion.PaymentBlindedContactInfo
import fr.acinq.lightning.utils.msat
import fr.acinq.lightning.utils.toByteVector

Expand Down Expand Up @@ -151,16 +152,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 that doesn't support trampoline.
*/
data class OutgoingBlindedPaths(val paths: List<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<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 @@ -205,30 +233,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 @@ -256,9 +260,10 @@ 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>,
// The following TLVs aren't official TLVs from the BOLTs.
OnionPaymentPayloadTlv.InvoiceRoutingInfo.tag to OnionPaymentPayloadTlv.InvoiceRoutingInfo.Companion as TlvValueReader<OnionPaymentPayloadTlv>,
)
)

Expand Down Expand Up @@ -423,6 +428,32 @@ object PaymentOnion {
}
}

data class BlindedChannelRelayPayload(val records: TlvStream<OnionPaymentPayloadTlv>) : PerHopPayload() {
override fun write(out: Output) = tlvSerializer.write(records, out)

companion object : PerHopPayloadReader<BlindedChannelRelayPayload> {
override fun read(input: Input): Either<InvalidOnionPayload, BlindedChannelRelayPayload> {
return PerHopPayload.read(input).flatMap { tlvs ->
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.OutgoingChannelId>() != null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.OutgoingChannelId.tag, 0))
tlvs.get<OnionPaymentPayloadTlv.EncryptedRecipientData>() == null -> Either.Left(InvalidOnionPayload(OnionPaymentPayloadTlv.EncryptedRecipientData.tag, 0))
else -> Either.Right(BlindedChannelRelayPayload(tlvs))
}
}
}

fun create(encryptedData: ByteVector, blinding: PublicKey?): BlindedChannelRelayPayload {
val tlvs = buildSet {
add(OnionPaymentPayloadTlv.EncryptedRecipientData(encryptedData))
blinding?.let { add(OnionPaymentPayloadTlv.BlindingPoint(it)) }
}
return BlindedChannelRelayPayload(TlvStream(tlvs))
}
}
}

data class NodeRelayPayload(val records: TlvStream<OnionPaymentPayloadTlv>) : PerHopPayload() {
val amountToForward = records.get<OnionPaymentPayloadTlv.AmountToForward>()!!.amount
val outgoingCltv = records.get<OnionPaymentPayloadTlv.OutgoingCltv>()!!.cltv
Expand Down Expand Up @@ -468,7 +499,7 @@ object PaymentOnion {
val outgoingNodeId = records.get<OnionPaymentPayloadTlv.OutgoingNodeId>()!!.nodeId
val paymentSecret = records.get<OnionPaymentPayloadTlv.PaymentData>()!!.secret
val paymentMetadata = records.get<OnionPaymentPayloadTlv.PaymentMetadata>()?.data
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.InvoiceFeatures>()!!.features
val invoiceFeatures = records.get<OnionPaymentPayloadTlv.RecipientFeatures>()!!.features
val invoiceRoutingInfo = records.get<OnionPaymentPayloadTlv.InvoiceRoutingInfo>()!!.extraHops

override fun write(out: Output) = tlvSerializer.write(records, out)
Expand All @@ -481,7 +512,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 @@ -499,19 +530,23 @@ 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(routingInfo.map { it.hints }))
}
)
)
}
}

/**
* Create a trampoline payload to tell our trampoline node to relay to a blinded path, where the recipient doesn't support trampoline.
* This only reveals the blinded path to our trampoline node, which doesn't reveal the recipient's identity.
*/
data class RelayToBlindedPayload(val records: TlvStream<OnionPaymentPayloadTlv>) : PerHopPayload() {
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 ?: Features.empty

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

Expand All @@ -521,7 +556,6 @@ 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.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 @@ -530,14 +564,14 @@ object PaymentOnion {
}
}

fun create(amount: MilliSatoshi, expiry: CltvExpiry, features: Features, blindedPaths: List<Bolt12Invoice.Companion.PaymentBlindedContactInfo>): RelayToBlindedPayload =
fun create(amount: MilliSatoshi, expiry: CltvExpiry, features: Features, blindedPaths: List<PaymentBlindedContactInfo>): RelayToBlindedPayload =
RelayToBlindedPayload(
TlvStream(
setOf(
OnionPaymentPayloadTlv.AmountToForward(amount),
OnionPaymentPayloadTlv.OutgoingCltv(expiry),
OnionPaymentPayloadTlv.OutgoingBlindedPaths(blindedPaths),
OnionPaymentPayloadTlv.InvoiceFeatures(features.toByteArray().toByteVector())
OnionPaymentPayloadTlv.RecipientFeatures(features.toByteArray().toByteVector())
)
)
)
Expand Down
Loading

0 comments on commit a375c43

Please sign in to comment.