1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-22 22:25:26 +01:00

Allow plain outgoing_node_id in blinded payment_relay (#2943)

When we're the introduction node of a trampoline blinded path, we
previously only allowed our custom `wallet_node_id` format when a
`short_channel_id` was not included. But we actually can allow plain
`node_id`s as well, as we only need to know the public key.

We've rejected some payments in the past months because they included an
`outgoing_node_id` that didn't use the wallet format: by removing that
limitation we ensure that those payments will be correctly relayed in
the future.
This commit is contained in:
Bastien Teinturier 2024-11-22 10:31:22 +01:00 committed by GitHub
parent 47fdfaec9f
commit 02abc3a7e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 17 additions and 17 deletions

View file

@ -101,7 +101,7 @@ object BlindedRouteData {
// This is usually a channel, unless the next node is a mobile wallet connected to our node.
val outgoing: Either[PublicKey, ShortChannelId] = records.get[RouteBlindingEncryptedDataTlv.OutgoingChannelId] match {
case Some(r) => Right(r.shortChannelId)
case None => Left(records.get[RouteBlindingEncryptedDataTlv.OutgoingNodeId].get.nodeId.asInstanceOf[EncodedNodeId.WithPublicKey.Wallet].publicKey)
case None => Left(records.get[RouteBlindingEncryptedDataTlv.OutgoingNodeId].get.nodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey)
}
val paymentRelay: PaymentRelay = records.get[RouteBlindingEncryptedDataTlv.PaymentRelay].get
val paymentConstraints: PaymentConstraints = records.get[RouteBlindingEncryptedDataTlv.PaymentConstraints].get
@ -114,9 +114,9 @@ object BlindedRouteData {
}
def validatePaymentRelayData(records: TlvStream[RouteBlindingEncryptedDataTlv]): Either[InvalidTlvPayload, PaymentRelayData] = {
// Note that the BOLTs require using an OutgoingChannelId, but we optionally support a wallet node_id.
// Note that the BOLTs require using an OutgoingChannelId, but we optionally support using a node_id.
if (records.get[OutgoingChannelId].isEmpty && records.get[OutgoingNodeId].isEmpty) return Left(MissingRequiredTlv(UInt64(2)))
if (records.get[OutgoingNodeId].nonEmpty && !records.get[OutgoingNodeId].get.nodeId.isInstanceOf[EncodedNodeId.WithPublicKey.Wallet]) return Left(ForbiddenTlv(UInt64(4)))
if (records.get[OutgoingNodeId].nonEmpty && !records.get[OutgoingNodeId].get.nodeId.isInstanceOf[EncodedNodeId.WithPublicKey]) return Left(ForbiddenTlv(UInt64(4)))
if (records.get[PaymentRelay].isEmpty) return Left(MissingRequiredTlv(UInt64(10)))
if (records.get[PaymentConstraints].isEmpty) return Left(MissingRequiredTlv(UInt64(12)))
if (records.get[PathId].nonEmpty) return Left(ForbiddenTlv(UInt64(6)))

View file

@ -119,18 +119,20 @@ class PaymentOnionSpec extends AnyFunSuite {
}
}
test("encode/decode channel relay blinded per-hop-payload (with wallet node_id)") {
val walletNodeId = PublicKey(hex"0221cd519eba9c8b840a5e40b65dc2c040e159a766979723ed770efceb97260ec8")
val blindedTlvs = TlvStream[RouteBlindingEncryptedDataTlv](
RouteBlindingEncryptedDataTlv.OutgoingNodeId(EncodedNodeId.WithPublicKey.Wallet(walletNodeId)),
RouteBlindingEncryptedDataTlv.PaymentRelay(CltvExpiryDelta(144), 100, 10 msat),
RouteBlindingEncryptedDataTlv.PaymentConstraints(CltvExpiry(1500), 1 msat),
)
val Right(payload) = IntermediatePayload.ChannelRelay.Blinded.validate(TlvStream(EncryptedRecipientData(hex"deadbeef")), blindedTlvs, randomKey().publicKey)
assert(payload.outgoing == Left(walletNodeId))
assert(payload.amountToForward(10_000 msat) == 9990.msat)
assert(payload.outgoingCltv(CltvExpiry(1000)) == CltvExpiry(856))
assert(payload.paymentRelayData.allowedFeatures.isEmpty)
test("encode/decode channel relay blinded per-hop-payload (with node_id)") {
val nextNodeId = PublicKey(hex"0221cd519eba9c8b840a5e40b65dc2c040e159a766979723ed770efceb97260ec8")
Seq(EncodedNodeId.WithPublicKey.Wallet(nextNodeId), EncodedNodeId.WithPublicKey.Plain(nextNodeId)).foreach(outgoingNodeId => {
val blindedTlvs = TlvStream[RouteBlindingEncryptedDataTlv](
RouteBlindingEncryptedDataTlv.OutgoingNodeId(outgoingNodeId),
RouteBlindingEncryptedDataTlv.PaymentRelay(CltvExpiryDelta(144), 100, 10 msat),
RouteBlindingEncryptedDataTlv.PaymentConstraints(CltvExpiry(1500), 1 msat),
)
val Right(payload) = IntermediatePayload.ChannelRelay.Blinded.validate(TlvStream(EncryptedRecipientData(hex"deadbeef")), blindedTlvs, randomKey().publicKey)
assert(payload.outgoing == Left(nextNodeId))
assert(payload.amountToForward(10_000 msat) == 9990.msat)
assert(payload.outgoingCltv(CltvExpiry(1000)) == CltvExpiry(856))
assert(payload.paymentRelayData.allowedFeatures.isEmpty)
})
}
test("encode/decode node relay per-hop payload") {
@ -306,8 +308,6 @@ class PaymentOnionSpec extends AnyFunSuite {
TestCase(MissingRequiredTlv(UInt64(10)), hex"23 0c21036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2", validBlindedTlvs),
// Missing encrypted outgoing channel.
TestCase(MissingRequiredTlv(UInt64(2)), hex"0a 0a080123456789abcdef", TlvStream(RouteBlindingEncryptedDataTlv.PaymentRelay(CltvExpiryDelta(144), 100, 10 msat), RouteBlindingEncryptedDataTlv.PaymentConstraints(CltvExpiry(1500), 1 msat))),
// Forbidden encrypted outgoing plain node_id.
TestCase(ForbiddenTlv(UInt64(4)), hex"0a 0a080123456789abcdef", TlvStream(RouteBlindingEncryptedDataTlv.OutgoingNodeId(EncodedNodeId.WithPublicKey.Plain(randomKey().publicKey)), RouteBlindingEncryptedDataTlv.PaymentRelay(CltvExpiryDelta(144), 100, 10 msat), RouteBlindingEncryptedDataTlv.PaymentConstraints(CltvExpiry(1500), 1 msat))),
// Missing encrypted payment relay data.
TestCase(MissingRequiredTlv(UInt64(10)), hex"0a 0a080123456789abcdef", TlvStream(RouteBlindingEncryptedDataTlv.OutgoingChannelId(ShortChannelId(42)), RouteBlindingEncryptedDataTlv.PaymentConstraints(CltvExpiry(1500), 1 msat))),
// Missing encrypted payment constraint.