diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Sphinx.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Sphinx.scala index 73e8c88a81..12bd21ce7f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Sphinx.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Sphinx.scala @@ -380,21 +380,20 @@ object Sphinx extends Logging { /** * @param publicKey introduction node's public key (which cannot be blinded since the sender need to find a route to it). - * @param blindingEphemeralKey blinding tweak that can be used by the introduction node to derive the private key that - * lets it decrypt the encrypted payload. + * @param blindedPublicKey blinded public key, which hides the real public key. + * @param blindingEphemeralKey blinding tweak that can be used by the receiving node to derive the private key that + * matches the blinded public key. * @param encryptedPayload encrypted payload that can be decrypted with the introduction node's private key and the * blinding ephemeral key. */ - case class IntroductionNode(publicKey: PublicKey, blindingEphemeralKey: PublicKey, encryptedPayload: ByteVector) + case class IntroductionNode(publicKey: PublicKey, blindedPublicKey: PublicKey, blindingEphemeralKey: PublicKey, encryptedPayload: ByteVector) /** - * @param blindedPublicKey blinded public key, which hides the real public key. - * @param blindingEphemeralKey blinding tweak that can be used by the receiving node to derive the private key that - * matches the blinded public key. - * @param encryptedPayload encrypted payload that can be decrypted with the receiving node's private key and the - * blinding ephemeral key. + * @param blindedPublicKey blinded public key, which hides the real public key. + * @param encryptedPayload encrypted payload that can be decrypted with the receiving node's private key and the + * blinding ephemeral key. */ - case class BlindedNode(blindedPublicKey: PublicKey, blindingEphemeralKey: PublicKey, encryptedPayload: ByteVector) + case class BlindedNode(blindedPublicKey: PublicKey, encryptedPayload: ByteVector) /** * @param introductionNode the first node should not be blinded, otherwise the sender cannot locate it. @@ -402,7 +401,6 @@ object Sphinx extends Logging { */ case class BlindedRoute(introductionNode: IntroductionNode, blindedNodes: Seq[BlindedNode]) { val nodeIds: Seq[PublicKey] = introductionNode.publicKey +: blindedNodes.map(_.blindedPublicKey) - val blindingEphemeralKeys: Seq[PublicKey] = introductionNode.blindingEphemeralKey +: blindedNodes.map(_.blindingEphemeralKey) val encryptedPayloads: Seq[ByteVector] = introductionNode.encryptedPayload +: blindedNodes.map(_.encryptedPayload) } @@ -417,16 +415,16 @@ object Sphinx extends Logging { def create(sessionKey: PrivateKey, publicKeys: Seq[PublicKey], payloads: Seq[ByteVector]): BlindedRoute = { require(publicKeys.length == payloads.length, "a payload must be provided for each node in the blinded path") var e = sessionKey - val blindedHops = publicKeys.zip(payloads).map { case (publicKey, payload) => + val (blindedHops, blindingKeys) = publicKeys.zip(payloads).map { case (publicKey, payload) => val blindingKey = e.publicKey val sharedSecret = computeSharedSecret(publicKey, e) val blindedPublicKey = blind(publicKey, generateKey("blinded_node_id", sharedSecret)) val rho = generateKey("rho", sharedSecret) val (encryptedPayload, mac) = ChaCha20Poly1305.encrypt(rho, zeroes(12), payload, ByteVector.empty) e = e.multiply(PrivateKey(Crypto.sha256(blindingKey.value ++ sharedSecret.bytes))) - BlindedNode(blindedPublicKey, blindingKey, encryptedPayload ++ mac) - } - val introductionNode = IntroductionNode(publicKeys.head, blindedHops.head.blindingEphemeralKey, blindedHops.head.encryptedPayload) + (BlindedNode(blindedPublicKey, encryptedPayload ++ mac), blindingKey) + }.unzip + val introductionNode = IntroductionNode(publicKeys.head, blindedHops.head.blindedPublicKey, blindingKeys.head, blindedHops.head.encryptedPayload) BlindedRoute(introductionNode, blindedHops.tail) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala index 7818d0eb5b..c3a2c4b124 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/SphinxSpec.scala @@ -368,6 +368,7 @@ class SphinxSpec extends AnyFunSuite { val sessionKey = PrivateKey(hex"0101010101010101010101010101010101010101010101010101010101010101") val blindedRoute = RouteBlinding.create(sessionKey, publicKeys, routeBlindingPayloads) assert(blindedRoute.introductionNode.publicKey === publicKeys(0)) + assert(blindedRoute.introductionNode.blindedPublicKey === PublicKey(hex"02ec68ed555f5d18b12fe0e2208563c3566032967cf11dc29b20c345449f9a50a2")) assert(blindedRoute.introductionNode.blindingEphemeralKey === PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")) assert(blindedRoute.introductionNode.encryptedPayload === hex"a245b767bd52520bdf8179b2dc681d1a36c2ededaf59429dfc4bea342fa460c9") assert(blindedRoute.nodeIds === Seq( @@ -383,13 +384,6 @@ class SphinxSpec extends AnyFunSuite { PublicKey(hex"03bfddd2253b42fe12edd37f9071a3883830ed61a4bc347eeac63421629cf032b5"), PublicKey(hex"03a8588bc4a0a2f0d2fb8d5c0f8d062fb4d78bfba24a85d0ddeb4fd35dd3b34110"), )) - assert(blindedRoute.blindingEphemeralKeys === blindedRoute.introductionNode.blindingEphemeralKey +: blindedRoute.blindedNodes.map(_.blindingEphemeralKey)) - assert(blindedRoute.blindedNodes.map(_.blindingEphemeralKey) === Seq( - PublicKey(hex"035cb4c003d58e16cc9207270b3596c2be3309eca64c36b208c946bbb599bfcad0"), - PublicKey(hex"02e105bc01a7af07074a1b0b1d9a112a1d89c6cd87cc4e2b6ba3a824731d9508bd"), - PublicKey(hex"0349164db5398925ef234002e62d2834da115b8eafc73436fab98ed12266e797cc"), - PublicKey(hex"020a6d1951916adcac22125063f62c35b3686f36e5db2f77073f3d35b19c7a118a"), - )) assert(blindedRoute.encryptedPayloads === blindedRoute.introductionNode.encryptedPayload +: blindedRoute.blindedNodes.map(_.encryptedPayload)) assert(blindedRoute.blindedNodes.map(_.encryptedPayload) === Seq( hex"38748f94ead7de2a54fc43e8bb927bfc377dda7ed5a2e36b327b739c3c82a602e43e07e378f17cd46ee32d987eb8b6d03b3403acb095bd2868f640b92ea1", @@ -399,27 +393,27 @@ class SphinxSpec extends AnyFunSuite { )) // The introduction point can decrypt its encrypted payload and obtain the next ephemeral public key. - val Success((payload0, ephKey1)) = RouteBlinding.decryptPayload(privKeys(0), blindedRoute.blindingEphemeralKeys(0), blindedRoute.encryptedPayloads(0)) + val Success((payload0, ephKey1)) = RouteBlinding.decryptPayload(privKeys(0), blindedRoute.introductionNode.blindingEphemeralKey, blindedRoute.encryptedPayloads(0)) assert(payload0 === routeBlindingPayloads(0)) - assert(ephKey1 === blindedRoute.blindingEphemeralKeys(1)) + assert(ephKey1 === PublicKey(hex"035cb4c003d58e16cc9207270b3596c2be3309eca64c36b208c946bbb599bfcad0")) // The next node can derive the private key used to unwrap the onion and decrypt its encrypted payload. assert(RouteBlinding.derivePrivateKey(privKeys(1), ephKey1).publicKey === blindedRoute.nodeIds(1)) val Success((payload1, ephKey2)) = RouteBlinding.decryptPayload(privKeys(1), ephKey1, blindedRoute.encryptedPayloads(1)) assert(payload1 === routeBlindingPayloads(1)) - assert(ephKey2 === blindedRoute.blindingEphemeralKeys(2)) + assert(ephKey2 === PublicKey(hex"02e105bc01a7af07074a1b0b1d9a112a1d89c6cd87cc4e2b6ba3a824731d9508bd")) // The next node can derive the private key used to unwrap the onion and decrypt its encrypted payload. assert(RouteBlinding.derivePrivateKey(privKeys(2), ephKey2).publicKey === blindedRoute.nodeIds(2)) val Success((payload2, ephKey3)) = RouteBlinding.decryptPayload(privKeys(2), ephKey2, blindedRoute.encryptedPayloads(2)) assert(payload2 === routeBlindingPayloads(2)) - assert(ephKey3 === blindedRoute.blindingEphemeralKeys(3)) + assert(ephKey3 === PublicKey(hex"0349164db5398925ef234002e62d2834da115b8eafc73436fab98ed12266e797cc")) // The next node can derive the private key used to unwrap the onion and decrypt its encrypted payload. assert(RouteBlinding.derivePrivateKey(privKeys(3), ephKey3).publicKey === blindedRoute.nodeIds(3)) val Success((payload3, ephKey4)) = RouteBlinding.decryptPayload(privKeys(3), ephKey3, blindedRoute.encryptedPayloads(3)) assert(payload3 === routeBlindingPayloads(3)) - assert(ephKey4 === blindedRoute.blindingEphemeralKeys(4)) + assert(ephKey4 === PublicKey(hex"020a6d1951916adcac22125063f62c35b3686f36e5db2f77073f3d35b19c7a118a")) // The last node can derive the private key used to unwrap the onion and decrypt its encrypted payload. assert(RouteBlinding.derivePrivateKey(privKeys(4), ephKey4).publicKey === blindedRoute.nodeIds(4))