Skip to content

Commit 6afc43f

Browse files
committed
multi: refactor and move HopPayload methods
This commit is a pure refactor and move commit. It moves the HopPayload methods out from the `path.go` file and into a dedicated `payload.go` file in order to prepare for a follow up commit that will add a blinded path struct to the `path.go` file. The opportunity is also taken to separate out the tlv vs legacy encoding of the hop payload a bit (seprate constructors for example) in order to prepare for a future where the legacy encoding will be removed.
1 parent 258028a commit 6afc43f

File tree

5 files changed

+357
-310
lines changed

5 files changed

+357
-310
lines changed

bench_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func BenchmarkPathPacketConstruction(b *testing.B) {
3333
}
3434
copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8))
3535

36-
hopPayload, err := NewHopPayload(&hopData, nil)
36+
hopPayload, err := NewLegacyHopPayload(&hopData)
3737
if err != nil {
3838
b.Fatalf("unable to create new hop payload: %v", err)
3939
}

cmd/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func parsePathData(data pathData) (*sphinx.PaymentPath, *btcec.PrivateKey,
158158
return nil, nil, nil, err
159159
}
160160

161-
hopPayload, err := sphinx.NewHopPayload(nil, payload)
161+
hopPayload, err := sphinx.NewTLVHopPayload(payload)
162162
if err != nil {
163163
return nil, nil, nil, err
164164
}

path.go

Lines changed: 0 additions & 300 deletions
Original file line numberDiff line numberDiff line change
@@ -1,309 +1,9 @@
11
package sphinx
22

33
import (
4-
"bufio"
5-
"bytes"
6-
"encoding/binary"
7-
"fmt"
8-
"io"
9-
104
"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
1145
)
1156

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-
3077
// NumMaxHops is the maximum path length. There is a maximum of 1300 bytes in
3088
// the routing info block. Legacy hop payloads are always 65 bytes, while tlv
3099
// payloads are at least 47 bytes (tlvlen 1, amt 2, timelock 2, nextchan 10,

0 commit comments

Comments
 (0)