11package sphinx
22
33import (
4+ "bufio"
45 "bytes"
56 "encoding/binary"
67 "fmt"
78 "io"
8- "math"
99
1010 "github.com/btcsuite/btcd/btcec"
11- )
12-
13- const (
14- // RealmMaskBytes is the mask to apply the realm in order to pack or
15- // decode the 4 LSB of the realm field.
16- RealmMaskBytes = 0x0f
17-
18- // NumFramesShift is the number of bytes to shift the encoding of the
19- // number of frames by in order to pack/unpack them into the 4 MSB bits
20- // of the realm field.
21- NumFramesShift = 4
11+ "github.com/btcsuite/btcd/wire"
2212)
2313
2414// HopData is the information destined for individual hops. It is a fixed size
@@ -28,6 +18,10 @@ const (
2818// hop, or zero if this is the packet is not to be forwarded, since this is the
2919// last hop.
3020type 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+
3125 // NextAddress is the address of the next hop that this packet should
3226 // be forward to.
3327 NextAddress [AddressSize ]byte
@@ -54,6 +48,10 @@ type HopData struct {
5448// Encode writes the serialized version of the target HopData into the passed
5549// io.Writer.
5650func (hd * HopData ) Encode (w io.Writer ) error {
51+ if _ , err := w .Write (hd .Realm [:]); err != nil {
52+ return err
53+ }
54+
5755 if _ , err := w .Write (hd .NextAddress [:]); err != nil {
5856 return err
5957 }
@@ -76,6 +74,10 @@ func (hd *HopData) Encode(w io.Writer) error {
7674// Decodes populates the target HopData with the contents of a serialized
7775// HopData packed into the passed io.Reader.
7876func (hd * HopData ) Decode (r io.Reader ) error {
77+ if _ , err := io .ReadFull (r , hd .Realm [:]); err != nil {
78+ return err
79+ }
80+
7981 if _ , err := io .ReadFull (r , hd .NextAddress [:]); err != nil {
8082 return err
8183 }
@@ -90,23 +92,34 @@ func (hd *HopData) Decode(r io.Reader) error {
9092 return err
9193 }
9294
93- if _ , err := io .ReadFull (r , hd .ExtraBytes [:]); err != nil {
94- return err
95- }
96-
95+ _ , err = io .ReadFull (r , hd .ExtraBytes [:])
9796 return err
9897}
9998
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+ )
115+
100116// HopPayload is a slice of bytes and associated payload-type that are destined
101117// for a specific hop in the PaymentPath. The payload itself is treated as an
102- // opaque data field by the onion router, while the Realm is modified to
103- // indicate how many hops are to be read by the processing node. The 4 MSB in
104- // the realm indicate how many additional hops are to be processed to collect
105- // the entire payload.
118+ // opaque data field by the onion router. The included Type field informs the
119+ // serialization/deserialziation of the raw payload.
106120type HopPayload struct {
107- // realm denotes the "real" of target chain of the next hop. For
108- // bitcoin, this value will be 0x00.
109- realm [RealmByteSize ]byte
121+ // Type is the type of the payload.
122+ Type PayloadType
110123
111124 // Payload is the raw bytes of the per-hop payload for this hop.
112125 // Depending on the realm, this pay be the regular legacy hop data, or
@@ -122,18 +135,22 @@ type HopPayload struct {
122135// instructions for a hop, and a set of optional opaque extra onion bytes to
123136// drop off at the target hop. If both values are not specified, then an error
124137// is returned.
125- func NewHopPayload (realm byte , hopData * HopData , eob []byte ) (HopPayload , error ) {
126-
138+ func NewHopPayload (hopData * HopData , eob []byte ) (HopPayload , error ) {
127139 var (
128140 h HopPayload
129141 b bytes.Buffer
130142 )
131143
132144 // We can't proceed if neither the hop data or the EOB has been
133145 // specified by the caller.
134- if hopData == nil && len (eob ) == 0 {
146+ switch {
147+ case hopData == nil && len (eob ) == 0 :
135148 return h , fmt .Errorf ("either hop data or eob must " +
136149 "be specified" )
150+
151+ case hopData != nil && len (eob ) > 0 :
152+ return h , fmt .Errorf ("cannot provide both hop data AND an eob" )
153+
137154 }
138155
139156 // If the hop data is specified, then we'll write that now, as it
@@ -142,90 +159,67 @@ func NewHopPayload(realm byte, hopData *HopData, eob []byte) (HopPayload, error)
142159 if err := hopData .Encode (& b ); err != nil {
143160 return h , nil
144161 }
145- }
146162
147- // Finally, we'll write out the EOB portion to the same buffer to
148- // ensure it comes after mandatory hop payload.
149- if _ , err := b .Write (eob ); err != nil {
150- return h , nil
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
151176 }
152177
153- h .realm = [RealmByteSize ]byte {realm }
154178 h .Payload = b .Bytes ()
155179
156180 return h , nil
157181}
158182
159- // Realm returns the context specific representation of the realm for a hop.
160- func (hp * HopPayload ) Realm () byte {
161- return hp .realm [0 ] & RealmMaskBytes
162- }
163-
164- // payloadRealm returns the realm that will be used within the raw packed hop
165- // payload. This differs from the Realm method above in that it uses space to
166- // encode the packet type and number of frames. The final encoding uses the
167- // first 4 bits of the realm to encode the number of frames used, and the
168- // latter 4 bits to encode the real realm type.
169- func (hp * HopPayload ) payloadRealm () byte {
170- maskedRealm := hp .realm [0 ] & 0x0F
171- numFrames := hp .NumFrames ()
172-
173- return maskedRealm | (byte (numFrames - 1 ) << NumFramesShift )
174- }
175-
176- // NumFrames returns the total number of frames it'll take to pack the target
177- // HopPayload into a Sphinx packet.
178- func (hp * HopPayload ) NumFrames () int {
179- // If it all fits in the legacy payload size, don't use any additional
180- // frames.
181- if len (hp .Payload ) <= 32 {
182- return 1
183- }
184-
185- // Otherwise we'll need at least one additional frame: subtract the 64
186- // bytes we can stuff into payload and hmac of the first, and the 33
187- // bytes we can pack into the payload of the second, then divide the
188- // remainder by 65.
189- remainder := len (hp .Payload ) - 64 - 33
190- return 2 + int (math .Ceil (float64 (remainder )/ 65 ))
191- }
192-
193- // packRealm writes out the proper realm encoding in place to the target
194- // io.Writer.
195- func (hp * HopPayload ) packRealm (w io.Writer ) error {
196- realm := hp .payloadRealm ()
197- if _ , err := w .Write ([]byte {realm }); err != nil {
198- return err
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 )))
199196 }
200197
201- return nil
198+ return size
202199}
203200
204201// Encode encodes the hop payload into the passed writer.
205202func (hp * HopPayload ) Encode (w io.Writer ) error {
206- // We'll need to add enough padding bytes to position the HMAC at the
207- // end of the payload
208- padding := hp .NumFrames ()* FrameSize - len (hp .Payload ) - 1 - HMACSize
209- if padding < 0 {
210- return fmt .Errorf ("cannot have negative padding: %v" , padding )
211- }
203+ switch hp .Type {
212204
213- if err := hp .packRealm (w ); err != nil {
214- return err
215- }
216- if _ , err := w .Write (hp .Payload ); err != nil {
217- return err
218- }
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
219209
220- // If we need to pad out the frame at all, then we'll do so now before
221- // we write out the HMAC .
222- if padding > 0 {
223- _ , err := w . Write ( bytes . Repeat ([] byte { 0x00 }, padding ))
210+ // For the TLV payload, we'll first prepend the length of the payload
211+ // as a var-int .
212+ case PayloadTLV :
213+ err := wire . WriteVarInt ( w , 0 , uint64 ( len ( hp . Payload ) ))
224214 if err != nil {
225215 return err
226216 }
227217 }
228218
219+ // Finally, we'll write out the raw payload, then the HMAC in series.
220+ if _ , err := w .Write (hp .Payload ); err != nil {
221+ return err
222+ }
229223 if _ , err := w .Write (hp .HMAC [:]); err != nil {
230224 return err
231225 }
@@ -236,19 +230,48 @@ func (hp *HopPayload) Encode(w io.Writer) error {
236230// Decode unpacks an encoded HopPayload from the passed reader into the target
237231// HopPayload.
238232func (hp * HopPayload ) Decode (r io.Reader ) error {
239- if _ , err := io .ReadFull (r , hp .realm [:]); err != nil {
233+ bufReader := bufio .NewReader (r )
234+
235+ // In order to properly parse the payload, we'll need to check the
236+ // first byte. We'll use a bufio reader to peek at it without consuming
237+ // it from the buffer.
238+ peekByte , err := bufReader .Peek (1 )
239+ if err != nil {
240240 return err
241241 }
242242
243- numFrames := int (hp .realm [0 ]>> NumFramesShift ) + 1
244- numBytes := (numFrames * FrameSize ) - 32 - 1
243+ var payloadSize uint32
244+
245+ switch int (peekByte [0 ]) {
246+ // If the first byte is a zero (the realm), then this is the normal
247+ // payload.
248+ case 0x00 :
249+ // Our size is just the payload, without the HMAC. This means
250+ // that this is the legacy payload type.
251+ payloadSize = HopDataSize - HMACSize
252+ hp .Type = PayloadLegacy
253+
254+ default :
255+ // Otherwise, this is the new TLV based payload type, so we'll
256+ // extract the payload length encoded as a var-int.
257+ varInt , err := wire .ReadVarInt (bufReader , 0 )
258+ if err != nil {
259+ return err
260+ }
245261
246- hp .Payload = make ([]byte , numBytes )
247- if _ , err := io .ReadFull (r , hp .Payload [:]); err != nil {
248- return err
262+ payloadSize = uint32 (varInt )
263+ hp .Type = PayloadTLV
249264 }
250265
251- if _ , err := io .ReadFull (r , hp .HMAC [:]); err != nil {
266+ // Now that we know the payload size, we'll create a new buffer to
267+ // read it out in full.
268+ //
269+ // TODO(roasbeef): can avoid all these copies
270+ hp .Payload = make ([]byte , payloadSize )
271+ if _ , err := io .ReadFull (bufReader , hp .Payload [:]); err != nil {
272+ return err
273+ }
274+ if _ , err := io .ReadFull (bufReader , hp .HMAC [:]); err != nil {
252275 return err
253276 }
254277
@@ -260,31 +283,29 @@ func (hp *HopPayload) Decode(r io.Reader) error {
260283// This method also returns the left over EOB that remain after the hop data
261284// has been parsed. Callers may want to map this blob into something more
262285// concrete.
263- func (hp * HopPayload ) HopData () (* HopData , []byte , error ) {
286+ func (hp * HopPayload ) HopData () (* HopData , error ) {
287+ payloadReader := bytes .NewBuffer (hp .Payload )
288+
264289 // If this isn't the "base" realm, then we can't extract the expected
265290 // hop payload structure from the payload.
266- if hp .Realm () != 0x00 {
267- return nil , nil , fmt .Errorf ("payload is not a HopData " +
268- "payload, realm=%d" , hp .Realm ())
291+ if hp .Type != PayloadLegacy {
292+ return nil , nil
269293 }
270294
271295 // Now that we know the payload has the structure we expect, we'll
272296 // decode the payload into the HopData.
273297 var hd HopData
274- payloadReader := bytes .NewBuffer (hp .Payload )
275298 if err := hd .Decode (payloadReader ); err != nil {
276- return & hd , nil , nil
299+ return nil , err
277300 }
278301
279- // What's left over in the buffer that wasn't parsed as part of the
280- // forwarding instructions is our lingering EOB.
281- eob := payloadReader .Bytes ()
282-
283- return & hd , eob , nil
302+ return & hd , nil
284303}
285304
286305// NumMaxHops is the maximum path length. This should be set to an estimate of
287306// the upper limit of the diameter of the node graph.
307+ //
308+ // TODO(roasbeef): adjust due to var-payloads?
288309const NumMaxHops = 20
289310
290311// PaymentPath represents a series of hops within the Lightning Network
@@ -352,17 +373,17 @@ func (p *PaymentPath) TrueRouteLength() int {
352373 return routeLength
353374}
354375
355- // TotalFrames returns the total numebr of frames that it'll take to create a
356- // Sphinx packet from the target PaymentPath .
357- func (p * PaymentPath ) TotalFrames () int {
358- var frameCount int
376+ // TotalPayloadSize returns the sum of the size of each payload in the "true"
377+ // route .
378+ func (p * PaymentPath ) TotalPayloadSize () int {
379+ var totalSize int
359380 for _ , hop := range p {
360381 if hop .IsEmpty () {
361- break
382+ continue
362383 }
363384
364- frameCount += hop .HopPayload .NumFrames ()
385+ totalSize += hop .HopPayload .NumBytes ()
365386 }
366387
367- return frameCount
388+ return totalSize
368389}
0 commit comments