From 962700051d6dc565337db32f6620c045c45464a0 Mon Sep 17 00:00:00 2001 From: Thomas HUET Date: Thu, 4 Jul 2024 10:45:48 +0200 Subject: [PATCH] Add custom offers Allow creating more offers with customizable amount and description. --- .../kotlin/fr/acinq/lightning/NodeParams.kt | 2 +- src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt | 11 +++++++++++ .../kotlin/fr/acinq/lightning/wire/OfferTypes.kt | 4 +++- .../lightning/payment/OfferManagerTestsCommon.kt | 5 ++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt b/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt index 6abd258e2..d3841e140 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt @@ -248,6 +248,6 @@ data class NodeParams( val blindingSecret = PrivateKey(Crypto.sha256("bolt 12 default offer".toByteArray(Charsets.UTF_8).byteVector() + trampolineNodeId.value + nodePrivateKey.value).byteVector32()) // We don't use our currently activated features, otherwise the offer would change when we add support for new features. // If we add a new feature that we would like to use by default, we will need to explicitly create a new offer. - return OfferTypes.Offer.createBlindedOffer(amount = null, description = null, this, trampolineNodeId, Features.empty, blindingSecret) + return OfferTypes.Offer.createBlindedOffer(amount = null, description = null, this, trampolineNodeId, Features.empty, blindingSecret, pathId = null) } } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt b/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt index e0766d7e0..17a9bdfdc 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt @@ -705,6 +705,17 @@ class Peer( return incomingPaymentHandler.createInvoice(paymentPreimage, amount, description, extraHops, expirySeconds) } + /** Creates a custom offer and register it with the `offerManager`. + * @param secret A random private key for creating the blinded path of the offer. Must be unique to this offer. + * The offer returned is deterministic, if you need to persist you just need to save the parameters used to create it. + */ + fun createOffer(secret: PrivateKey, amount: MilliSatoshi?, description: String?): OfferTypes.Offer { + val pathId = secret.value + val (offer, _) = OfferTypes.Offer.createBlindedOffer(amount, description, nodeParams, remoteNodeId, Features.empty, secret, pathId) + offerManager.registerOffer(offer, pathId) + return offer + } + // The (node_id, fcm_token) tuple only needs to be registered once. // And after that, only if the tuple changes (e.g. different fcm_token). fun registerFcmToken(token: String?) { diff --git a/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt b/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt index ad5fdfdc1..fc14c0553 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt @@ -779,6 +779,7 @@ object OfferTypes { * @param trampolineNodeId our trampoline node. * @param features features that should be advertised in the offer. * @param blindingSecret session key used for the blinded path included in the offer. + * @param pathId path id for this offer's blinded path. */ fun createBlindedOffer( amount: MilliSatoshi?, @@ -787,11 +788,12 @@ object OfferTypes { trampolineNodeId: PublicKey, features: Features, blindingSecret: PrivateKey, + pathId: ByteVector32?, additionalTlvs: Set = setOf(), customTlvs: Set = setOf() ): Pair { if (description == null) require(amount == null) { "an offer description must be provided if the amount isn't null" } - val blindedRouteDetails = OnionMessages.buildRouteToRecipient(blindingSecret, listOf(OnionMessages.IntermediateNode(EncodedNodeId.WithPublicKey.Plain(trampolineNodeId))), OnionMessages.Destination.Recipient(EncodedNodeId.WithPublicKey.Wallet(nodeParams.nodeId), null)) + val blindedRouteDetails = OnionMessages.buildRouteToRecipient(blindingSecret, listOf(OnionMessages.IntermediateNode(EncodedNodeId.WithPublicKey.Plain(trampolineNodeId))), OnionMessages.Destination.Recipient(EncodedNodeId.WithPublicKey.Wallet(nodeParams.nodeId), pathId)) val tlvs: Set = setOfNotNull( if (nodeParams.chainHash != Block.LivenetGenesisBlock.hash) OfferChains(listOf(nodeParams.chainHash)) else null, amount?.let { OfferAmount(it) }, diff --git a/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt index ffdbeebd7..b617c99a0 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt @@ -4,6 +4,7 @@ import fr.acinq.bitcoin.ByteVector import fr.acinq.bitcoin.PrivateKey import fr.acinq.bitcoin.utils.Either import fr.acinq.lightning.* +import fr.acinq.lightning.Lightning.randomBytes32 import fr.acinq.lightning.Lightning.randomKey import fr.acinq.lightning.crypto.RouteBlinding import fr.acinq.lightning.crypto.sphinx.DecryptedPacket @@ -51,6 +52,7 @@ class OfferManagerTestsCommon : LightningTestSuite() { private fun createOffer(offerManager: OfferManager, amount: MilliSatoshi? = null): OfferTypes.Offer { val blindingSecret = randomKey() + val pathId = randomBytes32() val (offer, _) = OfferTypes.Offer.createBlindedOffer( amount, "Blockaccino", @@ -58,8 +60,9 @@ class OfferManagerTestsCommon : LightningTestSuite() { offerManager.walletParams.trampolineNode.id, offerManager.nodeParams.features, blindingSecret, + pathId, ) - offerManager.registerOffer(offer, null) + offerManager.registerOffer(offer, pathId) return offer }