Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -119,12 +121,13 @@ object RouteBlinding {
fun decryptPayload(
privateKey: PrivateKey,
pathKey: PublicKey,
encryptedPayload: ByteVector
): Either<InvalidTlvPayload, Pair<ByteVector, PublicKey>> {
encryptedPayload: ByteVector,
allowCompactFormat: Boolean = false
): Either<InvalidTlvPayload, DecryptedPayload> {
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)
Expand All @@ -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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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}" }
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ object IncomingPaymentPacket {
}

private fun decryptRecipientData(privateKey: PrivateKey, pathKey: PublicKey, data: ByteVector, tlvs: TlvStream<OnionPaymentPayloadTlv>): Either<InvalidTlvPayload, PaymentOnion.FinalPayload.Blinded> {
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) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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)
}
Expand All @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class PaymentPacketTestsCommon : LightningTestSuite() {
val pathKey = payload.get<OnionPaymentPayloadTlv.PathKey>()?.publicKey ?: add.pathKey
assertNotNull(pathKey)
val encryptedData = payload.get<OnionPaymentPayloadTlv.EncryptedRecipientData>()!!.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)
}
Expand Down