@@ -41,26 +41,13 @@ const (
41
41
LegacyHopDataSize = (RealmByteSize + AddressSize + AmtForwardSize +
42
42
OutgoingCLTVSize + NumPaddingBytes + HMACSize )
43
43
44
- // MaxPayloadSize is the maximum size a payload for a single hop can be.
45
- // This is the worst case scenario of a single hop, consuming all
46
- // available space. We need to know this in order to generate a
47
- // sufficiently long stream of pseudo-random bytes when
48
- // encrypting/decrypting the payload.
49
- MaxPayloadSize = routingInfoSize
50
-
51
- // routingInfoSize is the fixed size of the the routing info. This
52
- // consists of a addressSize byte address and a HMACSize byte HMAC for
53
- // each hop of the route, the first pair in cleartext and the following
54
- // pairs increasingly obfuscated. If not all space is used up, the
55
- // remainder is padded with null-bytes, also obfuscated.
56
- routingInfoSize = 1300
57
-
58
- // numStreamBytes is the number of bytes produced by our CSPRG for the
59
- // key stream implementing our stream cipher to encrypt/decrypt the mix
60
- // header. The MaxPayloadSize bytes at the end are used to
61
- // encrypt/decrypt the fillers when processing the packet of generating
62
- // the HMACs when creating the packet.
63
- numStreamBytes = routingInfoSize * 2
44
+ // StandardRoutingInfoSize is the size of the routing info for a standard
45
+ // onion packet.
46
+ StandardRoutingInfoSize = 1300
47
+
48
+ // JumboRoutingInfoSize is the size of the routing info for a jumbo
49
+ // onion packet.
50
+ JumboRoutingInfoSize = 32768
64
51
65
52
// keyLen is the length of the keys used to generate cipher streams and
66
53
// encrypt payloads. Since we use SHA256 to generate the keys, the
@@ -72,8 +59,15 @@ const (
72
59
)
73
60
74
61
var (
75
- ErrMaxRoutingInfoSizeExceeded = fmt .Errorf (
76
- "max routing info size of %v bytes exceeded" , routingInfoSize )
62
+ ErrStandardRoutingInfoSizeExceeded = fmt .Errorf (
63
+ "max routing info size of %v bytes exceeded" ,
64
+ StandardRoutingInfoSize ,
65
+ )
66
+
67
+ ErrJumboRoutingInfoSizeExceeded = fmt .Errorf (
68
+ "max onion message routing info size of %v bytes exceeded" ,
69
+ JumboRoutingInfoSize ,
70
+ )
77
71
)
78
72
79
73
// OnionPacket is the onion wrapped hop-to-hop routing information necessary to
@@ -102,7 +96,7 @@ type OnionPacket struct {
102
96
// RoutingInfo is the full routing information for this onion packet.
103
97
// This encodes all the forwarding instructions for this current hop
104
98
// and all the hops in the route.
105
- RoutingInfo [routingInfoSize ]byte
99
+ RoutingInfo []byte
106
100
107
101
// HeaderMAC is an HMAC computed with the shared secret of the routing
108
102
// data and the associated data for this route. Including the
@@ -190,14 +184,54 @@ func generateSharedSecrets(paymentPath []*btcec.PublicKey,
190
184
return hopSharedSecrets , lastEphemeralPubKey , nil
191
185
}
192
186
187
+ // onionPacketCfg is a struct that holds the configuration for creating a new
188
+ // onion packet.
189
+ type onionPacketCfg struct {
190
+ isOnionMessage bool
191
+ }
192
+
193
+ // OnionPacketOption is a function that can be used to modify the onion packet
194
+ // configuration.
195
+ type OnionPacketOption func (* onionPacketCfg )
196
+
197
+ // WithOnionMessage is a functional option that signals that the onion packet
198
+ // being created is an onion message.
199
+ func WithOnionMessage () OnionPacketOption {
200
+ return func (cfg * onionPacketCfg ) {
201
+ cfg .isOnionMessage = true
202
+ }
203
+ }
204
+
193
205
// NewOnionPacket creates a new onion packet which is capable of obliviously
194
206
// routing a message through the mix-net path outline by 'paymentPath'.
195
207
func NewOnionPacket (paymentPath * PaymentPath , sessionKey * btcec.PrivateKey ,
196
- assocData []byte , pktFiller PacketFiller ) (* OnionPacket , error ) {
208
+ assocData []byte , pktFiller PacketFiller ,
209
+ opts ... OnionPacketOption ) (* OnionPacket , error ) {
210
+
211
+ cfg := & onionPacketCfg {}
212
+ for _ , o := range opts {
213
+ o (cfg )
214
+ }
215
+
216
+ totalPayloadSize := paymentPath .TotalPayloadSize ()
217
+
218
+ var routingInfoLen int
219
+ maxRoutingInfoErr := ErrStandardRoutingInfoSizeExceeded
220
+ if cfg .isOnionMessage {
221
+ switch {
222
+ case totalPayloadSize <= StandardRoutingInfoSize :
223
+ routingInfoLen = StandardRoutingInfoSize
224
+ default :
225
+ routingInfoLen = JumboRoutingInfoSize
226
+ maxRoutingInfoErr = ErrJumboRoutingInfoSizeExceeded
227
+ }
228
+ } else {
229
+ routingInfoLen = StandardRoutingInfoSize
230
+ }
197
231
198
232
// Check whether total payload size doesn't exceed the hard maximum.
199
- if paymentPath . TotalPayloadSize () > routingInfoSize {
200
- return nil , ErrMaxRoutingInfoSizeExceeded
233
+ if totalPayloadSize > routingInfoLen {
234
+ return nil , maxRoutingInfoErr
201
235
}
202
236
203
237
// If we don't actually have a partially populated route, then we'll
@@ -207,6 +241,20 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
207
241
return nil , fmt .Errorf ("route of length zero passed in" )
208
242
}
209
243
244
+ // Before we proceed, we'll check that the payload types of each hop
245
+ // in the payment path match the type of onion packet we're creating.
246
+ for i := 0 ; i < numHops ; i ++ {
247
+ hopPayload := (* paymentPath )[i ].HopPayload
248
+ isLegacy := hopPayload .Type == PayloadLegacy
249
+
250
+ // If this is an onion message, we only expect TLV
251
+ // payloads.
252
+ if cfg .isOnionMessage && isLegacy {
253
+ return nil , fmt .Errorf ("hop %d has legacy payload, " +
254
+ "but onion messages require TLV" , i )
255
+ }
256
+ }
257
+
210
258
// We'll force the caller to provide a packet filler, as otherwise we
211
259
// may default to an insecure filling method (which should only really
212
260
// be used to generate test vectors).
@@ -222,18 +270,20 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
222
270
}
223
271
224
272
// Generate the padding, called "filler strings" in the paper.
225
- filler := generateHeaderPadding ("rho" , paymentPath , hopSharedSecrets )
273
+ filler := generateHeaderPadding (
274
+ "rho" , paymentPath , hopSharedSecrets , routingInfoLen ,
275
+ )
226
276
227
277
// Allocate zero'd out byte slices to store the final mix header packet
228
278
// and the hmac for each hop.
229
279
var (
230
- mixHeader [ routingInfoSize ]byte
280
+ mixHeader = make ([ ]byte , routingInfoLen )
231
281
nextHmac [HMACSize ]byte
232
282
hopPayloadBuf bytes.Buffer
233
283
)
234
284
235
285
// Fill the packet using the caller specified methodology.
236
- if err := pktFiller (sessionKey , & mixHeader ); err != nil {
286
+ if err := pktFiller (sessionKey , mixHeader ); err != nil {
237
287
return nil , err
238
288
}
239
289
@@ -254,26 +304,26 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
254
304
// Next, using the key dedicated for our stream cipher, we'll
255
305
// generate enough bytes to obfuscate this layer of the onion
256
306
// packet.
257
- streamBytes := generateCipherStream (rhoKey , routingInfoSize )
307
+ streamBytes := generateCipherStream (rhoKey , uint ( routingInfoLen ) )
258
308
payload := paymentPath [i ].HopPayload
259
309
260
310
// Before we assemble the packet, we'll shift the current
261
311
// mix-header to the right in order to make room for this next
262
312
// per-hop data.
263
313
shiftSize := payload .NumBytes ()
264
- rightShift (mixHeader [:] , shiftSize )
314
+ rightShift (mixHeader , shiftSize )
265
315
266
316
err := payload .Encode (& hopPayloadBuf )
267
317
if err != nil {
268
318
return nil , err
269
319
}
270
320
271
- copy (mixHeader [:] , hopPayloadBuf .Bytes ())
321
+ copy (mixHeader , hopPayloadBuf .Bytes ())
272
322
273
323
// Once the packet for this hop has been assembled, we'll
274
324
// re-encrypt the packet by XOR'ing with a stream of bytes
275
325
// generated using our shared secret.
276
- xor (mixHeader [:] , mixHeader [:] , streamBytes [:] )
326
+ xor (mixHeader , mixHeader , streamBytes )
277
327
278
328
// If this is the "last" hop, then we'll override the tail of
279
329
// the hop data.
@@ -285,7 +335,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
285
335
// calculating the MAC, we'll also include the optional
286
336
// associated data which can allow higher level applications to
287
337
// prevent replay attacks.
288
- packet := append (mixHeader [:] , assocData ... )
338
+ packet := append (mixHeader , assocData ... )
289
339
nextHmac = calcMac (muKey , packet )
290
340
291
341
hopPayloadBuf .Reset ()
@@ -322,7 +372,8 @@ func rightShift(slice []byte, num int) {
322
372
// leaving only the original "filler" bytes produced by this function at the
323
373
// last hop. Using this methodology, the size of the field stays constant at
324
374
// each hop.
325
- func generateHeaderPadding (key string , path * PaymentPath , sharedSecrets []Hash256 ) []byte {
375
+ func generateHeaderPadding (key string , path * PaymentPath ,
376
+ sharedSecrets []Hash256 , routingInfoLen int ) []byte {
326
377
numHops := path .TrueRouteLength ()
327
378
328
379
// We have to generate a filler that matches all but the last hop (the
@@ -332,18 +383,18 @@ func generateHeaderPadding(key string, path *PaymentPath, sharedSecrets []Hash25
332
383
333
384
for i := 0 ; i < numHops - 1 ; i ++ {
334
385
// Sum up how many frames were used by prior hops.
335
- fillerStart := routingInfoSize
386
+ fillerStart := routingInfoLen
336
387
for _ , p := range path [:i ] {
337
388
fillerStart -= p .HopPayload .NumBytes ()
338
389
}
339
390
340
391
// The filler is the part dangling off of the end of the
341
392
// routingInfo, so offset it from there, and use the current
342
393
// hop's frame count as its size.
343
- fillerEnd := routingInfoSize + path [i ].HopPayload .NumBytes ()
394
+ fillerEnd := routingInfoLen + path [i ].HopPayload .NumBytes ()
344
395
345
396
streamKey := generateKey (key , & sharedSecrets [i ])
346
- streamBytes := generateCipherStream (streamKey , numStreamBytes )
397
+ streamBytes := generateCipherStream (streamKey , uint ( routingInfoLen * 2 ) )
347
398
348
399
xor (filler , filler , streamBytes [fillerStart :fillerEnd ])
349
400
}
@@ -365,7 +416,7 @@ func (f *OnionPacket) Encode(w io.Writer) error {
365
416
return err
366
417
}
367
418
368
- if _ , err := w .Write (f .RoutingInfo [:] ); err != nil {
419
+ if _ , err := w .Write (f .RoutingInfo ); err != nil {
369
420
return err
370
421
}
371
422
@@ -404,14 +455,24 @@ func (f *OnionPacket) Decode(r io.Reader) error {
404
455
return ErrInvalidOnionKey
405
456
}
406
457
407
- if _ , err := io .ReadFull (r , f .RoutingInfo [:]); err != nil {
458
+ // To figure out the length of the routing info, we'll read all the
459
+ // remaining bytes from the reader.
460
+ routingInfoAndMAC , err := io .ReadAll (r )
461
+ if err != nil {
408
462
return err
409
463
}
410
464
411
- if _ , err := io .ReadFull (r , f .HeaderMAC [:]); err != nil {
412
- return err
465
+ // The packet must have at least enough bytes for the HMAC.
466
+ if len (routingInfoAndMAC ) < HMACSize {
467
+ return fmt .Errorf ("onion packet is too small, missing HMAC" )
413
468
}
414
469
470
+ // With the remainder of the packet read, we can now properly slice the
471
+ // routing information and the MAC.
472
+ routingInfoLen := len (routingInfoAndMAC ) - HMACSize
473
+ f .RoutingInfo = routingInfoAndMAC [:routingInfoLen ]
474
+ copy (f .HeaderMAC [:], routingInfoAndMAC [routingInfoLen :])
475
+
415
476
return nil
416
477
}
417
478
@@ -644,11 +705,12 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256,
644
705
dhKey := onionPkt .EphemeralKey
645
706
routeInfo := onionPkt .RoutingInfo
646
707
headerMac := onionPkt .HeaderMAC
708
+ routingInfoLen := len (routeInfo )
647
709
648
710
// Using the derived shared secret, ensure the integrity of the routing
649
711
// information by checking the attached MAC without leaking timing
650
712
// information.
651
- message := append (routeInfo [:] , assocData ... )
713
+ message := append (routeInfo , assocData ... )
652
714
calculatedMac := calcMac (generateKey ("mu" , sharedSecret ), message )
653
715
if ! hmac .Equal (headerMac [:], calculatedMac [:]) {
654
716
return nil , nil , ErrInvalidOnionHMAC
@@ -658,13 +720,13 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256,
658
720
// layer off the routing info revealing the routing information for the
659
721
// next hop.
660
722
streamBytes := generateCipherStream (
661
- generateKey ("rho" , sharedSecret ), numStreamBytes ,
723
+ generateKey ("rho" , sharedSecret ), uint ( routingInfoLen * 2 ) ,
662
724
)
663
- zeroBytes := bytes .Repeat ([]byte {0 }, MaxPayloadSize )
664
- headerWithPadding := append (routeInfo [:] , zeroBytes ... )
725
+ zeroBytes := bytes .Repeat ([]byte {0 }, routingInfoLen )
726
+ headerWithPadding := append (routeInfo , zeroBytes ... )
665
727
666
- var hopInfo [ numStreamBytes ]byte
667
- xor (hopInfo [:] , headerWithPadding , streamBytes )
728
+ hopInfo := make ([ ]byte , routingInfoLen * 2 )
729
+ xor (hopInfo , headerWithPadding , streamBytes )
668
730
669
731
// Randomize the DH group element for the next hop using the
670
732
// deterministic blinding factor.
@@ -675,15 +737,15 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256,
675
737
// out the payload so we can derive the specified forwarding
676
738
// instructions.
677
739
var hopPayload HopPayload
678
- err := hopPayload .Decode (bytes .NewReader (hopInfo [:] ), isOnionMessage )
740
+ err := hopPayload .Decode (bytes .NewReader (hopInfo ), isOnionMessage )
679
741
if err != nil {
680
742
return nil , nil , err
681
743
}
682
744
683
745
// With the necessary items extracted, we'll copy of the onion packet
684
746
// for the next node, snipping off our per-hop data.
685
- var nextMixHeader [ routingInfoSize ]byte
686
- copy (nextMixHeader [:] , hopInfo [hopPayload .NumBytes ():])
747
+ var nextMixHeader = make ([ ]byte , routingInfoLen )
748
+ copy (nextMixHeader , hopInfo [hopPayload .NumBytes ():])
687
749
innerPkt := & OnionPacket {
688
750
Version : onionPkt .Version ,
689
751
EphemeralKey : nextDHKey ,
0 commit comments