|
1 | 1 | package sphinx |
2 | 2 |
|
3 | 3 | import ( |
4 | | - "bufio" |
5 | | - "bytes" |
6 | | - "encoding/binary" |
7 | | - "fmt" |
8 | | - "io" |
9 | | - |
10 | 4 | "github.com/btcsuite/btcd/btcec/v2" |
11 | | - "github.com/btcsuite/btcd/wire" |
12 | | -) |
13 | | - |
14 | | -// HopData is the information destined for individual hops. It is a fixed size |
15 | | -// 64 bytes, prefixed with a 1 byte realm that indicates how to interpret it. |
16 | | -// For now we simply assume it's the bitcoin realm (0x00) and hence the format |
17 | | -// is fixed. The last 32 bytes are always the HMAC to be passed to the next |
18 | | -// hop, or zero if this is the packet is not to be forwarded, since this is the |
19 | | -// last hop. |
20 | | -type HopData struct { |
21 | | - // Realm denotes the "real" of target chain of the next hop. For |
22 | | - // bitcoin, this value will be 0x00. |
23 | | - Realm [RealmByteSize]byte |
24 | | - |
25 | | - // NextAddress is the address of the next hop that this packet should |
26 | | - // be forward to. |
27 | | - NextAddress [AddressSize]byte |
28 | | - |
29 | | - // ForwardAmount is the HTLC amount that the next hop should forward. |
30 | | - // This value should take into account the fee require by this |
31 | | - // particular hop, and the cumulative fee for the entire route. |
32 | | - ForwardAmount uint64 |
33 | | - |
34 | | - // OutgoingCltv is the value of the outgoing absolute time-lock that |
35 | | - // should be included in the HTLC forwarded. |
36 | | - OutgoingCltv uint32 |
37 | | - |
38 | | - // ExtraBytes is the set of unused bytes within the onion payload. This |
39 | | - // extra set of bytes can be utilized by higher level applications to |
40 | | - // package additional data within the per-hop payload, or signal that a |
41 | | - // portion of the remaining set of hops are to be consumed as Extra |
42 | | - // Onion Blobs. |
43 | | - // |
44 | | - // TODO(roasbeef): rename to padding bytes? |
45 | | - ExtraBytes [NumPaddingBytes]byte |
46 | | -} |
47 | | - |
48 | | -// Encode writes the serialized version of the target HopData into the passed |
49 | | -// io.Writer. |
50 | | -func (hd *HopData) Encode(w io.Writer) error { |
51 | | - if _, err := w.Write(hd.Realm[:]); err != nil { |
52 | | - return err |
53 | | - } |
54 | | - |
55 | | - if _, err := w.Write(hd.NextAddress[:]); err != nil { |
56 | | - return err |
57 | | - } |
58 | | - |
59 | | - if err := binary.Write(w, binary.BigEndian, hd.ForwardAmount); err != nil { |
60 | | - return err |
61 | | - } |
62 | | - |
63 | | - if err := binary.Write(w, binary.BigEndian, hd.OutgoingCltv); err != nil { |
64 | | - return err |
65 | | - } |
66 | | - |
67 | | - if _, err := w.Write(hd.ExtraBytes[:]); err != nil { |
68 | | - return err |
69 | | - } |
70 | | - |
71 | | - return nil |
72 | | -} |
73 | | - |
74 | | -// Decodes populates the target HopData with the contents of a serialized |
75 | | -// HopData packed into the passed io.Reader. |
76 | | -func (hd *HopData) Decode(r io.Reader) error { |
77 | | - if _, err := io.ReadFull(r, hd.Realm[:]); err != nil { |
78 | | - return err |
79 | | - } |
80 | | - |
81 | | - if _, err := io.ReadFull(r, hd.NextAddress[:]); err != nil { |
82 | | - return err |
83 | | - } |
84 | | - |
85 | | - err := binary.Read(r, binary.BigEndian, &hd.ForwardAmount) |
86 | | - if err != nil { |
87 | | - return err |
88 | | - } |
89 | | - |
90 | | - err = binary.Read(r, binary.BigEndian, &hd.OutgoingCltv) |
91 | | - if err != nil { |
92 | | - return err |
93 | | - } |
94 | | - |
95 | | - _, err = io.ReadFull(r, hd.ExtraBytes[:]) |
96 | | - return err |
97 | | -} |
98 | | - |
99 | | -// PayloadType denotes the type of the payload included in the onion packet. |
100 | | -// Serialization of a raw HopPayload will depend on the payload type, as some |
101 | | -// include a varint length prefix, while others just encode the raw payload. |
102 | | -type PayloadType uint8 |
103 | | - |
104 | | -const ( |
105 | | - // PayloadLegacy is the legacy payload type. It includes a fixed 32 |
106 | | - // bytes, 12 of which are padding, and uses a "zero length" (the old |
107 | | - // realm) prefix. |
108 | | - PayloadLegacy PayloadType = iota |
109 | | - |
110 | | - // PayloadTLV is the new modern TLV based format. This payload includes |
111 | | - // a set of opaque bytes with a varint length prefix. The varint used |
112 | | - // is the same CompactInt as used in the Bitcoin protocol. |
113 | | - PayloadTLV |
114 | 5 | ) |
115 | 6 |
|
116 | | -// HopPayload is a slice of bytes and associated payload-type that are destined |
117 | | -// for a specific hop in the PaymentPath. The payload itself is treated as an |
118 | | -// opaque data field by the onion router. The included Type field informs the |
119 | | -// serialization/deserialziation of the raw payload. |
120 | | -type HopPayload struct { |
121 | | - // Type is the type of the payload. |
122 | | - Type PayloadType |
123 | | - |
124 | | - // Payload is the raw bytes of the per-hop payload for this hop. |
125 | | - // Depending on the realm, this pay be the regular legacy hop data, or |
126 | | - // a set of opaque blobs to be parsed by higher layers. |
127 | | - Payload []byte |
128 | | - |
129 | | - // HMAC is an HMAC computed over the entire per-hop payload that also |
130 | | - // includes the higher-level (optional) associated data bytes. |
131 | | - HMAC [HMACSize]byte |
132 | | -} |
133 | | - |
134 | | -// NewHopPayload creates a new hop payload given an optional set of forwarding |
135 | | -// instructions for a hop, and a set of optional opaque extra onion bytes to |
136 | | -// drop off at the target hop. If both values are not specified, then an error |
137 | | -// is returned. |
138 | | -func NewHopPayload(hopData *HopData, eob []byte) (HopPayload, error) { |
139 | | - var ( |
140 | | - h HopPayload |
141 | | - b bytes.Buffer |
142 | | - ) |
143 | | - |
144 | | - // We can't proceed if neither the hop data or the EOB has been |
145 | | - // specified by the caller. |
146 | | - switch { |
147 | | - case hopData == nil && len(eob) == 0: |
148 | | - return h, fmt.Errorf("either hop data or eob must " + |
149 | | - "be specified") |
150 | | - |
151 | | - case hopData != nil && len(eob) > 0: |
152 | | - return h, fmt.Errorf("cannot provide both hop data AND an eob") |
153 | | - |
154 | | - } |
155 | | - |
156 | | - // If the hop data is specified, then we'll write that now, as it |
157 | | - // should proceed the EOB portion of the payload. |
158 | | - if hopData != nil { |
159 | | - if err := hopData.Encode(&b); err != nil { |
160 | | - return h, nil |
161 | | - } |
162 | | - |
163 | | - // We'll also mark that this particular hop will be using the |
164 | | - // legacy format as the modern format packs the existing hop |
165 | | - // data information into the EOB space as a TLV stream. |
166 | | - h.Type = PayloadLegacy |
167 | | - } else { |
168 | | - // Otherwise, we'll write out the raw EOB which contains a set |
169 | | - // of opaque bytes that the recipient can decode to make a |
170 | | - // forwarding decision. |
171 | | - if _, err := b.Write(eob); err != nil { |
172 | | - return h, nil |
173 | | - } |
174 | | - |
175 | | - h.Type = PayloadTLV |
176 | | - } |
177 | | - |
178 | | - h.Payload = b.Bytes() |
179 | | - |
180 | | - return h, nil |
181 | | -} |
182 | | - |
183 | | -// NumBytes returns the number of bytes it will take to serialize the full |
184 | | -// payload. Depending on the payload type, this may include some additional |
185 | | -// signalling bytes. |
186 | | -func (hp *HopPayload) NumBytes() int { |
187 | | - // The base size is the size of the raw payload, and the size of the |
188 | | - // HMAC. |
189 | | - size := len(hp.Payload) + HMACSize |
190 | | - |
191 | | - // If this is the new TLV format, then we'll also accumulate the number |
192 | | - // of bytes that it would take to encode the size of the payload. |
193 | | - if hp.Type == PayloadTLV { |
194 | | - payloadSize := len(hp.Payload) |
195 | | - size += int(wire.VarIntSerializeSize(uint64(payloadSize))) |
196 | | - } |
197 | | - |
198 | | - return size |
199 | | -} |
200 | | - |
201 | | -// Encode encodes the hop payload into the passed writer. |
202 | | -func (hp *HopPayload) Encode(w io.Writer) error { |
203 | | - switch hp.Type { |
204 | | - |
205 | | - // For the legacy payload, we don't need to add any additional bytes as |
206 | | - // our realm byte serves as our zero prefix byte. |
207 | | - case PayloadLegacy: |
208 | | - break |
209 | | - |
210 | | - // For the TLV payload, we'll first prepend the length of the payload |
211 | | - // as a var-int. |
212 | | - case PayloadTLV: |
213 | | - var b [8]byte |
214 | | - err := WriteVarInt(w, uint64(len(hp.Payload)), &b) |
215 | | - if err != nil { |
216 | | - return err |
217 | | - } |
218 | | - } |
219 | | - |
220 | | - // Finally, we'll write out the raw payload, then the HMAC in series. |
221 | | - if _, err := w.Write(hp.Payload); err != nil { |
222 | | - return err |
223 | | - } |
224 | | - if _, err := w.Write(hp.HMAC[:]); err != nil { |
225 | | - return err |
226 | | - } |
227 | | - |
228 | | - return nil |
229 | | -} |
230 | | - |
231 | | -// Decode unpacks an encoded HopPayload from the passed reader into the target |
232 | | -// HopPayload. |
233 | | -func (hp *HopPayload) Decode(r io.Reader) error { |
234 | | - bufReader := bufio.NewReader(r) |
235 | | - |
236 | | - // In order to properly parse the payload, we'll need to check the |
237 | | - // first byte. We'll use a bufio reader to peek at it without consuming |
238 | | - // it from the buffer. |
239 | | - peekByte, err := bufReader.Peek(1) |
240 | | - if err != nil { |
241 | | - return err |
242 | | - } |
243 | | - |
244 | | - var payloadSize uint32 |
245 | | - |
246 | | - switch int(peekByte[0]) { |
247 | | - // If the first byte is a zero (the realm), then this is the normal |
248 | | - // payload. |
249 | | - case 0x00: |
250 | | - // Our size is just the payload, without the HMAC. This means |
251 | | - // that this is the legacy payload type. |
252 | | - payloadSize = LegacyHopDataSize - HMACSize |
253 | | - hp.Type = PayloadLegacy |
254 | | - |
255 | | - default: |
256 | | - // Otherwise, this is the new TLV based payload type, so we'll |
257 | | - // extract the payload length encoded as a var-int. |
258 | | - var b [8]byte |
259 | | - varInt, err := ReadVarInt(bufReader, &b) |
260 | | - if err != nil { |
261 | | - return err |
262 | | - } |
263 | | - |
264 | | - payloadSize = uint32(varInt) |
265 | | - hp.Type = PayloadTLV |
266 | | - } |
267 | | - |
268 | | - // Now that we know the payload size, we'll create a new buffer to |
269 | | - // read it out in full. |
270 | | - // |
271 | | - // TODO(roasbeef): can avoid all these copies |
272 | | - hp.Payload = make([]byte, payloadSize) |
273 | | - if _, err := io.ReadFull(bufReader, hp.Payload[:]); err != nil { |
274 | | - return err |
275 | | - } |
276 | | - if _, err := io.ReadFull(bufReader, hp.HMAC[:]); err != nil { |
277 | | - return err |
278 | | - } |
279 | | - |
280 | | - return nil |
281 | | -} |
282 | | - |
283 | | -// HopData attempts to extract a set of forwarding instructions from the target |
284 | | -// HopPayload. If the realm isn't what we expect, then an error is returned. |
285 | | -// This method also returns the left over EOB that remain after the hop data |
286 | | -// has been parsed. Callers may want to map this blob into something more |
287 | | -// concrete. |
288 | | -func (hp *HopPayload) HopData() (*HopData, error) { |
289 | | - payloadReader := bytes.NewBuffer(hp.Payload) |
290 | | - |
291 | | - // If this isn't the "base" realm, then we can't extract the expected |
292 | | - // hop payload structure from the payload. |
293 | | - if hp.Type != PayloadLegacy { |
294 | | - return nil, nil |
295 | | - } |
296 | | - |
297 | | - // Now that we know the payload has the structure we expect, we'll |
298 | | - // decode the payload into the HopData. |
299 | | - var hd HopData |
300 | | - if err := hd.Decode(payloadReader); err != nil { |
301 | | - return nil, err |
302 | | - } |
303 | | - |
304 | | - return &hd, nil |
305 | | -} |
306 | | - |
307 | 7 | // NumMaxHops is the maximum path length. There is a maximum of 1300 bytes in |
308 | 8 | // the routing info block. Legacy hop payloads are always 65 bytes, while tlv |
309 | 9 | // payloads are at least 47 bytes (tlvlen 1, amt 2, timelock 2, nextchan 10, |
|
0 commit comments