Skip to content

Commit 0896d03

Browse files
authored
Fix Bolt12 path fee hiding (#3311)
When using blinded paths in our Bolt12 invoices, we may use a feature where instead of making the payer pay the fees for the blinded path, we deduce them from the amount we wish to receive (the recipient pays for the privacy it gains from using blinded paths, instead of making the payer pay for something that they didn't opt into). See #2993 for more details. The constraint on path fees was wrong in some rounding cases, so we fix that, otherwise we would unnecessarily reject some payments. This was found by testing blinded trampoline payments.
1 parent 3540340 commit 0896d03

4 files changed

Lines changed: 8 additions & 8 deletions

File tree

eclair-core/src/main/resources/reference.conf

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,11 +734,10 @@ eclair {
734734
offers {
735735
// Minimum length of an offer blinded path when hiding our real node id
736736
message-path-min-length = 2
737-
738737
// Number of payment paths to put in Bolt12 invoices when hiding our real node id
739738
payment-path-count = 2
740739
// Length of payment paths to put in Bolt12 invoices when hiding our real node id
741-
payment-path-length = 4
740+
payment-path-length = 2
742741
// Expiry delta of payment paths to put in Bolt12 invoices when hiding our real node id
743742
payment-path-expiry-delta = 500
744743
}

eclair-core/src/main/scala/fr/acinq/eclair/payment/offer/OfferManager.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ object OfferManager {
285285
val minimalInvoice = MinimalBolt12Invoice(offer, nodeParams.chainHash, metadata.amount, metadata.quantity, Crypto.sha256(metadata.preimage), metadata.payerKey, metadata.createdAt, additionalTlvs, customTlvs)
286286
val incomingPayment = IncomingBlindedPayment(minimalInvoice, metadata.preimage, PaymentType.Blinded, TimestampMilli.now(), IncomingPaymentStatus.Pending)
287287
// We may be deducing some of the blinded path fees from the received amount.
288-
val maxRecipientPathFees = nodeFee(metadata.recipientPathFees, amount)
288+
val maxRecipientPathFees = nodeFee(metadata.recipientPathFees, Seq(amount, metadata.amount).max)
289289
replyTo ! MultiPartHandler.GetIncomingPaymentActor.ProcessPayment(incomingPayment, maxRecipientPathFees)
290290
Behaviors.stopped
291291
case RejectPayment(reason) =>

eclair-core/src/test/scala/fr/acinq/eclair/integration/basic/payment/OfferPaymentSpec.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ class OfferPaymentSpec extends FixtureSpec with IntegrationPatience {
783783
assert(offer.nodeId.isEmpty)
784784
assert(offer.contactInfos.size == 1)
785785
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.firstNodeId == EncodedNodeId.WithPublicKey.Plain(carol.nodeId))
786-
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.length == carol.nodeParams.offersConfig.messagePathMinLength)
786+
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.length >= carol.nodeParams.offersConfig.messagePathMinLength)
787787
assert(offer.description.contains("test offer"))
788788
assert(offer.amount.contains(amount))
789789

@@ -801,7 +801,7 @@ class OfferPaymentSpec extends FixtureSpec with IntegrationPatience {
801801
assert(offer.nodeId.isEmpty)
802802
assert(offer.contactInfos.size == 1)
803803
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.firstNodeId == EncodedNodeId.WithPublicKey.Plain(bob.nodeId))
804-
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.length == carol.nodeParams.offersConfig.messagePathMinLength)
804+
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.length >= carol.nodeParams.offersConfig.messagePathMinLength)
805805
assert(offer.description.contains("test offer"))
806806
assert(offer.amount.contains(amount))
807807

@@ -825,7 +825,7 @@ class OfferPaymentSpec extends FixtureSpec with IntegrationPatience {
825825
val offer = createOffer(carol, description_opt = Some("test offer"), amount_opt = Some(amount), issuer_opt = None, blindedPathsFirstNodeId_opt = Some(alice.nodeId))
826826
assert(offer.nodeId.isEmpty)
827827
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.firstNodeId == EncodedNodeId.WithPublicKey.Plain(alice.nodeId))
828-
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.length == carol.nodeParams.offersConfig.messagePathMinLength)
828+
assert(offer.contactInfos.head.asInstanceOf[BlindedPath].route.length >= carol.nodeParams.offersConfig.messagePathMinLength)
829829
assert(offer.description.contains("test offer"))
830830
assert(offer.amount.contains(amount))
831831

eclair-core/src/test/scala/fr/acinq/eclair/payment/offer/OfferManagerSpec.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import fr.acinq.eclair.router.Router.ChannelHop
3333
import fr.acinq.eclair.wire.protocol.OfferTypes.{InvoiceRequest, Offer}
3434
import fr.acinq.eclair.wire.protocol.RouteBlindingEncryptedDataCodecs.RouteBlindingDecryptedData
3535
import fr.acinq.eclair.wire.protocol._
36-
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, NodeParams, TestConstants, amountAfterFee, randomBytes32, randomKey}
36+
import fr.acinq.eclair.{CltvExpiry, CltvExpiryDelta, Features, MilliSatoshi, MilliSatoshiLong, NodeParams, TestConstants, amountAfterFee, nodeFee, randomBytes32, randomKey}
3737
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
3838
import org.scalatest.{Outcome, Tag}
3939
import scodec.bits.{ByteVector, HexStringSyntax}
@@ -332,6 +332,7 @@ class OfferManagerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("app
332332
assert(Crypto.sha256(incomingPayment.paymentPreimage) == invoice.paymentHash)
333333
assert(incomingPayment.invoice.nodeId == nodeParams.nodeId)
334334
assert(incomingPayment.invoice.paymentHash == invoice.paymentHash)
335-
assert(maxRecipientPathFees == paymentPayload.amount - amountReceived)
335+
assert(maxRecipientPathFees >= paymentPayload.amount - amountReceived)
336+
assert(maxRecipientPathFees == nodeFee(1000 msat, 200, amount))
336337
}
337338
}

0 commit comments

Comments
 (0)