@@ -27,7 +27,6 @@ import fr.acinq.eclair.wire.protocol._
27
27
import scodec .bits .ByteVector
28
28
import scodec .{Attempt , DecodeResult }
29
29
30
- import scala .annotation .tailrec
31
30
import scala .concurrent .duration .FiniteDuration
32
31
33
32
object OnionMessages {
@@ -44,23 +43,29 @@ object OnionMessages {
44
43
timeout : FiniteDuration ,
45
44
maxAttempts : Int )
46
45
47
- case class IntermediateNode (nodeId : PublicKey , outgoingChannel_opt : Option [ShortChannelId ] = None , padding : Option [ByteVector ] = None , customTlvs : Set [GenericTlv ] = Set .empty) {
48
- def toTlvStream (nextNodeId : PublicKey , nextBlinding_opt : Option [PublicKey ] = None ): TlvStream [RouteBlindingEncryptedDataTlv ] =
46
+ case class IntermediateNode (publicKey : PublicKey , encodedNodeId : EncodedNodeId , outgoingChannel_opt : Option [ShortChannelId ] = None , padding : Option [ByteVector ] = None , customTlvs : Set [GenericTlv ] = Set .empty) {
47
+ def toTlvStream (nextNodeId : EncodedNodeId , nextBlinding_opt : Option [PublicKey ] = None ): TlvStream [RouteBlindingEncryptedDataTlv ] =
49
48
TlvStream (Set [Option [RouteBlindingEncryptedDataTlv ]](
50
49
padding.map(Padding ),
51
50
outgoingChannel_opt.map(OutgoingChannelId ).orElse(Some (OutgoingNodeId (nextNodeId))),
52
51
nextBlinding_opt.map(NextBlinding )
53
52
).flatten, customTlvs)
54
53
}
55
54
55
+ object IntermediateNode {
56
+ def apply (publicKey : PublicKey ): IntermediateNode = IntermediateNode (publicKey, EncodedNodeId (publicKey))
57
+ }
58
+
56
59
// @formatter:off
57
60
sealed trait Destination {
58
- def nodeId : PublicKey
61
+ def introductionNodeId : EncodedNodeId
59
62
}
60
63
case class BlindedPath (route : Sphinx .RouteBlinding .BlindedRoute ) extends Destination {
61
- override def nodeId : PublicKey = route.introductionNodeId
64
+ override def introductionNodeId : EncodedNodeId = route.introductionNodeId
65
+ }
66
+ case class Recipient (nodeId : PublicKey , pathId : Option [ByteVector ], padding : Option [ByteVector ] = None , customTlvs : Set [GenericTlv ] = Set .empty) extends Destination {
67
+ override def introductionNodeId : EncodedNodeId = EncodedNodeId (nodeId)
62
68
}
63
- case class Recipient (nodeId : PublicKey , pathId : Option [ByteVector ], padding : Option [ByteVector ] = None , customTlvs : Set [GenericTlv ] = Set .empty) extends Destination
64
69
// @formatter:on
65
70
66
71
// @formatter:off
@@ -75,11 +80,11 @@ object OnionMessages {
75
80
}
76
81
// @formatter:on
77
82
78
- private def buildIntermediatePayloads (intermediateNodes : Seq [IntermediateNode ], lastNodeId : PublicKey , lastBlinding_opt : Option [PublicKey ] = None ): Seq [ByteVector ] = {
83
+ private def buildIntermediatePayloads (intermediateNodes : Seq [IntermediateNode ], lastNodeId : EncodedNodeId , lastBlinding_opt : Option [PublicKey ] = None ): Seq [ByteVector ] = {
79
84
if (intermediateNodes.isEmpty) {
80
85
Nil
81
86
} else {
82
- val intermediatePayloads = intermediateNodes.dropRight(1 ).zip(intermediateNodes.tail).map { case (hop, nextNode) => hop.toTlvStream(nextNode.nodeId ) }
87
+ val intermediatePayloads = intermediateNodes.dropRight(1 ).zip(intermediateNodes.tail).map { case (hop, nextNode) => hop.toTlvStream(nextNode.encodedNodeId ) }
83
88
val lastPayload = intermediateNodes.last.toTlvStream(lastNodeId, lastBlinding_opt)
84
89
(intermediatePayloads :+ lastPayload).map(tlvs => RouteBlindingEncryptedDataCodecs .blindedRouteDataCodec.encode(tlvs).require.bytes)
85
90
}
@@ -88,33 +93,22 @@ object OnionMessages {
88
93
def buildRoute (blindingSecret : PrivateKey ,
89
94
intermediateNodes : Seq [IntermediateNode ],
90
95
recipient : Recipient ): Sphinx .RouteBlinding .BlindedRoute = {
91
- val intermediatePayloads = buildIntermediatePayloads(intermediateNodes, recipient.nodeId)
96
+ val intermediatePayloads = buildIntermediatePayloads(intermediateNodes, EncodedNodeId ( recipient.nodeId) )
92
97
val tlvs : Set [RouteBlindingEncryptedDataTlv ] = Set (recipient.padding.map(Padding ), recipient.pathId.map(PathId )).flatten
93
98
val lastPayload = RouteBlindingEncryptedDataCodecs .blindedRouteDataCodec.encode(TlvStream (tlvs, recipient.customTlvs)).require.bytes
94
- Sphinx .RouteBlinding .create(blindingSecret, intermediateNodes.map(_.nodeId ) :+ recipient.nodeId, intermediatePayloads :+ lastPayload).route
99
+ Sphinx .RouteBlinding .create(blindingSecret, intermediateNodes.map(_.publicKey ) :+ recipient.nodeId, intermediatePayloads :+ lastPayload).route
95
100
}
96
101
97
- private [message] def buildRouteFrom (originKey : PrivateKey ,
98
- blindingSecret : PrivateKey ,
102
+ private [message] def buildRouteFrom (blindingSecret : PrivateKey ,
99
103
intermediateNodes : Seq [IntermediateNode ],
100
- destination : Destination ): Option [ Sphinx .RouteBlinding .BlindedRoute ] = {
104
+ destination : Destination ): Sphinx .RouteBlinding .BlindedRoute = {
101
105
destination match {
102
- case recipient : Recipient => Some (buildRoute(blindingSecret, intermediateNodes, recipient))
103
- case BlindedPath (route) if route.introductionNodeId == originKey.publicKey =>
104
- RouteBlindingEncryptedDataCodecs .decode(originKey, route.blindingKey, route.blindedNodes.head.encryptedPayload) match {
105
- case Left (_) => None
106
- case Right (decoded) =>
107
- decoded.tlvs.get[RouteBlindingEncryptedDataTlv .OutgoingNodeId ] match {
108
- case Some (RouteBlindingEncryptedDataTlv .OutgoingNodeId (EncodedNodeId .Plain (nextNodeId))) =>
109
- Some (Sphinx .RouteBlinding .BlindedRoute (nextNodeId, decoded.nextBlinding, route.blindedNodes.tail))
110
- case _ => None // TODO: allow compact node id and OutgoingChannelId
111
- }
112
- }
113
- case BlindedPath (route) if intermediateNodes.isEmpty => Some (route)
106
+ case recipient : Recipient => buildRoute(blindingSecret, intermediateNodes, recipient)
107
+ case BlindedPath (route) if intermediateNodes.isEmpty => route
114
108
case BlindedPath (route) =>
115
109
val intermediatePayloads = buildIntermediatePayloads(intermediateNodes, route.introductionNodeId, Some (route.blindingKey))
116
- val routePrefix = Sphinx .RouteBlinding .create(blindingSecret, intermediateNodes.map(_.nodeId ), intermediatePayloads).route
117
- Some ( Sphinx .RouteBlinding .BlindedRoute (routePrefix.introductionNodeId, routePrefix.blindingKey, routePrefix.blindedNodes ++ route.blindedNodes) )
110
+ val routePrefix = Sphinx .RouteBlinding .create(blindingSecret, intermediateNodes.map(_.publicKey ), intermediatePayloads).route
111
+ Sphinx .RouteBlinding .BlindedRoute (routePrefix.introductionNodeId, routePrefix.blindingKey, routePrefix.blindedNodes ++ route.blindedNodes)
118
112
}
119
113
}
120
114
@@ -134,32 +128,28 @@ object OnionMessages {
134
128
* @param content List of TLVs to send to the recipient of the message
135
129
* @return The node id to send the onion to and the onion containing the message
136
130
*/
137
- def buildMessage (nodeKey : PrivateKey ,
138
- sessionKey : PrivateKey ,
131
+ def buildMessage (sessionKey : PrivateKey ,
139
132
blindingSecret : PrivateKey ,
140
133
intermediateNodes : Seq [IntermediateNode ],
141
134
destination : Destination ,
142
- content : TlvStream [OnionMessagePayloadTlv ]): Either [BuildMessageError , (PublicKey , OnionMessage )] = {
143
- buildRouteFrom(nodeKey, blindingSecret, intermediateNodes, destination) match {
144
- case None => Left (InvalidDestination (destination))
145
- case Some (route) =>
146
- val lastPayload = MessageOnionCodecs .perHopPayloadCodec.encode(TlvStream (content.records + EncryptedData (route.encryptedPayloads.last), content.unknown)).require.bytes
147
- val payloads = route.encryptedPayloads.dropRight(1 ).map(encTlv => MessageOnionCodecs .perHopPayloadCodec.encode(TlvStream (EncryptedData (encTlv))).require.bytes) :+ lastPayload
148
- val payloadSize = payloads.map(_.length + Sphinx .MacLength ).sum
149
- val packetSize = if (payloadSize <= 1300 ) {
150
- 1300
151
- } else if (payloadSize <= 32768 ) {
152
- 32768
153
- } else if (payloadSize > 65432 ) {
154
- // A payload of size 65432 corresponds to a total lightning message size of 65535.
155
- return Left (MessageTooLarge (payloadSize))
156
- } else {
157
- payloadSize.toInt
158
- }
159
- // Since we are setting the packet size based on the payload, the onion creation should never fail (hence the `.get`).
160
- val Sphinx .PacketAndSecrets (packet, _) = Sphinx .create(sessionKey, packetSize, route.blindedNodes.map(_.blindedPublicKey), payloads, None ).get
161
- Right ((route.introductionNodeId, OnionMessage (route.blindingKey, packet)))
135
+ content : TlvStream [OnionMessagePayloadTlv ]): Either [BuildMessageError , OnionMessage ] = {
136
+ val route = buildRouteFrom(blindingSecret, intermediateNodes, destination)
137
+ val lastPayload = MessageOnionCodecs .perHopPayloadCodec.encode(TlvStream (content.records + EncryptedData (route.encryptedPayloads.last), content.unknown)).require.bytes
138
+ val payloads = route.encryptedPayloads.dropRight(1 ).map(encTlv => MessageOnionCodecs .perHopPayloadCodec.encode(TlvStream (EncryptedData (encTlv))).require.bytes) :+ lastPayload
139
+ val payloadSize = payloads.map(_.length + Sphinx .MacLength ).sum
140
+ val packetSize = if (payloadSize <= 1300 ) {
141
+ 1300
142
+ } else if (payloadSize <= 32768 ) {
143
+ 32768
144
+ } else if (payloadSize > 65432 ) {
145
+ // A payload of size 65432 corresponds to a total lightning message size of 65535.
146
+ return Left (MessageTooLarge (payloadSize))
147
+ } else {
148
+ payloadSize.toInt
162
149
}
150
+ // Since we are setting the packet size based on the payload, the onion creation should never fail (hence the `.get`).
151
+ val Sphinx .PacketAndSecrets (packet, _) = Sphinx .create(sessionKey, packetSize, route.blindedNodes.map(_.blindedPublicKey), payloads, None ).get
152
+ Right (OnionMessage (route.blindingKey, packet))
163
153
}
164
154
165
155
// @formatter:off
@@ -199,7 +189,6 @@ object OnionMessages {
199
189
}
200
190
}
201
191
202
- @ tailrec
203
192
def process (privateKey : PrivateKey , msg : OnionMessage ): Action = {
204
193
val blindedPrivateKey = Sphinx .RouteBlinding .derivePrivateKey(privateKey, msg.blindingKey)
205
194
decryptOnion(blindedPrivateKey, msg.onionRoutingPacket) match {
@@ -210,11 +199,7 @@ object OnionMessages {
210
199
decryptEncryptedData(privateKey, msg.blindingKey, encryptedData) match {
211
200
case Left (f) => DropMessage (f)
212
201
case Right (DecodedEncryptedData (blindedPayload, nextBlinding)) => nextPacket_opt match {
213
- case Some (nextPacket) => validateRelayPayload(payload, blindedPayload, nextBlinding, nextPacket) match {
214
- case SendMessage (Right (EncodedNodeId .Plain (publicKey)), nextMsg) if publicKey == privateKey.publicKey => process(privateKey, nextMsg) // TODO: remove and rely on MessageRelay
215
- case SendMessage (Left (outgoingChannelId), nextMsg) if outgoingChannelId == ShortChannelId .toSelf => process(privateKey, nextMsg) // TODO: remove and rely on MessageRelay
216
- case action => action
217
- }
202
+ case Some (nextPacket) => validateRelayPayload(payload, blindedPayload, nextBlinding, nextPacket)
218
203
case None => validateFinalPayload(payload, blindedPayload)
219
204
}
220
205
}
0 commit comments