diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/crypto/RouteBlinding.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/crypto/RouteBlinding.kt index a813b60a4..b3b73f138 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/crypto/RouteBlinding.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/crypto/RouteBlinding.kt @@ -108,6 +108,8 @@ object RouteBlinding { return privateKey * PrivateKey(Sphinx.generateKey("blinded_node_id", sharedSecret)) } + data class DecryptedPayload(val data: ByteVector, val nextPathKey: PublicKey, val useCompactRoute: Boolean) + /** * Decrypt the encrypted payload (usually found in the onion) that contains instructions to locate the next node. * @@ -119,12 +121,13 @@ object RouteBlinding { fun decryptPayload( privateKey: PrivateKey, pathKey: PublicKey, - encryptedPayload: ByteVector - ): Either> { + encryptedPayload: ByteVector, + allowCompactFormat: Boolean = false + ): Either { val sharedSecret = Sphinx.computeSharedSecret(pathKey, privateKey) val nextPathKey = Sphinx.blind(pathKey, Sphinx.computeBlindingFactor(pathKey, sharedSecret)) - if (encryptedPayload.isEmpty()) { - return Either.Right(Pair(encryptedPayload, nextPathKey)) + if (encryptedPayload.isEmpty() && allowCompactFormat) { + return Either.Right(DecryptedPayload(encryptedPayload, nextPathKey, useCompactRoute = true)) } else { return try { val rho = Sphinx.generateKey("rho", sharedSecret) @@ -135,7 +138,7 @@ object RouteBlinding { byteArrayOf(), encryptedPayload.takeRight(16).toByteArray() ) - Either.Right(Pair(ByteVector(decrypted), nextPathKey)) + Either.Right(DecryptedPayload(ByteVector(decrypted), nextPathKey, useCompactRoute = false)) } catch (_: Throwable) { Either.Left(CannotDecodeTlv(OnionPaymentPayloadTlv.EncryptedRecipientData.tag)) } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/message/OnionMessages.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/message/OnionMessages.kt index e4a905e6d..3f975a576 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/message/OnionMessages.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/message/OnionMessages.kt @@ -154,7 +154,7 @@ object OnionMessages { * @param blindedPrivateKey private key of the blinded node id used in our blinded path. * @param pathId path_id that we included in our blinded path for ourselves. */ - data class DecryptedMessage(val content: MessageOnion, val blindedPrivateKey: PrivateKey, val pathId: ByteVector?) + data class DecryptedMessage(val content: MessageOnion, val blindedPrivateKey: PrivateKey, val pathId: ByteVector?, val useCompactRoute: Boolean) fun decryptMessage(privateKey: PrivateKey, msg: OnionMessage, logger: MDCLogger): DecryptedMessage? { val blindedPrivateKey = RouteBlinding.derivePrivateKey(privateKey, msg.pathKey) @@ -166,13 +166,13 @@ object OnionMessages { logger.warning { "ignoring onion message that couldn't be decoded: ${e.message}" } return null } - when (val payload = RouteBlinding.decryptPayload(privateKey, msg.pathKey, message.encryptedData)) { + when (val payload = RouteBlinding.decryptPayload(privateKey, msg.pathKey, message.encryptedData, allowCompactFormat = true)) { is Either.Left -> { logger.warning { "ignoring onion message that couldn't be decrypted: ${payload.value}" } null } is Either.Right -> { - val (decryptedPayload, nextPathKey) = payload.value + val (decryptedPayload, nextPathKey, useCompactRoute) = payload.value when (val relayInfo = RouteBlindingEncryptedData.read(decryptedPayload.toByteArray())) { is Either.Left -> { logger.warning { "ignoring onion message with invalid relay info: ${relayInfo.value}" } @@ -184,7 +184,7 @@ object OnionMessages { val nextMessage = OnionMessage(relayInfo.value.nextPathKeyOverride ?: nextPathKey, decrypted.value.nextPacket) decryptMessage(privateKey, nextMessage, logger) } - decrypted.value.isLastPacket -> DecryptedMessage(message, blindedPrivateKey, relayInfo.value.pathId) + decrypted.value.isLastPacket -> DecryptedMessage(message, blindedPrivateKey, relayInfo.value.pathId, useCompactRoute) 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/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentPacket.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentPacket.kt index 63e1d99ec..eba3678b0 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentPacket.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/IncomingPaymentPacket.kt @@ -92,7 +92,7 @@ object IncomingPaymentPacket { } private fun decryptRecipientData(privateKey: PrivateKey, pathKey: PublicKey, data: ByteVector, tlvs: TlvStream): Either { - return RouteBlinding.decryptPayload(privateKey, pathKey, data) + return RouteBlinding.decryptPayload(privateKey, pathKey, data, allowCompactFormat = false) .flatMap { (decryptedRecipientData, _) -> RouteBlindingEncryptedData.read(decryptedRecipientData.toByteArray()) } .flatMap { blindedTlvs -> PaymentOnion.FinalPayload.Blinded.validate(tlvs, blindedTlvs) } } diff --git a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OfferManager.kt b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OfferManager.kt index 3efcaa6b8..4522a2ab0 100644 --- a/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OfferManager.kt +++ b/modules/core/src/commonMain/kotlin/fr/acinq/lightning/payment/OfferManager.kt @@ -238,7 +238,7 @@ class OfferManager(val nodeParams: NodeParams, val walletParams: WalletParams, v pathId != null && pathId.size() != 32 -> false // Compact offers are randomly generated, but they don't store the nonce in the path_id to save space. // It is instead the wallet's responsibility to store the corresponding blinded public key(s). - pathId == null && nodeParams.compactOfferKeys.value.contains(blindedPrivateKey.publicKey()) -> true + pathId == null && nodeParams.compactOfferKeys.value.contains(blindedPrivateKey.publicKey()) -> offer.isMinimal() else -> { val expected = deterministicOffer(nodeParams.chainHash, nodeParams.nodePrivateKey, walletParams.trampolineNode.id, offer.amount, offer.description, pathId?.let { ByteVector32(it) }) expected == OfferTypes.OfferAndKey(offer, blindedPrivateKey) 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 78ff36f49..b0c426465 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 @@ -758,6 +758,8 @@ object OfferTypes { val offerId: ByteVector32 = rootHash(records) + fun isMinimal(): Boolean = records.records.filterNot { it is OfferChains || it is OfferPaths || it is OfferIssuerId}.isEmpty() && records.unknown.isEmpty() + companion object { val hrp = "lno" diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/message/OnionMessagesTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/message/OnionMessagesTestsCommon.kt index bf58a3186..f71ea0ca9 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/message/OnionMessagesTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/message/OnionMessagesTestsCommon.kt @@ -152,7 +152,7 @@ class OnionMessagesTestsCommon { assertEquals(blindedAlice, blindedRoute.blindedHops.first().blindedPublicKey) assertEquals("bae3d9ea2b06efd1b7b9b49b6cdcaad0e789474a6939ffa54ff5ec9224d5b76c", Crypto.sha256(blindingKey.value + sharedSecret).toHexString()) assertEquals("6970e870b473ddbc27e3098bfa45bb1aa54f1f637f803d957e6271d8ffeba89da2665d62123763d9b634e30714144a1c165ac9", blindedRoute.blindedHops.first().encryptedPayload.toHex()) - val decryptedPayload = RouteBlindingEncryptedData.read(RouteBlinding.decryptPayload(alice, blindingKey, blindedRoute.blindedHops.first().encryptedPayload).right!!.first.toByteArray()).right!! + val decryptedPayload = RouteBlindingEncryptedData.read(RouteBlinding.decryptPayload(alice, blindingKey, blindedRoute.blindedHops.first().encryptedPayload).right!!.data.toByteArray()).right!! assertEquals(blindedPayload, decryptedPayload) } @@ -176,7 +176,7 @@ class OnionMessagesTestsCommon { assertEquals(blindedBob, blindedRoute.blindedHops.first().blindedPublicKey) assertEquals("9afb8b2ebc174dcf9e270be24771da7796542398d29d4ff6a4e7b6b4b9205cfe", Crypto.sha256(blindingKey.value + sharedSecret).toHexString()) assertEquals("1630da85e8759b8f3b94d74a539c6f0d870a87cf03d4986175865a2985553c997b560c32613bd9184c1a6d41a37027aabdab5433009d8409a1b638eb90373778a05716af2c2140b3196dca23997cdad4cfa7a7adc8d4", blindedRoute.blindedHops.first().encryptedPayload.toHex()) - val decryptedPayload = RouteBlindingEncryptedData.read(RouteBlinding.decryptPayload(bob, blindingKey, blindedRoute.blindedHops.first().encryptedPayload).right!!.first.toByteArray()).right!! + val decryptedPayload = RouteBlindingEncryptedData.read(RouteBlinding.decryptPayload(bob, blindingKey, blindedRoute.blindedHops.first().encryptedPayload).right!!.data.toByteArray()).right!! assertEquals(blindedPayload, decryptedPayload) assertEquals(blindingOverride, decryptedPayload.nextPathKeyOverride) } @@ -200,7 +200,7 @@ class OnionMessagesTestsCommon { assertEquals(blindedCarol, blindedRoute.blindedHops.first().blindedPublicKey) assertEquals("cc3b918cda6b1b049bdbe469c4dd952935e7c1518dd9c7ed0cd2cd5bc2742b82", Crypto.sha256(blindingKey.value + sharedSecret).toHexString()) assertEquals("8285acbceb37dfb38b877a888900539be656233cd74a55c55344fb068f9d8da365340d21db96fb41b76123207daeafdfb1f571e3fea07a22e10da35f03109a0380b3c69fcbed9c698086671809658761cf65ecbc3c07a2e5", blindedRoute.blindedHops.first().encryptedPayload.toHex()) - val decryptedPayload = RouteBlindingEncryptedData.read(RouteBlinding.decryptPayload(carol, blindingKey, blindedRoute.blindedHops.first().encryptedPayload).right!!.first.toByteArray()).right!! + val decryptedPayload = RouteBlindingEncryptedData.read(RouteBlinding.decryptPayload(carol, blindingKey, blindedRoute.blindedHops.first().encryptedPayload).right!!.data.toByteArray()).right!! assertEquals(blindedPayload, decryptedPayload) } diff --git a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/PaymentPacketTestsCommon.kt b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/PaymentPacketTestsCommon.kt index 43a30f4fb..35fee4ee3 100644 --- a/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/PaymentPacketTestsCommon.kt +++ b/modules/core/src/commonTest/kotlin/fr/acinq/lightning/payment/PaymentPacketTestsCommon.kt @@ -133,7 +133,7 @@ class PaymentPacketTestsCommon : LightningTestSuite() { val pathKey = payload.get()?.publicKey ?: add.pathKey assertNotNull(pathKey) val encryptedData = payload.get()!!.data - val nextPathKey = RouteBlinding.decryptPayload(privateKey, pathKey, encryptedData).map { it.second }.right + val nextPathKey = RouteBlinding.decryptPayload(privateKey, pathKey, encryptedData).map { it.nextPathKey }.right assertNotNull(nextPathKey) return Pair(decrypted.nextPacket, nextPathKey) }