diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala index 330eb34670..fb4dc32521 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelData.scala @@ -207,7 +207,7 @@ final case class CMD_ADD_HTLC(replyTo: ActorRef, paymentHash: ByteVector32, cltvExpiry: CltvExpiry, onion: OnionRoutingPacket, - nextBlindingKey_opt: Option[PublicKey], + nextPathKey_opt: Option[PublicKey], confidence: Double, fundingFee_opt: Option[LiquidityAds.FundingFee], origin: Origin.Hot, diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala index d7d80575ea..2cbe214432 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala @@ -851,7 +851,7 @@ case class Commitments(params: ChannelParams, return Left(HtlcValueTooSmall(params.channelId, minimum = htlcMinimum, actual = cmd.amount)) } - val add = UpdateAddHtlc(channelId, changes.localNextHtlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextBlindingKey_opt, cmd.confidence, cmd.fundingFee_opt) + val add = UpdateAddHtlc(channelId, changes.localNextHtlcId, cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextPathKey_opt, cmd.confidence, cmd.fundingFee_opt) // we increment the local htlc index and add an entry to the origins map val changes1 = changes.addLocalProposal(add).copy(localNextHtlcId = changes.localNextHtlcId + 1) val originChannels1 = originChannels + (add.id -> cmd.origin) 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 4ce929c900..4005209382 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 @@ -342,42 +342,42 @@ object Sphinx extends Logging { object RouteBlinding { /** - * @param nodeId introduction node's id (which cannot be blinded since the sender need to find a route to it). - * @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. + * @param nodeId first node's id (which cannot be blinded since the sender need to find a route to it). + * @param blindedPublicKey blinded public key, which hides the real public key. + * @param pathKey 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 + * path key. */ - case class IntroductionNode(nodeId: EncodedNodeId, blindedPublicKey: PublicKey, blindingEphemeralKey: PublicKey, encryptedPayload: ByteVector) + case class FirstNode(nodeId: EncodedNodeId, blindedPublicKey: PublicKey, pathKey: PublicKey, encryptedPayload: ByteVector) /** * @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. + * path key. */ - case class BlindedNode(blindedPublicKey: PublicKey, encryptedPayload: ByteVector) + case class BlindedHop(blindedPublicKey: PublicKey, encryptedPayload: ByteVector) /** - * @param introductionNodeId the first node, not blinded so that the sender can locate it. - * @param blindingKey blinding tweak that can be used by the introduction node to derive the private key that + * @param firstNodeId the first node, not blinded so that the sender can locate it. + * @param firstPathKey blinding tweak that can be used by the introduction node to derive the private key that * matches the blinded public key. - * @param blindedNodes blinded nodes (including the introduction node). + * @param blindedHops blinded nodes (including the introduction node). */ - case class BlindedRoute(introductionNodeId: EncodedNodeId, blindingKey: PublicKey, blindedNodes: Seq[BlindedNode]) { - require(blindedNodes.nonEmpty, "blinded route must not be empty") - val introductionNode: IntroductionNode = IntroductionNode(introductionNodeId, blindedNodes.head.blindedPublicKey, blindingKey, blindedNodes.head.encryptedPayload) - val subsequentNodes: Seq[BlindedNode] = blindedNodes.tail - val blindedNodeIds: Seq[PublicKey] = blindedNodes.map(_.blindedPublicKey) - val encryptedPayloads: Seq[ByteVector] = blindedNodes.map(_.encryptedPayload) - val length: Int = blindedNodes.length - 1 + case class BlindedRoute(firstNodeId: EncodedNodeId, firstPathKey: PublicKey, blindedHops: Seq[BlindedHop]) { + require(blindedHops.nonEmpty, "blinded route must not be empty") + val firstNode: FirstNode = FirstNode(firstNodeId, blindedHops.head.blindedPublicKey, firstPathKey, blindedHops.head.encryptedPayload) + val subsequentNodes: Seq[BlindedHop] = blindedHops.tail + val blindedNodeIds: Seq[PublicKey] = blindedHops.map(_.blindedPublicKey) + val encryptedPayloads: Seq[ByteVector] = blindedHops.map(_.encryptedPayload) + val length: Int = blindedHops.length - 1 } /** - * @param route blinded route. - * @param lastBlinding blinding point for the last node, which can be used to derive the blinded private key. + * @param route blinded route. + * @param lastPathKey path key for the last node, which can be used to derive the blinded private key. */ - case class BlindedRouteDetails(route: BlindedRoute, lastBlinding: PublicKey) + case class BlindedRouteDetails(route: BlindedRoute, lastPathKey: PublicKey) /** * Blind the provided route and encrypt intermediate nodes' payloads. @@ -390,44 +390,44 @@ object Sphinx extends Logging { def create(sessionKey: PrivateKey, publicKeys: Seq[PublicKey], payloads: Seq[ByteVector]): BlindedRouteDetails = { require(publicKeys.length == payloads.length, "a payload must be provided for each node in the blinded path") var e = sessionKey - val (blindedHops, blindingKeys) = publicKeys.zip(payloads).map { case (publicKey, payload) => - val blindingKey = e.publicKey + val (blindedHops, pathKeys) = publicKeys.zip(payloads).map { case (publicKey, payload) => + val pathKey = 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, encryptedPayload ++ mac), blindingKey) + e = e.multiply(PrivateKey(Crypto.sha256(pathKey.value ++ sharedSecret.bytes))) + (BlindedHop(blindedPublicKey, encryptedPayload ++ mac), pathKey) }.unzip - BlindedRouteDetails(BlindedRoute(EncodedNodeId.WithPublicKey.Plain(publicKeys.head), blindingKeys.head, blindedHops), blindingKeys.last) + BlindedRouteDetails(BlindedRoute(EncodedNodeId.WithPublicKey.Plain(publicKeys.head), pathKeys.head, blindedHops), pathKeys.last) } /** * Compute the blinded private key that must be used to decrypt an incoming blinded onion. * - * @param privateKey this node's private key. - * @param blindingEphemeralKey unblinding ephemeral key. + * @param privateKey this node's private key. + * @param pathKey unblinding ephemeral key. * @return this node's blinded private key. */ - def derivePrivateKey(privateKey: PrivateKey, blindingEphemeralKey: PublicKey): PrivateKey = { - val sharedSecret = computeSharedSecret(blindingEphemeralKey, privateKey) + def derivePrivateKey(privateKey: PrivateKey, pathKey: PublicKey): PrivateKey = { + val sharedSecret = computeSharedSecret(pathKey, privateKey) privateKey.multiply(PrivateKey(generateKey("blinded_node_id", sharedSecret))) } /** * Decrypt the encrypted payload (usually found in the onion) that contains instructions to locate the next node. * - * @param privateKey this node's private key. - * @param blindingEphemeralKey unblinding ephemeral key. - * @param encryptedPayload encrypted payload for this node. - * @return a tuple (decrypted payload, unblinding ephemeral key for the next node) + * @param privateKey this node's private key. + * @param pathKey unblinding ephemeral key. + * @param encryptedPayload encrypted payload for this node. + * @return a tuple (decrypted payload, path key for the next node) */ - def decryptPayload(privateKey: PrivateKey, blindingEphemeralKey: PublicKey, encryptedPayload: ByteVector): Try[(ByteVector, PublicKey)] = Try { - val sharedSecret = computeSharedSecret(blindingEphemeralKey, privateKey) + def decryptPayload(privateKey: PrivateKey, pathKey: PublicKey, encryptedPayload: ByteVector): Try[(ByteVector, PublicKey)] = Try { + val sharedSecret = computeSharedSecret(pathKey, privateKey) val rho = generateKey("rho", sharedSecret) val decrypted = ChaCha20Poly1305.decrypt(rho, zeroes(12), encryptedPayload.dropRight(16), ByteVector.empty, encryptedPayload.takeRight(16)) - val nextBlindingEphemeralKey = blind(blindingEphemeralKey, computeBlindingFactor(blindingEphemeralKey, sharedSecret)) - (decrypted, nextBlindingEphemeralKey) + val nextPathKey = blind(pathKey, computeBlindingFactor(pathKey, sharedSecret)) + (decrypted, nextPathKey) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala index 611306659a..cb59e6f0b4 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/io/Peer.scala @@ -255,7 +255,7 @@ class Peer(val nodeParams: NodeParams, case Event(cmd: ProposeOnTheFlyFunding, d: ConnectedData) => // We send the funding proposal to our peer, and report it to the sender. - val htlc = WillAddHtlc(nodeParams.chainHash, randomBytes32(), cmd.amount, cmd.paymentHash, cmd.expiry, cmd.onion, cmd.nextBlindingKey_opt) + val htlc = WillAddHtlc(nodeParams.chainHash, randomBytes32(), cmd.amount, cmd.paymentHash, cmd.expiry, cmd.onion, cmd.nextPathKey_opt) cmd.replyTo ! ProposeOnTheFlyFundingResponse.Proposed // We update our list of pending proposals for that payment. val pending = pendingOnTheFlyFunding.get(htlc.paymentHash) match { @@ -983,7 +983,7 @@ object Peer { case class SpawnChannelNonInitiator(open: Either[protocol.OpenChannel, protocol.OpenDualFundedChannel], channelConfig: ChannelConfig, channelType: SupportedChannelType, addFunding_opt: Option[LiquidityAds.AddFunding], localParams: LocalParams, peerConnection: ActorRef) /** If [[Features.OnTheFlyFunding]] is supported and we're connected, relay a funding proposal to our peer. */ - case class ProposeOnTheFlyFunding(replyTo: typed.ActorRef[ProposeOnTheFlyFundingResponse], amount: MilliSatoshi, paymentHash: ByteVector32, expiry: CltvExpiry, onion: OnionRoutingPacket, nextBlindingKey_opt: Option[PublicKey], upstream: Upstream.Hot) + case class ProposeOnTheFlyFunding(replyTo: typed.ActorRef[ProposeOnTheFlyFundingResponse], amount: MilliSatoshi, paymentHash: ByteVector32, expiry: CltvExpiry, onion: OnionRoutingPacket, nextPathKey_opt: Option[PublicKey], upstream: Upstream.Hot) sealed trait ProposeOnTheFlyFundingResponse object ProposeOnTheFlyFundingResponse { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala index 90e07ca162..2f390f13b9 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala @@ -468,8 +468,8 @@ object InvoiceSerializer extends MinimalSerializer({ UnknownFeatureSerializer ))), Some(JField("blindedPaths", JArray(p.blindedPaths.map(path => { - val introductionNode = path.route.introductionNodeId.toString - val blindedNodes = path.route.blindedNodes + val introductionNode = path.route.firstNodeId.toString + val blindedNodes = path.route.blindedHops JObject(List( JField("introductionNodeId", JString(introductionNode)), JField("blindedNodeIds", JArray(blindedNodes.map(n => JString(n.blindedPublicKey.toString)).toList)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/message/OnionMessages.scala b/eclair-core/src/main/scala/fr/acinq/eclair/message/OnionMessages.scala index da88853e1b..18b78ca9db 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/message/OnionMessages.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/message/OnionMessages.scala @@ -44,11 +44,11 @@ object OnionMessages { maxAttempts: Int) case class IntermediateNode(publicKey: PublicKey, encodedNodeId: EncodedNodeId, outgoingChannel_opt: Option[ShortChannelId] = None, padding: Option[ByteVector] = None, customTlvs: Set[GenericTlv] = Set.empty) { - def toTlvStream(nextNodeId: EncodedNodeId, nextBlinding_opt: Option[PublicKey] = None): TlvStream[RouteBlindingEncryptedDataTlv] = + def toTlvStream(nextNodeId: EncodedNodeId, nextPathKey_opt: Option[PublicKey] = None): TlvStream[RouteBlindingEncryptedDataTlv] = TlvStream(Set[Option[RouteBlindingEncryptedDataTlv]]( padding.map(Padding), outgoingChannel_opt.map(OutgoingChannelId).orElse(Some(OutgoingNodeId(nextNodeId))), - nextBlinding_opt.map(NextBlinding) + nextPathKey_opt.map(NextPathKey) ).flatten, customTlvs) } @@ -61,7 +61,7 @@ object OnionMessages { def introductionNodeId: EncodedNodeId } case class BlindedPath(route: Sphinx.RouteBlinding.BlindedRoute) extends Destination { - override def introductionNodeId: EncodedNodeId = route.introductionNodeId + override def introductionNodeId: EncodedNodeId = route.firstNodeId } case class Recipient(nodeId: PublicKey, pathId: Option[ByteVector], padding: Option[ByteVector] = None, customTlvs: Set[GenericTlv] = Set.empty) extends Destination { override def introductionNodeId: EncodedNodeId = EncodedNodeId.WithPublicKey.Plain(nodeId) @@ -80,35 +80,35 @@ object OnionMessages { } // @formatter:on - private def buildIntermediatePayloads(intermediateNodes: Seq[IntermediateNode], lastNodeId: EncodedNodeId, lastBlinding_opt: Option[PublicKey] = None): Seq[ByteVector] = { + private def buildIntermediatePayloads(intermediateNodes: Seq[IntermediateNode], lastNodeId: EncodedNodeId, lastPathKey_opt: Option[PublicKey] = None): Seq[ByteVector] = { if (intermediateNodes.isEmpty) { Nil } else { val intermediatePayloads = intermediateNodes.dropRight(1).zip(intermediateNodes.tail).map { case (hop, nextNode) => hop.toTlvStream(nextNode.encodedNodeId) } - val lastPayload = intermediateNodes.last.toTlvStream(lastNodeId, lastBlinding_opt) + val lastPayload = intermediateNodes.last.toTlvStream(lastNodeId, lastPathKey_opt) (intermediatePayloads :+ lastPayload).map(tlvs => RouteBlindingEncryptedDataCodecs.blindedRouteDataCodec.encode(tlvs).require.bytes) } } - def buildRoute(blindingSecret: PrivateKey, + def buildRoute(pathKeySecret: PrivateKey, intermediateNodes: Seq[IntermediateNode], recipient: Recipient): Sphinx.RouteBlinding.BlindedRouteDetails = { val intermediatePayloads = buildIntermediatePayloads(intermediateNodes, EncodedNodeId.WithPublicKey.Plain(recipient.nodeId)) val tlvs: Set[RouteBlindingEncryptedDataTlv] = Set(recipient.padding.map(Padding), recipient.pathId.map(PathId)).flatten val lastPayload = RouteBlindingEncryptedDataCodecs.blindedRouteDataCodec.encode(TlvStream(tlvs, recipient.customTlvs)).require.bytes - Sphinx.RouteBlinding.create(blindingSecret, intermediateNodes.map(_.publicKey) :+ recipient.nodeId, intermediatePayloads :+ lastPayload) + Sphinx.RouteBlinding.create(pathKeySecret, intermediateNodes.map(_.publicKey) :+ recipient.nodeId, intermediatePayloads :+ lastPayload) } - private[message] def buildRouteFrom(blindingSecret: PrivateKey, + private[message] def buildRouteFrom(pathKeySecret: PrivateKey, intermediateNodes: Seq[IntermediateNode], destination: Destination): Sphinx.RouteBlinding.BlindedRoute = { destination match { - case recipient: Recipient => buildRoute(blindingSecret, intermediateNodes, recipient).route + case recipient: Recipient => buildRoute(pathKeySecret, intermediateNodes, recipient).route case BlindedPath(route) if intermediateNodes.isEmpty => route case BlindedPath(route) => - val intermediatePayloads = buildIntermediatePayloads(intermediateNodes, route.introductionNodeId, Some(route.blindingKey)) - val routePrefix = Sphinx.RouteBlinding.create(blindingSecret, intermediateNodes.map(_.publicKey), intermediatePayloads).route - Sphinx.RouteBlinding.BlindedRoute(routePrefix.introductionNodeId, routePrefix.blindingKey, routePrefix.blindedNodes ++ route.blindedNodes) + val intermediatePayloads = buildIntermediatePayloads(intermediateNodes, route.firstNodeId, Some(route.firstPathKey)) + val routePrefix = Sphinx.RouteBlinding.create(pathKeySecret, intermediateNodes.map(_.publicKey), intermediatePayloads).route + Sphinx.RouteBlinding.BlindedRoute(routePrefix.firstNodeId, routePrefix.firstPathKey, routePrefix.blindedHops ++ route.blindedHops) } } @@ -118,18 +118,18 @@ object OnionMessages { * Builds an encrypted onion containing a message that should be relayed to the destination. * * @param sessionKey A random key to encrypt the onion - * @param blindingSecret A random key to encrypt the onion + * @param pathKeySecret A random key to encrypt the onion * @param intermediateNodes List of intermediate nodes between us and the destination, can be empty if we want to contact the destination directly * @param destination The destination of this message, can be a node id or a blinded route * @param content List of TLVs to send to the recipient of the message * @return The node id to send the onion to and the onion containing the message */ def buildMessage(sessionKey: PrivateKey, - blindingSecret: PrivateKey, + pathKeySecret: PrivateKey, intermediateNodes: Seq[IntermediateNode], destination: Destination, content: TlvStream[OnionMessagePayloadTlv]): Either[MessageTooLarge, OnionMessage] = { - val route = buildRouteFrom(blindingSecret, intermediateNodes, destination) + val route = buildRouteFrom(pathKeySecret, intermediateNodes, destination) val lastPayload = MessageOnionCodecs.perHopPayloadCodec.encode(TlvStream(content.records + EncryptedData(route.encryptedPayloads.last), content.unknown)).require.bytes val payloads = route.encryptedPayloads.dropRight(1).map(encTlv => MessageOnionCodecs.perHopPayloadCodec.encode(TlvStream(EncryptedData(encTlv))).require.bytes) :+ lastPayload val payloadSize = payloads.map(_.length + Sphinx.MacLength).sum @@ -144,8 +144,8 @@ object OnionMessages { payloadSize.toInt } // Since we are setting the packet size based on the payload, the onion creation should never fail (hence the `.get`). - val Sphinx.PacketAndSecrets(packet, _) = Sphinx.create(sessionKey, packetSize, route.blindedNodes.map(_.blindedPublicKey), payloads, None).get - Right(OnionMessage(route.blindingKey, packet)) + val Sphinx.PacketAndSecrets(packet, _) = Sphinx.create(sessionKey, packetSize, route.blindedHops.map(_.blindedPublicKey), payloads, None).get + Right(OnionMessage(route.firstPathKey, packet)) } // @formatter:off @@ -175,27 +175,27 @@ object OnionMessages { } } - case class DecodedEncryptedData(payload: TlvStream[RouteBlindingEncryptedDataTlv], nextBlinding: PublicKey) + case class DecodedEncryptedData(payload: TlvStream[RouteBlindingEncryptedDataTlv], nextPathKey: PublicKey) - private def decryptEncryptedData(privateKey: PrivateKey, blinding: PublicKey, encryptedData: ByteVector): Either[DropReason, DecodedEncryptedData] = { - RouteBlindingEncryptedDataCodecs.decode(privateKey, blinding, encryptedData) match { + private def decryptEncryptedData(privateKey: PrivateKey, pathKey: PublicKey, encryptedData: ByteVector): Either[DropReason, DecodedEncryptedData] = { + RouteBlindingEncryptedDataCodecs.decode(privateKey, pathKey, encryptedData) match { case Left(RouteBlindingEncryptedDataCodecs.CannotDecryptData(f)) => Left(CannotDecryptBlindedPayload(f)) case Left(RouteBlindingEncryptedDataCodecs.CannotDecodeData(f)) => Left(CannotDecodeBlindedPayload(f)) - case Right(decoded) => Right(DecodedEncryptedData(decoded.tlvs, decoded.nextBlinding)) + case Right(decoded) => Right(DecodedEncryptedData(decoded.tlvs, decoded.nextPathKey)) } } def process(privateKey: PrivateKey, msg: OnionMessage): Action = { - val blindedPrivateKey = Sphinx.RouteBlinding.derivePrivateKey(privateKey, msg.blindingKey) + val blindedPrivateKey = Sphinx.RouteBlinding.derivePrivateKey(privateKey, msg.pathKey) decryptOnion(blindedPrivateKey, msg.onionRoutingPacket) match { case Left(f) => DropMessage(f) case Right(DecodedOnionPacket(payload, nextPacket_opt)) => payload.get[OnionMessagePayloadTlv.EncryptedData] match { case Some(OnionMessagePayloadTlv.EncryptedData(encryptedData)) => - decryptEncryptedData(privateKey, msg.blindingKey, encryptedData) match { + decryptEncryptedData(privateKey, msg.pathKey, encryptedData) match { case Left(f) => DropMessage(f) - case Right(DecodedEncryptedData(blindedPayload, nextBlinding)) => nextPacket_opt match { - case Some(nextPacket) => validateRelayPayload(payload, blindedPayload, nextBlinding, nextPacket) + case Right(DecodedEncryptedData(blindedPayload, nextPathKey)) => nextPacket_opt match { + case Some(nextPacket) => validateRelayPayload(payload, blindedPayload, nextPathKey, nextPacket) case None => validateFinalPayload(payload, blindedPayload, blindedPrivateKey) } } @@ -207,10 +207,10 @@ object OnionMessages { } } - private def validateRelayPayload(payload: TlvStream[OnionMessagePayloadTlv], blindedPayload: TlvStream[RouteBlindingEncryptedDataTlv], nextBlinding: PublicKey, nextPacket: OnionRoutingPacket): Action = { - IntermediatePayload.validate(payload, blindedPayload, nextBlinding) match { + private def validateRelayPayload(payload: TlvStream[OnionMessagePayloadTlv], blindedPayload: TlvStream[RouteBlindingEncryptedDataTlv], nextPathKey: PublicKey, nextPacket: OnionRoutingPacket): Action = { + IntermediatePayload.validate(payload, blindedPayload, nextPathKey) match { case Left(f) => DropMessage(CannotDecodeBlindedPayload(f.failureMessage.message)) - case Right(relayPayload) => SendMessage(relayPayload.nextNode, OnionMessage(nextBlinding, nextPacket)) + case Right(relayPayload) => SendMessage(relayPayload.nextNode, OnionMessage(nextPathKey, nextPacket)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/message/Postman.scala b/eclair-core/src/main/scala/fr/acinq/eclair/message/Postman.scala index 36f0d658ef..b45c60626f 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/message/Postman.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/message/Postman.scala @@ -150,7 +150,7 @@ private class SendingMessage(nodeParams: NodeParams, case SendMessage => contactInfo match { case blindedPath: OfferTypes.BlindedPath => - blindedPath.route.introductionNodeId match { + blindedPath.route.firstNodeId match { case EncodedNodeId.ShortChannelIdDir(isNode1, scid) => router ! Router.GetNodeId(context.messageAdapter(WrappedNodeIdResponse), scid, isNode1) waitForNodeId(blindedPath.route) @@ -165,7 +165,7 @@ private class SendingMessage(nodeParams: NodeParams, private def waitForNodeId(compactBlindedPath: BlindedRoute): Behavior[Command] = { Behaviors.receiveMessagePartial { case WrappedNodeIdResponse(None) => - replyTo ! Postman.MessageFailed(s"Could not resolve introduction node for compact blinded path: ${compactBlindedPath.introductionNode.nodeId}") + replyTo ! Postman.MessageFailed(s"Could not resolve introduction node for compact blinded path: ${compactBlindedPath.firstNode.nodeId}") Behaviors.stopped case WrappedNodeIdResponse(Some(nodeId)) => sendToDestination(OnionMessages.BlindedPath(compactBlindedPath), nodeId) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala index 8e5f0f17e9..bcc1142294 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentPacket.scala @@ -79,23 +79,23 @@ object IncomingPaymentPacket { case Left(badOnion) => Left(badOnion) } - case class DecodedEncryptedRecipientData(payload: TlvStream[RouteBlindingEncryptedDataTlv], nextBlinding: PublicKey) + case class DecodedEncryptedRecipientData(payload: TlvStream[RouteBlindingEncryptedDataTlv], nextPathKey: PublicKey) private[payment] def decryptEncryptedRecipientData(add: UpdateAddHtlc, privateKey: PrivateKey, payload: TlvStream[OnionPaymentPayloadTlv], encryptedRecipientData: ByteVector): Either[FailureMessage, DecodedEncryptedRecipientData] = { - if (add.blinding_opt.isDefined && payload.get[OnionPaymentPayloadTlv.BlindingPoint].isDefined) { + if (add.pathKey_opt.isDefined && payload.get[OnionPaymentPayloadTlv.PathKey].isDefined) { Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) } else { - add.blinding_opt.orElse(payload.get[OnionPaymentPayloadTlv.BlindingPoint].map(_.publicKey)) match { - case Some(blinding) => RouteBlindingEncryptedDataCodecs.decode(privateKey, blinding, encryptedRecipientData) match { + add.pathKey_opt.orElse(payload.get[OnionPaymentPayloadTlv.PathKey].map(_.publicKey)) match { + case Some(pathKey) => RouteBlindingEncryptedDataCodecs.decode(privateKey, pathKey, encryptedRecipientData) match { case Left(_) => // There are two possibilities in this case: - // - the blinding point is invalid: the sender or the previous node is buggy or malicious + // - the path key is invalid: the sender or the previous node is buggy or malicious // - the encrypted data is invalid: the sender, the previous node or the recipient must be buggy or malicious Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) - case Right(decoded) => Right(DecodedEncryptedRecipientData(decoded.tlvs, decoded.nextBlinding)) + case Right(decoded) => Right(DecodedEncryptedRecipientData(decoded.tlvs, decoded.nextPathKey)) } case None => - // The sender is trying to use route blinding, but we didn't receive the blinding point used to derive + // The sender is trying to use route blinding, but we didn't receive the path key used to derive // the decryption key. The sender or the previous peer is buggy or malicious. Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) } @@ -115,7 +115,7 @@ object IncomingPaymentPacket { */ def decrypt(add: UpdateAddHtlc, privateKey: PrivateKey, features: Features[Feature]): Either[FailureMessage, IncomingPaymentPacket] = { // We first derive the decryption key used to peel the outer onion. - val outerOnionDecryptionKey = add.blinding_opt match { + val outerOnionDecryptionKey = add.pathKey_opt match { case Some(blinding) => Sphinx.RouteBlinding.derivePrivateKey(privateKey, blinding) case None => privateKey } @@ -127,14 +127,14 @@ object IncomingPaymentPacket { case Some(encrypted) => // We are inside a blinded path: channel relay information is encrypted. decryptEncryptedRecipientData(add, privateKey, payload, encrypted.data).flatMap { - case DecodedEncryptedRecipientData(blindedPayload, nextBlinding) => - validateBlindedChannelRelayPayload(add, payload, blindedPayload, nextBlinding, nextPacket).flatMap { + case DecodedEncryptedRecipientData(blindedPayload, nextPathKey) => + validateBlindedChannelRelayPayload(add, payload, blindedPayload, nextPathKey, nextPacket).flatMap { case ChannelRelayPacket(_, payload, nextPacket) if payload.outgoing == Right(ShortChannelId.toSelf) => - decrypt(add.copy(onionRoutingPacket = nextPacket, tlvStream = add.tlvStream.copy(records = Set(UpdateAddHtlcTlv.BlindingPoint(nextBlinding)))), privateKey, features) + decrypt(add.copy(onionRoutingPacket = nextPacket, tlvStream = add.tlvStream.copy(records = Set(UpdateAddHtlcTlv.PathKey(nextPathKey)))), privateKey, features) case relayPacket => Right(relayPacket) } } - case None if add.blinding_opt.isDefined => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) + case None if add.pathKey_opt.isDefined => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) case None => // We are not inside a blinded path: channel relay information is directly available. IntermediatePayload.ChannelRelay.Standard.validate(payload).left.map(_.failureMessage).map(payload => ChannelRelayPacket(add, payload, nextPacket)) @@ -150,13 +150,13 @@ object IncomingPaymentPacket { decryptEncryptedRecipientData(add, privateKey, payload, encrypted.data).flatMap { case DecodedEncryptedRecipientData(blindedPayload, _) => validateBlindedFinalPayload(add, payload, blindedPayload) } - case None if add.blinding_opt.isDefined => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) + case None if add.pathKey_opt.isDefined => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) case None => // We check if the payment is using trampoline: if it is, we may not be the final recipient. payload.get[OnionPaymentPayloadTlv.TrampolineOnion] match { case Some(OnionPaymentPayloadTlv.TrampolineOnion(trampolinePacket)) => // NB: when we enable blinded trampoline routes, we will need to check if the outer onion contains a - // blinding point and use it to derive the decryption key for the blinded trampoline onion. + // path key and use it to derive the decryption key for the blinded trampoline onion. decryptOnion(add.paymentHash, privateKey, trampolinePacket).flatMap { case DecodedOnionPacket(innerPayload, Some(next)) => // We are an intermediate trampoline node. @@ -193,9 +193,9 @@ object IncomingPaymentPacket { private def validateBlindedChannelRelayPayload(add: UpdateAddHtlc, payload: TlvStream[OnionPaymentPayloadTlv], blindedPayload: TlvStream[RouteBlindingEncryptedDataTlv], - nextBlinding: PublicKey, + nextPathKey: PublicKey, nextPacket: OnionRoutingPacket): Either[FailureMessage, ChannelRelayPacket] = { - IntermediatePayload.ChannelRelay.Blinded.validate(payload, blindedPayload, nextBlinding).left.map(_.failureMessage).flatMap { + IntermediatePayload.ChannelRelay.Blinded.validate(payload, blindedPayload, nextPathKey).left.map(_.failureMessage).flatMap { case payload if add.amountMsat < payload.paymentRelayData.paymentConstraints.minAmount => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) case payload if add.cltvExpiry > payload.paymentRelayData.paymentConstraints.maxCltvExpiry => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) case payload if !Features.areCompatible(Features.empty, payload.paymentRelayData.allowedFeatures) => Left(InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket))) @@ -284,10 +284,10 @@ object OutgoingPaymentPacket { // @formatter:off case class NodePayload(nodeId: PublicKey, payload: PerHopPayload) /** - * @param outerBlinding_opt (optional) blinding point that should be sent to the next node outside of the onion. + * @param outerPathKey_opt (optional) path key that should be sent to the next node outside of the onion. * This is set when the next node is not the blinded path's introduction node. */ - case class PaymentPayloads(amount: MilliSatoshi, expiry: CltvExpiry, payloads: Seq[NodePayload], outerBlinding_opt: Option[PublicKey]) + case class PaymentPayloads(amount: MilliSatoshi, expiry: CltvExpiry, payloads: Seq[NodePayload], outerPathKey_opt: Option[PublicKey]) sealed trait OutgoingPaymentError extends Throwable private case class CannotCreateOnion(message: String) extends OutgoingPaymentError { override def getMessage: String = message } @@ -324,7 +324,7 @@ object OutgoingPaymentPacket { payment <- recipient.buildPayloads(paymentHash, route) onion <- buildOnion(payment.payloads, paymentHash, Some(PaymentOnionCodecs.paymentOnionPayloadLength)) // BOLT 2 requires that associatedData == paymentHash } yield { - val cmd = CMD_ADD_HTLC(origin.replyTo, payment.amount, paymentHash, payment.expiry, onion.packet, payment.outerBlinding_opt, confidence, fundingFee_opt = None, origin, commit = true) + val cmd = CMD_ADD_HTLC(origin.replyTo, payment.amount, paymentHash, payment.expiry, onion.packet, payment.outerPathKey_opt, confidence, fundingFee_opt = None, origin, commit = true) OutgoingPaymentPacket(cmd, route.hops.head.shortChannelId, onion.sharedSecrets) } } @@ -342,7 +342,7 @@ object OutgoingPaymentPacket { } def buildHtlcFailure(nodeSecret: PrivateKey, cmd: CMD_FAIL_HTLC, add: UpdateAddHtlc): Either[CannotExtractSharedSecret, HtlcFailureMessage] = { - add.blinding_opt match { + add.pathKey_opt match { case Some(_) => // We are part of a blinded route and we're not the introduction node. val failure = InvalidOnionBlinding(Sphinx.hash(add.onionRoutingPacket)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala index 5663bba986..2ffbb2356a 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/receive/MultiPartHandler.scala @@ -370,7 +370,7 @@ object MultiPartHandler { createBlindedRouteFromHops(dummyHops, r.pathId, nodeParams.channelConf.htlcMinimum, route.maxFinalExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight)) } val contactInfo = route.shortChannelIdDir_opt match { - case Some(shortChannelIdDir) => BlindedRoute(shortChannelIdDir, blindedRoute.route.blindingKey, blindedRoute.route.blindedNodes) + case Some(shortChannelIdDir) => BlindedRoute(shortChannelIdDir, blindedRoute.route.firstPathKey, blindedRoute.route.blindedHops) case None => blindedRoute.route } val paymentInfo = aggregatePaymentInfo(r.amount, dummyHops, nodeParams.channelConf.minFinalExpiryDelta) @@ -381,7 +381,7 @@ object MultiPartHandler { val clearRoute = routeResponse.routes.head val blindedRoute = createBlindedRouteFromHops(clearRoute.hops ++ dummyHops, r.pathId, nodeParams.channelConf.htlcMinimum, route.maxFinalExpiryDelta.toCltvExpiry(nodeParams.currentBlockHeight)) val contactInfo = route.shortChannelIdDir_opt match { - case Some(shortChannelIdDir) => BlindedRoute(shortChannelIdDir, blindedRoute.route.blindingKey, blindedRoute.route.blindedNodes) + case Some(shortChannelIdDir) => BlindedRoute(shortChannelIdDir, blindedRoute.route.firstPathKey, blindedRoute.route.blindedHops) case None => blindedRoute.route } val paymentInfo = aggregatePaymentInfo(r.amount, clearRoute.hops ++ dummyHops, nodeParams.channelConf.minFinalExpiryDelta) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala index e6159d5ec2..eb46a81088 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala @@ -127,8 +127,8 @@ class ChannelRelay private(nodeParams: NodeParams, private val forwardNodeIdFailureAdapter = context.messageAdapter[Register.ForwardNodeIdFailure[Peer.ProposeOnTheFlyFunding]](_ => WrappedOnTheFlyFundingResponse(Peer.ProposeOnTheFlyFundingResponse.NotAvailable("peer not found"))) private val onTheFlyFundingResponseAdapter = context.messageAdapter[Peer.ProposeOnTheFlyFundingResponse](WrappedOnTheFlyFundingResponse) - private val nextBlindingKey_opt = r.payload match { - case payload: IntermediatePayload.ChannelRelay.Blinded => Some(payload.nextBlinding) + private val nextPathKey_opt = r.payload match { + case payload: IntermediatePayload.ChannelRelay.Blinded => Some(payload.nextPathKey) case _: IntermediatePayload.ChannelRelay.Standard => None } @@ -186,7 +186,7 @@ class ChannelRelay private(nodeParams: NodeParams, context.log.info("rejecting htlc reason={}", cmdFail.reason) safeSendAndStop(r.add.channelId, cmdFail) case RelayNeedsFunding(nextNodeId, cmdFail) => - val cmd = Peer.ProposeOnTheFlyFunding(onTheFlyFundingResponseAdapter, r.amountToForward, r.add.paymentHash, r.outgoingCltv, r.nextPacket, nextBlindingKey_opt, upstream) + val cmd = Peer.ProposeOnTheFlyFunding(onTheFlyFundingResponseAdapter, r.amountToForward, r.add.paymentHash, r.outgoingCltv, r.nextPacket, nextPathKey_opt, upstream) register ! Register.ForwardNodeId(forwardNodeIdFailureAdapter, nextNodeId, cmd) waitForOnTheFlyFundingResponse(cmdFail) case RelaySuccess(selectedChannelId, cmdAdd) => @@ -256,7 +256,7 @@ class ChannelRelay private(nodeParams: NodeParams, case payload: IntermediatePayload.ChannelRelay.Blinded => // We are inside a blinded route, so we must carefully choose the error we return to avoid leaking information. val failure = InvalidOnionBlinding(Sphinx.hash(r.add.onionRoutingPacket)) - payload.records.get[OnionPaymentPayloadTlv.BlindingPoint] match { + payload.records.get[OnionPaymentPayloadTlv.PathKey] match { case Some(_) => // We are the introduction node: we add a delay to make it look like it could come from further downstream. val delay = Some(Random.nextLong(1000).millis) @@ -376,7 +376,7 @@ class ChannelRelay private(nodeParams: NodeParams, RelayFailure(CMD_FAIL_HTLC(r.add.id, Right(ChannelDisabled(update.messageFlags, update.channelFlags, Some(update))), commit = true)) case None => val origin = Origin.Hot(addResponseAdapter.toClassic, upstream) - RelaySuccess(outgoingChannel.channelId, CMD_ADD_HTLC(addResponseAdapter.toClassic, r.amountToForward, r.add.paymentHash, r.outgoingCltv, r.nextPacket, nextBlindingKey_opt, confidence, fundingFee_opt = None, origin, commit = true)) + RelaySuccess(outgoingChannel.channelId, CMD_ADD_HTLC(addResponseAdapter.toClassic, r.amountToForward, r.add.paymentHash, r.outgoingCltv, r.nextPacket, nextPathKey_opt, confidence, fundingFee_opt = None, origin, commit = true)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala index 8d495b895f..43db330efb 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala @@ -411,7 +411,7 @@ class NodeRelay private(nodeParams: NodeParams, case Right(nextPacket) => val forwardNodeIdFailureAdapter = context.messageAdapter[Register.ForwardNodeIdFailure[Peer.ProposeOnTheFlyFunding]](_ => WrappedOnTheFlyFundingResponse(Peer.ProposeOnTheFlyFundingResponse.NotAvailable("peer not found"))) val onTheFlyFundingResponseAdapter = context.messageAdapter[Peer.ProposeOnTheFlyFundingResponse](WrappedOnTheFlyFundingResponse) - val cmd = Peer.ProposeOnTheFlyFunding(onTheFlyFundingResponseAdapter, nextPayload.amountToForward, paymentHash, nextPayload.outgoingCltv, nextPacket.cmd.onion, nextPacket.cmd.nextBlindingKey_opt, upstream) + val cmd = Peer.ProposeOnTheFlyFunding(onTheFlyFundingResponseAdapter, nextPayload.amountToForward, paymentHash, nextPayload.outgoingCltv, nextPacket.cmd.onion, nextPacket.cmd.nextPathKey_opt, upstream) register ! Register.ForwardNodeId(forwardNodeIdFailureAdapter, walletNodeId, cmd) Behaviors.receiveMessagePartial { rejectExtraHtlcPartialFunction orElse { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala index b12316ea62..69f99b7061 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/OnTheFlyFunding.scala @@ -96,7 +96,7 @@ object OnTheFlyFunding { def createFailureCommands(failure_opt: Option[Either[ByteVector, FailureMessage]]): Seq[(ByteVector32, CMD_FAIL_HTLC)] = upstream match { case _: Upstream.Local => Nil case u: Upstream.Hot.Channel => - val failure = htlc.blinding_opt match { + val failure = htlc.pathKey_opt match { case Some(_) => Right(InvalidOnionBlinding(Sphinx.hash(u.add.onionRoutingPacket))) case None => failure_opt.getOrElse(Right(UnknownNextPeer())) } @@ -292,7 +292,7 @@ object OnTheFlyFunding { // This lets us detect that this HTLC is an on-the-fly funded HTLC. val htlcFees = LiquidityAds.FundingFee(remainingFees.min(p.maxFees(htlcMinimum)), cmd.status.txId) val origin = Origin.Hot(htlcSettledAdapter.toClassic, p.upstream) - val add = CMD_ADD_HTLC(cmdAdapter.toClassic, p.htlc.amount - htlcFees.amount, paymentHash, p.htlc.expiry, p.htlc.finalPacket, p.htlc.blinding_opt, 1.0, Some(htlcFees), origin, commit = true) + val add = CMD_ADD_HTLC(cmdAdapter.toClassic, p.htlc.amount - htlcFees.amount, paymentHash, p.htlc.expiry, p.htlc.finalPacket, p.htlc.pathKey_opt, 1.0, Some(htlcFees), origin, commit = true) cmd.channel ! add remainingFees - htlcFees.amount } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala index ee1c3c52cb..9f5f7c639d 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/PostRestartHtlcCleaner.scala @@ -129,9 +129,9 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial Metrics.Resolved.withTag(Tags.Success, value = false).withTag(Metrics.Relayed, value = false).increment() if (e.currentState != CLOSING && e.currentState != CLOSED) { log.info(s"failing not relayed htlc=$htlc") - val cmd = htlc.blinding_opt match { + val cmd = htlc.pathKey_opt match { case Some(_) => - // The incoming HTLC contains a blinding point: we must be an intermediate node in a blinded path, + // The incoming HTLC contains a path key: we must be an intermediate node in a blinded path, // and we thus need to return an update_fail_malformed_htlc. val failure = InvalidOnionBlinding(ByteVector32.Zeroes) CMD_FAIL_MALFORMED_HTLC(htlc.id, failure.onionHash, failure.code, commit = true) @@ -261,7 +261,7 @@ class PostRestartHtlcCleaner(nodeParams: NodeParams, register: ActorRef, initial case Upstream.Cold.Channel(originChannelId, originHtlcId, _) => log.warning(s"payment failed for paymentHash=${failedHtlc.paymentHash}: failing 1 HTLC upstream") Metrics.Resolved.withTag(Tags.Success, value = false).withTag(Metrics.Relayed, value = true).increment() - val cmd = failedHtlc.blinding_opt match { + val cmd = failedHtlc.pathKey_opt match { case Some(_) => // If we are inside a blinded path, we cannot know whether we're the introduction node or not since // we don't have access to the incoming onion: to avoid leaking information, we act as if we were an diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala index 1600e185cd..c70c12751e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/Relayer.scala @@ -80,7 +80,7 @@ class Relayer(nodeParams: NodeParams, router: ActorRef, register: ActorRef, paym case Left(badOnion: BadOnion) => log.warning(s"couldn't parse onion: reason=${badOnion.message}") val cmdFail = badOnion match { - case _: InvalidOnionBlinding if add.blinding_opt.isEmpty => + case _: InvalidOnionBlinding if add.pathKey_opt.isEmpty => // We are the introduction point of a blinded path: we add a non-negligible delay to make it look like it // could come from a downstream node. val delay = Some(500.millis + Random.nextLong(1500).millis) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/BlindedPathsResolver.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/BlindedPathsResolver.scala index 12ee84f463..6b1e8e361c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/BlindedPathsResolver.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/BlindedPathsResolver.scala @@ -8,7 +8,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.channel.Helpers.getRelayFees import fr.acinq.eclair.channel.Register import fr.acinq.eclair.crypto.Sphinx.RouteBlinding -import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.BlindedNode +import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.BlindedHop import fr.acinq.eclair.payment.PaymentBlindedRoute import fr.acinq.eclair.router.Router import fr.acinq.eclair.wire.protocol.OfferTypes.PaymentInfo @@ -36,16 +36,15 @@ object BlindedPathsResolver { sealed trait ResolvedBlindedRoute { /** The resolved (non-blinded) node_id of the first node in the route. */ def firstNodeId: PublicKey - def blindedNodes: Seq[BlindedNode] - def blindedNodeIds: Seq[PublicKey] = blindedNodes.map(_.blindedPublicKey) - def encryptedPayloads: Seq[ByteVector] = blindedNodes.map(_.encryptedPayload) + def blindedHops: Seq[BlindedHop] + def blindedNodeIds: Seq[PublicKey] = blindedHops.map(_.blindedPublicKey) + def encryptedPayloads: Seq[ByteVector] = blindedHops.map(_.encryptedPayload) } /** A blinded route that starts at a remote node that we were able to identify. */ - case class FullBlindedRoute(introductionNodeId: PublicKey, firstBlinding: PublicKey, blindedNodes: Seq[BlindedNode]) extends ResolvedBlindedRoute { - override val firstNodeId: PublicKey = introductionNodeId + case class FullBlindedRoute(firstNodeId: PublicKey, firstpathKey: PublicKey, blindedHops: Seq[BlindedHop]) extends ResolvedBlindedRoute { } /** A partially unwrapped blinded route that started at our node: it only contains the part of the route after our node. */ - case class PartialBlindedRoute(nextNodeId: EncodedNodeId.WithPublicKey, nextBlinding: PublicKey, blindedNodes: Seq[BlindedNode]) extends ResolvedBlindedRoute { + case class PartialBlindedRoute(nextNodeId: EncodedNodeId.WithPublicKey, nextPathKey: PublicKey, blindedHops: Seq[BlindedHop]) extends ResolvedBlindedRoute { override val firstNodeId: PublicKey = nextNodeId.publicKey } // @formatter:on @@ -80,19 +79,19 @@ private class BlindedPathsResolver(nodeParams: NodeParams, @tailrec private def resolveBlindedPaths(toResolve: Seq[PaymentBlindedRoute], resolved: Seq[ResolvedPath]): Behavior[Command] = { toResolve.headOption match { - case Some(paymentRoute) => paymentRoute.route.introductionNodeId match { + case Some(paymentRoute) => paymentRoute.route.firstNodeId match { case EncodedNodeId.WithPublicKey.Plain(ourNodeId) if ourNodeId == nodeParams.nodeId && paymentRoute.route.length == 0 => context.log.warn("ignoring blinded path (empty route with ourselves as the introduction node)") resolveBlindedPaths(toResolve.tail, resolved) case EncodedNodeId.WithPublicKey.Plain(ourNodeId) if ourNodeId == nodeParams.nodeId => // We are the introduction node of the blinded route: we need to decrypt the first payload. - val firstBlinding = paymentRoute.route.introductionNode.blindingEphemeralKey - val firstEncryptedPayload = paymentRoute.route.introductionNode.encryptedPayload - RouteBlindingEncryptedDataCodecs.decode(nodeParams.privateKey, firstBlinding, firstEncryptedPayload) match { + val firstPathKey = paymentRoute.route.firstNode.pathKey + val firstEncryptedPayload = paymentRoute.route.firstNode.encryptedPayload + RouteBlindingEncryptedDataCodecs.decode(nodeParams.privateKey, firstPathKey, firstEncryptedPayload) match { case Left(f) => context.log.warn("ignoring blinded path starting at our node that we cannot decrypt: {}", f.message) resolveBlindedPaths(toResolve.tail, resolved) - case Right(RouteBlindingDecryptedData(decrypted, nextBlinding)) => + case Right(RouteBlindingDecryptedData(decrypted, nextPathKey)) => BlindedRouteData.validatePaymentRelayData(decrypted) match { case Left(f) => context.log.warn("ignoring blinded path starting at our node with invalid payment relay: {}", f.failureMessage.message) @@ -114,15 +113,15 @@ private class BlindedPathsResolver(nodeParams: NodeParams, paymentRelayData.outgoing match { case Left(outgoingNodeId) => // The next node seems to be a wallet node directly connected to us. - validateRelay(EncodedNodeId.WithPublicKey.Wallet(outgoingNodeId), nextPaymentInfo, paymentRelayData, nextBlinding, paymentRoute.route.subsequentNodes, toResolve.tail, resolved) + validateRelay(EncodedNodeId.WithPublicKey.Wallet(outgoingNodeId), nextPaymentInfo, paymentRelayData, nextPathKey, paymentRoute.route.subsequentNodes, toResolve.tail, resolved) case Right(outgoingChannelId) => register ! Register.GetNextNodeId(context.messageAdapter(WrappedNodeId), outgoingChannelId) - waitForNextNodeId(outgoingChannelId, nextPaymentInfo, paymentRelayData, nextBlinding, paymentRoute.route.subsequentNodes, toResolve.tail, resolved) + waitForNextNodeId(outgoingChannelId, nextPaymentInfo, paymentRelayData, nextPathKey, paymentRoute.route.subsequentNodes, toResolve.tail, resolved) } } } case encodedNodeId: EncodedNodeId.WithPublicKey => - val path = ResolvedPath(FullBlindedRoute(encodedNodeId.publicKey, paymentRoute.route.blindingKey, paymentRoute.route.blindedNodes), paymentRoute.paymentInfo) + val path = ResolvedPath(FullBlindedRoute(encodedNodeId.publicKey, paymentRoute.route.firstPathKey, paymentRoute.route.blindedHops), paymentRoute.paymentInfo) resolveBlindedPaths(toResolve.tail, resolved :+ path) case EncodedNodeId.ShortChannelIdDir(isNode1, scid) => router ! Router.GetNodeId(context.messageAdapter(WrappedNodeId), scid, isNode1) @@ -138,8 +137,8 @@ private class BlindedPathsResolver(nodeParams: NodeParams, private def waitForNextNodeId(outgoingChannelId: ShortChannelId, nextPaymentInfo: OfferTypes.PaymentInfo, paymentRelayData: BlindedRouteData.PaymentRelayData, - nextBlinding: PublicKey, - nextBlindedNodes: Seq[RouteBlinding.BlindedNode], + nextPathKey: PublicKey, + nextBlindedNodes: Seq[RouteBlinding.BlindedHop], toResolve: Seq[PaymentBlindedRoute], resolved: Seq[ResolvedPath]): Behavior[Command] = Behaviors.receiveMessagePartial { @@ -151,14 +150,14 @@ private class BlindedPathsResolver(nodeParams: NodeParams, context.log.warn("ignoring blinded path starting at our node relaying to ourselves") resolveBlindedPaths(toResolve, resolved) case WrappedNodeId(Some(nodeId)) => - validateRelay(EncodedNodeId.WithPublicKey.Plain(nodeId), nextPaymentInfo, paymentRelayData, nextBlinding, nextBlindedNodes, toResolve, resolved) + validateRelay(EncodedNodeId.WithPublicKey.Plain(nodeId), nextPaymentInfo, paymentRelayData, nextPathKey, nextBlindedNodes, toResolve, resolved) } private def validateRelay(nextNodeId: EncodedNodeId.WithPublicKey, nextPaymentInfo: OfferTypes.PaymentInfo, paymentRelayData: BlindedRouteData.PaymentRelayData, - nextBlinding: PublicKey, - nextBlindedNodes: Seq[RouteBlinding.BlindedNode], + nextPathKey: PublicKey, + nextBlindedNodes: Seq[RouteBlinding.BlindedHop], toResolve: Seq[PaymentBlindedRoute], resolved: Seq[ResolvedPath]): Behavior[Command] = { // Note that we default to private fees if we don't have a channel yet with that node. @@ -172,7 +171,7 @@ private class BlindedPathsResolver(nodeParams: NodeParams, nextPaymentInfo.cltvExpiryDelta.toInt >= 0 if (shouldRelay) { context.log.debug("unwrapped blinded path starting at our node: next_node={}", nextNodeId.publicKey) - val path = ResolvedPath(PartialBlindedRoute(nextNodeId, nextBlinding, nextBlindedNodes), nextPaymentInfo) + val path = ResolvedPath(PartialBlindedRoute(nextNodeId, nextPathKey, nextBlindedNodes), nextPaymentInfo) resolveBlindedPaths(toResolve, resolved :+ path) } else { context.log.warn("ignoring blinded path starting at our node: allocated fees are too low (base={}, proportional={}, expiryDelta={})", paymentRelayData.paymentRelay.feeBase, paymentRelayData.paymentRelay.feeProportionalMillionths, paymentRelayData.paymentRelay.cltvExpiryDelta) @@ -184,12 +183,12 @@ private class BlindedPathsResolver(nodeParams: NodeParams, private def waitForNodeId(paymentRoute: PaymentBlindedRoute, toResolve: Seq[PaymentBlindedRoute], resolved: Seq[ResolvedPath]): Behavior[Command] = Behaviors.receiveMessagePartial { case WrappedNodeId(None) => - context.log.warn("ignoring blinded path with unknown scid_dir={}", paymentRoute.route.introductionNodeId) + context.log.warn("ignoring blinded path with unknown scid_dir={}", paymentRoute.route.firstNodeId) resolveBlindedPaths(toResolve, resolved) case WrappedNodeId(Some(nodeId)) => - context.log.debug("successfully resolved scid_dir={} to node_id={}", paymentRoute.route.introductionNodeId, nodeId) + context.log.debug("successfully resolved scid_dir={} to node_id={}", paymentRoute.route.firstNodeId, nodeId) // We've identified the node matching this scid_dir, we retry resolving with that node_id. - val paymentRouteWithNodeId = paymentRoute.copy(route = paymentRoute.route.copy(introductionNodeId = EncodedNodeId.WithPublicKey.Plain(nodeId))) + val paymentRouteWithNodeId = paymentRoute.copy(route = paymentRoute.route.copy(firstNodeId = EncodedNodeId.WithPublicKey.Plain(nodeId))) resolveBlindedPaths(paymentRouteWithNodeId +: toResolve, resolved) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Recipient.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Recipient.scala index e377a4bc3d..8ba7dba68e 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Recipient.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/Recipient.scala @@ -142,24 +142,24 @@ case class BlindedRecipient(nodeId: PublicKey, val introductionExpiry = expiry + blindedHop.cltvExpiryDelta blindedHop.resolved.route match { case route: BlindedPathsResolver.FullBlindedRoute => - val payloads = if (route.blindedNodes.length == 1) { + val payloads = if (route.blindedHops.length == 1) { // The recipient is also the introduction node. - val payload = NodePayload(blindedHop.nodeId, OutgoingBlindedPerHopPayload.createFinalIntroductionPayload(amount, totalAmount, expiry, route.firstBlinding, route.encryptedPayloads.head, customTlvs)) + val payload = NodePayload(blindedHop.nodeId, OutgoingBlindedPerHopPayload.createFinalIntroductionPayload(amount, totalAmount, expiry, route.firstpathKey, route.encryptedPayloads.head, customTlvs)) Seq(payload) } else { - val introductionPayload = NodePayload(blindedHop.nodeId, OutgoingBlindedPerHopPayload.createIntroductionPayload(route.encryptedPayloads.head, route.firstBlinding)) - val intermediatePayloads = route.blindedNodes.tail.dropRight(1).map(n => NodePayload(n.blindedPublicKey, OutgoingBlindedPerHopPayload.createIntermediatePayload(n.encryptedPayload))) + val introductionPayload = NodePayload(blindedHop.nodeId, OutgoingBlindedPerHopPayload.createIntroductionPayload(route.encryptedPayloads.head, route.firstpathKey)) + val intermediatePayloads = route.blindedHops.tail.dropRight(1).map(n => NodePayload(n.blindedPublicKey, OutgoingBlindedPerHopPayload.createIntermediatePayload(n.encryptedPayload))) val finalPayload = NodePayload(route.blindedNodeIds.last, OutgoingBlindedPerHopPayload.createFinalPayload(amount, totalAmount, expiry, route.encryptedPayloads.last, customTlvs)) introductionPayload +: intermediatePayloads :+ finalPayload } - // The route starts at a remote introduction node: we include the blinding point in the onion, not in the HTLC. - PaymentPayloads(introductionAmount, introductionExpiry, payloads, outerBlinding_opt = None) + // The route starts at a remote introduction node: we include the path key in the onion, not in the HTLC. + PaymentPayloads(introductionAmount, introductionExpiry, payloads, outerPathKey_opt = None) case route: BlindedPathsResolver.PartialBlindedRoute => // The route started at our node: we already peeled the introduction part. - val intermediatePayloads = route.blindedNodes.dropRight(1).map(n => NodePayload(n.blindedPublicKey, OutgoingBlindedPerHopPayload.createIntermediatePayload(n.encryptedPayload))) + val intermediatePayloads = route.blindedHops.dropRight(1).map(n => NodePayload(n.blindedPublicKey, OutgoingBlindedPerHopPayload.createIntermediatePayload(n.encryptedPayload))) val finalPayload = NodePayload(route.blindedNodeIds.last, OutgoingBlindedPerHopPayload.createFinalPayload(amount, totalAmount, expiry, route.encryptedPayloads.last, customTlvs)) - // The next node is not the introduction node, so we must provide the blinding point in the HTLC. - PaymentPayloads(introductionAmount, introductionExpiry, intermediatePayloads :+ finalPayload, outerBlinding_opt = Some(route.nextBlinding)) + // The next node is not the introduction node, so we must provide the path key in the HTLC. + PaymentPayloads(introductionAmount, introductionExpiry, intermediatePayloads :+ finalPayload, outerPathKey_opt = Some(route.nextPathKey)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala index c511d586f4..7e608d36e5 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala @@ -31,10 +31,10 @@ import scodec.codecs._ sealed trait UpdateAddHtlcTlv extends Tlv object UpdateAddHtlcTlv { - /** Blinding ephemeral public key that should be used to derive shared secrets when using route blinding. */ - case class BlindingPoint(publicKey: PublicKey) extends UpdateAddHtlcTlv + /** Path key that should be used to derive shared secrets when using route blinding. */ + case class PathKey(publicKey: PublicKey) extends UpdateAddHtlcTlv - private val blindingPoint: Codec[BlindingPoint] = (("length" | constant(hex"21")) :: ("blinding" | publicKey)).as[BlindingPoint] + private val pathKey: Codec[PathKey] = (("length" | constant(hex"21")) :: ("pathKey" | publicKey)).as[PathKey] case class Endorsement(level: Int) extends UpdateAddHtlcTlv @@ -46,7 +46,7 @@ object UpdateAddHtlcTlv { private val fundingFee: Codec[FundingFeeTlv] = tlvField((("amount" | millisatoshi) :: ("txId" | txIdAsHash)).as[LiquidityAds.FundingFee]) val addHtlcTlvCodec: Codec[TlvStream[UpdateAddHtlcTlv]] = tlvStream(discriminated[UpdateAddHtlcTlv].by(varint) - .typecase(UInt64(0), blindingPoint) + .typecase(UInt64(0), pathKey) .typecase(UInt64(41041), fundingFee) .typecase(UInt64(106823), endorsement) ) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala index b422d8598c..9e6128d0a6 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecs.scala @@ -385,7 +385,7 @@ object LightningMessageCodecs { ("tlvStream" | GossipTimestampFilterTlv.gossipTimestampFilterTlvCodec)).as[GossipTimestampFilter] val onionMessageCodec: Codec[OnionMessage] = ( - ("blindingKey" | publicKey) :: + ("pathKey" | publicKey) :: ("onionPacket" | MessageOnionCodecs.messageOnionPacketCodec) :: ("tlvStream" | OnionMessageTlv.onionMessageTlvCodec)).as[OnionMessage] diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index ace095ca1a..40cc0633c1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -369,7 +369,7 @@ case class UpdateAddHtlc(channelId: ByteVector32, cltvExpiry: CltvExpiry, onionRoutingPacket: OnionRoutingPacket, tlvStream: TlvStream[UpdateAddHtlcTlv]) extends HtlcMessage with UpdateMessage with HasChannelId { - val blinding_opt: Option[PublicKey] = tlvStream.get[UpdateAddHtlcTlv.BlindingPoint].map(_.publicKey) + val pathKey_opt: Option[PublicKey] = tlvStream.get[UpdateAddHtlcTlv.PathKey].map(_.publicKey) val fundingFee_opt: Option[LiquidityAds.FundingFee] = tlvStream.get[UpdateAddHtlcTlv.FundingFeeTlv].map(_.fee) val endorsement: Int = tlvStream.get[UpdateAddHtlcTlv.Endorsement].map(_.level).getOrElse(0) @@ -385,11 +385,11 @@ object UpdateAddHtlc { paymentHash: ByteVector32, cltvExpiry: CltvExpiry, onionRoutingPacket: OnionRoutingPacket, - blinding_opt: Option[PublicKey], + pathKey_opt: Option[PublicKey], confidence: Double, fundingFee_opt: Option[LiquidityAds.FundingFee]): UpdateAddHtlc = { val tlvs = Set( - blinding_opt.map(UpdateAddHtlcTlv.BlindingPoint), + pathKey_opt.map(UpdateAddHtlcTlv.PathKey), fundingFee_opt.map(UpdateAddHtlcTlv.FundingFeeTlv), Some(UpdateAddHtlcTlv.Endorsement((confidence * 7.999).toInt)), ).flatten[UpdateAddHtlcTlv] @@ -600,7 +600,7 @@ object ReplyChannelRange { case class GossipTimestampFilter(chainHash: BlockHash, firstTimestamp: TimestampSecond, timestampRange: Long, tlvStream: TlvStream[GossipTimestampFilterTlv] = TlvStream.empty) extends RoutingMessage with HasChainHash -case class OnionMessage(blindingKey: PublicKey, onionRoutingPacket: OnionRoutingPacket, tlvStream: TlvStream[OnionMessageTlv] = TlvStream.empty) extends LightningMessage +case class OnionMessage(pathKey: PublicKey, onionRoutingPacket: OnionRoutingPacket, tlvStream: TlvStream[OnionMessageTlv] = TlvStream.empty) extends LightningMessage // NB: blank lines to minimize merge conflicts @@ -644,7 +644,7 @@ case class WillAddHtlc(chainHash: BlockHash, expiry: CltvExpiry, finalPacket: OnionRoutingPacket, tlvStream: TlvStream[WillAddHtlcTlv] = TlvStream.empty) extends OnTheFlyFundingMessage { - val blinding_opt: Option[PublicKey] = tlvStream.get[WillAddHtlcTlv.BlindingPoint].map(_.publicKey) + val pathKey_opt: Option[PublicKey] = tlvStream.get[WillAddHtlcTlv.PathKey].map(_.publicKey) } object WillAddHtlc { @@ -654,8 +654,8 @@ object WillAddHtlc { paymentHash: ByteVector32, expiry: CltvExpiry, finalPacket: OnionRoutingPacket, - blinding_opt: Option[PublicKey]): WillAddHtlc = { - val tlvs = blinding_opt.map(WillAddHtlcTlv.BlindingPoint).toSet[WillAddHtlcTlv] + pathKey_opt: Option[PublicKey]): WillAddHtlc = { + val tlvs = pathKey_opt.map(WillAddHtlcTlv.PathKey).toSet[WillAddHtlcTlv] WillAddHtlc(chainHash, id, amount, paymentHash, expiry, finalPacket, TlvStream(tlvs)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/MessageOnion.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/MessageOnion.scala index 368f3a8f53..da6c928573 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/MessageOnion.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/MessageOnion.scala @@ -72,14 +72,14 @@ object MessageOnion { } /** Per-hop payload for an intermediate node. */ - case class IntermediatePayload(records: TlvStream[OnionMessagePayloadTlv], blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv], nextBlinding: PublicKey) extends PerHopPayload { + case class IntermediatePayload(records: TlvStream[OnionMessagePayloadTlv], blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv], nextPathKey: PublicKey) extends PerHopPayload { val nextNode: Either[ShortChannelId, EncodedNodeId] = blindedRecords.get[RouteBlindingEncryptedDataTlv.OutgoingNodeId].map(outgoingNodeId => Right(outgoingNodeId.nodeId)) .getOrElse(Left(blindedRecords.get[RouteBlindingEncryptedDataTlv.OutgoingChannelId].get.shortChannelId)) } object IntermediatePayload { - def validate(records: TlvStream[OnionMessagePayloadTlv], blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv], nextBlinding: PublicKey): Either[InvalidTlvPayload, IntermediatePayload] = { + def validate(records: TlvStream[OnionMessagePayloadTlv], blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv], nextPathKey: PublicKey): Either[InvalidTlvPayload, IntermediatePayload] = { // Only EncryptedData is allowed (and required), unknown TLVs are forbidden too as they could allow probing the identity of the nodes. if (records.get[ReplyPath].nonEmpty) return Left(ForbiddenTlv(UInt64(2))) if (records.get[EncryptedData].isEmpty) return Left(MissingRequiredTlv(UInt64(4))) @@ -87,7 +87,7 @@ object MessageOnion { if (records.get[Invoice].nonEmpty) return Left(ForbiddenTlv(UInt64(66))) if (records.get[InvoiceError].nonEmpty) return Left(ForbiddenTlv(UInt64(68))) if (records.unknown.nonEmpty) return Left(ForbiddenTlv(records.unknown.head.tag)) - BlindedRouteData.validateMessageRelayData(blindedRecords).map(blindedRecords => IntermediatePayload(records, blindedRecords, nextBlinding)) + BlindedRouteData.validateMessageRelayData(blindedRecords).map(blindedRecords => IntermediatePayload(records, blindedRecords, nextPathKey)) } } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferCodecs.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferCodecs.scala index e970dcb30a..5a14c0bf03 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferCodecs.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OfferCodecs.scala @@ -18,7 +18,7 @@ package fr.acinq.eclair.wire.protocol import fr.acinq.bitcoin.scalacompat.BlockHash import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey -import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedNode, BlindedRoute} +import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedHop, BlindedRoute} import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.OfferTypes._ import fr.acinq.eclair.wire.protocol.TlvCodecs.{tlvField, tmillisatoshi, tu32, tu64overflow} @@ -57,15 +57,15 @@ object OfferCodecs { .subcaseP(0x04) { case e: EncodedNodeId.WithPublicKey.Wallet if e.publicKey.value.head == 0x02 => e }(tweakFirstByteCodec(2).xmap[EncodedNodeId.WithPublicKey.Wallet](EncodedNodeId.WithPublicKey.Wallet, _.publicKey)) .subcaseP(0x05) { case e: EncodedNodeId.WithPublicKey.Wallet if e.publicKey.value.head == 0x03 => e }(tweakFirstByteCodec(3).xmap[EncodedNodeId.WithPublicKey.Wallet](EncodedNodeId.WithPublicKey.Wallet, _.publicKey)) - private val blindedNodeCodec: Codec[BlindedNode] = + private val blindedNodeCodec: Codec[BlindedHop] = (("nodeId" | publicKey) :: - ("encryptedData" | variableSizeBytes(uint16, bytes))).as[BlindedNode] + ("encryptedData" | variableSizeBytes(uint16, bytes))).as[BlindedHop] - private val blindedNodesCodec: Codec[Seq[BlindedNode]] = listOfN(uint8, blindedNodeCodec).xmap(_.toSeq, _.toList) + private val blindedNodesCodec: Codec[Seq[BlindedHop]] = listOfN(uint8, blindedNodeCodec).xmap(_.toSeq, _.toList) val blindedRouteCodec: Codec[BlindedRoute] = (("firstNodeId" | encodedNodeIdCodec) :: - ("blinding" | publicKey) :: + ("firstPathKey" | publicKey) :: ("path" | blindedNodesCodec)).as[BlindedRoute] private val offerPaths: Codec[OfferPaths] = tlvField(list(blindedRouteCodec).xmap[Seq[BlindedRoute]](_.toSeq, _.toList)) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OnTheFlyFundingTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OnTheFlyFundingTlv.scala index cc28e72f2a..b01f2cca2b 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OnTheFlyFundingTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/OnTheFlyFundingTlv.scala @@ -31,12 +31,12 @@ import scodec.codecs._ sealed trait WillAddHtlcTlv extends Tlv object WillAddHtlcTlv { - /** Blinding ephemeral public key that should be used to derive shared secrets when using route blinding. */ - case class BlindingPoint(publicKey: PublicKey) extends WillAddHtlcTlv + /** Path key that should be used to derive shared secrets when using route blinding. */ + case class PathKey(publicKey: PublicKey) extends WillAddHtlcTlv - private val blindingPoint: Codec[BlindingPoint] = (("length" | constant(hex"21")) :: ("blinding" | publicKey)).as[BlindingPoint] + private val pathKey: Codec[PathKey] = (("length" | constant(hex"21")) :: ("pathKey" | publicKey)).as[PathKey] val willAddHtlcTlvCodec: Codec[TlvStream[WillAddHtlcTlv]] = tlvStream(discriminated[WillAddHtlcTlv].by(varint) - .typecase(UInt64(0), blindingPoint) + .typecase(UInt64(0), pathKey) ) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala index 66d8cd2093..1b2925b349 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/PaymentOnion.scala @@ -153,8 +153,8 @@ object OnionPaymentPayloadTlv { */ case class EncryptedRecipientData(data: ByteVector) extends OnionPaymentPayloadTlv - /** Blinding ephemeral public key for the introduction node of a blinded route. */ - case class BlindingPoint(publicKey: PublicKey) extends OnionPaymentPayloadTlv + /** Path key for the introduction node of a blinded route. */ + case class PathKey(publicKey: PublicKey) extends OnionPaymentPayloadTlv /** Total amount in blinded multi-part payments. */ case class TotalAmount(totalAmount: MilliSatoshi) extends OnionPaymentPayloadTlv @@ -260,9 +260,9 @@ object PaymentOnion { /** * @param paymentRelayData decrypted relaying data from the encrypted_recipient_data tlv. - * @param nextBlinding blinding point that must be forwarded to the next hop. + * @param nextPathKey path key that must be forwarded to the next hop. */ - case class Blinded(records: TlvStream[OnionPaymentPayloadTlv], paymentRelayData: PaymentRelayData, nextBlinding: PublicKey) extends ChannelRelay { + case class Blinded(records: TlvStream[OnionPaymentPayloadTlv], paymentRelayData: PaymentRelayData, nextPathKey: PublicKey) extends ChannelRelay { // @formatter:off override val outgoing = paymentRelayData.outgoing override def amountToForward(incomingAmount: MilliSatoshi): MilliSatoshi = paymentRelayData.amountToForward(incomingAmount) @@ -271,19 +271,19 @@ object PaymentOnion { } object Blinded { - def validate(records: TlvStream[OnionPaymentPayloadTlv], blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv], nextBlinding: PublicKey): Either[InvalidTlvPayload, Blinded] = { + def validate(records: TlvStream[OnionPaymentPayloadTlv], blindedRecords: TlvStream[RouteBlindingEncryptedDataTlv], nextPathKey: PublicKey): Either[InvalidTlvPayload, Blinded] = { if (records.get[EncryptedRecipientData].isEmpty) return Left(MissingRequiredTlv(UInt64(10))) - // Bolt 4: MUST return an error if the payload contains other tlv fields than `encrypted_recipient_data` and `current_blinding_point`. + // Bolt 4: MUST return an error if the payload contains other tlv fields than `encrypted_recipient_data` and `current_path_key`. if (records.unknown.nonEmpty) return Left(ForbiddenTlv(records.unknown.head.tag)) records.records.find { case _: EncryptedRecipientData => false - case _: BlindingPoint => false + case _: PathKey => false case _ => true } match { case Some(_) => return Left(ForbiddenTlv(UInt64(0))) case None => // no forbidden tlv found } - BlindedRouteData.validatePaymentRelayData(blindedRecords).map(paymentRelayData => Blinded(records, paymentRelayData, nextBlinding)) + BlindedRouteData.validatePaymentRelayData(blindedRecords).map(paymentRelayData => Blinded(records, paymentRelayData, nextPathKey)) } } } @@ -309,7 +309,7 @@ object PaymentOnion { if (records.get[OutgoingCltv].isEmpty) return Left(MissingRequiredTlv(UInt64(4))) if (records.get[OutgoingNodeId].isEmpty) return Left(MissingRequiredTlv(UInt64(66098))) if (records.get[EncryptedRecipientData].nonEmpty) return Left(ForbiddenTlv(UInt64(10))) - if (records.get[BlindingPoint].nonEmpty) return Left(ForbiddenTlv(UInt64(12))) + if (records.get[PathKey].nonEmpty) return Left(ForbiddenTlv(UInt64(12))) Right(Standard(records)) } @@ -353,7 +353,7 @@ object PaymentOnion { if (records.get[OutgoingNodeId].isEmpty) return Left(MissingRequiredTlv(UInt64(66098))) if (records.get[InvoiceRoutingInfo].isEmpty) return Left(MissingRequiredTlv(UInt64(66099))) if (records.get[EncryptedRecipientData].nonEmpty) return Left(ForbiddenTlv(UInt64(10))) - if (records.get[BlindingPoint].nonEmpty) return Left(ForbiddenTlv(UInt64(12))) + if (records.get[PathKey].nonEmpty) return Left(ForbiddenTlv(UInt64(12))) Right(ToNonTrampoline(records)) } } @@ -381,7 +381,7 @@ object PaymentOnion { if (records.get[OutgoingBlindedPaths].isEmpty) return Left(MissingRequiredTlv(UInt64(66102))) if (records.get[InvoiceFeatures].isEmpty) return Left(MissingRequiredTlv(UInt64(66097))) if (records.get[EncryptedRecipientData].nonEmpty) return Left(ForbiddenTlv(UInt64(10))) - if (records.get[BlindingPoint].nonEmpty) return Left(ForbiddenTlv(UInt64(12))) + if (records.get[PathKey].nonEmpty) return Left(ForbiddenTlv(UInt64(12))) Right(ToBlindedPaths(records)) } } @@ -451,7 +451,7 @@ object PaymentOnion { override val amount = records.get[AmountToForward].get.amount override val totalAmount = records.get[TotalAmount].get.totalAmount override val expiry = records.get[OutgoingCltv].get.cltv - val blinding_opt: Option[PublicKey] = records.get[BlindingPoint].map(_.publicKey) + val pathKey_opt: Option[PublicKey] = records.get[PathKey].map(_.publicKey) val pathId = blindedRecords.get[RouteBlindingEncryptedDataTlv.PathId].get.data val paymentConstraints = blindedRecords.get[RouteBlindingEncryptedDataTlv.PaymentConstraints].get val allowedFeatures = blindedRecords.get[RouteBlindingEncryptedDataTlv.AllowedFeatures].map(_.features).getOrElse(Features.empty) @@ -463,13 +463,13 @@ object PaymentOnion { if (records.get[OutgoingCltv].isEmpty) return Left(MissingRequiredTlv(UInt64(4))) if (records.get[EncryptedRecipientData].isEmpty) return Left(MissingRequiredTlv(UInt64(10))) if (records.get[TotalAmount].isEmpty) return Left(MissingRequiredTlv(UInt64(18))) - // Bolt 4: MUST return an error if the payload contains other tlv fields than `encrypted_recipient_data`, `current_blinding_point`, `amt_to_forward`, `outgoing_cltv_value` and `total_amount_msat`. + // Bolt 4: MUST return an error if the payload contains other tlv fields than `encrypted_recipient_data`, `current_path_key`, `amt_to_forward`, `outgoing_cltv_value` and `total_amount_msat`. if (records.unknown.nonEmpty) return Left(ForbiddenTlv(records.unknown.head.tag)) records.records.find { case _: AmountToForward => false case _: OutgoingCltv => false case _: EncryptedRecipientData => false - case _: BlindingPoint => false + case _: PathKey => false case _: TotalAmount => false case _ => true } match { @@ -491,8 +491,8 @@ object PaymentOnion { } object OutgoingBlindedPerHopPayload { - def createIntroductionPayload(encryptedRecipientData: ByteVector, blinding: PublicKey): OutgoingBlindedPerHopPayload = { - OutgoingBlindedPerHopPayload(TlvStream(EncryptedRecipientData(encryptedRecipientData), BlindingPoint(blinding))) + def createIntroductionPayload(encryptedRecipientData: ByteVector, pathKey: PublicKey): OutgoingBlindedPerHopPayload = { + OutgoingBlindedPerHopPayload(TlvStream(EncryptedRecipientData(encryptedRecipientData), PathKey(pathKey))) } def createIntermediatePayload(encryptedRecipientData: ByteVector): OutgoingBlindedPerHopPayload = { @@ -503,8 +503,8 @@ object PaymentOnion { OutgoingBlindedPerHopPayload(TlvStream(Set[OnionPaymentPayloadTlv](AmountToForward(amount), TotalAmount(totalAmount), OutgoingCltv(expiry), EncryptedRecipientData(encryptedRecipientData)), customTlvs)) } - def createFinalIntroductionPayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, blinding: PublicKey, encryptedRecipientData: ByteVector, customTlvs: Set[GenericTlv] = Set.empty): OutgoingBlindedPerHopPayload = { - OutgoingBlindedPerHopPayload(TlvStream(Set[OnionPaymentPayloadTlv](AmountToForward(amount), TotalAmount(totalAmount), OutgoingCltv(expiry), EncryptedRecipientData(encryptedRecipientData), BlindingPoint(blinding)), customTlvs)) + def createFinalIntroductionPayload(amount: MilliSatoshi, totalAmount: MilliSatoshi, expiry: CltvExpiry, pathKey: PublicKey, encryptedRecipientData: ByteVector, customTlvs: Set[GenericTlv] = Set.empty): OutgoingBlindedPerHopPayload = { + OutgoingBlindedPerHopPayload(TlvStream(Set[OnionPaymentPayloadTlv](AmountToForward(amount), TotalAmount(totalAmount), OutgoingCltv(expiry), EncryptedRecipientData(encryptedRecipientData), PathKey(pathKey)), customTlvs)) } } @@ -538,7 +538,7 @@ object PaymentOnionCodecs { private val encryptedRecipientData: Codec[EncryptedRecipientData] = tlvField(bytes) - private val blindingPoint: Codec[BlindingPoint] = fixedLengthTlvField(33, publicKey) + private val pathKey: Codec[PathKey] = fixedLengthTlvField(33, publicKey) private val outgoingNodeId: Codec[OutgoingNodeId] = fixedLengthTlvField(33, publicKey) @@ -569,7 +569,7 @@ object PaymentOnionCodecs { .typecase(UInt64(6), outgoingChannelId) .typecase(UInt64(8), paymentData) .typecase(UInt64(10), encryptedRecipientData) - .typecase(UInt64(12), blindingPoint) + .typecase(UInt64(12), pathKey) .typecase(UInt64(16), paymentMetadata) .typecase(UInt64(18), totalAmount) // Types below aren't specified - use cautiously when deploying (be careful with backwards-compatibility). diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RouteBlinding.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RouteBlinding.scala index df5d414381..c01bd7b454 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RouteBlinding.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/RouteBlinding.scala @@ -60,8 +60,8 @@ object RouteBlindingEncryptedDataTlv { */ case class PathId(data: ByteVector) extends RouteBlindingEncryptedDataTlv - /** Blinding override for the rest of the route. */ - case class NextBlinding(blinding: PublicKey) extends RouteBlindingEncryptedDataTlv + /** Path key override for the rest of the route. */ + case class NextPathKey(pathKey: PublicKey) extends RouteBlindingEncryptedDataTlv /** Information for the relaying node to build the next HTLC. */ case class PaymentRelay(cltvExpiryDelta: CltvExpiryDelta, feeProportionalMillionths: Long, feeBase: MilliSatoshi) extends RouteBlindingEncryptedDataTlv @@ -144,7 +144,7 @@ object RouteBlindingEncryptedDataCodecs { private val outgoingChannelId: Codec[OutgoingChannelId] = tlvField(shortchannelid) private val outgoingNodeId: Codec[OutgoingNodeId] = tlvField(encodedNodeIdCodec) private val pathId: Codec[PathId] = tlvField(bytes) - private val nextBlinding: Codec[NextBlinding] = fixedLengthTlvField(33, publicKey) + private val nextPathKey: Codec[NextPathKey] = fixedLengthTlvField(33, publicKey) private val paymentRelay: Codec[PaymentRelay] = tlvField(("cltv_expiry_delta" | cltvExpiryDelta) :: ("fee_proportional_millionths" | uint32) :: ("fee_base_msat" | tmillisatoshi32)) private val paymentConstraints: Codec[PaymentConstraints] = tlvField(("max_cltv_expiry" | cltvExpiry) :: ("htlc_minimum_msat" | tmillisatoshi)) private val allowedFeatures: Codec[AllowedFeatures] = tlvField(featuresCodec) @@ -154,7 +154,7 @@ object RouteBlindingEncryptedDataCodecs { .typecase(UInt64(2), outgoingChannelId) .typecase(UInt64(4), outgoingNodeId) .typecase(UInt64(6), pathId) - .typecase(UInt64(8), nextBlinding) + .typecase(UInt64(8), nextPathKey) .typecase(UInt64(10), paymentRelay) .typecase(UInt64(12), paymentConstraints) .typecase(UInt64(14), allowedFeatures) @@ -162,7 +162,7 @@ object RouteBlindingEncryptedDataCodecs { val blindedRouteDataCodec = TlvCodecs.tlvStream[RouteBlindingEncryptedDataTlv](encryptedDataTlvCodec).complete // @formatter:off - case class RouteBlindingDecryptedData(tlvs: TlvStream[RouteBlindingEncryptedDataTlv], nextBlinding: PublicKey) + case class RouteBlindingDecryptedData(tlvs: TlvStream[RouteBlindingEncryptedDataTlv], nextPathKey: PublicKey) sealed trait InvalidEncryptedData { def message: String } case class CannotDecryptData(message: String) extends InvalidEncryptedData case class CannotDecodeData(message: String) extends InvalidEncryptedData @@ -172,17 +172,17 @@ object RouteBlindingEncryptedDataCodecs { * Decrypt and decode the contents of an encrypted_recipient_data TLV field. * * @param nodePrivKey this node's private key. - * @param blindingKey blinding point (usually provided in the lightning message). + * @param pathKey path key (usually provided in the lightning message). * @param encryptedData encrypted route blinding data (usually provided inside an onion). */ - def decode(nodePrivKey: PrivateKey, blindingKey: PublicKey, encryptedData: ByteVector): Either[InvalidEncryptedData, RouteBlindingDecryptedData] = { - Sphinx.RouteBlinding.decryptPayload(nodePrivKey, blindingKey, encryptedData) match { + def decode(nodePrivKey: PrivateKey, pathKey: PublicKey, encryptedData: ByteVector): Either[InvalidEncryptedData, RouteBlindingDecryptedData] = { + Sphinx.RouteBlinding.decryptPayload(nodePrivKey, pathKey, encryptedData) match { case Failure(f) => Left(CannotDecryptData(f.getMessage)) - case Success((decryptedData, defaultNextBlinding)) => blindedRouteDataCodec.decode(decryptedData.bits) match { + case Success((decryptedData, defaultNextPathKey)) => blindedRouteDataCodec.decode(decryptedData.bits) match { case Attempt.Failure(f) => Left(CannotDecodeData(f.message)) case Attempt.Successful(DecodeResult(tlvs, _)) => - val nextBlinding = tlvs.get[NextBlinding].map(_.blinding).getOrElse(defaultNextBlinding) - Right(RouteBlindingDecryptedData(tlvs, nextBlinding)) + val nextPathKey = tlvs.get[NextPathKey].map(_.pathKey).getOrElse(defaultNextPathKey) + Right(RouteBlindingDecryptedData(tlvs, nextPathKey)) } } } diff --git a/eclair-core/src/test/resources/bolt4-test-onion-message.json b/eclair-core/src/test/resources/bolt4-test-onion-message.json index f66660cc79..fe5191ed12 100644 --- a/eclair-core/src/test/resources/bolt4-test-onion-message.json +++ b/eclair-core/src/test/resources/bolt4-test-onion-message.json @@ -6,12 +6,12 @@ "hops": [ { "alias": "Alice", - "comment": "Alice->Bob: note next_blinding_override to match that give by Dave for Bob", - "blinding_secret": "6363636363636363636363636363636363636363636363636363636363636363", + "comment": "Alice->Bob: note next_path_key_override to match that give by Dave for Bob", + "path_key_secret": "6363636363636363636363636363636363636363636363636363636363636363", "tlvs": { "next_node_id": "0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c", - "next_blinding_override": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", - "blinding_override_secret": "0101010101010101010101010101010101010101010101010101010101010101" + "next_path_key_override": "031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", + "path_key_override_secret": "0101010101010101010101010101010101010101010101010101010101010101" }, "encrypted_data_tlv": "04210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c0821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f", "ss": "c04d2a4c518241cb49f2800eea92554cb543f268b4c73f85693541e86d649205", @@ -26,7 +26,7 @@ { "alias": "Bob", "comment": "Bob->Carol", - "blinding_secret": "0101010101010101010101010101010101010101010101010101010101010101", + "path_key_secret": "0101010101010101010101010101010101010101010101010101010101010101", "tlvs": { "next_node_id": "027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007", "unknown_tag_561": "123456" @@ -44,7 +44,7 @@ { "alias": "Carol", "comment": "Carol->Dave", - "blinding_secret": "f7ab6dca6152f7b6b0c9d7c82d716af063d72d8eef8816dfc51a8ae828fa7dce", + "path_key_secret": "f7ab6dca6152f7b6b0c9d7c82d716af063d72d8eef8816dfc51a8ae828fa7dce", "tlvs": { "padding": "0000000000", "next_node_id": "032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991" @@ -62,7 +62,7 @@ { "alias": "Dave", "comment": "Dave is final node, hence path_id", - "blinding_secret": "5de52bb427cc148bf23e509fdc18012004202517e80abcfde21612ae408e6cea", + "path_key_secret": "5de52bb427cc148bf23e509fdc18012004202517e80abcfde21612ae408e6cea", "tlvs": { "padding": "", "path_id": "deadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0", @@ -82,8 +82,8 @@ }, "route": { "comment": "The resulting blinded route Alice to Dave.", - "introduction_node_id": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619", - "blinding": "031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd99", + "first_node_id": "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619", + "first_path_key": "031195a8046dcbb8e17034bca630065e7a0982e4e36f6f7e5a8d4554e4846fcd99", "hops": [ { "blinded_node_id": "02d1c3d73f8cac67e7c5b6ec517282d5ba0a52b06a29ec92ff01e12decf76003c1", diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala index f441a860a2..0ae008b0cf 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala @@ -352,7 +352,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL import f._ initiateQuiescence(f, sendInitialStfu = true) // have to build a htlc manually because eclair would refuse to accept this command as it's forbidden - val forbiddenMsg = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, blinding_opt = None, confidence = 1.0, fundingFee_opt = None) + val forbiddenMsg = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) // both parties will respond to a forbidden msg while quiescent with a warning (and disconnect) bob2alice.forward(alice, forbiddenMsg) alice2bob.expectMsg(Warning(channelId(alice), ForbiddenDuringSplice(channelId(alice), "UpdateAddHtlc").getMessage)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala index 3fb6adcc4f..468e175335 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala @@ -1435,7 +1435,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik alice2bob.expectMsgType[TxAddInput] // have to build a htlc manually because eclair would refuse to accept this command as it's forbidden - val fakeHtlc = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, blinding_opt = None, confidence = 1.0, fundingFee_opt = None) + val fakeHtlc = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) bob2alice.forward(alice, fakeHtlc) // alice returns a warning and schedules a disconnect after receiving UpdateAddHtlc alice2bob.expectMsg(Warning(channelId(alice), ForbiddenDuringSplice(channelId(alice), "UpdateAddHtlc").getMessage)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala index 241aebc3b1..4aa5046f62 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala @@ -115,7 +115,7 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with val initialState = alice.stateData.asInstanceOf[DATA_NORMAL] val sender = TestProbe() val h = randomBytes32() - val originHtlc = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = h, onionRoutingPacket = TestConstants.emptyOnionPacket, blinding_opt = None, confidence = 1.0, fundingFee_opt = None) + val originHtlc = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = h, onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) val origin = Origin.Hot(sender.ref, Upstream.Hot.Channel(originHtlc, TimestampMilli.now(), randomKey().publicKey)) val cmd = CMD_ADD_HTLC(sender.ref, originHtlc.amountMsat - 10_000.msat, h, originHtlc.cltvExpiry - CltvExpiryDelta(7), TestConstants.emptyOnionPacket, None, 1.0, None, origin) alice ! cmd 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 57a93308a1..a76b67b72b 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 @@ -404,24 +404,24 @@ class SphinxSpec extends AnyFunSuite { val evePayload = hex"011c00000000000000000000000000000000000000000000000000000000 0616c9cf92f45ade68345bc20ae672e2012f4af487ed4415 0c05000b71b032 0e00" // Eve creates a blinded route to herself through Dave: - val (blindingOverride, blindedRouteEnd, lastBlinding) = { + val (pathKeyOverride, blindedRouteEnd, lastPathKey) = { val sessionKey = PrivateKey(hex"0101010101010101010101010101010101010101010101010101010101010101") - val BlindedRouteDetails(blindedRoute, lastBlinding) = RouteBlinding.create(sessionKey, Seq(dave, eve).map(_.publicKey), Seq(davePayload, evePayload)) - assert(blindedRoute.blindingKey == PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")) - assert(lastBlinding == PublicKey(hex"03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")) - (blindedRoute.blindingKey, blindedRoute, lastBlinding) + val BlindedRouteDetails(blindedRoute, lastPathKey) = RouteBlinding.create(sessionKey, Seq(dave, eve).map(_.publicKey), Seq(davePayload, evePayload)) + assert(blindedRoute.firstPathKey == PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")) + assert(lastPathKey == PublicKey(hex"03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")) + (blindedRoute.firstPathKey, blindedRoute, lastPathKey) } // Bob also wants to use route blinding: - val (blinding, blindedRouteStart) = { + val (pathKey, blindedRouteStart) = { val sessionKey = PrivateKey(hex"0202020202020202020202020202020202020202020202020202020202020202") val blindedRoute = RouteBlinding.create(sessionKey, Seq(bob, carol).map(_.publicKey), Seq(bobPayload, carolPayload)).route - assert(blindedRoute.blindingKey == PublicKey(hex"024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766")) - (blindedRoute.blindingKey, blindedRoute) + assert(blindedRoute.firstPathKey == PublicKey(hex"024d4b6cd1361032ca9bd2aeb9d900aa4d45d9ead80ac9423374c451a7254d0766")) + (blindedRoute.firstPathKey, blindedRoute) } // We now have a blinded route Bob -> Carol -> Dave -> Eve - val blindedRoute = BlindedRoute(EncodedNodeId(bob.publicKey), blinding, blindedRouteStart.blindedNodes ++ blindedRouteEnd.blindedNodes) + val blindedRoute = BlindedRoute(EncodedNodeId(bob.publicKey), pathKey, blindedRouteStart.blindedHops ++ blindedRouteEnd.blindedHops) assert(blindedRoute.blindedNodeIds == Seq( PublicKey(hex"03da173ad2aee2f701f17e59fbd16cb708906d69838a5f088e8123fb36e89a2c25"), PublicKey(hex"02e466727716f044290abf91a14a6d90e87487da160c2a3cbd0d465d7a78eb83a7"), @@ -435,12 +435,12 @@ class SphinxSpec extends AnyFunSuite { hex"da1c7e5f7881219884beae6ae68971de73bab4c3055d9865b1afb60722a63c688768042ade22f2c22f5724767d171fd221d3e579e43b354cc72e3ef146ada91a892d95fc48662f5b158add0af457da", )) // After generating the blinded route, Eve is able to derive the private key corresponding to her blinded identity. - assert(RouteBlinding.derivePrivateKey(eve, lastBlinding).publicKey == blindedRoute.blindedNodeIds.last) + assert(RouteBlinding.derivePrivateKey(eve, lastPathKey).publicKey == blindedRoute.blindedNodeIds.last) - // Every node in the route is able to decrypt its payload and extract the blinding point for the next node: + // Every node in the route is able to decrypt its payload and extract the path key for the next node: { // Bob (the introduction point) can decrypt its encrypted payload and obtain the next ephemeral public key. - val Success((payload0, ephKey1)) = RouteBlinding.decryptPayload(bob, blindedRoute.blindingKey, blindedRoute.encryptedPayloads(0)) + val Success((payload0, ephKey1)) = RouteBlinding.decryptPayload(bob, blindedRoute.firstPathKey, blindedRoute.encryptedPayloads(0)) assert(payload0 == bobPayload) assert(ephKey1 == PublicKey(hex"034e09f450a80c3d252b258aba0a61215bf60dda3b0dc78ffb0736ea1259dfd8a0")) @@ -449,16 +449,16 @@ class SphinxSpec extends AnyFunSuite { val Success((payload1, ephKey2)) = RouteBlinding.decryptPayload(carol, ephKey1, blindedRoute.encryptedPayloads(1)) assert(payload1 == carolPayload) assert(ephKey2 == PublicKey(hex"03af5ccc91851cb294e3a364ce63347709a08cdffa58c672e9a5c587ddd1bbca60")) - // NB: Carol finds a blinding override and will transmit that instead of ephKey2 to the next node. - assert(payload1.containsSlice(blindingOverride.value)) + // NB: Carol finds a path key override and will transmit that instead of ephKey2 to the next node. + assert(payload1.containsSlice(pathKeyOverride.value)) - // Dave must be given the blinding override to derive the private key used to unwrap the onion and decrypt its encrypted payload. + // Dave must be given the path key override to derive the private key used to unwrap the onion and decrypt its encrypted payload. assert(RouteBlinding.decryptPayload(dave, ephKey2, blindedRoute.encryptedPayloads(2)).isFailure) - assert(RouteBlinding.derivePrivateKey(dave, blindingOverride).publicKey == blindedRoute.blindedNodeIds(2)) - val Success((payload2, ephKey3)) = RouteBlinding.decryptPayload(dave, blindingOverride, blindedRoute.encryptedPayloads(2)) + assert(RouteBlinding.derivePrivateKey(dave, pathKeyOverride).publicKey == blindedRoute.blindedNodeIds(2)) + val Success((payload2, ephKey3)) = RouteBlinding.decryptPayload(dave, pathKeyOverride, blindedRoute.encryptedPayloads(2)) assert(payload2 == davePayload) assert(ephKey3 == PublicKey(hex"03e09038ee76e50f444b19abf0a555e8697e035f62937168b80adf0931b31ce52a")) - assert(ephKey3 == lastBlinding) + assert(ephKey3 == lastPathKey) // Eve can derive the private key used to unwrap the onion and decrypt its encrypted payload. assert(RouteBlinding.derivePrivateKey(eve, ephKey3).publicKey == blindedRoute.blindedNodeIds(3)) @@ -472,8 +472,8 @@ class SphinxSpec extends AnyFunSuite { val payloads = Seq( // The first hop (Alice) receives a normal onion payload. TlvStream[OnionPaymentPayloadTlv](OnionPaymentPayloadTlv.AmountToForward(110_125 msat), OnionPaymentPayloadTlv.OutgoingCltv(CltvExpiry(749150)), OnionPaymentPayloadTlv.OutgoingChannelId(ShortChannelId(10))), - // The sender includes the blinding key and the first encrypted recipient data in the introduction node's payload. - TlvStream[OnionPaymentPayloadTlv](OnionPaymentPayloadTlv.BlindingPoint(blinding), OnionPaymentPayloadTlv.EncryptedRecipientData(blindedRoute.encryptedPayloads(0))), + // The sender includes the path key and the first encrypted recipient data in the introduction node's payload. + TlvStream[OnionPaymentPayloadTlv](OnionPaymentPayloadTlv.PathKey(pathKey), OnionPaymentPayloadTlv.EncryptedRecipientData(blindedRoute.encryptedPayloads(0))), // The sender includes the correct encrypted recipient data in each blinded node's payload. TlvStream[OnionPaymentPayloadTlv](OnionPaymentPayloadTlv.EncryptedRecipientData(blindedRoute.encryptedPayloads(1))), TlvStream[OnionPaymentPayloadTlv](OnionPaymentPayloadTlv.EncryptedRecipientData(blindedRoute.encryptedPayloads(2))), @@ -496,15 +496,15 @@ class SphinxSpec extends AnyFunSuite { // Bob is the introduction node. // He can decrypt the onion as usual, but the payload doesn't contain a shortChannelId or a nodeId to forward to. - // However it contains a blinding point and encrypted data, which he can decrypt to discover the next node. + // However it contains a path key and encrypted data, which he can decrypt to discover the next node. val Right(DecryptedPacket(onionPayloadBob, packetForCarol, sharedSecretBob)) = peel(bob, associatedData, packetForBob) val tlvsBob = PaymentOnionCodecs.perHopPayloadCodec.decode(onionPayloadBob.bits).require.value - assert(tlvsBob.get[OnionPaymentPayloadTlv.BlindingPoint].map(_.publicKey).contains(blinding)) + assert(tlvsBob.get[OnionPaymentPayloadTlv.PathKey].map(_.publicKey).contains(pathKey)) assert(tlvsBob.get[OnionPaymentPayloadTlv.EncryptedRecipientData].nonEmpty) - val Right(decryptedPayloadBob) = RouteBlindingEncryptedDataCodecs.decode(bob, blinding, tlvsBob.get[OnionPaymentPayloadTlv.EncryptedRecipientData].get.data) - val blindingEphemeralKeyForCarol = decryptedPayloadBob.nextBlinding - val Right(payloadBob) = PaymentOnion.IntermediatePayload.ChannelRelay.Blinded.validate(tlvsBob, decryptedPayloadBob.tlvs, blindingEphemeralKeyForCarol) + val Right(decryptedPayloadBob) = RouteBlindingEncryptedDataCodecs.decode(bob, pathKey, tlvsBob.get[OnionPaymentPayloadTlv.EncryptedRecipientData].get.data) + val pathKeyForCarol = decryptedPayloadBob.nextPathKey + val Right(payloadBob) = PaymentOnion.IntermediatePayload.ChannelRelay.Blinded.validate(tlvsBob, decryptedPayloadBob.tlvs, pathKeyForCarol) assert(payloadBob.outgoing.contains(ShortChannelId(1))) assert(payloadBob.amountToForward(110_125 msat) == 100_125.msat) assert(payloadBob.outgoingCltv(CltvExpiry(749150)) == CltvExpiry(749100)) @@ -513,37 +513,37 @@ class SphinxSpec extends AnyFunSuite { assert(payloadBob.paymentRelayData.allowedFeatures.isEmpty) // Carol is a blinded hop. - // She receives the blinding key from Bob (e.g. in a tlv field in update_add_htlc) which she can use to derive the + // She receives the path key from Bob (e.g. in a tlv field in update_add_htlc) which she can use to derive the // private key corresponding to her blinded node ID and decrypt the onion. // The payload doesn't contain a shortChannelId or a nodeId to forward to, but the encrypted data does. - val blindedPrivKeyCarol = RouteBlinding.derivePrivateKey(carol, blindingEphemeralKeyForCarol) + val blindedPrivKeyCarol = RouteBlinding.derivePrivateKey(carol, pathKeyForCarol) val Right(DecryptedPacket(onionPayloadCarol, packetForDave, sharedSecretCarol)) = peel(blindedPrivKeyCarol, associatedData, packetForCarol) val tlvsCarol = PaymentOnionCodecs.perHopPayloadCodec.decode(onionPayloadCarol.bits).require.value assert(tlvsCarol.get[OnionPaymentPayloadTlv.EncryptedRecipientData].nonEmpty) - val Right(decryptedPayloadCarol) = RouteBlindingEncryptedDataCodecs.decode(carol, blindingEphemeralKeyForCarol, tlvsCarol.get[OnionPaymentPayloadTlv.EncryptedRecipientData].get.data) - val blindingEphemeralKeyForDave = decryptedPayloadCarol.nextBlinding - val Right(payloadCarol) = PaymentOnion.IntermediatePayload.ChannelRelay.Blinded.validate(tlvsCarol, decryptedPayloadCarol.tlvs, blindingEphemeralKeyForDave) + val Right(decryptedPayloadCarol) = RouteBlindingEncryptedDataCodecs.decode(carol, pathKeyForCarol, tlvsCarol.get[OnionPaymentPayloadTlv.EncryptedRecipientData].get.data) + val pathKeyForDave = decryptedPayloadCarol.nextPathKey + val Right(payloadCarol) = PaymentOnion.IntermediatePayload.ChannelRelay.Blinded.validate(tlvsCarol, decryptedPayloadCarol.tlvs, pathKeyForDave) assert(payloadCarol.outgoing.contains(ShortChannelId(2))) assert(payloadCarol.amountToForward(100_125 msat) == 100_010.msat) assert(payloadCarol.outgoingCltv(CltvExpiry(749100)) == CltvExpiry(749025)) assert(payloadCarol.paymentRelayData.paymentRelay == RouteBlindingEncryptedDataTlv.PaymentRelay(CltvExpiryDelta(75), 150, 100 msat)) assert(payloadCarol.paymentRelayData.paymentConstraints == RouteBlindingEncryptedDataTlv.PaymentConstraints(CltvExpiry(750100), 50 msat)) assert(payloadCarol.paymentRelayData.allowedFeatures.isEmpty) - // Carol's payload contains a blinding override. - val blindingEphemeralKeyForDaveOverride = payloadCarol.paymentRelayData.records.get[RouteBlindingEncryptedDataTlv.NextBlinding].map(_.blinding) - assert(blindingEphemeralKeyForDaveOverride.contains(blindingOverride)) - assert(blindingEphemeralKeyForDave == blindingOverride) + // Carol's payload contains a path key override. + val pathKeyForDaveOverride = payloadCarol.paymentRelayData.records.get[RouteBlindingEncryptedDataTlv.NextPathKey].map(_.pathKey) + assert(pathKeyForDaveOverride.contains(pathKeyOverride)) + assert(pathKeyForDave == pathKeyOverride) // Dave is a blinded hop. - // He receives the blinding key from Carol (e.g. in a tlv field in update_add_htlc) which he can use to derive the + // He receives the path key from Carol (e.g. in a tlv field in update_add_htlc) which he can use to derive the // private key corresponding to his blinded node ID and decrypt the onion. - val blindedPrivKeyDave = RouteBlinding.derivePrivateKey(dave, blindingOverride) + val blindedPrivKeyDave = RouteBlinding.derivePrivateKey(dave, pathKeyOverride) val Right(DecryptedPacket(onionPayloadDave, packetForEve, sharedSecretDave)) = peel(blindedPrivKeyDave, associatedData, packetForDave) val tlvsDave = PaymentOnionCodecs.perHopPayloadCodec.decode(onionPayloadDave.bits).require.value assert(tlvsDave.get[OnionPaymentPayloadTlv.EncryptedRecipientData].nonEmpty) - val Right(decryptedPayloadDave) = RouteBlindingEncryptedDataCodecs.decode(dave, blindingOverride, tlvsDave.get[OnionPaymentPayloadTlv.EncryptedRecipientData].get.data) - val blindingEphemeralKeyForEve = decryptedPayloadDave.nextBlinding - val Right(payloadDave) = PaymentOnion.IntermediatePayload.ChannelRelay.Blinded.validate(tlvsDave, decryptedPayloadDave.tlvs, blindingEphemeralKeyForEve) + val Right(decryptedPayloadDave) = RouteBlindingEncryptedDataCodecs.decode(dave, pathKeyOverride, tlvsDave.get[OnionPaymentPayloadTlv.EncryptedRecipientData].get.data) + val pathKeyForEve = decryptedPayloadDave.nextPathKey + val Right(payloadDave) = PaymentOnion.IntermediatePayload.ChannelRelay.Blinded.validate(tlvsDave, decryptedPayloadDave.tlvs, pathKeyForEve) assert(payloadDave.outgoing.contains(ShortChannelId(3))) assert(payloadDave.amountToForward(100_010 msat) == 100_000.msat) assert(payloadDave.outgoingCltv(CltvExpiry(749025)) == CltvExpiry(749000)) @@ -552,13 +552,13 @@ class SphinxSpec extends AnyFunSuite { assert(payloadDave.paymentRelayData.allowedFeatures.isEmpty) // Eve is the blinded recipient. - // She receives the blinding key from Dave (e.g. in a tlv field in update_add_htlc) which she can use to derive + // She receives the path key from Dave (e.g. in a tlv field in update_add_htlc) which she can use to derive // the private key corresponding to her blinded node ID and decrypt the onion. - val blindedPrivKeyEve = RouteBlinding.derivePrivateKey(eve, blindingEphemeralKeyForEve) + val blindedPrivKeyEve = RouteBlinding.derivePrivateKey(eve, pathKeyForEve) val Right(DecryptedPacket(onionPayloadEve, packetForNobody, sharedSecretEve)) = peel(blindedPrivKeyEve, associatedData, packetForEve) val tlvsEve = PaymentOnionCodecs.perHopPayloadCodec.decode(onionPayloadEve.bits).require.value assert(tlvsEve.get[OnionPaymentPayloadTlv.EncryptedRecipientData].nonEmpty) - val Right(decryptedPayloadEve) = RouteBlindingEncryptedDataCodecs.decode(eve, blindingEphemeralKeyForEve, tlvsEve.get[OnionPaymentPayloadTlv.EncryptedRecipientData].get.data) + val Right(decryptedPayloadEve) = RouteBlindingEncryptedDataCodecs.decode(eve, pathKeyForEve, tlvsEve.get[OnionPaymentPayloadTlv.EncryptedRecipientData].get.data) val Right(payloadEve) = PaymentOnion.FinalPayload.Blinded.validate(tlvsEve, decryptedPayloadEve.tlvs) assert(payloadEve.pathId == hex"c9cf92f45ade68345bc20ae672e2012f4af487ed4415") assert(payloadEve.paymentConstraints == RouteBlindingEncryptedDataTlv.PaymentConstraints(CltvExpiry(750000), 50 msat)) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala index a163ddb91b..885c60f198 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/MessageIntegrationSpec.scala @@ -83,7 +83,7 @@ class MessageIntegrationSpec extends IntegrationSpec { nodes("B").system.eventStream.subscribe(eventListener.ref, classOf[OnionMessages.ReceiveMessage]) val blindedRoute = buildRoute(randomKey(), Seq(IntermediateNode(nodes("A").nodeParams.nodeId), IntermediateNode(nodes("B").nodeParams.nodeId), IntermediateNode(nodes("B").nodeParams.nodeId)), Recipient(nodes("B").nodeParams.nodeId, None)).route - assert(blindedRoute.introductionNodeId == EncodedNodeId(nodes("A").nodeParams.nodeId)) + assert(blindedRoute.firstNodeId == EncodedNodeId(nodes("A").nodeParams.nodeId)) alice.sendOnionMessage(None, Right(blindedRoute), expectsReply = false, ByteVector.empty).pipeTo(probe.ref) assert(probe.expectMsgType[SendOnionMessageResponse].sent) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala index 3ba9d1ad5b..ef4042e9bd 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/integration/PaymentIntegrationSpec.scala @@ -790,7 +790,7 @@ class PaymentIntegrationSpec extends IntegrationSpec { ShortChannelIdDir(channelBE.nodeId1 == nodes("B").nodeParams.nodeId, channelBE.shortChannelId) } val offerBlindedRoute = buildRoute(randomKey(), Seq(IntermediateNode(nodes("B").nodeParams.nodeId), IntermediateNode(nodes("C").nodeParams.nodeId)), Recipient(nodes("C").nodeParams.nodeId, Some(pathId))).route - val offerPath = BlindedRoute(scidDirEB, offerBlindedRoute.blindingKey, offerBlindedRoute.blindedNodes) + val offerPath = BlindedRoute(scidDirEB, offerBlindedRoute.firstPathKey, offerBlindedRoute.blindedHops) val offer = Offer(Some(amount), Some("test offer"), recipientKey.publicKey, nodes("C").nodeParams.features.bolt12Features(), chain, additionalTlvs = Set(OfferPaths(Seq(offerPath)))) val offerHandler = TypedProbe[HandlerCommand]()(nodes("C").system.toTyped) nodes("C").offerManager ! RegisterOffer(offer, Some(recipientKey), Some(pathId), offerHandler.ref) @@ -818,7 +818,7 @@ class PaymentIntegrationSpec extends IntegrationSpec { assert(paymentSent.recipientAmount == amount, paymentSent) assert(paymentSent.feesPaid >= 0.msat, paymentSent) val Some(invoice: Bolt12Invoice) = nodes("A").nodeParams.db.payments.listOutgoingPaymentsToOffer(offer.offerId).head.invoice - assert(invoice.blindedPaths.forall(_.route.introductionNodeId.isInstanceOf[EncodedNodeId.ShortChannelIdDir])) + assert(invoice.blindedPaths.forall(_.route.firstNodeId.isInstanceOf[EncodedNodeId.ShortChannelIdDir])) awaitCond(nodes("C").nodeParams.db.payments.getIncomingPayment(paymentSent.paymentHash).exists(_.status.isInstanceOf[IncomingPaymentStatus.Received])) val Some(IncomingBlindedPayment(_, _, _, _, IncomingPaymentStatus.Received(receivedAmount, _))) = nodes("C").nodeParams.db.payments.getIncomingPayment(paymentSent.paymentHash) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala index 2bb3b81042..743e3e13a8 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/json/JsonSerializersSpec.scala @@ -254,7 +254,7 @@ class JsonSerializersSpec extends TestKitBaseClass with AnyFunSuiteLike with Mat payload = hex"3c7a66997c681a3de1bae56438abeee4fc50a16554725a430ade1dc8db6bdd76704d45c6151c4051d710cf487e63f8cbe9f5e537e6e518d7998f11c40277d551bee036d227a421f344c0185b4533660d200acbc4f4aa6148e29f813bb537e950a79ba961b80ccaa6ad808cb88f858ee73f8b1f129d3214d194f76fc011c46e18c2caec0fcb69715e79cb381e449e5be20281e0aaa92defa089c98b7e29c22181f2d1af9f5fe2a37ede4ac3163b123aa72318201b1128b17053c381e7bf111620dfb7ea3dcc5c28cafdd9cb7bb1a4202b64199aa02af145c563ba1b9f10288203ce2666f486aacb2ee385dc0b67915864c95174dfaac1e0ea195329d1741cd1febb4b49b33f84e0d10a5ec8ee0a1a94f73abf081ec69bb863edfeb46d24ce424b025ac3f593a7ba9419ec9b17fb39f0fbeac80e0898f95b6709cbc95f7c097e22e3a6ca0efbc947bbcd4a9077f6bd9daba25b18bb16179fca9d9cb2ce49fc7cbd2de589237926cacb87ea60e2cc60a90b47575517921b5529b8a95823dd0c3d02a7747d74c4ca927ba6b70c06c1c1ef27e14d371e8dd8f5d9380a65b08ae1e6384f9b3575c5d7278de22ce80e63612a27f3b3f45dbe32ee855185293c719e5a7203a682a08fd810c46fa12b67e61349831f8fae3f558090ea988e1a22ec877b790ea09169055529247c4dd597857aad74eaeb3a5879e96453e681e213f2796ed704d620509f34f91d9d16f881fd397e2836a0a4d2f1bcd230067f7acb5381a2b17e8c5135e38c4d258afbe4f69ac7ad39b789e99686ee926b3ad31b98993673313b7b18a4faaea238d8055824fde7339a791fc7777ef28cc4a1a5d177b3c3882ced4921c6cd85ae82e1fe13fe680ae432a9918ce37b15f88d4d18fb16b69e5369d18c204aaf7ee49b830bf2328380e6ad96e8f6a9e01bc2c97ffbaa402d5406dc7b83c6eb5d515ffc3bea8c42cf299c9e2bea693515246c6ff859d33ba6c4da4c4c1706e0c6b4a574e927f02eb92b7d56722cff80c3e6f6b98d1c84cb576abdcc27a6bc7b110fc2ac5fead57f05ad854c3331ce1ff94c0dc97303540ee797d71566497af09f20e3554d467528e1fed8e69438171072fe2deca3979a8f5ec9043b9bc4da921b095c29dc0294148c1b7001dafda4c48600d1194f745e6d0689c561bf19d20758c4d25fac64d81780607a4106e220ef546fc4026af7b9da8defb2fe3c21d48798ac67c794fb40aabe44618a8911673466be06808c6f54a772b87bcfafb4d120a9bebffb8051bf24bb332eaa769cf175c1aadb0186f8946dc32513fd81fe1a61bfb860886bdd070359a43e06e74607d300bd2e01a3f1ee900c4039e8db742170228db61ef0c77724c49d1573144564a80cc1ebc0449b34f84be35187ceba3fbc2facf5ad1f1e15945e3c6c236579aca7bc97e4cc76a3310022693b64008562b254a7d11c0086813e551c4817bbb72a1d6fbfc84d326ce973651200f80aa8ab0976c53c390249ca8e7e5ec21b80e70c3e0205983d313b28a5d1b9d9149501e05d3257c8ae88c6308e9e00feeab19121d1032a582e68ca1f9f64a1fd91cb5d8613b985fd4be22a4d5c14a132c20811a75ee3cc61de0b3fbc3254d61995d086603032269888b942ec0971ad26ea4b8df1746c5ec1de904ddeb5045abc0a6ede9d6a199ed0782cb69efa3a4dc00747553dbef12fb8299ca364b4cdf2ac3eba03b0d8b273684116bba4458b5717bc4aca5406901173a89b3643ccc076f22779fccf1ad69981e24eef18c711a2d58dfe5834b41f9e7166d54dc8628e754baaca1cbb7db8256f88ebc889de6078ba83a1af14a4", hmac = ByteVector32(hex"9442626f72c475963dbddf8a57ab2cef3013eb3d6a5e8afbea9e631dac4481f5") ), - blinding_opt = None, + pathKey_opt = None, confidence = 0.7, fundingFee_opt = None, ) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/message/OnionMessagesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/message/OnionMessagesSpec.scala index 0bb7a05233..8ce52a0ef0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/message/OnionMessagesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/message/OnionMessagesSpec.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{ByteVector32, Crypto} import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.crypto.Sphinx.PacketAndSecrets -import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedNode, BlindedRoute} +import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedHop, BlindedRoute} import fr.acinq.eclair.message.OnionMessages._ import fr.acinq.eclair.wire.protocol.MessageOnionCodecs.perHopPayloadCodec import fr.acinq.eclair.wire.protocol.OnionMessagePayloadTlv.EncryptedData @@ -41,9 +41,9 @@ class OnionMessagesSpec extends AnyFunSuite { test("single-hop onion message without path_id") { val sessionKey = randomKey() - val blindingSecret = randomKey() + val pathSecret = randomKey() val destination = randomKey() - val Right(message) = buildMessage(sessionKey, blindingSecret, Nil, Recipient(destination.publicKey, None), TlvStream.empty) + val Right(message) = buildMessage(sessionKey, pathSecret, Nil, Recipient(destination.publicKey, None), TlvStream.empty) process(destination, message) match { case ReceiveMessage(finalPayload, _) => assert(finalPayload.pathId_opt.isEmpty) @@ -61,16 +61,16 @@ class OnionMessagesSpec extends AnyFunSuite { val dave = PrivateKey(hex"444444444444444444444444444444444444444444444444444444444444444401") assert(dave.publicKey == PublicKey(hex"032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991")) - val blindingSecret = PrivateKey(hex"050505050505050505050505050505050505050505050505050505050505050501") - assert(blindingSecret.publicKey == PublicKey(hex"0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f7")) - val blindingOverride = PrivateKey(hex"070707070707070707070707070707070707070707070707070707070707070701") - assert(blindingOverride.publicKey == PublicKey(hex"02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f")) + val pathSecret = PrivateKey(hex"050505050505050505050505050505050505050505050505050505050505050501") + assert(pathSecret.publicKey == PublicKey(hex"0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f7")) + val pathKeyOverride = PrivateKey(hex"070707070707070707070707070707070707070707070707070707070707070701") + assert(pathKeyOverride.publicKey == PublicKey(hex"02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f")) // Building the onion manually val messageForAlice = TlvStream[RouteBlindingEncryptedDataTlv](OutgoingNodeId(bob.publicKey)) val encodedForAlice = blindedRouteDataCodec.encode(messageForAlice).require.bytes assert(encodedForAlice == hex"04210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c") - val messageForBob = TlvStream[RouteBlindingEncryptedDataTlv](OutgoingNodeId(carol.publicKey), NextBlinding(blindingOverride.publicKey)) + val messageForBob = TlvStream[RouteBlindingEncryptedDataTlv](OutgoingNodeId(carol.publicKey), NextPathKey(pathKeyOverride.publicKey)) val encodedForBob = blindedRouteDataCodec.encode(messageForBob).require.bytes assert(encodedForBob == hex"0421027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007082102989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f") val messageForCarol = TlvStream[RouteBlindingEncryptedDataTlv](Padding(hex"0000000000000000000000000000000000000000000000000000000000000000000000"), OutgoingNodeId(dave.publicKey)) @@ -81,12 +81,12 @@ class OnionMessagesSpec extends AnyFunSuite { assert(encodedForDave == hex"060401234567") // Building blinded path Carol -> Dave - val routeFromCarol = Sphinx.RouteBlinding.create(blindingOverride, carol.publicKey :: dave.publicKey :: Nil, encodedForCarol :: encodedForDave :: Nil).route + val routeFromCarol = Sphinx.RouteBlinding.create(pathKeyOverride, carol.publicKey :: dave.publicKey :: Nil, encodedForCarol :: encodedForDave :: Nil).route // Building blinded path Alice -> Bob - val routeToCarol = Sphinx.RouteBlinding.create(blindingSecret, alice.publicKey :: bob.publicKey :: Nil, encodedForAlice :: encodedForBob :: Nil).route + val routeToCarol = Sphinx.RouteBlinding.create(pathSecret, alice.publicKey :: bob.publicKey :: Nil, encodedForAlice :: encodedForBob :: Nil).route - val publicKeys = routeToCarol.blindedNodes.map(_.blindedPublicKey) concat routeFromCarol.blindedNodes.map(_.blindedPublicKey) + val publicKeys = routeToCarol.blindedHops.map(_.blindedPublicKey) concat routeFromCarol.blindedHops.map(_.blindedPublicKey) val encryptedPayloads = routeToCarol.encryptedPayloads ++ routeFromCarol.encryptedPayloads val payloads = encryptedPayloads.map(encTlv => perHopPayloadCodec.encode(TlvStream(EncryptedData(encTlv))).require.bytes) val expectedPayloads = List( @@ -103,12 +103,12 @@ class OnionMessagesSpec extends AnyFunSuite { assert(packet.hmac == ByteVector32(hex"d84e7135092450c8cc98bb969aa6d9127dd07da53a3c46b2e9339d111f5f301d")) assert(packet.publicKey == PublicKey(hex"0256b328b30c8bf5839e24058747879408bdb36241dc9c2e7c619faa12b2920967").value) assert(packet.payload == hex"37d167dcefdb678725cb8074d3224dfe235ba3f22f71ac8a2c9d1398b1175295b1dd3f14c02d698021e8a8856637306c6f195e01494e6d75bfc0812f3f6d74e4dce347ffc1c8e01595fa595f68f3e7358aad4bf2d9412e9f307a25b6d5e4045174551b1c867264d3905e4f05b2e5bcfed7e7276660bf7e956bce5afa3d5e7e4c15883b856bc93dd9d6a968838ef51314d38dd41e5ab84b8846dca3c61d87e54c0ecf116b3cd5b3f1fcfbba3067cc329437cb301749447ad106f43955a643b52c66d465fc7abd2add1ab398aa63c890ae3dc564395bb7a4bbe28325ccdb07503285dacf90b5e09f4e455fb42459741f9d497000298b99f1e70adc28f59a1be85a96952f27b6a6c5d6a08822b4f5cae05daa6c2ce2f8ca5fdd4e8f0df46b94791b3159fe8eace11bcf8d58b532967a024f7e7e85929456a1332d9139ce7de92b9a5985acab8cd7630c9a0580bfd74b28e7ce5bd25e63e7ae369795dfe74c21e24b8bbf02d1f4eb8fbd86920f41d573488abe059166aabbc3be187c435423ead6a5473994e0246efe76e419893aa2d7566b2645f3496d97585de9c92b8c5a5226398cc459ce84abc02fe2b45b5ecaf21961730d4a34bbe6fdfe720e71e3d81a494c01080d8039360d534c6ee5a3c47a1874e526969add9126b30d9192f85ba45bcfd7029cc7560f0e25e14b5deaa805360c4967705e85325ac055922863470f5397e8404022488caebf9204acd6cb02a11088aebf7e497b4ff1172f0a9c6bf980914cc4eb42fc78b457add549abf1134f84922b217502938b42d10b35079f44c5168d4c3e9fe7ca8094ef72ed73ef84f1d3530b6b3545f9f4f013e7e8cbcf2619f57754a7380ce6a9532ee14c55990faa43df6c09530a314b5f4ce597f5ec9b776e8597ce258ac47dac43bd3ac9e52788ff3a66b7dc07cd1bc3e6d197339d85fa8d3d6c3054dd1a5e416c714b544de6eb55209e40e3cac412a51748370160d2d73b6d97abd62f7bae70df27cd199c511fa693019c5717d471e934906b98cd974fda4dd1cb5e2d721044a0be2bdf24d0971e09f2f39488fe389fc5230699b4df7cec7447e5be4ea49bd7c3fe1a5ec7358510dc1dd9c1a8da68c0863188d80549e49f7c00f57d2009b2427b2aed1569603fc247734039469f9fdf3ddd3a22fa95c5d8066a468327a02b474c9915419af82c8edc67686984767fe7885207c6820f6c2e57cb8fd0bcb9981ebc8065c74e970a5d593c3b73ee25a0877ca096a9f7edfee6d43bd817c7d415fea9abb6f206c61aa36942df9318762a76b9da26d0d41a0ae9eee042a175f82dc134bf6f2d46a218db358d6852940e6e30df4a58ac6cb409e7ce99afe1e3f42768bd617af4d0a235d0ba0dd5075f9cc091784395d30e7e42d4e006db21bea9b45d1f122b75c051e84e2281573ef54ebad053218fff0cc28ea89a06adc218d4134f407654990592e75462f5ee4a463c1e46425222d48761162da8049613cafd7ecc52ff8024e9d58512b958e3a3d12dede84e1441247700bca0f992875349448b430683c756438fd4e91f3d44f3cf624ed21f3c63cf92615ecc201d0cd3159b1b3fccd8f29d2daba9ac5ba87b1dd2f83323a2b2d3176b803ce9c7bdc4bae615925eb22a213df1eeb2f8ff95586536caf042d565984aacf1425a120a5d8d7a9cbb70bf4852e116b89ff5b198d672220af2be4246372e7c3836cf50d732212a3e3346ff92873ace57fa687b2b1aab3e8dc6cb9f93f865d998cff0a1680d9012a9597c90a070e525f66226cc287814f4ac4157b15a0b25aa110946cd69fd404fafd5656669bfd1d9e509eabc004c5a") - val onionForAlice = OnionMessage(blindingSecret.publicKey, packet) + val onionForAlice = OnionMessage(pathSecret.publicKey, packet) // Building the onion with functions from `OnionMessages` - val replyPath = buildRoute(blindingOverride, IntermediateNode(carol.publicKey, EncodedNodeId(carol.publicKey), padding = Some(hex"0000000000000000000000000000000000000000000000000000000000000000000000")) :: Nil, Recipient(dave.publicKey, pathId = Some(hex"01234567"))).route + val replyPath = buildRoute(pathKeyOverride, IntermediateNode(carol.publicKey, EncodedNodeId(carol.publicKey), padding = Some(hex"0000000000000000000000000000000000000000000000000000000000000000000000")) :: Nil, Recipient(dave.publicKey, pathId = Some(hex"01234567"))).route assert(replyPath == routeFromCarol) - val Right(message) = buildMessage(sessionKey, blindingSecret, IntermediateNode(alice.publicKey) :: IntermediateNode(bob.publicKey) :: Nil, BlindedPath(replyPath), TlvStream.empty) + val Right(message) = buildMessage(sessionKey, pathSecret, IntermediateNode(alice.publicKey) :: IntermediateNode(bob.publicKey) :: Nil, BlindedPath(replyPath), TlvStream.empty) assert(message == onionForAlice) // Checking that the onion is relayed properly @@ -136,81 +136,81 @@ class OnionMessagesSpec extends AnyFunSuite { test("relay message from alice to bob") { val alice = PrivateKey(hex"414141414141414141414141414141414141414141414141414141414141414101") val bob = PrivateKey(hex"424242424242424242424242424242424242424242424242424242424242424201") - val blindingSecret = PrivateKey(hex"050505050505050505050505050505050505050505050505050505050505050501") - val blindingKey = PublicKey(hex"0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f7") - assert(blindingSecret.publicKey == blindingKey) + val pathSecret = PrivateKey(hex"050505050505050505050505050505050505050505050505050505050505050501") + val pathKey = PublicKey(hex"0362c0a046dacce86ddd0343c6d3c7c79c2208ba0d9c9cf24a6d046d21d21f90f7") + assert(pathSecret.publicKey == pathKey) val sharedSecret = ByteVector32(hex"2e83e9bc7821d3f6cec7301fa8493aee407557624fb5745bede9084852430e3f") - assert(Sphinx.computeSharedSecret(alice.publicKey, blindingSecret) == sharedSecret) - assert(Sphinx.computeSharedSecret(blindingKey, alice) == sharedSecret) + assert(Sphinx.computeSharedSecret(alice.publicKey, pathSecret) == sharedSecret) + assert(Sphinx.computeSharedSecret(pathKey, alice) == sharedSecret) assert(Sphinx.mac(ByteVector("blinded_node_id".getBytes), sharedSecret) == ByteVector32(hex"7d846b3445621d49a665e5698c52141e9dda8fa2fe0c3da7e0f9008ccc588a38")) val blindedAlice = PublicKey(hex"02004b5662061e9db495a6ad112b6c4eba228a079e8e304d9df50d61043acbc014") val blindedPayload = TlvStream[RouteBlindingEncryptedDataTlv](OutgoingNodeId(bob.publicKey)) val encodedBlindedPayload = blindedRouteDataCodec.encode(blindedPayload).require.bytes assert(encodedBlindedPayload == hex"04210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c") - val blindedRoute = Sphinx.RouteBlinding.create(blindingSecret, alice.publicKey :: Nil, encodedBlindedPayload :: Nil).route - assert(blindedRoute.blindedNodes.head.blindedPublicKey == blindedAlice) - assert(Crypto.sha256(blindingKey.value ++ sharedSecret.bytes) == ByteVector32(hex"bae3d9ea2b06efd1b7b9b49b6cdcaad0e789474a6939ffa54ff5ec9224d5b76c")) - assert(blindedRoute.blindedNodes.head.encryptedPayload == hex"6970e870b473ddbc27e3098bfa45bb1aa54f1f637f803d957e6271d8ffeba89da2665d62123763d9b634e30714144a1c165ac9") - val Right(decryptedPayload) = RouteBlindingEncryptedDataCodecs.decode(alice, blindingKey, blindedRoute.blindedNodes.head.encryptedPayload) + val blindedRoute = Sphinx.RouteBlinding.create(pathSecret, alice.publicKey :: Nil, encodedBlindedPayload :: Nil).route + assert(blindedRoute.blindedHops.head.blindedPublicKey == blindedAlice) + assert(Crypto.sha256(pathKey.value ++ sharedSecret.bytes) == ByteVector32(hex"bae3d9ea2b06efd1b7b9b49b6cdcaad0e789474a6939ffa54ff5ec9224d5b76c")) + assert(blindedRoute.blindedHops.head.encryptedPayload == hex"6970e870b473ddbc27e3098bfa45bb1aa54f1f637f803d957e6271d8ffeba89da2665d62123763d9b634e30714144a1c165ac9") + val Right(decryptedPayload) = RouteBlindingEncryptedDataCodecs.decode(alice, pathKey, blindedRoute.blindedHops.head.encryptedPayload) assert(decryptedPayload.tlvs == blindedPayload) } - test("relay message from bob to carol with blinding override") { + test("relay message from bob to carol with path key override") { val bob = PrivateKey(hex"424242424242424242424242424242424242424242424242424242424242424201") val carol = PrivateKey(hex"434343434343434343434343434343434343434343434343434343434343434301") - val blindingSecret = PrivateKey(hex"76d4de6c329c79623842dcf8f8eaee90c9742df1b5231f5350df4a231d16ebcf01") - val blindingKey = PublicKey(hex"03fc5e56da97b462744c9a6b0ba9d5b3ffbfb1a08367af9cc6ea5ae03c79a78eec") - assert(blindingSecret.publicKey == blindingKey) + val pathSecret = PrivateKey(hex"76d4de6c329c79623842dcf8f8eaee90c9742df1b5231f5350df4a231d16ebcf01") + val pathKey = PublicKey(hex"03fc5e56da97b462744c9a6b0ba9d5b3ffbfb1a08367af9cc6ea5ae03c79a78eec") + assert(pathSecret.publicKey == pathKey) val sharedSecret = ByteVector32(hex"f18a1ddb1cb27d8fc4faf2cf317e87524fcc6b7f053496d95bf6e6809d09851e") - assert(Sphinx.computeSharedSecret(bob.publicKey, blindingSecret) == sharedSecret) - assert(Sphinx.computeSharedSecret(blindingKey, bob) == sharedSecret) + assert(Sphinx.computeSharedSecret(bob.publicKey, pathSecret) == sharedSecret) + assert(Sphinx.computeSharedSecret(pathKey, bob) == sharedSecret) assert(Sphinx.mac(ByteVector("blinded_node_id".getBytes), sharedSecret) == ByteVector32(hex"8074773a3745818b0d97dd875023486cc35e7afd95f5e9ec1363f517979e8373")) val blindedBob = PublicKey(hex"026ea8e36f78e038c659beba9229699796127471d9c7a24a0308533371fd63ad48") - val blindingOverride = PrivateKey(hex"070707070707070707070707070707070707070707070707070707070707070701").publicKey - val blindedPayload = TlvStream[RouteBlindingEncryptedDataTlv](OutgoingNodeId(carol.publicKey), NextBlinding(blindingOverride)) + val pathKeyOverride = PrivateKey(hex"070707070707070707070707070707070707070707070707070707070707070701").publicKey + val blindedPayload = TlvStream[RouteBlindingEncryptedDataTlv](OutgoingNodeId(carol.publicKey), NextPathKey(pathKeyOverride)) val encodedBlindedPayload = blindedRouteDataCodec.encode(blindedPayload).require.bytes assert(encodedBlindedPayload == hex"0421027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007082102989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f") - val blindedRoute = Sphinx.RouteBlinding.create(blindingSecret, bob.publicKey :: Nil, encodedBlindedPayload :: Nil).route - assert(blindedRoute.blindedNodes.head.blindedPublicKey == blindedBob) - assert(Crypto.sha256(blindingKey.value ++ sharedSecret.bytes) == ByteVector32(hex"9afb8b2ebc174dcf9e270be24771da7796542398d29d4ff6a4e7b6b4b9205cfe")) - assert(blindedRoute.blindedNodes.head.encryptedPayload == hex"1630da85e8759b8f3b94d74a539c6f0d870a87cf03d4986175865a2985553c997b560c32613bd9184c1a6d41a37027aabdab5433009d8409a1b638eb90373778a05716af2c2140b3196dca23997cdad4cfa7a7adc8d4") - val Right(decryptedPayload) = RouteBlindingEncryptedDataCodecs.decode(bob, blindingKey, blindedRoute.blindedNodes.head.encryptedPayload) + val blindedRoute = Sphinx.RouteBlinding.create(pathSecret, bob.publicKey :: Nil, encodedBlindedPayload :: Nil).route + assert(blindedRoute.blindedHops.head.blindedPublicKey == blindedBob) + assert(Crypto.sha256(pathKey.value ++ sharedSecret.bytes) == ByteVector32(hex"9afb8b2ebc174dcf9e270be24771da7796542398d29d4ff6a4e7b6b4b9205cfe")) + assert(blindedRoute.blindedHops.head.encryptedPayload == hex"1630da85e8759b8f3b94d74a539c6f0d870a87cf03d4986175865a2985553c997b560c32613bd9184c1a6d41a37027aabdab5433009d8409a1b638eb90373778a05716af2c2140b3196dca23997cdad4cfa7a7adc8d4") + val Right(decryptedPayload) = RouteBlindingEncryptedDataCodecs.decode(bob, pathKey, blindedRoute.blindedHops.head.encryptedPayload) assert(decryptedPayload.tlvs == blindedPayload) - assert(decryptedPayload.nextBlinding == blindingOverride) + assert(decryptedPayload.nextPathKey == pathKeyOverride) } test("relay message from carol to dave with padding") { val carol = PrivateKey(hex"434343434343434343434343434343434343434343434343434343434343434301") val dave = PrivateKey(hex"444444444444444444444444444444444444444444444444444444444444444401") - val blindingSecret = PrivateKey(hex"070707070707070707070707070707070707070707070707070707070707070701") - val blindingKey = PublicKey(hex"02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f") - assert(blindingSecret.publicKey == blindingKey) + val pathSecret = PrivateKey(hex"070707070707070707070707070707070707070707070707070707070707070701") + val pathKey = PublicKey(hex"02989c0b76cb563971fdc9bef31ec06c3560f3249d6ee9e5d83c57625596e05f6f") + assert(pathSecret.publicKey == pathKey) val sharedSecret = ByteVector32(hex"8c0f7716da996c4913d720dbf691b559a4945bf70cdd18e0b61e3e42635efc9c") - assert(Sphinx.computeSharedSecret(carol.publicKey, blindingSecret) == sharedSecret) - assert(Sphinx.computeSharedSecret(blindingKey, carol) == sharedSecret) + assert(Sphinx.computeSharedSecret(carol.publicKey, pathSecret) == sharedSecret) + assert(Sphinx.computeSharedSecret(pathKey, carol) == sharedSecret) assert(Sphinx.mac(ByteVector("blinded_node_id".getBytes), sharedSecret) == ByteVector32(hex"02afb2187075c8af51488242194b44c02624785ccd6fd43b5796c68f3025bf88")) val blindedCarol = PublicKey(hex"02f4f524562868a09d5f54fb956ade3fa51ef071d64d923e395cc6db5e290ec67b") val blindedPayload = TlvStream[RouteBlindingEncryptedDataTlv](Padding(hex"0000000000000000000000000000000000000000000000000000000000000000000000"), OutgoingNodeId(dave.publicKey)) val encodedBlindedPayload = blindedRouteDataCodec.encode(blindedPayload).require.bytes assert(encodedBlindedPayload == hex"012300000000000000000000000000000000000000000000000000000000000000000000000421032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991") - val blindedRoute = Sphinx.RouteBlinding.create(blindingSecret, carol.publicKey :: Nil, encodedBlindedPayload :: Nil).route - assert(blindedRoute.blindedNodes.head.blindedPublicKey == blindedCarol) - assert(Crypto.sha256(blindingKey.value ++ sharedSecret.bytes) == ByteVector32(hex"cc3b918cda6b1b049bdbe469c4dd952935e7c1518dd9c7ed0cd2cd5bc2742b82")) - assert(blindedRoute.blindedNodes.head.encryptedPayload == hex"8285acbceb37dfb38b877a888900539be656233cd74a55c55344fb068f9d8da365340d21db96fb41b76123207daeafdfb1f571e3fea07a22e10da35f03109a0380b3c69fcbed9c698086671809658761cf65ecbc3c07a2e5") - val Right(decryptedPayload) = RouteBlindingEncryptedDataCodecs.decode(carol, blindingKey, blindedRoute.blindedNodes.head.encryptedPayload) + val blindedRoute = Sphinx.RouteBlinding.create(pathSecret, carol.publicKey :: Nil, encodedBlindedPayload :: Nil).route + assert(blindedRoute.blindedHops.head.blindedPublicKey == blindedCarol) + assert(Crypto.sha256(pathKey.value ++ sharedSecret.bytes) == ByteVector32(hex"cc3b918cda6b1b049bdbe469c4dd952935e7c1518dd9c7ed0cd2cd5bc2742b82")) + assert(blindedRoute.blindedHops.head.encryptedPayload == hex"8285acbceb37dfb38b877a888900539be656233cd74a55c55344fb068f9d8da365340d21db96fb41b76123207daeafdfb1f571e3fea07a22e10da35f03109a0380b3c69fcbed9c698086671809658761cf65ecbc3c07a2e5") + val Right(decryptedPayload) = RouteBlindingEncryptedDataCodecs.decode(carol, pathKey, blindedRoute.blindedHops.head.encryptedPayload) assert(decryptedPayload.tlvs == blindedPayload) } test("build message with existing route") { val sessionKey = randomKey() - val blindingSecret = randomKey() - val blindingOverride = randomKey() + val pathSecret = randomKey() + val pathKeyOverride = randomKey() val destination = randomKey() - val replyPath = buildRoute(blindingOverride, IntermediateNode(destination.publicKey) :: Nil, Recipient(destination.publicKey, pathId = Some(hex"01234567"))).route - assert(replyPath.blindingKey == blindingOverride.publicKey) - assert(replyPath.introductionNodeId == EncodedNodeId(destination.publicKey)) - val Right(message) = buildMessage(sessionKey, blindingSecret, Nil, BlindedPath(replyPath), TlvStream.empty) - assert(message.blindingKey == blindingOverride.publicKey) // blindingSecret was not used as the replyPath was used as is + val replyPath = buildRoute(pathKeyOverride, IntermediateNode(destination.publicKey) :: Nil, Recipient(destination.publicKey, pathId = Some(hex"01234567"))).route + assert(replyPath.firstPathKey == pathKeyOverride.publicKey) + assert(replyPath.firstNodeId == EncodedNodeId(destination.publicKey)) + val Right(message) = buildMessage(sessionKey, pathSecret, Nil, BlindedPath(replyPath), TlvStream.empty) + assert(message.pathKey == pathKeyOverride.publicKey) // pathSecret was not used as the replyPath was used as is process(destination, message) match { case SendMessage(Right(EncodedNodeId.WithPublicKey.Plain(nextNodeId2)), message2) => @@ -228,9 +228,9 @@ class OnionMessagesSpec extends AnyFunSuite { val bob = randomKey() val carol = randomKey() val sessionKey = randomKey() - val blindingSecret = randomKey() + val pathSecret = randomKey() val pathId = randomBytes(65201) - val Right(messageForAlice) = buildMessage(sessionKey, blindingSecret, IntermediateNode(alice.publicKey) :: IntermediateNode(bob.publicKey) :: Nil, Recipient(carol.publicKey, Some(pathId)), TlvStream.empty) + val Right(messageForAlice) = buildMessage(sessionKey, pathSecret, IntermediateNode(alice.publicKey) :: IntermediateNode(bob.publicKey) :: Nil, Recipient(carol.publicKey, Some(pathId)), TlvStream.empty) // Checking that the onion is relayed properly process(alice, messageForAlice) match { @@ -254,17 +254,17 @@ class OnionMessagesSpec extends AnyFunSuite { val bob = randomKey() val carol = randomKey() val sessionKey = randomKey() - val blindingSecret = randomKey() + val pathKeySecret = randomKey() val pathId = randomBytes(65202) - assert(buildMessage(sessionKey, blindingSecret, IntermediateNode(alice.publicKey) :: IntermediateNode(bob.publicKey) :: Nil, Recipient(carol.publicKey, Some(pathId)), TlvStream.empty) == Left(MessageTooLarge(65433))) + assert(buildMessage(sessionKey, pathKeySecret, IntermediateNode(alice.publicKey) :: IntermediateNode(bob.publicKey) :: Nil, Recipient(carol.publicKey, Some(pathId)), TlvStream.empty) == Left(MessageTooLarge(65433))) } private case class OnionMessageTestVector(comment: String, generate: OMTVGenerate, route: OMTVRoute, onionmessage: OMTVOnionmessage, decrypt: OMTVDecrypt) private case class OMTVGenerate(comment: String, session_key: String, hops: Seq[OMTVGenerateHop]) - private case class OMTVGenerateHop(alias: String, comment: String, blinding_secret: String, tlvs: OMTVTlvs, encrypted_data_tlv: String, encrypted_recipient_data: String, blinded_node_id: String) - private case class OMTVRoute(comment: String, introduction_node_id: String, blinding: String, hops: Seq[OMTVRouteHop]) + private case class OMTVGenerateHop(alias: String, comment: String, path_key_secret: String, tlvs: OMTVTlvs, encrypted_data_tlv: String, encrypted_recipient_data: String, blinded_node_id: String) + private case class OMTVRoute(comment: String, first_node_id: String, first_path_key: String, hops: Seq[OMTVRouteHop]) private case class OMTVRouteHop(blinded_node_id: String, encrypted_recipient_data: String) private case class OMTVOnionmessage(comment: String, onion_message_packet: String, unknown_tag_1: Option[String]) { def getCustomTlvs: Set[GenericTlv] = Set( @@ -276,7 +276,7 @@ class OnionMessagesSpec extends AnyFunSuite { private case class OMTVTlvs(padding: Option[String], path_id: Option[String], next_node_id: Option[String], - next_blinding_override: Option[String], + next_pathKey_override: Option[String], encrypted_recipient_data: Option[String], unknown_tag_1: Option[String], unknown_tag_561: Option[String], @@ -306,30 +306,30 @@ class OnionMessagesSpec extends AnyFunSuite { def makeIntermediateNode(nodeKey: PrivateKey, tlvs: OMTVTlvs): IntermediateNode = IntermediateNode(nodeKey.publicKey, EncodedNodeId(nodeKey.publicKey), None, tlvs.padding.map(ByteVector.fromValidHex(_)), tlvs.getCustomTlvs) - val blindingSecretBob = PrivateKey(ByteVector32.fromValidHex(testVector.generate.hops(1).blinding_secret)) + val pathSecretBob = PrivateKey(ByteVector32.fromValidHex(testVector.generate.hops(1).path_key_secret)) val pathId = ByteVector.fromValidHex(testVector.generate.hops(3).tlvs.path_id.get) val pathBobToDave = - buildRoute(blindingSecretBob, + buildRoute(pathSecretBob, Seq(makeIntermediateNode(bob, testVector.generate.hops(1).tlvs), makeIntermediateNode(carol, testVector.generate.hops(2).tlvs)), makeRecipient(dave, testVector.generate.hops(3).tlvs)).route - val blindingSecretAlice = PrivateKey(ByteVector32.fromValidHex(testVector.generate.hops(0).blinding_secret)) + val pathSecretAlice = PrivateKey(ByteVector32.fromValidHex(testVector.generate.hops(0).path_key_secret)) val intermediateAlice = Seq(makeIntermediateNode(alice, testVector.generate.hops(0).tlvs)) - val pathAliceToDave = buildRouteFrom(blindingSecretAlice, intermediateAlice, BlindedPath(pathBobToDave)) + val pathAliceToDave = buildRouteFrom(pathSecretAlice, intermediateAlice, BlindedPath(pathBobToDave)) val expectedPath = BlindedRoute( - EncodedNodeId(PublicKey(ByteVector.fromValidHex(testVector.route.introduction_node_id))), - PublicKey(ByteVector.fromValidHex(testVector.route.blinding)), + EncodedNodeId(PublicKey(ByteVector.fromValidHex(testVector.route.first_node_id))), + PublicKey(ByteVector.fromValidHex(testVector.route.first_path_key)), Seq( - BlindedNode( + BlindedHop( PublicKey(ByteVector.fromValidHex(testVector.route.hops(0).blinded_node_id)), ByteVector.fromValidHex(testVector.route.hops(0).encrypted_recipient_data)), - BlindedNode( + BlindedHop( PublicKey(ByteVector.fromValidHex(testVector.route.hops(1).blinded_node_id)), ByteVector.fromValidHex(testVector.route.hops(1).encrypted_recipient_data)), - BlindedNode( + BlindedHop( PublicKey(ByteVector.fromValidHex(testVector.route.hops(2).blinded_node_id)), ByteVector.fromValidHex(testVector.route.hops(2).encrypted_recipient_data)), - BlindedNode( + BlindedHop( PublicKey(ByteVector.fromValidHex(testVector.route.hops(3).blinded_node_id)), ByteVector.fromValidHex(testVector.route.hops(3).encrypted_recipient_data)), ) @@ -339,7 +339,7 @@ class OnionMessagesSpec extends AnyFunSuite { val sessionKey = PrivateKey(ByteVector32.fromValidHex(testVector.generate.session_key)) val messageContent = TlvStream(Set.empty[OnionMessagePayloadTlv], testVector.onionmessage.getCustomTlvs) - val Right(message) = buildMessage(sessionKey, blindingSecretAlice, intermediateAlice, BlindedPath(pathBobToDave), messageContent) + val Right(message) = buildMessage(sessionKey, pathSecretAlice, intermediateAlice, BlindedPath(pathBobToDave), messageContent) val encodedPacket = OnionRoutingCodecs.onionRoutingPacketCodec(1300).encode(message.onionRoutingPacket).require.bytes assert(encodedPacket.toHex == testVector.onionmessage.onion_message_packet) @@ -372,9 +372,9 @@ class OnionMessagesSpec extends AnyFunSuite { val bob2carol = ShortChannelId(2) val carol = randomKey() val sessionKey = randomKey() - val blindingSecret = randomKey() + val pathSecret = randomKey() val pathId = randomBytes(64) - val Right(messageForAlice) = buildMessage(sessionKey, blindingSecret, IntermediateNode(alice.publicKey, EncodedNodeId(alice.publicKey), outgoingChannel_opt = Some(alice2bob)) :: IntermediateNode(bob.publicKey, EncodedNodeId(bob.publicKey), outgoingChannel_opt = Some(bob2carol)) :: Nil, Recipient(carol.publicKey, Some(pathId)), TlvStream.empty) + val Right(messageForAlice) = buildMessage(sessionKey, pathSecret, IntermediateNode(alice.publicKey, EncodedNodeId(alice.publicKey), outgoingChannel_opt = Some(alice2bob)) :: IntermediateNode(bob.publicKey, EncodedNodeId(bob.publicKey), outgoingChannel_opt = Some(bob2carol)) :: Nil, Recipient(carol.publicKey, Some(pathId)), TlvStream.empty) // Checking that the onion is relayed properly process(alice, messageForAlice) match { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/message/PostmanSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/message/PostmanSpec.scala index b34133a6b0..72f345d28f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/message/PostmanSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/message/PostmanSpec.scala @@ -23,7 +23,7 @@ import akka.actor.typed.scaladsl.adapter.TypedActorRefOps import com.typesafe.config.ConfigFactory import fr.acinq.bitcoin.scalacompat.Block import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} -import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedNode, BlindedRoute} +import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedHop, BlindedRoute} import fr.acinq.eclair.io.MessageRelay.{Disconnected, Sent} import fr.acinq.eclair.io.PeerConnection.ConnectionResult import fr.acinq.eclair.io.{Peer, PeerConnection} @@ -203,7 +203,7 @@ class PostmanSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat val offer = OfferTypes.Offer(None, None, randomKey().publicKey, Features.empty, Block.LivenetGenesisBlock.hash) val invoiceRequest = OfferTypes.InvoiceRequest(offer, 1000 msat, 1, Features.empty, randomKey(), Block.LivenetGenesisBlock.hash) - val replyPath = BlindedRoute(EncodedNodeId(randomKey().publicKey), randomKey().publicKey, Seq(BlindedNode(randomKey().publicKey, hex""))) + val replyPath = BlindedRoute(EncodedNodeId(randomKey().publicKey), randomKey().publicKey, Seq(BlindedHop(randomKey().publicKey, hex""))) val invoiceRequestPayload = MessageOnion.InvoiceRequestPayload(TlvStream(InvoiceRequest(invoiceRequest.records), ReplyPath(replyPath)), TlvStream(PathId(hex"abcd"))) postman ! WrappedMessage(invoiceRequestPayload, randomKey()) @@ -235,7 +235,7 @@ class PostmanSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat assert(payload.records.unknown == Set(GenericTlv(UInt64(11), hex"012345"))) assert(payload.records.get[ReplyPath].nonEmpty) val replyPath = payload.records.get[ReplyPath].get.blindedRoute - assert(replyPath.introductionNodeId == EncodedNodeId(d.publicKey)) + assert(replyPath.firstNodeId == EncodedNodeId(d.publicKey)) assert(replyPath.length >= nodeParams.onionMessageConfig.minIntermediateHops) assert(nodeParams.onionMessageConfig.minIntermediateHops > 5) @@ -253,7 +253,7 @@ class PostmanSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat val recipientKey = randomKey() val route = buildRoute(randomKey(), Seq(), Recipient(recipientKey.publicKey, None)).route - val compactRoute = OfferTypes.BlindedPath(route.copy(introductionNodeId = EncodedNodeId.ShortChannelIdDir(isNode1 = false, RealShortChannelId(1234)))) + val compactRoute = OfferTypes.BlindedPath(route.copy(firstNodeId = EncodedNodeId.ShortChannelIdDir(isNode1 = false, RealShortChannelId(1234)))) postman ! SendMessage(compactRoute, FindRoute, TlvStream(Set.empty[OnionMessagePayloadTlv], Set(GenericTlv(UInt64(33), hex"abcd"))), expectsReply = false, messageSender.ref) val getNodeId = router.expectMessageType[Router.GetNodeId] @@ -287,7 +287,7 @@ class PostmanSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat val recipientKey = randomKey() val route = buildRoute(randomKey(), Seq(IntermediateNode(nodeParams.nodeId)), Recipient(recipientKey.publicKey, None)).route - val compactRoute = OfferTypes.BlindedPath(route.copy(introductionNodeId = EncodedNodeId.ShortChannelIdDir(isNode1 = true, RealShortChannelId(1234)))) + val compactRoute = OfferTypes.BlindedPath(route.copy(firstNodeId = EncodedNodeId.ShortChannelIdDir(isNode1 = true, RealShortChannelId(1234)))) postman ! SendMessage(compactRoute, FindRoute, TlvStream(Set.empty[OnionMessagePayloadTlv], Set(GenericTlv(UInt64(33), hex"abcd"))), expectsReply = false, messageSender.ref) val getNodeId = router.expectMessageType[Router.GetNodeId] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/Bolt12InvoiceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/Bolt12InvoiceSpec.scala index f8cd6e884e..10410d4d3a 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/Bolt12InvoiceSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/Bolt12InvoiceSpec.scala @@ -113,8 +113,8 @@ class Bolt12InvoiceSpec extends AnyFunSuite { val offer = Offer.withPaths(None, None, Seq(path1.route, path2.route), Features.empty, chain) val request = InvoiceRequest(offer, 11000 msat, 1, Features.empty, payerKey, chain) // Invoice is requested using path1. - assert(RouteBlinding.derivePrivateKey(nodeKey, path1.lastBlinding).publicKey == path1.route.blindedNodeIds.last) - val invoice = Bolt12Invoice(request, randomBytes32(), RouteBlinding.derivePrivateKey(nodeKey, path1.lastBlinding), 300 seconds, Features.empty, Seq(createPaymentBlindedRoute(nodeKey.publicKey))) + assert(RouteBlinding.derivePrivateKey(nodeKey, path1.lastPathKey).publicKey == path1.route.blindedNodeIds.last) + val invoice = Bolt12Invoice(request, randomBytes32(), RouteBlinding.derivePrivateKey(nodeKey, path1.lastPathKey), 300 seconds, Features.empty, Seq(createPaymentBlindedRoute(nodeKey.publicKey))) assert(invoice.validateFor(request, nodeKey.publicKey).isLeft) assert(invoice.validateFor(request, path1.route.blindedNodeIds.last).isRight) assert(invoice.validateFor(request, path2.route.blindedNodeIds.last).isLeft) @@ -290,7 +290,7 @@ class Bolt12InvoiceSpec extends AnyFunSuite { val invoice = Bolt12Invoice(decodedRequest, preimage, nodeKey, 300 seconds, Features.empty, Seq(createPaymentBlindedRoute(nodeKey.publicKey))) assert(Bolt12Invoice.fromString(invoice.toString).get.records == invoice.records) assert(invoice.validateFor(decodedRequest, nodeKey.publicKey).isRight) - // Invoice generation is not reproducible as the timestamp and blinding point will change but all other fields should be the same. + // Invoice generation is not reproducible as the timestamp and path key will change but all other fields should be the same. val encodedInvoice = "lni1qqswluyyp7j9aamd8l2ma23jyvvuvujqu5wq73jp38t02yr72s23evskyypylaf30uz3clmw4spxd3wvat4kc4m449q04wv9ferml6lkh3aqgp6syph79rq2kmcmxukp563ydtnr7a8ex85rvhs45zyudrtpjqqqqqqqq5srkudsqkppqv00gsulvwy3fhneygzg8hdr9hah5sc70xd9eed8vslm6u9jzx8yag9qqf8l2vtlq5w87m4vqfnvtn82adk9wadfgratnp2wg7l7ha4u0gzqwqahgwxsycqwtqlvu32j8mqxln456sxzh50k6avmgsndugtcp6wqcvqsxft50dexrade3n9us6tegq60tjjuc5f50jg8h43jr02r263wjfnwqqapd2vrfrwj2es7ne0wla08xnndgg655spddpn0zlru8fvqk6776fff60jphldzuw6wxgtxlne7ttvlp4tpmsghfh54atau5gwqqqqqqyqqqqqzqqpsqqqqqqqqqqqyqqqqqqqqqqqq2qqq5szxvtagn6nqyqfv4qsgtlj8vuulq6aca20u59mx5xzdg84pksgxgnahfamrsnnup8ck4l92qwm3kq9syypylaf30uz3clmw4spxd3wvat4kc4m449q04wv9ferml6lkh3aqgplsgrznfv8aysjyphv0usapr06mc4svfj9hlg4k9s263xd50dp0qdttrffypamzdxz84ftcvd52afx0je8adu4ppxq9z7yse0zh9qjmdwgz" val decodedInvoice = Bolt12Invoice.fromString(encodedInvoice).get assert(decodedInvoice.amount == invoice.amount) @@ -320,7 +320,7 @@ class Bolt12InvoiceSpec extends AnyFunSuite { val invoice = Bolt12Invoice(decodedRequest, preimage, nodeKey, 300 seconds, Features.empty, Seq(createPaymentBlindedRoute(nodeKey.publicKey))) assert(Bolt12Invoice.fromString(invoice.toString).get.records == invoice.records) assert(invoice.validateFor(decodedRequest, nodeKey.publicKey).isRight) - // Invoice generation is not reproducible as the timestamp and blinding point will change but all other fields should be the same. + // Invoice generation is not reproducible as the timestamp and path key will change but all other fields should be the same. val encodedInvoice = "lni1qqswg5pzt6anzaxaypy8y46zknl8zn2a2jqyzrp74gtfm4lp6utpkzcgqsdjuqsqpgvk66twd9kkzmpqdanxvetjypmkjargypsk6mm4de6pvggrcj9vjlsf709m46e4kq425mg89dthy6zp5dxjt9fp2l9v5c9pet64qgr0u2xq4dh3kdevrf4zg6hx8a60jv0gxe0ptgyfc6xkryqqqqqqqpfqgxewqmf9sggr86209t74drgj3upwe6zy449q58wl9f8r5z97ktd6zxelzy6tq5t6pgqrcj9vjlsf709m46e4kq425mg89dthy6zp5dxjt9fp2l9v5c9pet6s9x0wj2xtjxkql2urqn70fsyyhy8pcervfcaxdygsu74qe9jcss8uqypwa9rd3q0jh7tpruvr7xq7e4uzrk8z3mn68n5vzhxu4ds6d83qr4cq8f0mp833xq58twvuwlpm4gqkv5uwv07gl665ye2a33mk0tdkkzls04h25z3943cv5nq6e64dharmudq37remmgdvdv2vpt4zrsqqqqqpqqqqqqsqqvqqqqqqqqqqqpqqqqqqqqqqqqzsqq9yq3nzl2gl5cpqzt9gyqktpeas2gmx0p69psea4akj7tpukcfjygfjdcwpkjdvjl7a06mjp2syrvhqd54syypufzkf0cyl8ja6av6mq242d5rjk4mjdpq6xnf9j5s40jk2vzsu4a0sgq5pde5afeshaze029mqk5r48v07ph0uykc3ks034czmw58khfcw9gpv6d9l3nea06ajl4dqjr7ryrv9alx0eff9rklp7gnrkra0vuj3" val decodedInvoice = Bolt12Invoice.fromString(encodedInvoice).get assert(decodedInvoice.amount == invoice.amount) @@ -356,7 +356,7 @@ class Bolt12InvoiceSpec extends AnyFunSuite { val invoice = Bolt12Invoice(decodedRequest, preimage, nodeKey, 300 seconds, Features.empty, Seq(createPaymentBlindedRoute(nodeKey.publicKey))) assert(Bolt12Invoice.fromString(invoice.toString).get.records == invoice.records) assert(invoice.validateFor(decodedRequest, nodeKey.publicKey).isRight) - // Invoice generation is not reproducible as the timestamp and blinding point will change but all other fields should be the same. + // Invoice generation is not reproducible as the timestamp and path key will change but all other fields should be the same. val encodedInvoice = "lni1qqs8lqvnh3kg9uj003lxlxyj8hthymgq4p9ms0ag0ryx5uw8gsuus4gzypp5jl7hlqnf2ugg7j3slkwwcwht57vhyzzwjr4dq84rxzgqqqqqqzqrqxr2qzsndanxvetjypmkjargypch2ctww35hg7gjz9skc6trv4qxy6t8wd5x7upwvdhk69qzq05pvggry7hatxw6xgn0gcytj64sgtl9tzl4tqs360z7vlkv305evv3qgd84qgzrf9la07pxj4cs3a9rplvuasawhfuewgyyay826q02xvysqqqqqpfqxmwaqptqzjzcyyp8cmgrl28nvm3wlqqheha0t570rgaszg7mzvvzvwmx9s92nmyujkdq5qpj0t74n8dryfh5vz9ed2cy9lj43064sgga830x0mxgh6vkxgsyxnczgew6pkkhja3cl3dfxthumcmp6gkp446ha4tcj884eqch6g57newqzquqmar5nynwtg9lknq98yzslwla3vdxefulhq2jkwnqnsf7umpl5cqr58qkj63hkpl7ffyd6f3qgn3m5kuegehhakvxw7fuw29tf3r5wgj37uecjdw2th4t5fp7f99xvk4f3gwl0wyf2a558wqa9w3pcqqqqqqsqqqqqgqqxqqqqqqqqqqqqsqqqqqqqqqqqpgqqzjqgcuctck2vqsp9j5zqlsxsv7uy23npygenelt4q5sdh8ftc3x7rpd0hqlachjnj9z834s4gpkmhgqkqssxfa06kva5v3x73sgh94tqsh72k9l2kppr579uelvezlfjcezqs607pqxa3afljxyf2ua9dlqs33wrfzakt5tpraklpzfpn63uxa7el475x4sc0w4hs75e3nhe689slfz4ldqlwja3zaq0w3mnz79f4ne0c3r3c" val decodedInvoice = Bolt12Invoice.fromString(encodedInvoice).get assert(decodedInvoice.amount == invoice.amount) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala index 05da03564a..e3bae392a9 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartHandlerSpec.scala @@ -293,13 +293,13 @@ class MultiPartHandlerSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike assert(invoice.invoiceRequest.offer == offer) assert(invoice.blindedPaths.length == 3) assert(invoice.blindedPaths(0).route.blindedNodeIds.length == 4) - assert(invoice.blindedPaths(0).route.introductionNodeId == EncodedNodeId(a)) + assert(invoice.blindedPaths(0).route.firstNodeId == EncodedNodeId(a)) assert(invoice.blindedPaths(0).paymentInfo == PaymentInfo(1950 msat, 0, CltvExpiryDelta(193), 1 msat, 25_000 msat, Features.empty)) assert(invoice.blindedPaths(1).route.blindedNodeIds.length == 4) - assert(invoice.blindedPaths(1).route.introductionNodeId == EncodedNodeId(c)) + assert(invoice.blindedPaths(1).route.firstNodeId == EncodedNodeId(c)) assert(invoice.blindedPaths(1).paymentInfo == PaymentInfo(400 msat, 0, CltvExpiryDelta(183), 1 msat, 25_000 msat, Features.empty)) assert(invoice.blindedPaths(2).route.blindedNodeIds.length == 1) - assert(invoice.blindedPaths(2).route.introductionNodeId == EncodedNodeId(d)) + assert(invoice.blindedPaths(2).route.firstNodeId == EncodedNodeId(d)) assert(invoice.blindedPaths(2).paymentInfo == PaymentInfo(0 msat, 0, CltvExpiryDelta(18), 0 msat, 25_000 msat, Features.empty)) // Offer invoices shouldn't be stored in the DB until we receive a payment for it. assert(nodeParams.db.payments.getIncomingPayment(invoice.paymentHash).isEmpty) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala index 69464e1608..1ef679f196 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentInitiatorSpec.scala @@ -297,8 +297,8 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike val payerKey = randomKey() val invoice = createBolt12Invoice(Features.empty, payerKey) val resolvedPaths = invoice.blindedPaths.map(path => { - val introductionNodeId = path.route.introductionNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey - ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.blindingKey, path.route.blindedNodes), path.paymentInfo) + val introductionNodeId = path.route.firstNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey + ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.firstPathKey, path.route.blindedHops), path.paymentInfo) }) val req = SendPaymentToNode(sender.ref, finalAmount, invoice, resolvedPaths, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams, payerKey_opt = Some(payerKey)) sender.send(initiator, req) @@ -331,8 +331,8 @@ class PaymentInitiatorSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike val payerKey = randomKey() val invoice = createBolt12Invoice(Features(BasicMultiPartPayment -> Optional), payerKey) val resolvedPaths = invoice.blindedPaths.map(path => { - val introductionNodeId = path.route.introductionNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey - ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.blindingKey, path.route.blindedNodes), path.paymentInfo) + val introductionNodeId = path.route.firstNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey + ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.firstPathKey, path.route.blindedHops), path.paymentInfo) }) val req = SendPaymentToNode(sender.ref, finalAmount, invoice, resolvedPaths, 1, routeParams = nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams, payerKey_opt = Some(payerKey)) sender.send(initiator, req) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala index 761504b7ab..93cc4e5d35 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PaymentPacketSpec.scala @@ -168,9 +168,9 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { assert(payment.outgoingChannel == channelUpdate_ab.shortChannelId) assert(payment.cmd.amount >= amount_ab) assert(payment.cmd.cltvExpiry == expiry_ab) - assert(payment.cmd.nextBlindingKey_opt.isEmpty) + assert(payment.cmd.nextPathKey_opt.isEmpty) - val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Right(relay_b@ChannelRelayPacket(_, payload_b, packet_c)) = decrypt(add_b, priv_b.privateKey, Features.empty) assert(packet_c.payload.length == PaymentOnionCodecs.paymentOnionPayloadLength) assert(relay_b.amountToForward >= amount_bc) @@ -189,9 +189,9 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { assert(relay_c.relayFeeMsat == fee_c) assert(relay_c.expiryDelta == channelUpdate_cd.cltvExpiryDelta) assert(payload_c.isInstanceOf[IntermediatePayload.ChannelRelay.Blinded]) - val blinding_d = payload_c.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextBlinding + val pathKey_d = payload_c.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextPathKey - val add_d = UpdateAddHtlc(randomBytes32(), 2, relay_c.amountToForward, relay_c.add.paymentHash, relay_c.outgoingCltv, packet_d, Some(blinding_d), 1.0, None) + val add_d = UpdateAddHtlc(randomBytes32(), 2, relay_c.amountToForward, relay_c.add.paymentHash, relay_c.outgoingCltv, packet_d, Some(pathKey_d), 1.0, None) val Right(relay_d@ChannelRelayPacket(_, payload_d, packet_e)) = decrypt(add_d, priv_d.privateKey, Features(RouteBlinding -> Optional)) assert(packet_e.payload.length == PaymentOnionCodecs.paymentOnionPayloadLength) assert(relay_d.amountToForward >= amount_de) @@ -200,9 +200,9 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { assert(relay_d.relayFeeMsat == fee_d) assert(relay_d.expiryDelta == channelUpdate_de.cltvExpiryDelta) assert(payload_d.isInstanceOf[IntermediatePayload.ChannelRelay.Blinded]) - val blinding_e = payload_d.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextBlinding + val pathKey_e = payload_d.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextPathKey - val add_e = UpdateAddHtlc(randomBytes32(), 2, relay_d.amountToForward, relay_d.add.paymentHash, relay_d.outgoingCltv, packet_e, Some(blinding_e), 1.0, None) + val add_e = UpdateAddHtlc(randomBytes32(), 2, relay_d.amountToForward, relay_d.add.paymentHash, relay_d.outgoingCltv, packet_e, Some(pathKey_e), 1.0, None) val Right(FinalPacket(_, payload_e)) = decrypt(add_e, priv_e.privateKey, Features(RouteBlinding -> Optional)) assert(payload_e.amount == finalAmount) assert(payload_e.totalAmount == finalAmount) @@ -222,8 +222,8 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { val paymentInfo = PaymentInfo(0 msat, 0, CltvExpiryDelta(0), 1 msat, amount_bc, Features.empty) val invoice = Bolt12Invoice(invoiceRequest, paymentPreimage, recipientKey, 300 seconds, features, Seq(PaymentBlindedRoute(blindedRoute, paymentInfo))) val resolvedPaths = invoice.blindedPaths.map(path => { - val introductionNodeId = path.route.introductionNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey - ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.blindingKey, path.route.blindedNodes), path.paymentInfo) + val introductionNodeId = path.route.firstNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey + ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.firstPathKey, path.route.blindedHops), path.paymentInfo) }) val recipient = BlindedRecipient(invoice, resolvedPaths, amount_bc, expiry_bc, Set.empty) val hops = Seq(channelHopFromUpdate(a, b, channelUpdate_ab), channelHopFromUpdate(b, c, channelUpdate_bc)) @@ -231,9 +231,9 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { assert(payment.outgoingChannel == channelUpdate_ab.shortChannelId) assert(payment.cmd.amount == amount_ab) assert(payment.cmd.cltvExpiry == expiry_ab) - assert(payment.cmd.nextBlindingKey_opt.isEmpty) + assert(payment.cmd.nextPathKey_opt.isEmpty) - val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Right(relay_b@ChannelRelayPacket(_, payload_b, packet_c)) = decrypt(add_b, priv_b.privateKey, Features.empty) assert(packet_c.payload.length == PaymentOnionCodecs.paymentOnionPayloadLength) assert(relay_b.amountToForward >= amount_bc) @@ -257,9 +257,9 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { assert(payment.outgoingChannel == channelUpdate_ab.shortChannelId) assert(payment.cmd.amount == finalAmount) assert(payment.cmd.cltvExpiry == finalExpiry) - assert(payment.cmd.nextBlindingKey_opt.nonEmpty) + assert(payment.cmd.nextPathKey_opt.nonEmpty) - val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Right(FinalPacket(_, payload_b)) = decrypt(add_b, priv_b.privateKey, Features(RouteBlinding -> Optional)) assert(payload_b.amount == finalAmount) assert(payload_b.totalAmount == finalAmount) @@ -272,7 +272,7 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { val Right(payment) = buildOutgoingBlindedPaymentAB(paymentHash) assert(payment.outgoingChannel == channelUpdate_ab.shortChannelId) - val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount + 100.msat, payment.cmd.paymentHash, payment.cmd.cltvExpiry + CltvExpiryDelta(6), payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount + 100.msat, payment.cmd.paymentHash, payment.cmd.cltvExpiry + CltvExpiryDelta(6), payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Right(FinalPacket(_, payload_b)) = decrypt(add_b, priv_b.privateKey, Features(RouteBlinding -> Optional)) assert(payload_b.amount == finalAmount) assert(payload_b.totalAmount == finalAmount) @@ -473,12 +473,12 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { val invoiceRequest = InvoiceRequest(offer, amount_bc, 1, features, randomKey(), Block.RegtestGenesisBlock.hash) // We send the wrong blinded payload to the introduction node. val tmpBlindedRoute = BlindedRouteCreation.createBlindedRouteFromHops(Seq(channelHopFromUpdate(b, c, channelUpdate_bc)), hex"deadbeef", 1 msat, CltvExpiry(500_000)).route - val blindedRoute = tmpBlindedRoute.copy(blindedNodes = tmpBlindedRoute.blindedNodes.reverse) + val blindedRoute = tmpBlindedRoute.copy(blindedHops = tmpBlindedRoute.blindedHops.reverse) val paymentInfo = OfferTypes.PaymentInfo(fee_b, 0, channelUpdate_bc.cltvExpiryDelta, 0 msat, amount_bc, Features.empty) val invoice = Bolt12Invoice(invoiceRequest, paymentPreimage, priv_c.privateKey, 300 seconds, features, Seq(PaymentBlindedRoute(blindedRoute, paymentInfo))) val resolvedPaths = invoice.blindedPaths.map(path => { - val introductionNodeId = path.route.introductionNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey - ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.blindingKey, path.route.blindedNodes), path.paymentInfo) + val introductionNodeId = path.route.firstNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey + ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.firstPathKey, path.route.blindedHops), path.paymentInfo) }) val recipient = BlindedRecipient(invoice, resolvedPaths, amount_bc, expiry_bc, Set.empty) val route = Route(amount_bc, Seq(channelHopFromUpdate(a, b, channelUpdate_ab)), Some(recipient.blindedHops.head)) @@ -488,7 +488,7 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { assert(payment.outgoingChannel == channelUpdate_ab.shortChannelId) assert(payment.cmd.amount == amount_bc + fee_b) - val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Left(failure) = decrypt(add_b, priv_b.privateKey, Features(RouteBlinding -> Optional)) assert(failure.isInstanceOf[InvalidOnionBlinding]) } @@ -496,7 +496,7 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { test("fail to decrypt blinded payment when route blinding is disabled") { val (route, recipient) = shortBlindedHops() val Right(payment) = buildOutgoingPayment(TestConstants.emptyOrigin, paymentHash, route, recipient, 1.0) - val add_d = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_d = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Left(failure) = decrypt(add_d, priv_d.privateKey, Features.empty) // d doesn't support route blinding assert(failure == InvalidOnionPayload(UInt64(10), 0)) } @@ -526,15 +526,15 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { assert(payment.cmd.amount == amount_cd) // A smaller amount is sent to d, who doesn't know that it's invalid. - val add_d = UpdateAddHtlc(randomBytes32(), 0, amount_de, paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_d = UpdateAddHtlc(randomBytes32(), 0, amount_de, paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Right(relay_d@ChannelRelayPacket(_, payload_d, packet_e)) = decrypt(add_d, priv_d.privateKey, Features(RouteBlinding -> Optional)) assert(payload_d.outgoing.contains(channelUpdate_de.shortChannelId)) assert(relay_d.amountToForward < amount_de) assert(payload_d.isInstanceOf[IntermediatePayload.ChannelRelay.Blinded]) - val blinding_e = payload_d.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextBlinding + val pathKey_e = payload_d.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextPathKey // When e receives a smaller amount than expected, it rejects the payment. - val add_e = UpdateAddHtlc(randomBytes32(), 0, relay_d.amountToForward, paymentHash, relay_d.outgoingCltv, packet_e, Some(blinding_e), 1.0, None) + val add_e = UpdateAddHtlc(randomBytes32(), 0, relay_d.amountToForward, paymentHash, relay_d.outgoingCltv, packet_e, Some(pathKey_e), 1.0, None) val Left(failure) = decrypt(add_e, priv_e.privateKey, Features(RouteBlinding -> Optional)) assert(failure.isInstanceOf[InvalidOnionBlinding]) } @@ -548,15 +548,15 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { // A smaller expiry is sent to d, who doesn't know that it's invalid. // Intermediate nodes can reduce the expiry by at most min_final_expiry_delta. val invalidExpiry = payment.cmd.cltvExpiry - Channel.MIN_CLTV_EXPIRY_DELTA - CltvExpiryDelta(1) - val add_d = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, paymentHash, invalidExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_d = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, paymentHash, invalidExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Right(relay_d@ChannelRelayPacket(_, payload_d, packet_e)) = decrypt(add_d, priv_d.privateKey, Features(RouteBlinding -> Optional)) assert(payload_d.outgoing.contains(channelUpdate_de.shortChannelId)) assert(relay_d.outgoingCltv < CltvExpiry(currentBlockCount)) assert(payload_d.isInstanceOf[IntermediatePayload.ChannelRelay.Blinded]) - val blinding_e = payload_d.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextBlinding + val pathKey_e = payload_d.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextPathKey // When e receives a smaller expiry than expected, it rejects the payment. - val add_e = UpdateAddHtlc(randomBytes32(), 0, relay_d.amountToForward, paymentHash, relay_d.outgoingCltv, packet_e, Some(blinding_e), 1.0, None) + val add_e = UpdateAddHtlc(randomBytes32(), 0, relay_d.amountToForward, paymentHash, relay_d.outgoingCltv, packet_e, Some(pathKey_e), 1.0, None) val Left(failure) = decrypt(add_e, priv_e.privateKey, Features(RouteBlinding -> Optional)) assert(failure.isInstanceOf[InvalidOnionBlinding]) } @@ -568,7 +568,7 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { assert(payment.outgoingChannel == channelUpdate_cd.shortChannelId) assert(payment.cmd.cltvExpiry > expiry_de) - val add_d = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_d = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Left(failure) = decrypt(add_d, priv_d.privateKey, Features(RouteBlinding -> Optional)) assert(failure.isInstanceOf[InvalidOnionBlinding]) } @@ -666,15 +666,15 @@ class PaymentPacketSpec extends AnyFunSuite with BeforeAndAfterAll { // a -> b -> c -> d -> e, blinded after c val (_, route, recipient) = longBlindedHops(hex"0451") val Right(payment) = buildOutgoingPayment(TestConstants.emptyOrigin, paymentHash, route, recipient, 1.0) - val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_b = UpdateAddHtlc(randomBytes32(), 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) val Right(ChannelRelayPacket(_, _, packet_c)) = decrypt(add_b, priv_b.privateKey, Features.empty) val add_c = UpdateAddHtlc(randomBytes32(), 1, amount_bc, paymentHash, expiry_bc, packet_c, None, 1.0, None) val Right(ChannelRelayPacket(_, payload_c, packet_d)) = decrypt(add_c, priv_c.privateKey, Features(RouteBlinding -> Optional)) - val blinding_d = payload_c.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextBlinding - val add_d = UpdateAddHtlc(randomBytes32(), 2, amount_cd, paymentHash, expiry_cd, packet_d, Some(blinding_d), 1.0, None) + val pathKey_d = payload_c.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextPathKey + val add_d = UpdateAddHtlc(randomBytes32(), 2, amount_cd, paymentHash, expiry_cd, packet_d, Some(pathKey_d), 1.0, None) val Right(ChannelRelayPacket(_, payload_d, packet_e)) = decrypt(add_d, priv_d.privateKey, Features(RouteBlinding -> Optional)) - val blinding_e = payload_d.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextBlinding - val add_e = UpdateAddHtlc(randomBytes32(), 3, amount_de, paymentHash, expiry_de, packet_e, Some(blinding_e), 1.0, None) + val pathKey_e = payload_d.asInstanceOf[IntermediatePayload.ChannelRelay.Blinded].nextPathKey + val add_e = UpdateAddHtlc(randomBytes32(), 3, amount_de, paymentHash, expiry_de, packet_e, Some(pathKey_e), 1.0, None) val Right(FinalPacket(_, payload_e)) = decrypt(add_e, priv_e.privateKey, Features(RouteBlinding -> Optional)) assert(payload_e.isInstanceOf[FinalPayload.Blinded]) @@ -770,9 +770,9 @@ object PaymentPacketSpec { def buildOutgoingBlindedPaymentAB(paymentHash: ByteVector32, routeExpiry: CltvExpiry = CltvExpiry(500_000)): Either[OutgoingPaymentError, OutgoingPaymentPacket] = { val blindedRoute = BlindedRouteCreation.createBlindedRouteWithoutHops(b, hex"deadbeef", 1.msat, routeExpiry).route - val finalPayload = NodePayload(blindedRoute.introductionNode.blindedPublicKey, OutgoingBlindedPerHopPayload.createFinalPayload(finalAmount, finalAmount, finalExpiry, blindedRoute.introductionNode.encryptedPayload)) + val finalPayload = NodePayload(blindedRoute.firstNode.blindedPublicKey, OutgoingBlindedPerHopPayload.createFinalPayload(finalAmount, finalAmount, finalExpiry, blindedRoute.firstNode.encryptedPayload)) val onion = buildOnion(Seq(finalPayload), paymentHash, Some(PaymentOnionCodecs.paymentOnionPayloadLength)).toOption.get // BOLT 2 requires that associatedData == paymentHash - val cmd = CMD_ADD_HTLC(ActorRef.noSender, finalAmount, paymentHash, finalExpiry, onion.packet, Some(blindedRoute.blindingKey), 1.0, None, TestConstants.emptyOrigin, commit = true) + val cmd = CMD_ADD_HTLC(ActorRef.noSender, finalAmount, paymentHash, finalExpiry, onion.packet, Some(blindedRoute.firstPathKey), 1.0, None, TestConstants.emptyOrigin, commit = true) Right(OutgoingPaymentPacket(cmd, channelUpdate_ab.shortChannelId, onion.sharedSecrets)) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala index f2e4d12e90..480bcb9db7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/PostRestartHtlcCleanerSpec.scala @@ -787,7 +787,7 @@ object PostRestartHtlcCleanerSpec { val (route, recipient) = (Route(finalAmount, hops, None), SpontaneousRecipient(e, finalAmount, finalExpiry, randomBytes32())) buildOutgoingPayment(TestConstants.emptyOrigin, paymentHash, route, recipient, 1.0) } - UpdateAddHtlc(channelId, htlcId, payment.cmd.amount, paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + UpdateAddHtlc(channelId, htlcId, payment.cmd.amount, paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) } def buildHtlcIn(htlcId: Long, channelId: ByteVector32, paymentHash: ByteVector32, blinded: Boolean = false): DirectedHtlc = IncomingHtlc(buildHtlc(htlcId, channelId, paymentHash, blinded)) @@ -819,11 +819,11 @@ object PostRestartHtlcCleanerSpec { val parentId = UUID.randomUUID() val (id1, id2, id3) = (UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()) - val add1 = UpdateAddHtlc(channelId_bc_1, 72, 561 msat, paymentHash1, CltvExpiry(4200), onionRoutingPacket = TestConstants.emptyOnionPacket, blinding_opt = None, confidence = 1.0, fundingFee_opt = None) + val add1 = UpdateAddHtlc(channelId_bc_1, 72, 561 msat, paymentHash1, CltvExpiry(4200), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) val origin1 = Origin.Cold(Upstream.Local(id1)) - val add2 = UpdateAddHtlc(channelId_bc_1, 75, 1105 msat, paymentHash2, CltvExpiry(4250), onionRoutingPacket = TestConstants.emptyOnionPacket, blinding_opt = None, confidence = 1.0, fundingFee_opt = None) + val add2 = UpdateAddHtlc(channelId_bc_1, 75, 1105 msat, paymentHash2, CltvExpiry(4250), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) val origin2 = Origin.Cold(Upstream.Local(id2)) - val add3 = UpdateAddHtlc(channelId_bc_1, 78, 1729 msat, paymentHash2, CltvExpiry(4300), onionRoutingPacket = TestConstants.emptyOnionPacket, blinding_opt = None, confidence = 1.0, fundingFee_opt = None) + val add3 = UpdateAddHtlc(channelId_bc_1, 78, 1729 msat, paymentHash2, CltvExpiry(4300), onionRoutingPacket = TestConstants.emptyOnionPacket, pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) val origin3 = Origin.Cold(Upstream.Local(id3)) // Prepare channels and payment state before restart. diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/offer/OfferManagerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/offer/OfferManagerSpec.scala index 1b41f2c07d..bfc9008bca 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/offer/OfferManagerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/offer/OfferManagerSpec.scala @@ -89,7 +89,7 @@ class OfferManagerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("app assert(invoice.blindedPaths.length == 1) val blindedPath = invoice.blindedPaths.head.route - val Right(RouteBlindingDecryptedData(encryptedDataTlvs, _)) = RouteBlindingEncryptedDataCodecs.decode(nodeParams.privateKey, blindedPath.blindingKey, blindedPath.encryptedPayloads.head) + val Right(RouteBlindingDecryptedData(encryptedDataTlvs, _)) = RouteBlindingEncryptedDataCodecs.decode(nodeParams.privateKey, blindedPath.firstPathKey, blindedPath.encryptedPayloads.head) val paymentTlvs = TlvStream[OnionPaymentPayloadTlv]( OnionPaymentPayloadTlv.AmountToForward(invoice.amount), OnionPaymentPayloadTlv.TotalAmount(invoice.amount), diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala index ea2d35b05a..883d4c23f4 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala @@ -241,7 +241,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a val fwdNodeId = register.expectMessageType[ForwardNodeId[Peer.ProposeOnTheFlyFunding]] assert(fwdNodeId.nodeId == outgoingNodeId) - assert(fwdNodeId.message.nextBlindingKey_opt.nonEmpty) + assert(fwdNodeId.message.nextPathKey_opt.nonEmpty) assert(fwdNodeId.message.amount == outgoingAmount) assert(fwdNodeId.message.expiry == outgoingExpiry) } @@ -832,7 +832,7 @@ object ChannelRelayerSpec { def createBlindedPayload(outgoing: Either[PublicKey, ShortChannelId], update: ChannelUpdate, isIntroduction: Boolean): ChannelRelay.Blinded = { val tlvs = TlvStream[OnionPaymentPayloadTlv](Set( Some(OnionPaymentPayloadTlv.EncryptedRecipientData(hex"2a")), - if (isIntroduction) Some(OnionPaymentPayloadTlv.BlindingPoint(randomKey().publicKey)) else None, + if (isIntroduction) Some(OnionPaymentPayloadTlv.PathKey(randomKey().publicKey)) else None, ).flatten[OnionPaymentPayloadTlv]) val blindedTlvs = TlvStream[RouteBlindingEncryptedDataTlv]( outgoing match { @@ -846,11 +846,11 @@ object ChannelRelayerSpec { } def createValidIncomingPacket(payload: IntermediatePayload.ChannelRelay, amountIn: MilliSatoshi = 11_000_000 msat, expiryIn: CltvExpiry = CltvExpiry(400_100), endorsementIn: Int = 7): IncomingPaymentPacket.ChannelRelayPacket = { - val nextBlinding_opt = payload match { - case p: ChannelRelay.Blinded => Some(UpdateAddHtlcTlv.BlindingPoint(p.nextBlinding)) + val nextPathKey_opt = payload match { + case p: ChannelRelay.Blinded => Some(UpdateAddHtlcTlv.PathKey(p.nextPathKey)) case _: ChannelRelay.Standard => None } - val tlvs = TlvStream(Set[Option[UpdateAddHtlcTlv]](nextBlinding_opt, Some(UpdateAddHtlcTlv.Endorsement(endorsementIn))).flatten) + val tlvs = TlvStream(Set[Option[UpdateAddHtlcTlv]](nextPathKey_opt, Some(UpdateAddHtlcTlv.Endorsement(endorsementIn))).flatten) val add_ab = UpdateAddHtlc(channelId = randomBytes32(), id = 123456, amountIn, paymentHash, expiryIn, emptyOnionPacket, tlvs) ChannelRelayPacket(add_ab, payload, emptyOnionPacket) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala index d8a4ce75e9..c534b641e0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/NodeRelayerSpec.scala @@ -660,7 +660,7 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl outgoingPayment.replyTo ! PaymentFailed(relayId, paymentHash, LocalFailure(outgoingAmount, Nil, BalanceTooLow) :: Nil) val fwd = register.expectMessageType[Register.ForwardNodeId[Peer.ProposeOnTheFlyFunding]] assert(fwd.nodeId == outgoingNodeId) - assert(fwd.message.nextBlindingKey_opt.isEmpty) + assert(fwd.message.nextPathKey_opt.isEmpty) assert(fwd.message.onion.payload.size == PaymentOnionCodecs.paymentOnionPayloadLength) // We verify that the next node is able to decrypt the onion that we will send in will_add_htlc. val dummyAdd = UpdateAddHtlc(randomBytes32(), 0, fwd.message.amount, fwd.message.paymentHash, fwd.message.expiry, fwd.message.onion, None, 1.0, None) @@ -950,10 +950,10 @@ class NodeRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("appl outgoingPayment.replyTo ! PaymentFailed(relayId, paymentHash, LocalFailure(outgoingAmount, Nil, BalanceTooLow) :: Nil) val fwd = register.expectMessageType[Register.ForwardNodeId[Peer.ProposeOnTheFlyFunding]] assert(fwd.nodeId == outgoingNodeId) - assert(fwd.message.nextBlindingKey_opt.nonEmpty) + assert(fwd.message.nextPathKey_opt.nonEmpty) assert(fwd.message.onion.payload.size == PaymentOnionCodecs.paymentOnionPayloadLength) // We verify that the next node is able to decrypt the onion that we will send in will_add_htlc. - val dummyAdd = UpdateAddHtlc(randomBytes32(), 0, fwd.message.amount, fwd.message.paymentHash, fwd.message.expiry, fwd.message.onion, fwd.message.nextBlindingKey_opt, 1.0, None) + val dummyAdd = UpdateAddHtlc(randomBytes32(), 0, fwd.message.amount, fwd.message.paymentHash, fwd.message.expiry, fwd.message.onion, fwd.message.nextPathKey_opt, 1.0, None) val Right(incoming) = IncomingPaymentPacket.decrypt(dummyAdd, outgoingNodeKey, nodeParams.features) assert(incoming.isInstanceOf[IncomingPaymentPacket.FinalPacket]) val finalPayload = incoming.asInstanceOf[IncomingPaymentPacket.FinalPacket].payload.asInstanceOf[FinalPayload.Blinded] @@ -1162,7 +1162,7 @@ object NodeRelayerSpec { val paymentBlindedRoute = scidDir_opt match { case Some(scidDir) => val nonCompact = createPaymentBlindedRoute(outgoingNodeId) - nonCompact.copy(route = nonCompact.route.copy(introductionNodeId = scidDir)) + nonCompact.copy(route = nonCompact.route.copy(firstNodeId = scidDir)) case None => createPaymentBlindedRoute(outgoingNodeId) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala index 00ff39e944..a20be3039f 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/OnTheFlyFundingSpec.scala @@ -88,12 +88,12 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { } def createProposal(amount: MilliSatoshi, expiry: CltvExpiry, paymentHash: ByteVector32 = randomBytes32(), upstream: Upstream.Hot = Upstream.Local(UUID.randomUUID())): ProposeOnTheFlyFunding = { - val blindingKey = upstream match { - case u: Upstream.Hot.Channel if u.add.blinding_opt.nonEmpty => Some(randomKey().publicKey) - case u: Upstream.Hot.Trampoline if u.received.exists(_.add.blinding_opt.nonEmpty) => Some(randomKey().publicKey) + val pathKey = upstream match { + case u: Upstream.Hot.Channel if u.add.pathKey_opt.nonEmpty => Some(randomKey().publicKey) + case u: Upstream.Hot.Trampoline if u.received.exists(_.add.pathKey_opt.nonEmpty) => Some(randomKey().publicKey) case _ => None } - ProposeOnTheFlyFunding(probe.ref, amount, paymentHash, expiry, TestConstants.emptyOnionPacket, blindingKey, upstream) + ProposeOnTheFlyFunding(probe.ref, amount, paymentHash, expiry, TestConstants.emptyOnionPacket, pathKey, upstream) } def proposeFunding(amount: MilliSatoshi, expiry: CltvExpiry, paymentHash: ByteVector32 = randomBytes32(), upstream: Upstream.Hot = Upstream.Local(UUID.randomUUID())): WillAddHtlc = { @@ -821,7 +821,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { // The payments are fulfilled. val (add1, add2) = if (cmd1.paymentHash == paymentHash1) (cmd1, cmd2) else (cmd2, cmd1) - val outgoing = Seq(add1, add2).map(add => UpdateAddHtlc(purchase.channelId, randomHtlcId(), add.amount, add.paymentHash, add.cltvExpiry, add.onion, add.nextBlindingKey_opt, add.confidence, add.fundingFee_opt)) + val outgoing = Seq(add1, add2).map(add => UpdateAddHtlc(purchase.channelId, randomHtlcId(), add.amount, add.paymentHash, add.cltvExpiry, add.onion, add.nextPathKey_opt, add.confidence, add.fundingFee_opt)) add1.replyTo ! RES_ADD_SETTLED(add1.origin, outgoing.head, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(purchase.channelId, outgoing.head.id, preimage1))) verifyFulfilledUpstream(upstream1, preimage1) add2.replyTo ! RES_ADD_SETTLED(add2.origin, outgoing.last, HtlcResult.OnChainFulfill(preimage2)) @@ -873,7 +873,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { // The recipient fails the payments: we don't relay the failure upstream and will retry. adds1.take(2).foreach(add => { - val htlc = UpdateAddHtlc(channelId, randomHtlcId(), add.amount, paymentHash, add.cltvExpiry, add.onion, add.nextBlindingKey_opt, add.confidence, add.fundingFee_opt) + val htlc = UpdateAddHtlc(channelId, randomHtlcId(), add.amount, paymentHash, add.cltvExpiry, add.onion, add.nextPathKey_opt, add.confidence, add.fundingFee_opt) val fail = UpdateFailHtlc(channelId, htlc.id, randomBytes(50)) add.replyTo ! RES_SUCCESS(add, purchase.channelId) add.replyTo ! RES_ADD_SETTLED(add.origin, htlc, HtlcResult.RemoteFail(fail)) @@ -893,7 +893,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { // The payment succeeds. adds2.foreach(add => { - val htlc = UpdateAddHtlc(channelId, randomHtlcId(), add.amount, paymentHash, add.cltvExpiry, add.onion, add.nextBlindingKey_opt, add.confidence, add.fundingFee_opt) + val htlc = UpdateAddHtlc(channelId, randomHtlcId(), add.amount, paymentHash, add.cltvExpiry, add.onion, add.nextPathKey_opt, add.confidence, add.fundingFee_opt) add.replyTo ! RES_ADD_SETTLED(add.origin, htlc, HtlcResult.OnChainFulfill(preimage)) }) val fwds = Seq( @@ -933,7 +933,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { // We don't collect additional fees if they were paid from our peer's channel balance already. val cmd1 = channel.expectMsgType[CMD_ADD_HTLC] cmd1.replyTo ! RES_SUCCESS(cmd1, purchase.channelId) - val htlc = UpdateAddHtlc(channelId, 0, cmd1.amount, paymentHash, cmd1.cltvExpiry, cmd1.onion, cmd1.nextBlindingKey_opt, cmd1.confidence, cmd1.fundingFee_opt) + val htlc = UpdateAddHtlc(channelId, 0, cmd1.amount, paymentHash, cmd1.cltvExpiry, cmd1.onion, cmd1.nextPathKey_opt, cmd1.confidence, cmd1.fundingFee_opt) assert(cmd1.fundingFee_opt.contains(LiquidityAds.FundingFee(0 msat, purchase.txId))) channel.expectNoMessage(100 millis) @@ -1005,7 +1005,7 @@ class OnTheFlyFundingSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike { cmd.replyTo ! RES_SUCCESS(cmd, purchase.channelId) channel.expectNoMessage(100 millis) - val add = UpdateAddHtlc(purchase.channelId, randomHtlcId(), cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextBlindingKey_opt, cmd.confidence, cmd.fundingFee_opt) + val add = UpdateAddHtlc(purchase.channelId, randomHtlcId(), cmd.amount, cmd.paymentHash, cmd.cltvExpiry, cmd.onion, cmd.nextPathKey_opt, cmd.confidence, cmd.fundingFee_opt) cmd.replyTo ! RES_ADD_SETTLED(cmd.origin, add, HtlcResult.RemoteFulfill(UpdateFulfillHtlc(purchase.channelId, add.id, preimage2))) verifyFulfilledUpstream(upstream2, preimage2) register.expectNoMessage(100 millis) @@ -1225,13 +1225,13 @@ object OnTheFlyFundingSpec { def randomHtlcId(): Long = Math.abs(randomLong()) % 50_000 def upstreamChannel(amountIn: MilliSatoshi, expiryIn: CltvExpiry, paymentHash: ByteVector32 = randomBytes32(), blinded: Boolean = false): Upstream.Hot.Channel = { - val blindingKey = if (blinded) Some(randomKey().publicKey) else None - val add = UpdateAddHtlc(randomBytes32(), randomHtlcId(), amountIn, paymentHash, expiryIn, TestConstants.emptyOnionPacket, blindingKey, 1.0, None) + val pathKey = if (blinded) Some(randomKey().publicKey) else None + val add = UpdateAddHtlc(randomBytes32(), randomHtlcId(), amountIn, paymentHash, expiryIn, TestConstants.emptyOnionPacket, pathKey, 1.0, None) Upstream.Hot.Channel(add, TimestampMilli.now(), randomKey().publicKey) } - def createWillAdd(amount: MilliSatoshi, paymentHash: ByteVector32, expiry: CltvExpiry, blinding_opt: Option[PublicKey] = None): WillAddHtlc = { - WillAddHtlc(Block.RegtestGenesisBlock.hash, randomBytes32(), amount, paymentHash, expiry, randomOnion(), blinding_opt) + def createWillAdd(amount: MilliSatoshi, paymentHash: ByteVector32, expiry: CltvExpiry, pathKey_opt: Option[PublicKey] = None): WillAddHtlc = { + WillAddHtlc(Block.RegtestGenesisBlock.hash, randomBytes32(), amount, paymentHash, expiry, randomOnion(), pathKey_opt) } def createStatus(): OnTheFlyFunding.Status = OnTheFlyFunding.Status.Funded(randomBytes32(), TxId(randomBytes32()), 0, 2500 msat) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala index 8ad5769db4..16cb925a35 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/RelayerSpec.scala @@ -161,7 +161,7 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat val (_, blindedHop, recipient) = blindedRouteFromHops(finalAmount, finalExpiry, Seq(channelHopFromUpdate(b, c, channelUpdate_bc)), routeExpiry, paymentPreimage, hex"deadbeef") val route = Route(finalAmount, Seq(channelHopFromUpdate(priv_a.publicKey, b, channelUpdate_ab)), Some(blindedHop)) val Right(payment) = buildOutgoingPayment(TestConstants.emptyOrigin, paymentHash, route, recipient, 1.0) - val add_ab = UpdateAddHtlc(channelId_ab, 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_ab = UpdateAddHtlc(channelId_ab, 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) relayer ! RelayForward(add_ab, priv_a.publicKey) val fail = register.expectMessageType[Register.Forward[CMD_FAIL_HTLC]].message @@ -177,7 +177,7 @@ class RelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("applicat // we use an expired blinded route. val Right(payment) = buildOutgoingBlindedPaymentAB(paymentHash, routeExpiry = CltvExpiry(nodeParams.currentBlockHeight - 1)) - val add_ab = UpdateAddHtlc(channelId_ab, 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextBlindingKey_opt, 1.0, payment.cmd.fundingFee_opt) + val add_ab = UpdateAddHtlc(channelId_ab, 0, payment.cmd.amount, payment.cmd.paymentHash, payment.cmd.cltvExpiry, payment.cmd.onion, payment.cmd.nextPathKey_opt, 1.0, payment.cmd.fundingFee_opt) relayer ! RelayForward(add_ab, priv_a.publicKey) val fail = register.expectMessageType[Register.Forward[CMD_FAIL_MALFORMED_HTLC]].message diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/send/BlindedPathsResolverSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/send/BlindedPathsResolverSpec.scala index 8a9ae7d641..9603b81af3 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/send/BlindedPathsResolverSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/send/BlindedPathsResolverSpec.scala @@ -86,7 +86,7 @@ class BlindedPathsResolverSpec extends ScalaTestWithActorTestKit(ConfigFactory.l val probe = TestProbe() val introductionNodeId = randomKey().publicKey val scidDir = EncodedNodeId.ShortChannelIdDir(isNode1 = false, RealShortChannelId(BlockHeight(750_000), 3, 7)) - val route = RouteBlinding.create(randomKey(), Seq(introductionNodeId), Seq(hex"deadbeef")).route.copy(introductionNodeId = scidDir) + val route = RouteBlinding.create(randomKey(), Seq(introductionNodeId), Seq(hex"deadbeef")).route.copy(firstNodeId = scidDir) val paymentInfo = PaymentInfo(100 msat, 250, CltvExpiryDelta(36), 1 msat, 50_000_000 msat, Features.empty) resolver ! Resolve(probe.ref, Seq(PaymentBlindedRoute(route, paymentInfo))) // We must resolve the scid_dir to a node_id. @@ -99,8 +99,8 @@ class BlindedPathsResolverSpec extends ScalaTestWithActorTestKit(ConfigFactory.l assert(resolved.head.route.isInstanceOf[FullBlindedRoute]) val fullRoute = resolved.head.route.asInstanceOf[FullBlindedRoute] assert(fullRoute.firstNodeId == introductionNodeId) - assert(fullRoute.firstBlinding == route.blindingKey) - assert(fullRoute.blindedNodes == route.blindedNodes) + assert(fullRoute.firstpathKey == route.firstPathKey) + assert(fullRoute.blindedHops == route.blindedHops) assert(resolved.head.paymentInfo == paymentInfo) } @@ -119,7 +119,7 @@ class BlindedPathsResolverSpec extends ScalaTestWithActorTestKit(ConfigFactory.l Seq(true, false).foreach { useScidDir => val toResolve = if (useScidDir) { val scidDir = EncodedNodeId.ShortChannelIdDir(isNode1 = true, edges.head.shortChannelId.asInstanceOf[RealShortChannelId]) - route.copy(introductionNodeId = scidDir) + route.copy(firstNodeId = scidDir) } else { route } @@ -142,8 +142,8 @@ class BlindedPathsResolverSpec extends ScalaTestWithActorTestKit(ConfigFactory.l assert(resolved.head.route.isInstanceOf[PartialBlindedRoute]) val partialRoute = resolved.head.route.asInstanceOf[PartialBlindedRoute] assert(partialRoute.firstNodeId == nextNodeId) - assert(partialRoute.blindedNodes == route.subsequentNodes) - assert(partialRoute.nextBlinding != route.blindingKey) + assert(partialRoute.blindedHops == route.subsequentNodes) + assert(partialRoute.nextPathKey != route.firstPathKey) // The payment info for the partial route should be greater than the actual payment info. assert(750_000.msat <= resolved.head.paymentInfo.feeBase && resolved.head.paymentInfo.feeBase <= 1_000_000.msat) assert(150 <= resolved.head.paymentInfo.feeProportionalMillionths && resolved.head.paymentInfo.feeProportionalMillionths <= 200) @@ -169,8 +169,8 @@ class BlindedPathsResolverSpec extends ScalaTestWithActorTestKit(ConfigFactory.l val partialRoute = resolved.head.route.asInstanceOf[PartialBlindedRoute] assert(partialRoute.firstNodeId == walletNodeId) assert(partialRoute.nextNodeId == EncodedNodeId.WithPublicKey.Wallet(walletNodeId)) - assert(partialRoute.blindedNodes == route.subsequentNodes) - assert(partialRoute.nextBlinding != route.blindingKey) + assert(partialRoute.blindedHops == route.subsequentNodes) + assert(partialRoute.nextPathKey != route.firstPathKey) // We don't need to resolve the nodeId. register.expectNoMessage(100 millis) router.expectNoMessage(100 millis) @@ -186,7 +186,7 @@ class BlindedPathsResolverSpec extends ScalaTestWithActorTestKit(ConfigFactory.l val route = BlindedRouteCreation.createBlindedRouteFromHops(Seq(hop), hex"deadbeef", 1 msat, CltvExpiry(800_000)).route val paymentInfo = BlindedRouteCreation.aggregatePaymentInfo(50_000_000 msat, Seq(hop), CltvExpiryDelta(12)) val toResolve = Seq( - PaymentBlindedRoute(route.copy(introductionNodeId = EncodedNodeId.ShortChannelIdDir(isNode1 = true, scid)), paymentInfo), + PaymentBlindedRoute(route.copy(firstNodeId = EncodedNodeId.ShortChannelIdDir(isNode1 = true, scid)), paymentInfo), PaymentBlindedRoute(route, paymentInfo), PaymentBlindedRoute(route, paymentInfo), ) @@ -219,7 +219,7 @@ class BlindedPathsResolverSpec extends ScalaTestWithActorTestKit(ConfigFactory.l // We reject blinded routes with low fees, even when the next node seems to be a wallet node. BlindedRouteCreation.createBlindedRouteToWallet(ChannelHop(scid, nodeParams.nodeId, edgeLowFees.targetNodeId, HopRelayParams.FromHint(edgeLowFees)), hex"deadbeef", 1 msat, CltvExpiry(800_000)).route, // We reject blinded routes that cannot be decrypted. - BlindedRouteCreation.createBlindedRouteFromHops(Seq(ChannelHop(scid, nodeParams.nodeId, edgeLowFees.targetNodeId, HopRelayParams.FromHint(edgeLowFees))), hex"deadbeef", 1 msat, CltvExpiry(800_000)).route.copy(blindingKey = randomKey().publicKey) + BlindedRouteCreation.createBlindedRouteFromHops(Seq(ChannelHop(scid, nodeParams.nodeId, edgeLowFees.targetNodeId, HopRelayParams.FromHint(edgeLowFees))), hex"deadbeef", 1 msat, CltvExpiry(800_000)).route.copy(firstPathKey = randomKey().publicKey) ).map(r => PaymentBlindedRoute(r, PaymentInfo(1_000_000 msat, 2500, CltvExpiryDelta(300), 1 msat, 500_000_000 msat, Features.empty))) resolver ! Resolve(probe.ref, toResolve) // The routes with low fees or expiry require resolving the next node. diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/send/OfferPaymentSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/send/OfferPaymentSpec.scala index 093db9fa1a..af5e1f2f79 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/send/OfferPaymentSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/send/OfferPaymentSpec.scala @@ -149,7 +149,7 @@ class OfferPaymentSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("app val preimage = randomBytes32() val paymentRoute = PaymentBlindedRoute(RouteBlinding.create(randomKey(), Seq(merchantKey.publicKey), Seq(hex"7777")).route, PaymentInfo(0 msat, 0, CltvExpiryDelta(0), 0 msat, 1_000_000_000 msat, Features.empty)) - val blindedMerchantKey = RouteBlinding.derivePrivateKey(merchantKey, route.lastBlinding) + val blindedMerchantKey = RouteBlinding.derivePrivateKey(merchantKey, route.lastPathKey) val invoice = Bolt12Invoice(invoiceRequest, preimage, blindedMerchantKey, 1 minute, Features.empty, Seq(paymentRoute)) replyTo ! Postman.Response(InvoicePayload(TlvStream(OnionMessagePayloadTlv.Invoice(invoice.records)), TlvStream.empty)) val send = paymentInitiator.expectMsgType[SendPaymentToNode] diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala index 3cf635a52a..913b2a621d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BaseRouterSpec.scala @@ -277,8 +277,8 @@ object BaseRouterSpec { }) val invoice = Bolt12Invoice(invoiceRequest, preimage, recipientKey, 300 seconds, features, blindedRoutes) val resolvedPaths = invoice.blindedPaths.map(path => { - val introductionNodeId = path.route.introductionNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey - ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.blindingKey, path.route.blindedNodes), path.paymentInfo) + val introductionNodeId = path.route.firstNodeId.asInstanceOf[EncodedNodeId.WithPublicKey].publicKey + ResolvedPath(FullBlindedRoute(introductionNodeId, path.route.firstPathKey, path.route.blindedHops), path.paymentInfo) }) val recipient = BlindedRecipient(invoice, resolvedPaths, amount, expiry, Set.empty, duplicatePaths = 1) (invoice, recipient) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/router/BlindedRouteCreationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/router/BlindedRouteCreationSpec.scala index 896fff9bf6..55833d5feb 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/router/BlindedRouteCreationSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/router/BlindedRouteCreationSpec.scala @@ -31,10 +31,10 @@ class BlindedRouteCreationSpec extends AnyFunSuite with ParallelTestExecution { val a = randomKey() val pathId = randomBytes32() val route = createBlindedRouteWithoutHops(a.publicKey, pathId, 1 msat, CltvExpiry(500)) - assert(route.route.introductionNodeId == EncodedNodeId(a.publicKey)) + assert(route.route.firstNodeId == EncodedNodeId(a.publicKey)) assert(route.route.encryptedPayloads.length == 1) - assert(route.route.blindingKey == route.lastBlinding) - val Right(decoded) = RouteBlindingEncryptedDataCodecs.decode(a, route.route.blindingKey, route.route.encryptedPayloads.head) + assert(route.route.firstPathKey == route.lastPathKey) + val Right(decoded) = RouteBlindingEncryptedDataCodecs.decode(a, route.route.firstPathKey, route.route.encryptedPayloads.head) assert(BlindedRouteData.validPaymentRecipientData(decoded.tlvs).isRight) assert(decoded.tlvs.get[RouteBlindingEncryptedDataTlv.PathId].get.data == pathId.bytes) } @@ -48,21 +48,21 @@ class BlindedRouteCreationSpec extends AnyFunSuite with ParallelTestExecution { ChannelHop(scid2, b.publicKey, c.publicKey, HopRelayParams.FromAnnouncement(makeUpdateShort(scid2, b.publicKey, c.publicKey, 20 msat, 150, cltvDelta = CltvExpiryDelta(600)))), ) val route = createBlindedRouteFromHops(hops, pathId, 1 msat, CltvExpiry(500)) - assert(route.route.introductionNodeId == EncodedNodeId(a.publicKey)) + assert(route.route.firstNodeId == EncodedNodeId(a.publicKey)) assert(route.route.encryptedPayloads.length == 3) - val Right(decoded1) = RouteBlindingEncryptedDataCodecs.decode(a, route.route.blindingKey, route.route.encryptedPayloads(0)) + val Right(decoded1) = RouteBlindingEncryptedDataCodecs.decode(a, route.route.firstPathKey, route.route.encryptedPayloads(0)) assert(BlindedRouteData.validatePaymentRelayData(decoded1.tlvs).isRight) assert(decoded1.tlvs.get[RouteBlindingEncryptedDataTlv.OutgoingChannelId].get.shortChannelId == scid1) assert(decoded1.tlvs.get[RouteBlindingEncryptedDataTlv.PaymentRelay].get.feeBase == 10.msat) assert(decoded1.tlvs.get[RouteBlindingEncryptedDataTlv.PaymentRelay].get.feeProportionalMillionths == 300) assert(decoded1.tlvs.get[RouteBlindingEncryptedDataTlv.PaymentRelay].get.cltvExpiryDelta == CltvExpiryDelta(200)) - val Right(decoded2) = RouteBlindingEncryptedDataCodecs.decode(b, decoded1.nextBlinding, route.route.encryptedPayloads(1)) + val Right(decoded2) = RouteBlindingEncryptedDataCodecs.decode(b, decoded1.nextPathKey, route.route.encryptedPayloads(1)) assert(BlindedRouteData.validatePaymentRelayData(decoded2.tlvs).isRight) assert(decoded2.tlvs.get[RouteBlindingEncryptedDataTlv.OutgoingChannelId].get.shortChannelId == scid2) assert(decoded2.tlvs.get[RouteBlindingEncryptedDataTlv.PaymentRelay].get.feeBase == 20.msat) assert(decoded2.tlvs.get[RouteBlindingEncryptedDataTlv.PaymentRelay].get.feeProportionalMillionths == 150) assert(decoded2.tlvs.get[RouteBlindingEncryptedDataTlv.PaymentRelay].get.cltvExpiryDelta == CltvExpiryDelta(600)) - val Right(decoded3) = RouteBlindingEncryptedDataCodecs.decode(c, decoded2.nextBlinding, route.route.encryptedPayloads(2)) + val Right(decoded3) = RouteBlindingEncryptedDataCodecs.decode(c, decoded2.nextPathKey, route.route.encryptedPayloads(2)) assert(BlindedRouteData.validPaymentRecipientData(decoded3.tlvs).isRight) assert(decoded3.tlvs.get[RouteBlindingEncryptedDataTlv.PathId].get.data == pathId.bytes) } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala index 733541c790..2fb4def03c 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/internal/channel/version1/ChannelCodecs1Spec.scala @@ -75,7 +75,7 @@ class ChannelCodecs1Spec extends AnyFunSuite { cltvExpiry = CltvExpiry(Random.nextInt(Int.MaxValue)), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, - blinding_opt = None, + pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) val htlc1 = IncomingHtlc(add) @@ -92,7 +92,7 @@ class ChannelCodecs1Spec extends AnyFunSuite { cltvExpiry = CltvExpiry(Random.nextInt(Int.MaxValue)), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, - blinding_opt = None, + pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) val add2 = UpdateAddHtlc( @@ -102,7 +102,7 @@ class ChannelCodecs1Spec extends AnyFunSuite { cltvExpiry = CltvExpiry(Random.nextInt(Int.MaxValue)), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, - blinding_opt = None, + pathKey_opt = None, confidence = 1.0, fundingFee_opt = None) val htlc1 = IncomingHtlc(add1) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala index 3e67307905..5f7dbc939d 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/LightningMessageCodecsSpec.scala @@ -590,14 +590,14 @@ class LightningMessageCodecsSpec extends AnyFunSuite { test("encode/decode on-the-fly funding messages") { val channelId = ByteVector32(hex"c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c") val paymentId = ByteVector32(hex"3118a7954088c27b19923894ed27923c297f88ec3734f90b2b4aafcb11238503") - val blinding = PublicKey(hex"0296d5c32655a5eaa8be086479d7bcff967b6e9ca8319b69565747ae16ff20fad6") + val pathKey = PublicKey(hex"0296d5c32655a5eaa8be086479d7bcff967b6e9ca8319b69565747ae16ff20fad6") val paymentHash1 = ByteVector32(hex"80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734") val paymentHash2 = ByteVector32(hex"3213a810a0bfc54566d9be09da1484538b5d19229e928dfa8b692966a8df6785") val fundingFee = LiquidityAds.FundingFee(5_000_100 msat, TxId(TxHash(ByteVector32(hex"24e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566")))) val testCases = Seq( - UpdateAddHtlc(channelId, 7, 75_000_000 msat, paymentHash1, CltvExpiry(840_000), TestConstants.emptyOnionPacket, blinding_opt = None, confidence = 0, fundingFee_opt = Some(fundingFee)) -> hex"0080 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 0000000000000007 00000000047868c0 80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734 000cd140 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 fda0512800000000004c4ba424e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566 fe0001a1470100", - WillAddHtlc(Block.RegtestGenesisBlock.hash, paymentId, 50_000_000 msat, paymentHash1, CltvExpiry(840_000), TestConstants.emptyOnionPacket, blinding_opt = None) -> hex"a051 06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f 3118a7954088c27b19923894ed27923c297f88ec3734f90b2b4aafcb11238503 0000000002faf080 80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734 000cd140 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - WillAddHtlc(Block.RegtestGenesisBlock.hash, paymentId, 50_000_000 msat, paymentHash1, CltvExpiry(840_000), TestConstants.emptyOnionPacket, blinding_opt = Some(blinding)) -> hex"a051 06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f 3118a7954088c27b19923894ed27923c297f88ec3734f90b2b4aafcb11238503 0000000002faf080 80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734 000cd140 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00210296d5c32655a5eaa8be086479d7bcff967b6e9ca8319b69565747ae16ff20fad6", + UpdateAddHtlc(channelId, 7, 75_000_000 msat, paymentHash1, CltvExpiry(840_000), TestConstants.emptyOnionPacket, pathKey_opt = None, confidence = 0, fundingFee_opt = Some(fundingFee)) -> hex"0080 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 0000000000000007 00000000047868c0 80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734 000cd140 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 fda0512800000000004c4ba424e1b2c94c4e734dd5b9c5f3c910fbb6b3b436ced6382c7186056a5a23f14566 fe0001a1470100", + WillAddHtlc(Block.RegtestGenesisBlock.hash, paymentId, 50_000_000 msat, paymentHash1, CltvExpiry(840_000), TestConstants.emptyOnionPacket, pathKey_opt = None) -> hex"a051 06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f 3118a7954088c27b19923894ed27923c297f88ec3734f90b2b4aafcb11238503 0000000002faf080 80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734 000cd140 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + WillAddHtlc(Block.RegtestGenesisBlock.hash, paymentId, 50_000_000 msat, paymentHash1, CltvExpiry(840_000), TestConstants.emptyOnionPacket, pathKey_opt = Some(pathKey)) -> hex"a051 06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f 3118a7954088c27b19923894ed27923c297f88ec3734f90b2b4aafcb11238503 0000000002faf080 80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734 000cd140 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 00210296d5c32655a5eaa8be086479d7bcff967b6e9ca8319b69565747ae16ff20fad6", WillFailHtlc(paymentId, paymentHash1, hex"deadbeef") -> hex"a052 3118a7954088c27b19923894ed27923c297f88ec3734f90b2b4aafcb11238503 80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734 0004 deadbeef", WillFailMalformedHtlc(paymentId, paymentHash1, ByteVector32(hex"9d60e5791eee0799ce7b00009f56f56c6b988f6129b6a88494cce2cf2fa8b319"), 49157) -> hex"a053 3118a7954088c27b19923894ed27923c297f88ec3734f90b2b4aafcb11238503 80417c0c91deb72606958425ea1552a045a55a250e91870231b486dcb2106734 9d60e5791eee0799ce7b00009f56f56c6b988f6129b6a88494cce2cf2fa8b319 c005", CancelOnTheFlyFunding(channelId, Nil, hex"deadbeef") -> hex"a054 c11b8fbd682b3c6ee11f9d7268e22bb5887cd4d3bf3338bfcc340583f685733c 0000 0004 deadbeef", @@ -760,9 +760,9 @@ class LightningMessageCodecsSpec extends AnyFunSuite { test("encode/decode UpdateAddHtlc") { val testCases = Map( hex"2e184fc141277ba9a3fbe752206f5714c3cfe50765c258dfbdb10cead1ef57f9 0000000000000001 000000000000b5bb 05fc4f9f94ecb97574a90c9154740a3a6c16195d6d0136b71d60d9dae33ce999 000d5fff 00 02c606691a88f80fdc10d007ef5dfa0b91ce33b7b3fa40a6df84f7285aaf37174e 708636c4c5bab2c45e0e9b94f48791e468a9e54af63ee9dfd09d947d6a845b03133eb69226293754caf18b41b99c66830e327938b2fe44e54e31cd8a2c0ee1c43c6c50a8d29bd3f27eb88f70d6ecd7f1b2afb7d721dbad9ac34a511c5e49e5c44e0beb5e513b930fea34eb3ce22e5cebc55c85efa2b24a698ee4f45207977693cebc59f3cad9088387ea89ae45cbb9700bb3e0d93b82d10ea994979a90f4d6265312ae370c80a0c323f5abaa8dcc09aa637b85f2b40d7324885fc744719ec966154c2cd4a512abc618232b82855261886937af9f92d308eba5a5b03e99f4e96535a7e4aeb29c4a260938b85bc16218eb0fe2c765519dd811e0e633bb6e26004393db285ffd04bf6be33ec410bcad437d515484e910960b3d2b1f719963c215c26a0f29b86dfde41c098780d5aaf9b48c95f7e3d6582955feee058e5d0f87671202caf4899a2f5f238f4938b20d14f3d1e94893666e9c36f9c559f05f065ec547f417b58b81d7f5e71563f0565c30f82e9e8e4755f74b8633fb5c7645a551ebf27b9535aa1bde6f12df2b85cc30b9083d602f7eea0e9093f86aa2346e8900851e884470026f6f46e9748322c786145cd0cc8a0d712aef89466a06e5c2795cf5d326f78a5af746f61f4df3b08f17104b1ce3099a20fab9b2751b3635aec743a986173861d790c31942bd608258a927d75309c15ffd690a0713179d62a60b459be7486d03b774119d12168a9d0761134d789264662ed1e21329c840aa6f958cd0bfddd8cbeb61ac9fb5f379ee8557e3962f85871928d2fae5d9f1026eb95248f38689f44597d1b316a1860597abb77e08fa58778f39fbb13f38c727cdabf58592f3932a272195dde4ccd62d57239cb82d274ed239f39132cf83fa4629435af985ef24a8aecc4e8837ce53658cdbe97951b83f5f3643525f19f3e46312285684631b93e12e47b6922855cdf81ef5459bc26667a17459c537fbc169485bc23daa9c573a86010b9627864842eb3fb01afd90b288e86050c87e5f1e8d49fd6fae7c5c5256d27471baf29017e092b4c5a96f063a8c56ccf90838e2da89f42a9d4af35236e3f12b3253f6981e5db1a4cf453622fcc2e11b51afd2a88b09ed13905bbaeef91b9b80523e13fe6b8c2386f6c83c3cad5de89405e2da894bf30733846266904be964ac8d67e8eff54b9ee3f8e6c88797fdaf9832a2693e7d0b6471bbc234fd72c1f02a8e48f3fb43cf0609802d129e6b46ad542dac1feee2128cf2688a354dacddd0a50ec88b517e9f315c7df81d5002f2809b4009b0ed55b2d960f0390eec4c1824afe013332aa6d0e1f0d65877485471918e7667addd27e592731011e813a5085035f1dc11b4d7dac122f05a033b702d91708e0c708337b3be0fa8463cce23e52f667520908dc5e8a94ff051ffaf50fec11b8a3f880e4d0ededd7b0a6c71f6e939553402eb2ea370334776336e726880a184d7dcfd7c84d12c8feb31a479259f2c6520c5432cf71babc522b7f090cc527df41b1c7d8e3b5e2c1f5a8d4a3e1da578921321e472be5f6f5076be9e9d2255a46e072e19771c461973c44bb47e154e85bb76f0f1152e9bb1209c1707b0f42e6507cd9e4f026156a788ef65b221f0c864efc334132624ba1b96a1ecd7c7d460acb7c1d7b408395410b189c4ae374d143c96c48392abceee4366903a05d9ba884496bbff603b65967fd5a434530fe5accd48f40f00f1a5347f1602dd4abe545e5826f24344bbe88cd3b2ffd1320fb40407ec175f8c17c16a52ade59383357e52eff8b8d5c318172b703c69c4a04786088ee63f2fc9cea63294a33546ea1a954ae9a79c 47de7249c4b76e4db71df5d070dab15ea294f22011fc21a544c26416aaff2682 00 21 0396b9c21b054a49f35ee7abb96e677ebbcaae876d602349cd58b3854380782818 fe0001a147 01 05" -> - UpdateAddHtlc(ByteVector32(hex"2e184fc141277ba9a3fbe752206f5714c3cfe50765c258dfbdb10cead1ef57f9"), 1, 46523 msat, ByteVector32(hex"05fc4f9f94ecb97574a90c9154740a3a6c16195d6d0136b71d60d9dae33ce999"), CltvExpiry(876543), OnionRoutingPacket(0, hex"02c606691a88f80fdc10d007ef5dfa0b91ce33b7b3fa40a6df84f7285aaf37174e", payload = hex"708636c4c5bab2c45e0e9b94f48791e468a9e54af63ee9dfd09d947d6a845b03133eb69226293754caf18b41b99c66830e327938b2fe44e54e31cd8a2c0ee1c43c6c50a8d29bd3f27eb88f70d6ecd7f1b2afb7d721dbad9ac34a511c5e49e5c44e0beb5e513b930fea34eb3ce22e5cebc55c85efa2b24a698ee4f45207977693cebc59f3cad9088387ea89ae45cbb9700bb3e0d93b82d10ea994979a90f4d6265312ae370c80a0c323f5abaa8dcc09aa637b85f2b40d7324885fc744719ec966154c2cd4a512abc618232b82855261886937af9f92d308eba5a5b03e99f4e96535a7e4aeb29c4a260938b85bc16218eb0fe2c765519dd811e0e633bb6e26004393db285ffd04bf6be33ec410bcad437d515484e910960b3d2b1f719963c215c26a0f29b86dfde41c098780d5aaf9b48c95f7e3d6582955feee058e5d0f87671202caf4899a2f5f238f4938b20d14f3d1e94893666e9c36f9c559f05f065ec547f417b58b81d7f5e71563f0565c30f82e9e8e4755f74b8633fb5c7645a551ebf27b9535aa1bde6f12df2b85cc30b9083d602f7eea0e9093f86aa2346e8900851e884470026f6f46e9748322c786145cd0cc8a0d712aef89466a06e5c2795cf5d326f78a5af746f61f4df3b08f17104b1ce3099a20fab9b2751b3635aec743a986173861d790c31942bd608258a927d75309c15ffd690a0713179d62a60b459be7486d03b774119d12168a9d0761134d789264662ed1e21329c840aa6f958cd0bfddd8cbeb61ac9fb5f379ee8557e3962f85871928d2fae5d9f1026eb95248f38689f44597d1b316a1860597abb77e08fa58778f39fbb13f38c727cdabf58592f3932a272195dde4ccd62d57239cb82d274ed239f39132cf83fa4629435af985ef24a8aecc4e8837ce53658cdbe97951b83f5f3643525f19f3e46312285684631b93e12e47b6922855cdf81ef5459bc26667a17459c537fbc169485bc23daa9c573a86010b9627864842eb3fb01afd90b288e86050c87e5f1e8d49fd6fae7c5c5256d27471baf29017e092b4c5a96f063a8c56ccf90838e2da89f42a9d4af35236e3f12b3253f6981e5db1a4cf453622fcc2e11b51afd2a88b09ed13905bbaeef91b9b80523e13fe6b8c2386f6c83c3cad5de89405e2da894bf30733846266904be964ac8d67e8eff54b9ee3f8e6c88797fdaf9832a2693e7d0b6471bbc234fd72c1f02a8e48f3fb43cf0609802d129e6b46ad542dac1feee2128cf2688a354dacddd0a50ec88b517e9f315c7df81d5002f2809b4009b0ed55b2d960f0390eec4c1824afe013332aa6d0e1f0d65877485471918e7667addd27e592731011e813a5085035f1dc11b4d7dac122f05a033b702d91708e0c708337b3be0fa8463cce23e52f667520908dc5e8a94ff051ffaf50fec11b8a3f880e4d0ededd7b0a6c71f6e939553402eb2ea370334776336e726880a184d7dcfd7c84d12c8feb31a479259f2c6520c5432cf71babc522b7f090cc527df41b1c7d8e3b5e2c1f5a8d4a3e1da578921321e472be5f6f5076be9e9d2255a46e072e19771c461973c44bb47e154e85bb76f0f1152e9bb1209c1707b0f42e6507cd9e4f026156a788ef65b221f0c864efc334132624ba1b96a1ecd7c7d460acb7c1d7b408395410b189c4ae374d143c96c48392abceee4366903a05d9ba884496bbff603b65967fd5a434530fe5accd48f40f00f1a5347f1602dd4abe545e5826f24344bbe88cd3b2ffd1320fb40407ec175f8c17c16a52ade59383357e52eff8b8d5c318172b703c69c4a04786088ee63f2fc9cea63294a33546ea1a954ae9a79c", ByteVector32(hex"47de7249c4b76e4db71df5d070dab15ea294f22011fc21a544c26416aaff2682")), TlvStream(UpdateAddHtlcTlv.BlindingPoint(PublicKey(hex"0396b9c21b054a49f35ee7abb96e677ebbcaae876d602349cd58b3854380782818")), UpdateAddHtlcTlv.Endorsement(5))), + UpdateAddHtlc(ByteVector32(hex"2e184fc141277ba9a3fbe752206f5714c3cfe50765c258dfbdb10cead1ef57f9"), 1, 46523 msat, ByteVector32(hex"05fc4f9f94ecb97574a90c9154740a3a6c16195d6d0136b71d60d9dae33ce999"), CltvExpiry(876543), OnionRoutingPacket(0, hex"02c606691a88f80fdc10d007ef5dfa0b91ce33b7b3fa40a6df84f7285aaf37174e", payload = hex"708636c4c5bab2c45e0e9b94f48791e468a9e54af63ee9dfd09d947d6a845b03133eb69226293754caf18b41b99c66830e327938b2fe44e54e31cd8a2c0ee1c43c6c50a8d29bd3f27eb88f70d6ecd7f1b2afb7d721dbad9ac34a511c5e49e5c44e0beb5e513b930fea34eb3ce22e5cebc55c85efa2b24a698ee4f45207977693cebc59f3cad9088387ea89ae45cbb9700bb3e0d93b82d10ea994979a90f4d6265312ae370c80a0c323f5abaa8dcc09aa637b85f2b40d7324885fc744719ec966154c2cd4a512abc618232b82855261886937af9f92d308eba5a5b03e99f4e96535a7e4aeb29c4a260938b85bc16218eb0fe2c765519dd811e0e633bb6e26004393db285ffd04bf6be33ec410bcad437d515484e910960b3d2b1f719963c215c26a0f29b86dfde41c098780d5aaf9b48c95f7e3d6582955feee058e5d0f87671202caf4899a2f5f238f4938b20d14f3d1e94893666e9c36f9c559f05f065ec547f417b58b81d7f5e71563f0565c30f82e9e8e4755f74b8633fb5c7645a551ebf27b9535aa1bde6f12df2b85cc30b9083d602f7eea0e9093f86aa2346e8900851e884470026f6f46e9748322c786145cd0cc8a0d712aef89466a06e5c2795cf5d326f78a5af746f61f4df3b08f17104b1ce3099a20fab9b2751b3635aec743a986173861d790c31942bd608258a927d75309c15ffd690a0713179d62a60b459be7486d03b774119d12168a9d0761134d789264662ed1e21329c840aa6f958cd0bfddd8cbeb61ac9fb5f379ee8557e3962f85871928d2fae5d9f1026eb95248f38689f44597d1b316a1860597abb77e08fa58778f39fbb13f38c727cdabf58592f3932a272195dde4ccd62d57239cb82d274ed239f39132cf83fa4629435af985ef24a8aecc4e8837ce53658cdbe97951b83f5f3643525f19f3e46312285684631b93e12e47b6922855cdf81ef5459bc26667a17459c537fbc169485bc23daa9c573a86010b9627864842eb3fb01afd90b288e86050c87e5f1e8d49fd6fae7c5c5256d27471baf29017e092b4c5a96f063a8c56ccf90838e2da89f42a9d4af35236e3f12b3253f6981e5db1a4cf453622fcc2e11b51afd2a88b09ed13905bbaeef91b9b80523e13fe6b8c2386f6c83c3cad5de89405e2da894bf30733846266904be964ac8d67e8eff54b9ee3f8e6c88797fdaf9832a2693e7d0b6471bbc234fd72c1f02a8e48f3fb43cf0609802d129e6b46ad542dac1feee2128cf2688a354dacddd0a50ec88b517e9f315c7df81d5002f2809b4009b0ed55b2d960f0390eec4c1824afe013332aa6d0e1f0d65877485471918e7667addd27e592731011e813a5085035f1dc11b4d7dac122f05a033b702d91708e0c708337b3be0fa8463cce23e52f667520908dc5e8a94ff051ffaf50fec11b8a3f880e4d0ededd7b0a6c71f6e939553402eb2ea370334776336e726880a184d7dcfd7c84d12c8feb31a479259f2c6520c5432cf71babc522b7f090cc527df41b1c7d8e3b5e2c1f5a8d4a3e1da578921321e472be5f6f5076be9e9d2255a46e072e19771c461973c44bb47e154e85bb76f0f1152e9bb1209c1707b0f42e6507cd9e4f026156a788ef65b221f0c864efc334132624ba1b96a1ecd7c7d460acb7c1d7b408395410b189c4ae374d143c96c48392abceee4366903a05d9ba884496bbff603b65967fd5a434530fe5accd48f40f00f1a5347f1602dd4abe545e5826f24344bbe88cd3b2ffd1320fb40407ec175f8c17c16a52ade59383357e52eff8b8d5c318172b703c69c4a04786088ee63f2fc9cea63294a33546ea1a954ae9a79c", ByteVector32(hex"47de7249c4b76e4db71df5d070dab15ea294f22011fc21a544c26416aaff2682")), TlvStream(UpdateAddHtlcTlv.PathKey(PublicKey(hex"0396b9c21b054a49f35ee7abb96e677ebbcaae876d602349cd58b3854380782818")), UpdateAddHtlcTlv.Endorsement(5))), hex"f865a44f81f02f3539842b863668403a68ddb3703e03cd91045c9ac114dbd28e 0000000000000002 0000000000000092 d3708298fd195572cceb86a1745210543c42f931a1a2baeed7f705d333ebed22 00013368 00 037cb785d5a9de762adc62e3f2407d452bd1f13d368d0429caee5541a89488e3b9 5b90dd3803ff56400a40dc539efa0a0e29736c76e83d2d8b44775adcb4d485be5eb84eefbfedc69b687ee5c7080f83ff31760ec036990d904de480064966bba393af729d57437c91bea905a66464461a6462c5a0c66976ecaeded73d330dfaeb5651ba68e74b210c123e63ac6ee15d6673cf126f046840bbf4364c907f56382870da85101045fc9392357b0b32081cde0e28460d20c1d5d61a5d56fe50d107304e4b184dd005048f9bcd159eff3cfaaef4ab5a8f29d38de109ea41a7ceaa886a0559a4fd0c7448cf28db85c3758fbde23443776e08dd29c435b1195f205aa787f2ec4a7259afeff078faa419c9af5706b9c08e7ce5772326e09df22eb85b0abc625b969aae34a881c12bd9653a08dc62b8e82cf89d0d66974f96149c6dcd7fc4363eecbf4fdac39e500c416b8d6a2e5871d80775acd1c13df4e4ad30a150390d500869ee6a4eab1c4285952d0549c45960a0b1b5ccfb93c75b63923fbc02b1d5a91e53f7424478cc58bf4366a612d40deec5efea700a7b127f7fdded1c4232e5b2eb7190f964e20b156e62d63d097005a24d96457a3db387a6b25e2a65fb169de0323a48f275780dc320d1c7d22642d3371e65559bf5510a3990b67aceb87daccd40e7f82fbba5a0065a63849dbb6600b420e8cb3561154dc69a3b063784737a7eb9d3f770fa1312c03e8518200cefc1be356bf0b0d1928816e712d24b5021b72f20d84a062a47ef50acf5cc9de025378611fbd7fd6473bc0258aa1697b057f6ce4c99f7a6ade34411a008aaccb3e3b2f78e2bc0720c3050960ad8b2988907a410a8fe565b671d3f2a274b9ed230786da769ebc73e9212b41902bec589d602dc941a6dc8a3c37ede467fdb21101cc8befe111b8a365b94612be8eef16a14dc1f163647744c2d0eabc17dc0bd297ba2e9237fea5d8c845e11ae207c4ce8d5d17b2dffdb6ba20c0474ab9e8547b90bdf61d41602b64b84b3d725279e818dccb82f25c6a75dc473af074c97bd6ea775eb575dd07bd2574dfe308748ecc0d39df14659e1958dbc0413fa7f214b6ffb4059b0ef21e9c2602723695988382201c36dfb9bb5916246f0ecad281b1431846e43b5651a85745a4b81586282285f56625fdb8f46a7b752f1a81159f04c12ec20723c22ae2dadd384bcedecc16de9ae253081ec8b5616b94ec716eb2e91c6af58eddcb360841ec942782fe7b44e9ea191e98007faa5722f12b5e0ff23304297f3aeb369a3ff79434f2ff2e7575bd7e7a1d2592043ea245ca0f69cc0c781b42258230b45391d1c1545ed0a9dcdbedc454c6e7dd313b2e6e757f91309d8fa7801bba864f60f04a1b12e0770e4aa62b7d388f8a0b85d38defa8a21a7764388dd7273b941a17f1f1b1a7acefc8f1726a7cb4ea3f19ddb7a70082c1c5cb6921d4c0eea07cf4d226c98ed1c57a92652b2687181da8091db3ac77bbf5634c351990296494868dd1e365af320c88148a0ed887a06f4be3256cf4556f00ce3376a7016ffddc26f36272d48fc5500451a2ea6de5f6778948e9ba856db5a38129ad1367b983d1f2b624ac5e2e35ce9145eee563d047c853a30c6cabb8064ea183dba622adebae8d2149b93752776e55155319009f2f9a3c1aec9a2b266fdb0451c97fbcc8ca7c1adff0806adc1dc8105b678a30af95cfc5f370dd0e4a6db9811b2deac2ed1fc8716b0571210ee7208316f24dc2af7394519401173d36c3f25fbe4ebd965aa19f53e6fef550ab72216dcfd20ac974b40b456ce143bd4e495dd51fd58e23877ac91e28abed7e9a12c7fbf694e5b5cf144f011da4aca445cf5546d68b2341401460bf2d717663c 022406339068be457a4430d0de697ee810f0911afc344bd3d4d662771874d2ce 00 21 02039885cd5b9ffd24b5ce83f464a7d4c0e3f23c1a8061c9fc85730db67ffdbd0c fe0001a147 01 00" -> - UpdateAddHtlc(ByteVector32(hex"f865a44f81f02f3539842b863668403a68ddb3703e03cd91045c9ac114dbd28e"), 2, 146 msat, ByteVector32(hex"d3708298fd195572cceb86a1745210543c42f931a1a2baeed7f705d333ebed22"), CltvExpiry(78696), OnionRoutingPacket(0, hex"037cb785d5a9de762adc62e3f2407d452bd1f13d368d0429caee5541a89488e3b9", payload = hex"5b90dd3803ff56400a40dc539efa0a0e29736c76e83d2d8b44775adcb4d485be5eb84eefbfedc69b687ee5c7080f83ff31760ec036990d904de480064966bba393af729d57437c91bea905a66464461a6462c5a0c66976ecaeded73d330dfaeb5651ba68e74b210c123e63ac6ee15d6673cf126f046840bbf4364c907f56382870da85101045fc9392357b0b32081cde0e28460d20c1d5d61a5d56fe50d107304e4b184dd005048f9bcd159eff3cfaaef4ab5a8f29d38de109ea41a7ceaa886a0559a4fd0c7448cf28db85c3758fbde23443776e08dd29c435b1195f205aa787f2ec4a7259afeff078faa419c9af5706b9c08e7ce5772326e09df22eb85b0abc625b969aae34a881c12bd9653a08dc62b8e82cf89d0d66974f96149c6dcd7fc4363eecbf4fdac39e500c416b8d6a2e5871d80775acd1c13df4e4ad30a150390d500869ee6a4eab1c4285952d0549c45960a0b1b5ccfb93c75b63923fbc02b1d5a91e53f7424478cc58bf4366a612d40deec5efea700a7b127f7fdded1c4232e5b2eb7190f964e20b156e62d63d097005a24d96457a3db387a6b25e2a65fb169de0323a48f275780dc320d1c7d22642d3371e65559bf5510a3990b67aceb87daccd40e7f82fbba5a0065a63849dbb6600b420e8cb3561154dc69a3b063784737a7eb9d3f770fa1312c03e8518200cefc1be356bf0b0d1928816e712d24b5021b72f20d84a062a47ef50acf5cc9de025378611fbd7fd6473bc0258aa1697b057f6ce4c99f7a6ade34411a008aaccb3e3b2f78e2bc0720c3050960ad8b2988907a410a8fe565b671d3f2a274b9ed230786da769ebc73e9212b41902bec589d602dc941a6dc8a3c37ede467fdb21101cc8befe111b8a365b94612be8eef16a14dc1f163647744c2d0eabc17dc0bd297ba2e9237fea5d8c845e11ae207c4ce8d5d17b2dffdb6ba20c0474ab9e8547b90bdf61d41602b64b84b3d725279e818dccb82f25c6a75dc473af074c97bd6ea775eb575dd07bd2574dfe308748ecc0d39df14659e1958dbc0413fa7f214b6ffb4059b0ef21e9c2602723695988382201c36dfb9bb5916246f0ecad281b1431846e43b5651a85745a4b81586282285f56625fdb8f46a7b752f1a81159f04c12ec20723c22ae2dadd384bcedecc16de9ae253081ec8b5616b94ec716eb2e91c6af58eddcb360841ec942782fe7b44e9ea191e98007faa5722f12b5e0ff23304297f3aeb369a3ff79434f2ff2e7575bd7e7a1d2592043ea245ca0f69cc0c781b42258230b45391d1c1545ed0a9dcdbedc454c6e7dd313b2e6e757f91309d8fa7801bba864f60f04a1b12e0770e4aa62b7d388f8a0b85d38defa8a21a7764388dd7273b941a17f1f1b1a7acefc8f1726a7cb4ea3f19ddb7a70082c1c5cb6921d4c0eea07cf4d226c98ed1c57a92652b2687181da8091db3ac77bbf5634c351990296494868dd1e365af320c88148a0ed887a06f4be3256cf4556f00ce3376a7016ffddc26f36272d48fc5500451a2ea6de5f6778948e9ba856db5a38129ad1367b983d1f2b624ac5e2e35ce9145eee563d047c853a30c6cabb8064ea183dba622adebae8d2149b93752776e55155319009f2f9a3c1aec9a2b266fdb0451c97fbcc8ca7c1adff0806adc1dc8105b678a30af95cfc5f370dd0e4a6db9811b2deac2ed1fc8716b0571210ee7208316f24dc2af7394519401173d36c3f25fbe4ebd965aa19f53e6fef550ab72216dcfd20ac974b40b456ce143bd4e495dd51fd58e23877ac91e28abed7e9a12c7fbf694e5b5cf144f011da4aca445cf5546d68b2341401460bf2d717663c", ByteVector32(hex"022406339068be457a4430d0de697ee810f0911afc344bd3d4d662771874d2ce")), TlvStream(UpdateAddHtlcTlv.BlindingPoint(PublicKey(hex"02039885cd5b9ffd24b5ce83f464a7d4c0e3f23c1a8061c9fc85730db67ffdbd0c")), UpdateAddHtlcTlv.Endorsement(0))), + UpdateAddHtlc(ByteVector32(hex"f865a44f81f02f3539842b863668403a68ddb3703e03cd91045c9ac114dbd28e"), 2, 146 msat, ByteVector32(hex"d3708298fd195572cceb86a1745210543c42f931a1a2baeed7f705d333ebed22"), CltvExpiry(78696), OnionRoutingPacket(0, hex"037cb785d5a9de762adc62e3f2407d452bd1f13d368d0429caee5541a89488e3b9", payload = hex"5b90dd3803ff56400a40dc539efa0a0e29736c76e83d2d8b44775adcb4d485be5eb84eefbfedc69b687ee5c7080f83ff31760ec036990d904de480064966bba393af729d57437c91bea905a66464461a6462c5a0c66976ecaeded73d330dfaeb5651ba68e74b210c123e63ac6ee15d6673cf126f046840bbf4364c907f56382870da85101045fc9392357b0b32081cde0e28460d20c1d5d61a5d56fe50d107304e4b184dd005048f9bcd159eff3cfaaef4ab5a8f29d38de109ea41a7ceaa886a0559a4fd0c7448cf28db85c3758fbde23443776e08dd29c435b1195f205aa787f2ec4a7259afeff078faa419c9af5706b9c08e7ce5772326e09df22eb85b0abc625b969aae34a881c12bd9653a08dc62b8e82cf89d0d66974f96149c6dcd7fc4363eecbf4fdac39e500c416b8d6a2e5871d80775acd1c13df4e4ad30a150390d500869ee6a4eab1c4285952d0549c45960a0b1b5ccfb93c75b63923fbc02b1d5a91e53f7424478cc58bf4366a612d40deec5efea700a7b127f7fdded1c4232e5b2eb7190f964e20b156e62d63d097005a24d96457a3db387a6b25e2a65fb169de0323a48f275780dc320d1c7d22642d3371e65559bf5510a3990b67aceb87daccd40e7f82fbba5a0065a63849dbb6600b420e8cb3561154dc69a3b063784737a7eb9d3f770fa1312c03e8518200cefc1be356bf0b0d1928816e712d24b5021b72f20d84a062a47ef50acf5cc9de025378611fbd7fd6473bc0258aa1697b057f6ce4c99f7a6ade34411a008aaccb3e3b2f78e2bc0720c3050960ad8b2988907a410a8fe565b671d3f2a274b9ed230786da769ebc73e9212b41902bec589d602dc941a6dc8a3c37ede467fdb21101cc8befe111b8a365b94612be8eef16a14dc1f163647744c2d0eabc17dc0bd297ba2e9237fea5d8c845e11ae207c4ce8d5d17b2dffdb6ba20c0474ab9e8547b90bdf61d41602b64b84b3d725279e818dccb82f25c6a75dc473af074c97bd6ea775eb575dd07bd2574dfe308748ecc0d39df14659e1958dbc0413fa7f214b6ffb4059b0ef21e9c2602723695988382201c36dfb9bb5916246f0ecad281b1431846e43b5651a85745a4b81586282285f56625fdb8f46a7b752f1a81159f04c12ec20723c22ae2dadd384bcedecc16de9ae253081ec8b5616b94ec716eb2e91c6af58eddcb360841ec942782fe7b44e9ea191e98007faa5722f12b5e0ff23304297f3aeb369a3ff79434f2ff2e7575bd7e7a1d2592043ea245ca0f69cc0c781b42258230b45391d1c1545ed0a9dcdbedc454c6e7dd313b2e6e757f91309d8fa7801bba864f60f04a1b12e0770e4aa62b7d388f8a0b85d38defa8a21a7764388dd7273b941a17f1f1b1a7acefc8f1726a7cb4ea3f19ddb7a70082c1c5cb6921d4c0eea07cf4d226c98ed1c57a92652b2687181da8091db3ac77bbf5634c351990296494868dd1e365af320c88148a0ed887a06f4be3256cf4556f00ce3376a7016ffddc26f36272d48fc5500451a2ea6de5f6778948e9ba856db5a38129ad1367b983d1f2b624ac5e2e35ce9145eee563d047c853a30c6cabb8064ea183dba622adebae8d2149b93752776e55155319009f2f9a3c1aec9a2b266fdb0451c97fbcc8ca7c1adff0806adc1dc8105b678a30af95cfc5f370dd0e4a6db9811b2deac2ed1fc8716b0571210ee7208316f24dc2af7394519401173d36c3f25fbe4ebd965aa19f53e6fef550ab72216dcfd20ac974b40b456ce143bd4e495dd51fd58e23877ac91e28abed7e9a12c7fbf694e5b5cf144f011da4aca445cf5546d68b2341401460bf2d717663c", ByteVector32(hex"022406339068be457a4430d0de697ee810f0911afc344bd3d4d662771874d2ce")), TlvStream(UpdateAddHtlcTlv.PathKey(PublicKey(hex"02039885cd5b9ffd24b5ce83f464a7d4c0e3f23c1a8061c9fc85730db67ffdbd0c")), UpdateAddHtlcTlv.Endorsement(0))), hex"2c2c2f7eb2eed5b415aed6671228a90d428d9c9fa1dbf492b0625dc4c7d243c3 0000000000000003 00000000000ba6f3 0b7cb0fb7cedeb92573b8865018730232430c8d2365c4e22017f306ae3853ff7 00001211 00 0344ad77d64a38466f8cabc92a956ccceb64e451d09945a45d4be7a16bcb59d84c a62cd346fe5002786e00d538f85d0606b7e946717c62f0df9cd806c04c27d02ce1e536560633099d81fa158aba333c4ccfb91b30bc7e361fcde9705457a0efb1aa5e5983448833b3603d4459a1322275cc29b79d285b762c38b916e176fb779c5ca8cb1804300342462cf4c10d6229e73e5a382976306d0d65583490a5b595fb7f41f4fef20496791381b16a51840c17e805ee44961316cb0ca62d7a0a23a1d42c0d8098128b09ab21ce4a6b5b8749c45f5e0f761c1e24c2e141714fa32bba27da8a113ea26f9af687dd6bafc902b4fb7e53af3dea9fc675928207c898eb2327cea938b342cab7e57cf9c34ec443ace8e66bdc41f98f934e8ba18db357f49d1bdf540632b369435b2e378cd97304e49ce037f531f2faf381a70aaef06582eb2b0956a6b7e39dcaf469253ed6a508947f1a715f6c42c028af2342ced466d7d65bf7d3282ee403b6f220403abad14541d806b355ac38c262dc943c7c239c23b1f863f87259838288a6b5868da8436a56d4d14e7eca32b92070f95ef332c09f3693e952841d6771cd5904a903910b8333337786acdc3099733534ac237e0fbef3acd8e0b4fd665afae94f886dcd86ba0ffe39b6a2fff7e761125ed48c9ef7340d73c3b52a4acf4336b9cc891196158ad77442b242016c1b2333d4be6708c51e5d4ca42cf90438cf21bb7e63731a3be083b20c74954997114c4d08ca886e93055f0fdb34efc3237ca40d28f386b441a699dbaf27f2abb82a49b864d67bca6db2b8b393c02181ec058e350f0f28037ccdca3815fe3f85af3fbcc9c2bcfbffec9ddbc292539ccd16df09c6c892533b3831d7463ac0091d6e3ddd1a5a282a686ce037d47a7c6e373f98110616a1f5f2031a8d231532beebc1703cbaf262c286db4d42ddcdf11c338a0b15dddf422ca09e76a43d453414b8900db9a1e2a6791543e8d9d3d0e3cbe82f2c6ffaa433bb675322e9ae104a9d7b7af7052a44f4b58b522418563ff4c859d5e954c50a8af1295d71f575a888fc30ce25c9d23f1954636ae6f6b2f987ce15a25fbfd7432ca83d2d6f1292dbb1c557b82b9d5bd0fed0b9e21e15401c23d08dfd613403d9127d6b8e4b7673c6ff6c07c47f806f251e36e71e5778f38e73233008d1968a5e6adab26cf77c6fcadd62ae3304c7ba89614107faeaa8eec0c9e9ea9307abcca1e44e40228991aec789cac3d190bb9ab425ad834b6fc69ca8776d926dc6215de382a1275bc327447a5f5ef6c92ae1a2c45cf27441692a5f5ff13a1d5b365aec77c726923f14e376a6aaa9b4ada3931350b8b7eab50e101a9714b884c73ad4fef78520f2582c4f4328b18b1102ea5c93b96055bdc9c955adbeda29a58acc1937e4cb185a481a7d9ec56050d5216869bdcb7773718c68f36de348043116f6f33d98c9b56f8111a2a08f76cf1dbcb0e86660dab947900ed0c6592429b23a9f1d21c72d9a27544a8e135241eb52e3080fa517efd232d6f7f7fb03f82e9332ab5292be9bdf8d67978dd1bc99eff426e02fb3dc9dd15c660747c88a46dd92aeba448be690bf1659a30b1ef304db9d5d607e82a5120439c36e70225a234ef3e4699920426826098ea215553fcb933a4e1ee8a86e53d9cbbcb1b3da0122cfaa2c245b0c60abd5a6dcbf27d4d5cc3374c0285c973bce4f2ab75c7fe19b1e75694568db79c7181a91f36737bb02d635a831e35afa93cb068e8389c3968443a6d2f679ace7e71c1525689b34cfb714e46843fe268320193ce5afdc9dd7aa506f5ed845292dbdd96f91dcbd436e870d59aa4dcac71b19c9756498b2ac9e4d8c7bb7c456559a0775494c326510a2d84a60ac eb26d892176ae2cddd393e1bb626ec2df1f1ae4c65e89f091cb4201e6aa132a5 fe0001a147 01 03" -> UpdateAddHtlc(ByteVector32(hex"2c2c2f7eb2eed5b415aed6671228a90d428d9c9fa1dbf492b0625dc4c7d243c3"), 3, 763635 msat, ByteVector32(hex"0b7cb0fb7cedeb92573b8865018730232430c8d2365c4e22017f306ae3853ff7"), CltvExpiry(4625), OnionRoutingPacket(0, hex"0344ad77d64a38466f8cabc92a956ccceb64e451d09945a45d4be7a16bcb59d84c", payload = hex"a62cd346fe5002786e00d538f85d0606b7e946717c62f0df9cd806c04c27d02ce1e536560633099d81fa158aba333c4ccfb91b30bc7e361fcde9705457a0efb1aa5e5983448833b3603d4459a1322275cc29b79d285b762c38b916e176fb779c5ca8cb1804300342462cf4c10d6229e73e5a382976306d0d65583490a5b595fb7f41f4fef20496791381b16a51840c17e805ee44961316cb0ca62d7a0a23a1d42c0d8098128b09ab21ce4a6b5b8749c45f5e0f761c1e24c2e141714fa32bba27da8a113ea26f9af687dd6bafc902b4fb7e53af3dea9fc675928207c898eb2327cea938b342cab7e57cf9c34ec443ace8e66bdc41f98f934e8ba18db357f49d1bdf540632b369435b2e378cd97304e49ce037f531f2faf381a70aaef06582eb2b0956a6b7e39dcaf469253ed6a508947f1a715f6c42c028af2342ced466d7d65bf7d3282ee403b6f220403abad14541d806b355ac38c262dc943c7c239c23b1f863f87259838288a6b5868da8436a56d4d14e7eca32b92070f95ef332c09f3693e952841d6771cd5904a903910b8333337786acdc3099733534ac237e0fbef3acd8e0b4fd665afae94f886dcd86ba0ffe39b6a2fff7e761125ed48c9ef7340d73c3b52a4acf4336b9cc891196158ad77442b242016c1b2333d4be6708c51e5d4ca42cf90438cf21bb7e63731a3be083b20c74954997114c4d08ca886e93055f0fdb34efc3237ca40d28f386b441a699dbaf27f2abb82a49b864d67bca6db2b8b393c02181ec058e350f0f28037ccdca3815fe3f85af3fbcc9c2bcfbffec9ddbc292539ccd16df09c6c892533b3831d7463ac0091d6e3ddd1a5a282a686ce037d47a7c6e373f98110616a1f5f2031a8d231532beebc1703cbaf262c286db4d42ddcdf11c338a0b15dddf422ca09e76a43d453414b8900db9a1e2a6791543e8d9d3d0e3cbe82f2c6ffaa433bb675322e9ae104a9d7b7af7052a44f4b58b522418563ff4c859d5e954c50a8af1295d71f575a888fc30ce25c9d23f1954636ae6f6b2f987ce15a25fbfd7432ca83d2d6f1292dbb1c557b82b9d5bd0fed0b9e21e15401c23d08dfd613403d9127d6b8e4b7673c6ff6c07c47f806f251e36e71e5778f38e73233008d1968a5e6adab26cf77c6fcadd62ae3304c7ba89614107faeaa8eec0c9e9ea9307abcca1e44e40228991aec789cac3d190bb9ab425ad834b6fc69ca8776d926dc6215de382a1275bc327447a5f5ef6c92ae1a2c45cf27441692a5f5ff13a1d5b365aec77c726923f14e376a6aaa9b4ada3931350b8b7eab50e101a9714b884c73ad4fef78520f2582c4f4328b18b1102ea5c93b96055bdc9c955adbeda29a58acc1937e4cb185a481a7d9ec56050d5216869bdcb7773718c68f36de348043116f6f33d98c9b56f8111a2a08f76cf1dbcb0e86660dab947900ed0c6592429b23a9f1d21c72d9a27544a8e135241eb52e3080fa517efd232d6f7f7fb03f82e9332ab5292be9bdf8d67978dd1bc99eff426e02fb3dc9dd15c660747c88a46dd92aeba448be690bf1659a30b1ef304db9d5d607e82a5120439c36e70225a234ef3e4699920426826098ea215553fcb933a4e1ee8a86e53d9cbbcb1b3da0122cfaa2c245b0c60abd5a6dcbf27d4d5cc3374c0285c973bce4f2ab75c7fe19b1e75694568db79c7181a91f36737bb02d635a831e35afa93cb068e8389c3968443a6d2f679ace7e71c1525689b34cfb714e46843fe268320193ce5afdc9dd7aa506f5ed845292dbdd96f91dcbd436e870d59aa4dcac71b19c9756498b2ac9e4d8c7bb7c456559a0775494c326510a2d84a60ac", ByteVector32(hex"eb26d892176ae2cddd393e1bb626ec2df1f1ae4c65e89f091cb4201e6aa132a5")), TlvStream(UpdateAddHtlcTlv.Endorsement(3))), hex"6c963f8e8b9be358f190a3ac3e12a34400bda4796ed9c23daf179794474a9b62 0000000000000004 00000000000000f5 d9b2563807d4830dc7a42e2df0a146b2acecd54ca3870a928f2b4ac5b489d0eb 000b3c4e 00 033df0a97d288ef59a42b68c03083c36f06b75e651f2620275347e49456e924949 afe9ae18f4780afe43a1450247b5c790e47a27983aa63b82356d049c277517f4991776396cfbbbb5905059a8ebcd49a1c63299a40df59bb8e1842025c8644defa4a0f0bd80d159c68b49747ad1625fbb5182a48634238d42b2678d39d5db9a67fbb3624cf10249b286ba780ced9ede8e37d93a248f756dc134401656d787d2106303082d26601a48aa30804632877de8bc721556f30e57caa3787b04f3712b4d320c24afa7891e70e6f76751cc47a09ddf86aea7099c43809c7f244b21e551d63d363f1c6b5db02504c46449fcfc8038e057713ed1bc5e6daa1b44a90a9db259964b963be6cbdfb4aa000caaf9984aa12ae5a2dc2323b9ab57c1ca35f722c29adeb08789aff2f25936070f38b9b390937983ba8d6434fed6cfd9077e6508b85a2ba020ffc9dc2507beb3278fda821f2ae61ef0ec6a4a226f7b067cc7e69122eeb91dda7885bf9d358d1dfd4ea5af1df4bae30eebe79ddc27abb4edfa4882e9167e557bd0aabb71c5b906f4d5c537a816ee958a1d7a76597e262b50198ba25fd0fb4971c5e22ad0724d1686afa1edb8a5ddf8ad57443258d8044f331463c6ce0f278b16a9a11b8c7b88a494c2c524bdfc37d67f0635f36b15356762f825d23e8228602421e065d828d628f3e76a0505be69179772aa62ee481def1ce1621f874e1ea74afcf0f42c3ab559163afd06c493a56ab0e0ce2563f3351dda1096ff7f7215d61689dd3adc51f2204c664c2cb429237423c7cca52d222662577ab5411b1ed05810b2b1e43ade1958fed3b21623cbf19933dd35e6596c886b3fcfb11b7efa78067786740f0ea887921c8d6a6841b74d2166b6cf83d4432b1f17cefdcffebc0ef08fe5416f5f1f5072d44fa835b5f7078723727aba801343669c8a1afc4e9a2ec3c3821af297c5ee5fd6364b866d7f8b47b6709303246a09274f5d17640b6c60fa9eeb2f7e35472f33db8538ca1cff95e39dd09ac3680faa4ca8ba1ed33f9726adc84a50619605a1b763765f1c26d884d74b884351cbca23d935b25095e08b8cce04e360e0587c034d883f1c7a44cfebf82c7c67dc13c6b76d396cb90a8158fa8d270084f716237eb9b6dc464b2c3e857443f0e8f3073079fefdd7f757abaf19b38da991956034ce1be47315022433bbe766e1d6d02c822314706702c2a61234345ff374c4291f5bd00d8d4caeef0a48785c63afb8196d4874f9c19bb53199bc7ed81a0e94108a7b6851b9a2e3a6f4e12a0eaddf16d4ff1cf6ff9f9da6d81cfc167896ecc3b7a2b6f774a1f394a321bdfb40bdaedc2ecc7148a6d6b1ef64e38c6ea35b0bb17986351e82be82aa2233ed069a6913bbf3a87e5b1094bc2c0ff28b918974357217e160562748a2440670ea1055df53e18a9a3afc0f9f34e40f222cb4f9f35a19488f0ca1b23ada14804f32d183971cb918d7b2430b3f2e4b7633204b0793862521d130e926b6583ca466acf4300020e2c85297f617e29e1c4f0e1ea5676062d8fabc8035f71d2598e3cf7f38e5f61b0f4896442b1c0b102f85fbd1068339dafc9debf90b88e89420337ac34643acf017debff60d030de65c22883205327c0af6cdf70349722073195e2597775514a86f766590c43a3b844f78618b7c7a63d2665a800d5bd1edee916c93ede8c0c8dc980ab9f85ff33c3b4740a4b0fc3f3b3e324a349e9c21e0aec8fdcc0a14b0e35b68b3d46cfcfd991eefc8b616f1a376030de33c1662c0210cfbaa27653ff8a814b4acd2ad0a09761db5f0ba8ef2a00cf66053725a4e422b0cd22f9d4881e28573ccfd3b9b3088698c1acb647d8ddeda65303fc57d9ad663a016b1c1a0dd6712 f6514b5e1eae383e2c5ae1ec1820f28583304274fa11ef2d2e2d6f3cafa2ede0 fe0001a147 01 07" -> diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/OfferTypesSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/OfferTypesSpec.scala index 8ff0bbf7a1..7973b33b70 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/OfferTypesSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/OfferTypesSpec.scala @@ -21,7 +21,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.{PrivateKey, PublicKey} import fr.acinq.bitcoin.scalacompat.{Block, BlockHash, ByteVector32} import fr.acinq.eclair.FeatureSupport.{Mandatory, Optional} import fr.acinq.eclair.Features.BasicMultiPartPayment -import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedNode, BlindedRoute} +import fr.acinq.eclair.crypto.Sphinx.RouteBlinding.{BlindedHop, BlindedRoute} import fr.acinq.eclair.wire.protocol.OfferCodecs.{invoiceRequestTlvCodec, offerTlvCodec} import fr.acinq.eclair.wire.protocol.OfferTypes._ import fr.acinq.eclair.{BlockHeight, EncodedNodeId, Features, MilliSatoshiLong, RealShortChannelId, randomBytes32, randomKey} @@ -269,13 +269,13 @@ class OfferTypesSpec extends AnyFunSuite { val testCases = Seq( TestCase(hex"00 00000000000004d2 0379b470d00b78ded936f8972a0f3ecda2bb6e6df40dcd581dbaeb3742b30008ff 01 02fba71b72623187dd24670110eec870e28b848f255ba2edc0486d3a8e89ec44b7 0002 1dea", - BlindedRoute(EncodedNodeId.ShortChannelIdDir(isNode1 = true, RealShortChannelId(1234)), PublicKey(hex"0379b470d00b78ded936f8972a0f3ecda2bb6e6df40dcd581dbaeb3742b30008ff"), Seq(BlindedNode(PublicKey(hex"02fba71b72623187dd24670110eec870e28b848f255ba2edc0486d3a8e89ec44b7"), hex"1dea")))), + BlindedRoute(EncodedNodeId.ShortChannelIdDir(isNode1 = true, RealShortChannelId(1234)), PublicKey(hex"0379b470d00b78ded936f8972a0f3ecda2bb6e6df40dcd581dbaeb3742b30008ff"), Seq(BlindedHop(PublicKey(hex"02fba71b72623187dd24670110eec870e28b848f255ba2edc0486d3a8e89ec44b7"), hex"1dea")))), TestCase(hex"01 000000000000ddd5 0353a081bb02d6e361be3df3e92b41b788ca65667f6ea0c01e2bfa03664460ef86 01 03bce3f0cdb4172caac82ec8a9251eb35df1201bdcb977c5a03f3624ec4156a65f 0003 c0ffee", - BlindedRoute(EncodedNodeId.ShortChannelIdDir(isNode1 = false, RealShortChannelId(56789)), PublicKey(hex"0353a081bb02d6e361be3df3e92b41b788ca65667f6ea0c01e2bfa03664460ef86"), Seq(BlindedNode(PublicKey(hex"03bce3f0cdb4172caac82ec8a9251eb35df1201bdcb977c5a03f3624ec4156a65f"), hex"c0ffee")))), + BlindedRoute(EncodedNodeId.ShortChannelIdDir(isNode1 = false, RealShortChannelId(56789)), PublicKey(hex"0353a081bb02d6e361be3df3e92b41b788ca65667f6ea0c01e2bfa03664460ef86"), Seq(BlindedHop(PublicKey(hex"03bce3f0cdb4172caac82ec8a9251eb35df1201bdcb977c5a03f3624ec4156a65f"), hex"c0ffee")))), TestCase(hex"022d3b15cea00ee4a8e710b082bef18f0f3409cc4e7aff41c26eb0a4d3ab20dd73 0379a3b6e4bceb7519d09db776994b1f82cf6a9fa4d3ec2e52314c5938f2f9f966 01 02b446aaa523df82a992ab468e5298eabb6168e2c466455c210d8c97dbb8981328 0002 cafe", - BlindedRoute(EncodedNodeId.WithPublicKey.Plain(PublicKey(hex"022d3b15cea00ee4a8e710b082bef18f0f3409cc4e7aff41c26eb0a4d3ab20dd73")), PublicKey(hex"0379a3b6e4bceb7519d09db776994b1f82cf6a9fa4d3ec2e52314c5938f2f9f966"), Seq(BlindedNode(PublicKey(hex"02b446aaa523df82a992ab468e5298eabb6168e2c466455c210d8c97dbb8981328"), hex"cafe")))), + BlindedRoute(EncodedNodeId.WithPublicKey.Plain(PublicKey(hex"022d3b15cea00ee4a8e710b082bef18f0f3409cc4e7aff41c26eb0a4d3ab20dd73")), PublicKey(hex"0379a3b6e4bceb7519d09db776994b1f82cf6a9fa4d3ec2e52314c5938f2f9f966"), Seq(BlindedHop(PublicKey(hex"02b446aaa523df82a992ab468e5298eabb6168e2c466455c210d8c97dbb8981328"), hex"cafe")))), TestCase(hex"03ba3c458e3299eb19d2e07ae86453f4290bcdf8689707f0862f35194397c45922 028aa5d1a10463d598a0a0ab7296af21619049f94fe03ef664a87561009e58c3dd 01 02988d7381d0434cfebbe521031505fb9987ae6cefd0bab0e5927852eb96bb6cc2 0003 ec1a13", - BlindedRoute(EncodedNodeId.WithPublicKey.Plain(PublicKey(hex"03ba3c458e3299eb19d2e07ae86453f4290bcdf8689707f0862f35194397c45922")), PublicKey(hex"028aa5d1a10463d598a0a0ab7296af21619049f94fe03ef664a87561009e58c3dd"), Seq(BlindedNode(PublicKey(hex"02988d7381d0434cfebbe521031505fb9987ae6cefd0bab0e5927852eb96bb6cc2"), hex"ec1a13")))), + BlindedRoute(EncodedNodeId.WithPublicKey.Plain(PublicKey(hex"03ba3c458e3299eb19d2e07ae86453f4290bcdf8689707f0862f35194397c45922")), PublicKey(hex"028aa5d1a10463d598a0a0ab7296af21619049f94fe03ef664a87561009e58c3dd"), Seq(BlindedHop(PublicKey(hex"02988d7381d0434cfebbe521031505fb9987ae6cefd0bab0e5927852eb96bb6cc2"), hex"ec1a13")))), ) testCases.foreach { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/PaymentOnionSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/PaymentOnionSpec.scala index 686eaa487e..3444c54eae 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/PaymentOnionSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/PaymentOnionSpec.scala @@ -103,7 +103,7 @@ class PaymentOnionSpec extends AnyFunSuite { ) val testCases = Map( TlvStream[OnionPaymentPayloadTlv](EncryptedRecipientData(hex"0123456789abcdef")) -> hex"0a 0a080123456789abcdef ", - TlvStream[OnionPaymentPayloadTlv](EncryptedRecipientData(hex"0123456789abcdef"), BlindingPoint(PublicKey(hex"036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"))) -> hex"2d 0a080123456789abcdef 0c21036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2", + TlvStream[OnionPaymentPayloadTlv](EncryptedRecipientData(hex"0123456789abcdef"), PathKey(PublicKey(hex"036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"))) -> hex"2d 0a080123456789abcdef 0c21036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2", ) for ((expected, bin) <- testCases) { @@ -182,7 +182,7 @@ class PaymentOnionSpec extends AnyFunSuite { val blindedRoute = BlindedRoute( EncodedNodeId.ShortChannelIdDir(isNode1 = false, RealShortChannelId(468)), PublicKey(hex"0232882c4982576e00f0d6bd4998f5b3e92d47ecc8fbad5b6a5e7521819d891d9e"), - Seq(RouteBlinding.BlindedNode(PublicKey(hex"03823aa560d631e9d7b686be4a9227e577009afb5173023b458a6a6aff056ac980"), hex"")) + Seq(RouteBlinding.BlindedHop(PublicKey(hex"03823aa560d631e9d7b686be4a9227e577009afb5173023b458a6a6aff056ac980"), hex"")) ) val path = PaymentBlindedRoute(blindedRoute, OfferTypes.PaymentInfo(1000 msat, 678, CltvExpiryDelta(82), 300 msat, 4000000 msat, Features.empty)) val expected = TlvStream[OnionPaymentPayloadTlv](AmountToForward(341 msat), OutgoingCltv(CltvExpiry(826483)), OutgoingBlindedPaths(Seq(path)), InvoiceFeatures(features)) @@ -240,7 +240,7 @@ class PaymentOnionSpec extends AnyFunSuite { ) val testCases = Map( TlvStream[OnionPaymentPayloadTlv](AmountToForward(561 msat), OutgoingCltv(CltvExpiry(1234567)), EncryptedRecipientData(hex"deadbeef"), TotalAmount(1105 msat)) -> hex"13 02020231 040312d687 0a04deadbeef 12020451", - TlvStream[OnionPaymentPayloadTlv](AmountToForward(561 msat), OutgoingCltv(CltvExpiry(1234567)), EncryptedRecipientData(hex"deadbeef"), BlindingPoint(PublicKey(hex"036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2")), TotalAmount(1105 msat)) -> hex"36 02020231 040312d687 0a04deadbeef 0c21036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2 12020451", + TlvStream[OnionPaymentPayloadTlv](AmountToForward(561 msat), OutgoingCltv(CltvExpiry(1234567)), EncryptedRecipientData(hex"deadbeef"), PathKey(PublicKey(hex"036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2")), TotalAmount(1105 msat)) -> hex"36 02020231 040312d687 0a04deadbeef 0c21036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2 12020451", ) for ((expected, bin) <- testCases) { @@ -324,7 +324,7 @@ class PaymentOnionSpec extends AnyFunSuite { (MissingRequiredTlv(UInt64(4)), hex"2b 02020231 fe000102322102eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), // missing cltv (MissingRequiredTlv(UInt64(66098)), hex"07 02020231 04012a"), // missing node id (ForbiddenTlv(UInt64(10)), hex"34 02020231 04012a 0a04ffffffff fe000102322102eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), // forbidden encrypted data - (ForbiddenTlv(UInt64(12)), hex"51 02020231 04012a 0c21036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2 fe000102322102eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), // forbidden blinding point + (ForbiddenTlv(UInt64(12)), hex"51 02020231 04012a 0c21036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2 fe000102322102eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619"), // forbidden path key ) for ((expectedErr, bin) <- testCases) { diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/RouteBlindingSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/RouteBlindingSpec.scala index 9b9d6f9ae2..304909ea37 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/RouteBlindingSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/wire/protocol/RouteBlindingSpec.scala @@ -16,7 +16,7 @@ class RouteBlindingSpec extends AnyFunSuiteLike { val payloads = Map[ByteVector, TlvStream[RouteBlindingEncryptedDataTlv]]( // Payment reference test vector: see https://github.com/lightning/bolts/blob/master/bolt04/route-blinding-test.json hex"011a0000000000000000000000000000000000000000000000000000 020800000000000006c1 0a080024000000962710 0c06000b69e505dc 0e00 fd023103123456" -> TlvStream(Set[RouteBlindingEncryptedDataTlv](Padding(hex"0000000000000000000000000000000000000000000000000000"), OutgoingChannelId(ShortChannelId(1729)), PaymentRelay(CltvExpiryDelta(36), 150, 10000 msat), PaymentConstraints(CltvExpiry(748005), 1500 msat), AllowedFeatures(Features.empty)), Set(GenericTlv(UInt64(561), hex"123456"))), - hex"02080000000000000451 0821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f 0a0800300000006401f4 0c06000b69c105dc 0e00" -> TlvStream(OutgoingChannelId(ShortChannelId(1105)), NextBlinding(PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), PaymentRelay(CltvExpiryDelta(48), 100, 500 msat), PaymentConstraints(CltvExpiry(747969), 1500 msat), AllowedFeatures(Features.empty)), + hex"02080000000000000451 0821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f 0a0800300000006401f4 0c06000b69c105dc 0e00" -> TlvStream(OutgoingChannelId(ShortChannelId(1105)), NextPathKey(PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), PaymentRelay(CltvExpiryDelta(48), 100, 500 msat), PaymentConstraints(CltvExpiry(747969), 1500 msat), AllowedFeatures(Features.empty)), hex"01230000000000000000000000000000000000000000000000000000000000000000000000 02080000000000000231 0a060090000000fa 0c06000b699105dc 0e00" -> TlvStream(Padding(hex"0000000000000000000000000000000000000000000000000000000000000000000000"), OutgoingChannelId(ShortChannelId(561)), PaymentRelay(CltvExpiryDelta(144), 250, 0 msat), PaymentConstraints(CltvExpiry(747921), 1500 msat), AllowedFeatures(Features.empty)), hex"011a0000000000000000000000000000000000000000000000000000 0604deadbeef 0c06000b690105dc 0e0f020000000000000000000000000000 fdffff0206c1" -> TlvStream(Set[RouteBlindingEncryptedDataTlv](Padding(hex"0000000000000000000000000000000000000000000000000000"), PathId(hex"deadbeef"), PaymentConstraints(CltvExpiry(747777), 1500 msat), AllowedFeatures(Features(Map.empty[Feature, FeatureSupport], Set(UnknownFeature(113))))), Set(GenericTlv(UInt64(65535), hex"06c1"))), // Onion message reference test vector. @@ -26,7 +26,7 @@ class RouteBlindingSpec extends AnyFunSuiteLike { hex"042102edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145" -> TlvStream(OutgoingNodeId(PublicKey(hex"02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145"))), hex"010f000000000000000000000000000000 061000112233445566778899aabbccddeeff" -> TlvStream(Padding(hex"000000000000000000000000000000"), PathId(hex"00112233445566778899aabbccddeeff")), hex"0121000000000000000000000000000000000000000000000000000000000000000000 04210324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c" -> TlvStream(Padding(hex"000000000000000000000000000000000000000000000000000000000000000000"), OutgoingNodeId(PublicKey(hex"0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c"))), - hex"0421027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007 0821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" -> TlvStream(OutgoingNodeId(PublicKey(hex"027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), NextBlinding(PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"))), + hex"0421027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007 0821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f" -> TlvStream(OutgoingNodeId(PublicKey(hex"027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007")), NextPathKey(PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"))), ) for ((encoded, data) <- payloads) { @@ -41,7 +41,7 @@ class RouteBlindingSpec extends AnyFunSuiteLike { // See https://github.com/lightning/bolts/blob/master/bolt04/blinded-payment-onion-test.json val payloads = Map[ByteVector, TlvStream[RouteBlindingEncryptedDataTlv]]( hex"01200000000000000000000000000000000000000000000000000000000000000000 02080000000000000001 0a080032000000002710 0c05000b724632 0e00" -> TlvStream(Padding(hex"0000000000000000000000000000000000000000000000000000000000000000"), OutgoingChannelId(ShortChannelId(1)), PaymentRelay(CltvExpiryDelta(50), 0, 10000 msat), PaymentConstraints(CltvExpiry(750150), 50 msat), AllowedFeatures(Features.empty)), - hex"02080000000000000002 0821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f 0a07004b0000009664 0c05000b721432 0e00" -> TlvStream(OutgoingChannelId(ShortChannelId(2)), NextBlinding(PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), PaymentRelay(CltvExpiryDelta(75), 150, 100 msat), PaymentConstraints(CltvExpiry(750100), 50 msat), AllowedFeatures(Features.empty)), + hex"02080000000000000002 0821031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f 0a07004b0000009664 0c05000b721432 0e00" -> TlvStream(OutgoingChannelId(ShortChannelId(2)), NextPathKey(PublicKey(hex"031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")), PaymentRelay(CltvExpiryDelta(75), 150, 100 msat), PaymentConstraints(CltvExpiry(750100), 50 msat), AllowedFeatures(Features.empty)), hex"012200000000000000000000000000000000000000000000000000000000000000000000 02080000000000000003 0a06001900000064 0c05000b71c932 0e00" -> TlvStream(Padding(hex"00000000000000000000000000000000000000000000000000000000000000000000"), OutgoingChannelId(ShortChannelId(3)), PaymentRelay(CltvExpiryDelta(25), 100, 0 msat), PaymentConstraints(CltvExpiry(750025), 50 msat), AllowedFeatures(Features.empty)), hex"011c00000000000000000000000000000000000000000000000000000000 0616c9cf92f45ade68345bc20ae672e2012f4af487ed4415 0c05000b71b032 0e00" -> TlvStream(Padding(hex"00000000000000000000000000000000000000000000000000000000"), PathId(hex"c9cf92f45ade68345bc20ae672e2012f4af487ed4415"), PaymentConstraints(CltvExpiry(750000), 50 msat), AllowedFeatures(Features.empty)), ) @@ -78,22 +78,22 @@ class RouteBlindingSpec extends AnyFunSuiteLike { (TlvStream(Set[RouteBlindingEncryptedDataTlv](OutgoingChannelId(ShortChannelId(42)), PaymentRelay(CltvExpiryDelta(123), 200, 900 msat), PaymentConstraints(CltvExpiry(734576), 756001234 msat), AllowedFeatures(Features.empty)), Set(GenericTlv(UInt64(65535), hex"06c1"))), hex"0208000000000000002a 0a08007b000000c80384 0c08000b35702d0fa9d2 0e00 fdffff0206c1"), ) - val BlindedRouteDetails(blindedRoute, lastBlinding) = Sphinx.RouteBlinding.create(sessionKey, nodePrivKeys.map(_.publicKey), payloads.map(_._2)) - val blinding0 = sessionKey.publicKey - val Right(RouteBlindingDecryptedData(decryptedPayload0, blinding1)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(0), blinding0, blindedRoute.encryptedPayloads(0)) - val Right(RouteBlindingDecryptedData(decryptedPayload1, blinding2)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(1), blinding1, blindedRoute.encryptedPayloads(1)) - val Right(RouteBlindingDecryptedData(decryptedPayload2, blinding3)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(2), blinding2, blindedRoute.encryptedPayloads(2)) - val Right(RouteBlindingDecryptedData(decryptedPayload3, blinding4)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(3), blinding3, blindedRoute.encryptedPayloads(3)) - val Right(RouteBlindingDecryptedData(decryptedPayload4, _)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(4), blinding4, blindedRoute.encryptedPayloads(4)) + val BlindedRouteDetails(blindedRoute, lastPathKey) = Sphinx.RouteBlinding.create(sessionKey, nodePrivKeys.map(_.publicKey), payloads.map(_._2)) + val pathKey0 = sessionKey.publicKey + val Right(RouteBlindingDecryptedData(decryptedPayload0, pathKey1)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(0), pathKey0, blindedRoute.encryptedPayloads(0)) + val Right(RouteBlindingDecryptedData(decryptedPayload1, pathKey2)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(1), pathKey1, blindedRoute.encryptedPayloads(1)) + val Right(RouteBlindingDecryptedData(decryptedPayload2, pathKey3)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(2), pathKey2, blindedRoute.encryptedPayloads(2)) + val Right(RouteBlindingDecryptedData(decryptedPayload3, pathKey4)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(3), pathKey3, blindedRoute.encryptedPayloads(3)) + val Right(RouteBlindingDecryptedData(decryptedPayload4, _)) = RouteBlindingEncryptedDataCodecs.decode(nodePrivKeys(4), pathKey4, blindedRoute.encryptedPayloads(4)) assert(Seq(decryptedPayload0, decryptedPayload1, decryptedPayload2, decryptedPayload3, decryptedPayload4) == payloads.map(_._1)) - assert(lastBlinding == blinding4) + assert(lastPathKey == pathKey4) } test("decode invalid encrypted route blinding tlv stream") { val testCases = Seq( hex"02080000000000000231 0a0a00000000000003e8002a 0c08000b35702d0fa9d2 ff", // additional trailing bytes after tlv stream hex"01040000 02080000000000000231 0a0a00000000000003e8002a 0c08000b35702d0fa9d2", // invalid padding tlv - hex"02080000000000000231 0a0a00000000000003e8002a 0820025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce14 0c08000b35702d0fa9d2", // invalid next blinding length + hex"02080000000000000231 0a0a00000000000003e8002a 0820025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce14 0c08000b35702d0fa9d2", // invalid next path key length hex"0a0a00000000000003e8002a 02080000000000000231 0103000000 0c08000b35702d0fa9d2", // invalid tlv stream ordering hex"02080000000000000231 0a0a00000000000003e8002a 0c08000b35702d0fa9d2 10080000000000000231", // unknown even tlv field ) diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 636659f747..7aeba55e88 100644 --- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -1243,7 +1243,7 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM GenericTlv(UInt64(5), hex"1111") )), TlvStream(RouteBlindingEncryptedDataTlv.PathId(hex"2222"))) val msgrcv = OnionMessages.ReceiveMessage(payload, PrivateKey(hex"515151515151515151515151515151515151515151515151515151515151515101")) - val expectedSerializedMsgrcv = """{"type":"onion-message-received","pathId":"2222","tlvs":{"EncryptedData":{"data":""},"ReplyPath":{"blindedRoute":{"introductionNodeId":{"publicKey":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585"},"blindingKey":"02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619","blindedNodes":[{"blindedPublicKey":"020303f91e620504cde242df38d04599d8b4d4c555149cc742a5f12de452cbdd40","encryptedPayload":"126a26221759247584d704b382a5789f1d8c5a"}]}},"Unknown5":"1111"}}""" + val expectedSerializedMsgrcv = """{"type":"onion-message-received","pathId":"2222","tlvs":{"EncryptedData":{"data":""},"ReplyPath":{"blindedRoute":{"firstNodeId":{"publicKey":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585"},"firstPathKey":"02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619","blindedHops":[{"blindedPublicKey":"020303f91e620504cde242df38d04599d8b4d4c555149cc742a5f12de452cbdd40","encryptedPayload":"126a26221759247584d704b382a5789f1d8c5a"}]}},"Unknown5":"1111"}}""" assert(serialization.write(msgrcv) == expectedSerializedMsgrcv) system.eventStream.publish(msgrcv) wsClient.expectMessage(expectedSerializedMsgrcv)