diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt index cef290e79..792f9a55a 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt @@ -286,6 +286,6 @@ data class NodeParams( val sessionKey = 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, sessionKey) + return OfferTypes.Offer.createBlindedOffer(amount = null, description = null, this, trampolineNodeId, Features.empty, sessionKey, pathId = null) } } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt index 346365403..65bbe0734 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt @@ -746,6 +746,17 @@ class Peer( return incomingPaymentHandler.createInvoice(paymentPreimage, amount, description, extraHops, expiry ?: nodeParams.bolt11InvoiceExpiry) } + /** 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/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt index a341840ea..1be0ef63e 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt @@ -787,6 +787,7 @@ object OfferTypes { * @param trampolineNodeId our trampoline node. * @param features features that should be advertised in the offer. * @param blindedPathSessionKey session key used for the blinded path included in the offer, the corresponding public key will be the first path key. + * @param pathId path id for this offer's blinded path. */ fun createBlindedOffer( amount: MilliSatoshi?, @@ -795,11 +796,12 @@ object OfferTypes { trampolineNodeId: PublicKey, features: Features, blindedPathSessionKey: 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(blindedPathSessionKey, listOf(OnionMessages.IntermediateNode(EncodedNodeId.WithPublicKey.Plain(trampolineNodeId))), OnionMessages.Destination.Recipient(EncodedNodeId.WithPublicKey.Wallet(nodeParams.nodeId), null)) + val blindedRouteDetails = OnionMessages.buildRouteToRecipient(blindedPathSessionKey, 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/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt index 420c4a92b..b7c143a8c 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt +++ b/modules/core/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 }