66 "crypto/sha256"
77 "fmt"
88 "io"
9- "sort "
9+ "slices "
1010 "sync"
1111
1212 "github.com/btcsuite/btcd/btcec/v2"
@@ -60,10 +60,11 @@ const (
6060
6161 // baseVersion represent the current supported version of onion packet.
6262 baseVersion = 0
63- )
6463
65- var (
66- ErrPayloadSizeExceeded = fmt .Errorf ("max custom payload size exceeded" )
64+ // streamBytesMultiplier is the multiplier used to calculate the number
65+ // of bytes that needs to be produced by our CSPRNG for the key stream
66+ // implementing our stream cipher.
67+ streamBytesMultiplier = 2
6768)
6869
6970// OnionPacket is the onion wrapped hop-to-hop routing information necessary to
@@ -180,66 +181,59 @@ func generateSharedSecrets(paymentPath []*btcec.PublicKey,
180181 return hopSharedSecrets , lastEphemeralPubKey , nil
181182}
182183
184+ type newOnionPacketCfg struct {
185+ payloadSize int
186+ }
187+
188+ // NewOnionPacketOpt defines the signature of a function option that can be used
189+ // with NewOnionPacket.
190+ type NewOnionPacketOpt func (cfg * newOnionPacketCfg )
191+
192+ // WithMaxPayloadSize is a function option that can be used to set the maximum
193+ // payload size.
194+ func WithMaxPayloadSize (size int ) NewOnionPacketOpt {
195+ return func (cfg * newOnionPacketCfg ) {
196+ cfg .payloadSize = size
197+ }
198+ }
199+
183200// NewOnionPacket creates a new onion packet which is capable of obliviously
184201// routing a message through the mix-net path outline by 'paymentPath'. The
185202// total size of the onion 'clicks' to the first value in payloadSizes that is
186203// bigger than the total payload size of the path. If no size is given, it
187204// defaults to the maximum routing payload size.
188205func NewOnionPacket (paymentPath * PaymentPath , sessionKey * btcec.PrivateKey ,
189206 assocData []byte , pktFiller PacketFiller ,
190- payloadSizes ... int ) (* OnionPacket , error ) {
207+ opts ... NewOnionPacketOpt ) (* OnionPacket , error ) {
191208
192- // If we don't actually have a partially populated route, then we'll
193- // exit early.
194- numHops := paymentPath .TrueRouteLength ()
195- if numHops == 0 {
196- return nil , fmt .Errorf ("route of length zero passed in" )
209+ cfg := & newOnionPacketCfg {}
210+ for _ , o := range opts {
211+ o (cfg )
197212 }
198213
199- totalPayloadSize := paymentPath .TotalPayloadSize ()
214+ if cfg .payloadSize < 0 {
215+ return nil , ErrNegativePayloadSize
216+ }
200217
201218 // We default to the maximum routing payload size if the caller didn't
202219 // provide any payload sizes.
203- if len ( payloadSizes ) == 0 {
204- payloadSizes = [] int { MaxRoutingPayloadSize }
220+ if cfg . payloadSize == 0 {
221+ cfg . payloadSize = MaxRoutingPayloadSize
205222 }
206223
207- sort .Ints (payloadSizes )
208-
209- // We'll now select the smallest payload size that is large enough to
210- // fit the entire onion payload. If no such size exists, then we'll
211- // return an error
212- var payloadSize int
213- found := false
214- for _ , size := range payloadSizes {
215- if size >= totalPayloadSize {
216- payloadSize = size
217- found = true
218- break
219- }
224+ // If we don't actually have a partially populated route, then we'll
225+ // exit early.
226+ numHops := paymentPath .TrueRouteLength ()
227+ if numHops == 0 {
228+ return nil , fmt .Errorf ("route of length zero passed in" )
220229 }
221230
222- // Return an error if we couldn't find a suitable payload size.
223- if ! found {
224- return nil , ErrPayloadSizeExceeded
225- }
231+ totalPayloadSize := paymentPath .TotalPayloadSize ()
226232
227- // If the payload size is not equal to MaxRoutingPayloadSize, then we
228- // check if any of the hops have a legacy payload. If so, we return an
229- // error as legacy payloads are not supported for those payload sizes.
230- if payloadSize != MaxRoutingPayloadSize {
231- for i := 0 ; i < numHops ; i ++ {
232- hopPayload := (* paymentPath )[i ].HopPayload
233- isLegacy := hopPayload .Type == PayloadLegacy
234-
235- // For any onion size other than MaxRoutingPayloadSize,
236- // we only expect TLV payloads.
237- if isLegacy {
238- return nil , fmt .Errorf ("hop %d has legacy " +
239- "payload, but this payload size " +
240- "requires TLV," , i )
241- }
242- }
233+ // Return an error if the actual payload size exceeds the configured
234+ // payload size.
235+ if totalPayloadSize > cfg .payloadSize {
236+ return nil , ErrPayloadSizeExceeded
243237 }
244238
245239 // We'll force the caller to provide a packet filler, as otherwise we
@@ -253,25 +247,28 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
253247 paymentPath .NodeKeys (), sessionKey ,
254248 )
255249 if err != nil {
256- return nil , fmt .Errorf ("error generating shared secret: %v" ,
257- err )
250+ return nil , fmt .Errorf ("%w: %w" , ErrSharedSecretDerivation , err )
258251 }
259252
260253 // Generate the padding, called "filler strings" in the paper.
261- filler := generateHeaderPadding (
262- "rho" , paymentPath , hopSharedSecrets , payloadSize ,
254+ filler , err := generateHeaderPadding (
255+ "rho" , paymentPath , hopSharedSecrets , cfg . payloadSize ,
263256 )
257+ if err != nil {
258+ return nil , err
259+ }
264260
265261 // Allocate zero'd out byte slices to store the final mix header packet
266262 // and the hmac for each hop.
267263 var (
268- mixHeader = make ([]byte , payloadSize )
264+ mixHeader = make ([]byte , cfg . payloadSize )
269265 nextHmac [HMACSize ]byte
270266 hopPayloadBuf bytes.Buffer
271267 )
272268
273269 // Fill the packet using the caller specified methodology.
274- if err := pktFiller (sessionKey , mixHeader ); err != nil {
270+ err = pktFiller (sessionKey , mixHeader )
271+ if err != nil {
275272 return nil , err
276273 }
277274
@@ -292,7 +289,9 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
292289 // Next, using the key dedicated for our stream cipher, we'll
293290 // generate enough bytes to obfuscate this layer of the onion
294291 // packet.
295- streamBytes := generateCipherStream (rhoKey , uint (payloadSize ))
292+ streamBytes := generateCipherStream (
293+ rhoKey , uint (cfg .payloadSize ),
294+ )
296295 payload := paymentPath [i ].HopPayload
297296
298297 // Before we assemble the packet, we'll shift the current
@@ -323,7 +322,7 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
323322 // calculating the MAC, we'll also include the optional
324323 // associated data which can allow higher level applications to
325324 // prevent replay attacks.
326- packet := append (mixHeader , assocData ... )
325+ packet := slices . Concat (mixHeader , assocData )
327326 nextHmac = calcMac (muKey , packet )
328327
329328 hopPayloadBuf .Reset ()
@@ -361,7 +360,7 @@ func rightShift(slice []byte, num int) {
361360// last hop. Using this methodology, the size of the field stays constant at
362361// each hop.
363362func generateHeaderPadding (key string , path * PaymentPath ,
364- sharedSecrets []Hash256 , routingInfoLen int ) []byte {
363+ sharedSecrets []Hash256 , routingInfoLen int ) ( []byte , error ) {
365364
366365 numHops := path .TrueRouteLength ()
367366
@@ -383,14 +382,18 @@ func generateHeaderPadding(key string, path *PaymentPath,
383382 fillerEnd := routingInfoLen + path [i ].HopPayload .NumBytes ()
384383
385384 streamKey := generateKey (key , & sharedSecrets [i ])
386- streamBytes := generateCipherStream (
387- streamKey , numStreamBytes (routingInfoLen ),
388- )
385+
386+ streamBytesLen , err := numStreamBytes (routingInfoLen )
387+ if err != nil {
388+ return nil , err
389+ }
390+
391+ streamBytes := generateCipherStream (streamKey , streamBytesLen )
389392
390393 xor (filler , filler , streamBytes [fillerStart :fillerEnd ])
391394 }
392395
393- return filler
396+ return filler , nil
394397}
395398
396399// Encode serializes the raw bytes of the onion packet into the passed
@@ -407,7 +410,8 @@ func (f *OnionPacket) Encode(w io.Writer) error {
407410 return err
408411 }
409412
410- if _ , err := w .Write (f .RoutingInfo ); err != nil {
413+ _ , err = w .Write (f .RoutingInfo )
414+ if err != nil {
411415 return err
412416 }
413417
@@ -455,7 +459,7 @@ func (f *OnionPacket) Decode(r io.Reader) error {
455459
456460 // The packet must have at least enough bytes for the HMAC.
457461 if len (routingInfoAndMAC ) < HMACSize {
458- return fmt . Errorf ( "onion packet is too small, missing HMAC" )
462+ return ErrMissingHMAC
459463 }
460464
461465 // With the remainder of the packet read, we can now properly slice the
@@ -701,7 +705,7 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256,
701705 // Using the derived shared secret, ensure the integrity of the routing
702706 // information by checking the attached MAC without leaking timing
703707 // information.
704- message := append (routeInfo , assocData ... )
708+ message := slices . Concat (routeInfo , assocData )
705709 calculatedMac := calcMac (generateKey ("mu" , sharedSecret ), message )
706710 if ! hmac .Equal (headerMac [:], calculatedMac [:]) {
707711 return nil , nil , ErrInvalidOnionHMAC
@@ -710,14 +714,17 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256,
710714 // Attach the padding zeroes in order to properly strip an encryption
711715 // layer off the routing info revealing the routing information for the
712716 // next hop.
717+ streamBytesLen , err := numStreamBytes (routingInfoLen )
718+ if err != nil {
719+ return nil , nil , err
720+ }
713721 streamBytes := generateCipherStream (
714- generateKey ("rho" , sharedSecret ),
715- numStreamBytes (routingInfoLen ),
722+ generateKey ("rho" , sharedSecret ), streamBytesLen ,
716723 )
717724 zeroBytes := bytes .Repeat ([]byte {0 }, routingInfoLen )
718- headerWithPadding := append (routeInfo , zeroBytes ... )
725+ headerWithPadding := slices . Concat (routeInfo , zeroBytes )
719726
720- hopInfo := make ([]byte , numStreamBytes ( routingInfoLen ) )
727+ hopInfo := make ([]byte , streamBytesLen )
721728 xor (hopInfo , headerWithPadding , streamBytes )
722729
723730 // Randomize the DH group element for the next hop using the
@@ -736,7 +743,8 @@ func unwrapPacket(onionPkt *OnionPacket, sharedSecret *Hash256,
736743 // payload is treated as such.
737744 hopPayload .Type = PayloadTLV
738745 }
739- err := hopPayload .Decode (bytes .NewReader (hopInfo ))
746+
747+ err = hopPayload .Decode (bytes .NewReader (hopInfo ))
740748 if err != nil {
741749 return nil , nil , err
742750 }
@@ -908,11 +916,15 @@ func (t *Tx) Commit() ([]ProcessedPacket, *ReplaySet, error) {
908916 return t .packets , rs , err
909917}
910918
911- // numStreamBytes is the number of bytes that needs to be produced by our CSPRG
919+ // numStreamBytes is the number of bytes that needs to be produced by our CSPRNG
912920// for the key stream implementing our stream cipher to encrypt/decrypt the mix
913921// header. The routingInfoSize bytes at the end are used to encrypt/decrypt the
914922// fillers when processing the packet of generating the HMACs when creating the
915923// packet.
916- func numStreamBytes (routingInfoSize int ) uint {
917- return uint (routingInfoSize * 2 )
924+ func numStreamBytes (routingInfoSize int ) (uint , error ) {
925+ if routingInfoSize < 0 {
926+ return 0 , ErrNegativeRoutingInfoSize
927+ }
928+
929+ return uint (routingInfoSize ) * streamBytesMultiplier , nil
918930}
0 commit comments