forked from lightningnetwork/lightning-onion
-
Notifications
You must be signed in to change notification settings - Fork 0
/
path.go
395 lines (328 loc) · 12.2 KB
/
path.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
package sphinx
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire"
)
// HopData is the information destined for individual hops. It is a fixed size
// 64 bytes, prefixed with a 1 byte realm that indicates how to interpret it.
// For now we simply assume it's the bitcoin realm (0x00) and hence the format
// is fixed. The last 32 bytes are always the HMAC to be passed to the next
// hop, or zero if this is the packet is not to be forwarded, since this is the
// last hop.
type HopData struct {
// Realm denotes the "real" of target chain of the next hop. For
// bitcoin, this value will be 0x00.
Realm [RealmByteSize]byte
// NextAddress is the address of the next hop that this packet should
// be forward to.
NextAddress [AddressSize]byte
// ForwardAmount is the HTLC amount that the next hop should forward.
// This value should take into account the fee require by this
// particular hop, and the cumulative fee for the entire route.
ForwardAmount uint64
// OutgoingCltv is the value of the outgoing absolute time-lock that
// should be included in the HTLC forwarded.
OutgoingCltv uint32
// ExtraBytes is the set of unused bytes within the onion payload. This
// extra set of bytes can be utilized by higher level applications to
// package additional data within the per-hop payload, or signal that a
// portion of the remaining set of hops are to be consumed as Extra
// Onion Blobs.
//
// TODO(roasbeef): rename to padding bytes?
ExtraBytes [NumPaddingBytes]byte
}
// Encode writes the serialized version of the target HopData into the passed
// io.Writer.
func (hd *HopData) Encode(w io.Writer) error {
if _, err := w.Write(hd.Realm[:]); err != nil {
return err
}
if _, err := w.Write(hd.NextAddress[:]); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, hd.ForwardAmount); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, hd.OutgoingCltv); err != nil {
return err
}
if _, err := w.Write(hd.ExtraBytes[:]); err != nil {
return err
}
return nil
}
// Decodes populates the target HopData with the contents of a serialized
// HopData packed into the passed io.Reader.
func (hd *HopData) Decode(r io.Reader) error {
if _, err := io.ReadFull(r, hd.Realm[:]); err != nil {
return err
}
if _, err := io.ReadFull(r, hd.NextAddress[:]); err != nil {
return err
}
err := binary.Read(r, binary.BigEndian, &hd.ForwardAmount)
if err != nil {
return err
}
err = binary.Read(r, binary.BigEndian, &hd.OutgoingCltv)
if err != nil {
return err
}
_, err = io.ReadFull(r, hd.ExtraBytes[:])
return err
}
// PayloadType denotes the type of the payload included in the onion packet.
// Serialization of a raw HopPayload will depend on the payload type, as some
// include a varint length prefix, while others just encode the raw payload.
type PayloadType uint8
const (
// PayloadLegacy is the legacy payload type. It includes a fixed 32
// bytes, 12 of which are padding, and uses a "zero length" (the old
// realm) prefix.
PayloadLegacy PayloadType = iota
// PayloadTLV is the new modern TLV based format. This payload includes
// a set of opaque bytes with a varint length prefix. The varint used
// is the same CompactInt as used in the Bitcoin protocol.
PayloadTLV
)
// HopPayload is a slice of bytes and associated payload-type that are destined
// for a specific hop in the PaymentPath. The payload itself is treated as an
// opaque data field by the onion router. The included Type field informs the
// serialization/deserialziation of the raw payload.
type HopPayload struct {
// Type is the type of the payload.
Type PayloadType
// Payload is the raw bytes of the per-hop payload for this hop.
// Depending on the realm, this pay be the regular legacy hop data, or
// a set of opaque blobs to be parsed by higher layers.
Payload []byte
// HMAC is an HMAC computed over the entire per-hop payload that also
// includes the higher-level (optional) associated data bytes.
HMAC [HMACSize]byte
}
// NewHopPayload creates a new hop payload given an optional set of forwarding
// instructions for a hop, and a set of optional opaque extra onion bytes to
// drop off at the target hop. If both values are not specified, then an error
// is returned.
func NewHopPayload(hopData *HopData, eob []byte) (HopPayload, error) {
var (
h HopPayload
b bytes.Buffer
)
// We can't proceed if neither the hop data or the EOB has been
// specified by the caller.
switch {
case hopData == nil && len(eob) == 0:
return h, fmt.Errorf("either hop data or eob must " +
"be specified")
case hopData != nil && len(eob) > 0:
return h, fmt.Errorf("cannot provide both hop data AND an eob")
}
// If the hop data is specified, then we'll write that now, as it
// should proceed the EOB portion of the payload.
if hopData != nil {
if err := hopData.Encode(&b); err != nil {
return h, nil
}
// We'll also mark that this particular hop will be using the
// legacy format as the modern format packs the existing hop
// data information into the EOB space as a TLV stream.
h.Type = PayloadLegacy
} else {
// Otherwise, we'll write out the raw EOB which contains a set
// of opaque bytes that the recipient can decode to make a
// forwarding decision.
if _, err := b.Write(eob); err != nil {
return h, nil
}
h.Type = PayloadTLV
}
h.Payload = b.Bytes()
return h, nil
}
// NumBytes returns the number of bytes it will take to serialize the full
// payload. Depending on the payload type, this may include some additional
// signalling bytes.
func (hp *HopPayload) NumBytes() int {
// The base size is the size of the raw payload, and the size of the
// HMAC.
size := len(hp.Payload) + HMACSize
// If this is the new TLV format, then we'll also accumulate the number
// of bytes that it would take to encode the size of the payload.
if hp.Type == PayloadTLV {
payloadSize := len(hp.Payload)
size += int(wire.VarIntSerializeSize(uint64(payloadSize)))
}
return size
}
// Encode encodes the hop payload into the passed writer.
func (hp *HopPayload) Encode(w io.Writer) error {
switch hp.Type {
// For the legacy payload, we don't need to add any additional bytes as
// our realm byte serves as our zero prefix byte.
case PayloadLegacy:
break
// For the TLV payload, we'll first prepend the length of the payload
// as a var-int.
case PayloadTLV:
var b [8]byte
err := WriteVarInt(w, uint64(len(hp.Payload)), &b)
if err != nil {
return err
}
}
// Finally, we'll write out the raw payload, then the HMAC in series.
if _, err := w.Write(hp.Payload); err != nil {
return err
}
if _, err := w.Write(hp.HMAC[:]); err != nil {
return err
}
return nil
}
// Decode unpacks an encoded HopPayload from the passed reader into the target
// HopPayload.
func (hp *HopPayload) Decode(r io.Reader) error {
bufReader := bufio.NewReader(r)
// In order to properly parse the payload, we'll need to check the
// first byte. We'll use a bufio reader to peek at it without consuming
// it from the buffer.
peekByte, err := bufReader.Peek(1)
if err != nil {
return err
}
var payloadSize uint32
switch int(peekByte[0]) {
// If the first byte is a zero (the realm), then this is the normal
// payload.
case 0x00:
// Our size is just the payload, without the HMAC. This means
// that this is the legacy payload type.
payloadSize = LegacyHopDataSize - HMACSize
hp.Type = PayloadLegacy
default:
// Otherwise, this is the new TLV based payload type, so we'll
// extract the payload length encoded as a var-int.
var b [8]byte
varInt, err := ReadVarInt(bufReader, &b)
if err != nil {
return err
}
payloadSize = uint32(varInt)
hp.Type = PayloadTLV
}
// Now that we know the payload size, we'll create a new buffer to
// read it out in full.
//
// TODO(roasbeef): can avoid all these copies
hp.Payload = make([]byte, payloadSize)
if _, err := io.ReadFull(bufReader, hp.Payload[:]); err != nil {
return err
}
if _, err := io.ReadFull(bufReader, hp.HMAC[:]); err != nil {
return err
}
return nil
}
// HopData attempts to extract a set of forwarding instructions from the target
// HopPayload. If the realm isn't what we expect, then an error is returned.
// This method also returns the left over EOB that remain after the hop data
// has been parsed. Callers may want to map this blob into something more
// concrete.
func (hp *HopPayload) HopData() (*HopData, error) {
payloadReader := bytes.NewBuffer(hp.Payload)
// If this isn't the "base" realm, then we can't extract the expected
// hop payload structure from the payload.
if hp.Type != PayloadLegacy {
return nil, nil
}
// Now that we know the payload has the structure we expect, we'll
// decode the payload into the HopData.
var hd HopData
if err := hd.Decode(payloadReader); err != nil {
return nil, err
}
return &hd, nil
}
// NumMaxHops is the maximum path length. There is a maximum of 1300 bytes in
// the routing info block. Legacy hop payloads are always 65 bytes, while tlv
// payloads are at least 47 bytes (tlvlen 1, amt 2, timelock 2, nextchan 10,
// hmac 32) for the intermediate hops and 37 bytes (tlvlen 1, amt 2, timelock 2,
// hmac 32) for the exit hop. The maximum path length can therefore only be
// reached by using tlv payloads only. With that, the maximum number of
// intermediate hops is: Floor((1300 - 37) / 47) = 26. Including the exit hop,
// the maximum path length is 27 hops.
const NumMaxHops = 27
// PaymentPath represents a series of hops within the Lightning Network
// starting at a sender and terminating at a receiver. Each hop contains a set
// of mandatory data which contains forwarding instructions for that hop.
// Additionally, we can also transmit additional data to each hop by utilizing
// the un-used hops (see TrueRouteLength()) to pack in additional data. In
// order to do this, we encrypt the several hops with the same node public key,
// and unroll the extra data into the space used for route forwarding
// information.
type PaymentPath [NumMaxHops]OnionHop
// OnionHop represents an abstract hop (a link between two nodes) within the
// Lightning Network. A hop is composed of the incoming node (able to decrypt
// the encrypted routing information), and the routing information itself.
// Optionally, the crafter of a route can indicate that additional data aside
// from the routing information is be delivered, which will manifest as
// additional hops to pack the data.
type OnionHop struct {
// NodePub is the target node for this hop. The payload will enter this
// hop, it'll decrypt the routing information, and hand off the
// internal packet to the next hop.
NodePub btcec.PublicKey
// HopPayload is the opaque payload provided to this node. If the
// HopData above is specified, then it'll be packed into this payload.
HopPayload HopPayload
}
// IsEmpty returns true if the hop isn't populated.
func (o OnionHop) IsEmpty() bool {
return o.NodePub.X().BitLen() == 0 || o.NodePub.Y().BitLen() == 0
}
// NodeKeys returns a slice pointing to node keys that this route comprises of.
// The size of the returned slice will be TrueRouteLength().
func (p *PaymentPath) NodeKeys() []*btcec.PublicKey {
var nodeKeys [NumMaxHops]*btcec.PublicKey
routeLen := p.TrueRouteLength()
for i := 0; i < routeLen; i++ {
nodeKeys[i] = &p[i].NodePub
}
return nodeKeys[:routeLen]
}
// TrueRouteLength returns the "true" length of the PaymentPath. The max
// payment path is NumMaxHops size, but in practice routes are much smaller.
// This method will return the number of actual hops (nodes) involved in this
// route. For references, a direct path has a length of 1, path through an
// intermediate node has a length of 2 (3 nodes involved).
func (p *PaymentPath) TrueRouteLength() int {
var routeLength int
for _, hop := range p {
// When we hit the first empty hop, we know we're now in the
// zero'd out portion of the array.
if hop.IsEmpty() {
return routeLength
}
routeLength++
}
return routeLength
}
// TotalPayloadSize returns the sum of the size of each payload in the "true"
// route.
func (p *PaymentPath) TotalPayloadSize() int {
var totalSize int
for _, hop := range p {
if hop.IsEmpty() {
continue
}
totalSize += hop.HopPayload.NumBytes()
}
return totalSize
}