From e5b73646cbe3802825d0f07f8a7cbb5572810622 Mon Sep 17 00:00:00 2001 From: Thomas HUET <81159533+thomash-acinq@users.noreply.github.com> Date: Fri, 24 May 2024 17:11:34 +0200 Subject: [PATCH] Smaller default offer (#648) Shaved 32 bytes from the default offer by removing the pathId. --- .../kotlin/fr/acinq/lightning/NodeParams.kt | 12 +++++------- .../fr/acinq/lightning/message/OnionMessages.kt | 7 ++----- .../fr/acinq/lightning/payment/OfferManager.kt | 7 +++---- .../kotlin/fr/acinq/lightning/wire/OfferTypes.kt | 6 +++--- .../lightning/payment/OfferManagerTestsCommon.kt | 6 +++--- .../fr/acinq/lightning/wire/OfferTypesTestsCommon.kt | 6 ++---- 6 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt b/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt index 9ce43b8dd..abec81b2a 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/NodeParams.kt @@ -238,17 +238,15 @@ data class NodeParams( /** * We generate a default, deterministic Bolt 12 offer based on the node's seed and its trampoline node. * This offer will stay valid after restoring the seed on a different device. - * We also return the path_id included in this offer, which should be used to route onion messages. */ - fun defaultOffer(trampolineNode: NodeUri): Pair { - // We generate a deterministic path_id based on: + fun defaultOffer(trampolineNode: NodeUri): OfferTypes.Offer { + // We generate a deterministic blindingSecret based on: // - a custom tag indicating that this is used in the Bolt 12 context // - our trampoline node, which is used as an introduction node for the offer's blinded path - // - our private key, which ensures that nobody else can generate the same path_id - val pathId = Crypto.sha256("bolt 12 default offer".toByteArray(Charsets.UTF_8) + trampolineNode.id.value.toByteArray() + nodePrivateKey.value.toByteArray()).byteVector32() + // - our private key, which ensures that nobody else can generate the same blindingSecret + val blindingSecret = PrivateKey(Crypto.sha256("bolt 12 default offer".toByteArray(Charsets.UTF_8) + trampolineNode.id.value.toByteArray() + nodePrivateKey.value.toByteArray()).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. - val offer = OfferTypes.Offer.createBlindedOffer(amount = null, description = null, this, trampolineNode, Features.empty, pathId) - return Pair(pathId, offer) + return OfferTypes.Offer.createBlindedOffer(amount = null, description = null, this, trampolineNode, Features.empty, blindingSecret) } } diff --git a/src/commonMain/kotlin/fr/acinq/lightning/message/OnionMessages.kt b/src/commonMain/kotlin/fr/acinq/lightning/message/OnionMessages.kt index 4ecffda90..fd7a8bd25 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/message/OnionMessages.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/message/OnionMessages.kt @@ -1,9 +1,6 @@ package fr.acinq.lightning.message -import fr.acinq.bitcoin.ByteVector -import fr.acinq.bitcoin.PrivateKey -import fr.acinq.bitcoin.PublicKey -import fr.acinq.bitcoin.byteVector +import fr.acinq.bitcoin.* import fr.acinq.bitcoin.utils.Either import fr.acinq.lightning.EncodedNodeId import fr.acinq.lightning.ShortChannelId @@ -175,7 +172,7 @@ object OnionMessages { val nextMessage = OnionMessage(relayInfo.value.nextBlindingOverride ?: nextBlinding, decrypted.value.nextPacket) decryptMessage(privateKey, nextMessage, logger) } - decrypted.value.isLastPacket && relayInfo.value.pathId != null -> DecryptedMessage(message, blindedPrivateKey, relayInfo.value.pathId!!) + decrypted.value.isLastPacket -> DecryptedMessage(message, blindedPrivateKey, relayInfo.value.pathId ?: ByteVector32.Zeroes) else -> { logger.warning { "ignoring onion message for which we're not the destination (next_node_id=${relayInfo.value.nextNodeId}, path_id=${relayInfo.value.pathId?.toHex()})" } null diff --git a/src/commonMain/kotlin/fr/acinq/lightning/payment/OfferManager.kt b/src/commonMain/kotlin/fr/acinq/lightning/payment/OfferManager.kt index 4dddc0111..9ed0870bf 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/payment/OfferManager.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/payment/OfferManager.kt @@ -38,12 +38,11 @@ class OfferManager(val nodeParams: NodeParams, val walletParams: WalletParams, v private val localOffers: HashMap = HashMap() init { - val (defaultOfferPathId, defaultOffer) = nodeParams.defaultOffer(walletParams.trampolineNode) - registerOffer(defaultOffer, defaultOfferPathId) + registerOffer(nodeParams.defaultOffer(walletParams.trampolineNode), null) } - fun registerOffer(offer: OfferTypes.Offer, pathId: ByteVector32) { - localOffers[pathId] = offer + fun registerOffer(offer: OfferTypes.Offer, pathId: ByteVector32?) { + localOffers[pathId ?: ByteVector32.Zeroes] = offer } /** diff --git a/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt b/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt index 1f1933f87..bb561932f 100644 --- a/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt +++ b/src/commonMain/kotlin/fr/acinq/lightning/wire/OfferTypes.kt @@ -773,7 +773,7 @@ object OfferTypes { * @param nodeParams our node parameters. * @param trampolineNode our trampoline node. * @param features features that should be advertised in the offer. - * @param pathId pathId on which we will listen for invoice requests. + * @param blindingSecret session key used for the blinded path included in the offer. */ fun createBlindedOffer( amount: MilliSatoshi?, @@ -781,12 +781,12 @@ object OfferTypes { nodeParams: NodeParams, trampolineNode: NodeUri, features: Features, - pathId: ByteVector32, + blindingSecret: PrivateKey, additionalTlvs: Set = setOf(), customTlvs: Set = setOf() ): Offer { if (description == null) require(amount == null) { "an offer description must be provided if the amount isn't null" } - val path = OnionMessages.buildRoute(PrivateKey(pathId), listOf(OnionMessages.IntermediateNode(trampolineNode.id, ShortChannelId.peerId(nodeParams.nodeId))), OnionMessages.Destination.Recipient(nodeParams.nodeId, pathId)) + val path = OnionMessages.buildRoute(blindingSecret, listOf(OnionMessages.IntermediateNode(trampolineNode.id, ShortChannelId.peerId(nodeParams.nodeId))), OnionMessages.Destination.Recipient(nodeParams.nodeId, null)) 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 b1eb9d963..4030a079d 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/payment/OfferManagerTestsCommon.kt @@ -51,16 +51,16 @@ class OfferManagerTestsCommon : LightningTestSuite() { } private fun createOffer(offerManager: OfferManager, amount: MilliSatoshi? = null): OfferTypes.Offer { - val pathId = randomBytes32() + val blindingSecret = randomKey() val offer = OfferTypes.Offer.createBlindedOffer( amount, "Blockaccino", offerManager.nodeParams, offerManager.walletParams.trampolineNode, offerManager.nodeParams.features, - pathId, + blindingSecret, ) - offerManager.registerOffer(offer, pathId) + offerManager.registerOffer(offer, null) return offer } diff --git a/src/commonTest/kotlin/fr/acinq/lightning/wire/OfferTypesTestsCommon.kt b/src/commonTest/kotlin/fr/acinq/lightning/wire/OfferTypesTestsCommon.kt index e2a4516a1..651cf2533 100644 --- a/src/commonTest/kotlin/fr/acinq/lightning/wire/OfferTypesTestsCommon.kt +++ b/src/commonTest/kotlin/fr/acinq/lightning/wire/OfferTypesTestsCommon.kt @@ -508,7 +508,7 @@ class OfferTypesTestsCommon : LightningTestSuite() { fun `generate deterministic blinded offer through trampoline node`() { val trampolineNode = NodeUri(PublicKey.fromHex("03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f"), "3.33.236.230", 9735) val nodeParams = TestConstants.Alice.nodeParams.copy(chain = Chain.Mainnet) - val (pathId, offer) = nodeParams.defaultOffer(trampolineNode) + val offer = nodeParams.defaultOffer(trampolineNode) assertNull(offer.amount) assertNull(offer.description) assertEquals(Features.empty, offer.features) // the offer shouldn't have any feature to guarantee stability @@ -518,9 +518,7 @@ class OfferTypesTestsCommon : LightningTestSuite() { val path = offer.contactInfos.first() assertIs(path) assertEquals(EncodedNodeId(trampolineNode.id), path.route.introductionNodeId) - val expectedPathId = ByteVector32.fromValidHex("69e2c45e00f6e76c50f612b87294191cc634abfbf25eb2eb51f241bec3209897") - assertEquals(expectedPathId, pathId) - val expectedOffer = Offer.decode("lno1zr2s8pjw7qjlm68mtp7e3yvxee4y5xrgjhhyf2fxhlphpckrvevh50u0qf70a6j2x2akrhazctejaaqr8y4qtzjtjzmfesay6mzr3s789uryuqsr8dpgfgxuk56vh7cl89769zdpdrkqwtypzhu2t8ehp73dqeeq65lsqxhq3x0946e6y8hgjpqh5ej0dxpnftcmerz4320cx3luqwlpkg8vdcqsfvz89axkmv5sgdysmwn95tpsct6mdercmz8jh2r82qpjkzq2t69g44vdaj2hxed33qeatadtw8vzj0ze2ezd98u5q4dp9993dkzy8jpr883ayzf95kzv0dvm2fe4").get() + val expectedOffer = Offer.decode("lno1zzes8pjw7qjlm68mtp7e3yvxee4y5xrgjhhyf2fxhlphpckrvevh50u0qf70a6j2x2akrhazctejaaqr8y4qtzjtjzmfesay6mzr3s789uryuqsr8dpgfgxuk56vh7cl89769zdpdrkqwtypzhu2t8ehp73dqeeq65lsqxhq3x0946e6y8hgjpqh5ej0dxpnftcmerz4320cx3luqwlpkg8vdcqsfvz89axkmv5sgdysmwn95tpsct6mdercmz8jh2r82qqscrf6uc3tse5gw5sv5xjdfw8f6c").get() assertEquals(expectedOffer, offer) } } \ No newline at end of file