Skip to content

Commit 2905a97

Browse files
committed
sphinx: update decrypter for attr errors
We enhance the decrypter to now also use attr data. This structure is now being decrypted alongside the legacy error message. If any of the two fields fails to decrypt we stop the decryption process and blame the further decryptable hop.
1 parent 2a7048e commit 2905a97

File tree

2 files changed

+123
-22
lines changed

2 files changed

+123
-22
lines changed

crypto.go

Lines changed: 115 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ type DecryptedError struct {
106106

107107
// Message is the decrypted error message.
108108
Message []byte
109+
110+
// HoldTimes is an array of hold times reported by each node on the error
111+
// path.
112+
HoldTimes []uint32
109113
}
110114

111115
// zeroHMAC is the special HMAC value that allows the final node to determine
@@ -333,16 +337,20 @@ const minOnionErrorLength = minPaddedOnionErrorLength + sha256.Size
333337
// onion failure is encrypted in backward manner, starting from the node where
334338
// error have occurred. As a result, in order to decrypt the error we need get
335339
// all shared secret and apply decryption in the reverse order. A structure is
336-
// returned that contains the decrypted error message and information on the
337-
// sender.
338-
func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
339-
*DecryptedError, error) {
340-
341-
// Ensure the error message length is as expected.
342-
if len(encryptedData) < minOnionErrorLength {
343-
return nil, fmt.Errorf("invalid error length: "+
344-
"expected at least %v got %v", minOnionErrorLength,
345-
len(encryptedData))
340+
// returned that contains the decrypted error message and information of the
341+
// error sender. We also report the hold times in ms for each hop on the error
342+
// path.
343+
func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte,
344+
attrData []byte) (*DecryptedError, error) {
345+
346+
// Ensure the error message and attribution data length is as expected.
347+
if len(encryptedData) < minOnionErrorLength ||
348+
len(attrData) < o.hmacsAndPayloadsLen() {
349+
350+
return &DecryptedError{
351+
Sender: o.circuit.PaymentPath[0],
352+
SenderIdx: 1,
353+
}, nil
346354
}
347355

348356
sharedSecrets, err := generateSharedSecrets(
@@ -361,10 +369,16 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
361369
)
362370
copy(dummySecret[:], bytes.Repeat([]byte{1}, 32))
363371

372+
// Copy the failure message data in a new variable.
373+
failData := make([]byte, len(encryptedData))
374+
copy(failData, encryptedData)
375+
376+
hopPayloads := make([]uint32, 0)
377+
364378
// We'll iterate a constant amount of hops to ensure that we don't give
365379
// away an timing information pertaining to the position in the route
366380
// that the error emanated from.
367-
for i := 0; i < NumMaxHops; i++ {
381+
for i := 0; i < o.hopCount; i++ {
368382
var sharedSecret Hash256
369383

370384
// If we've already found the sender, then we'll use our dummy
@@ -378,15 +392,54 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
378392
}
379393

380394
// With the shared secret, we'll now strip off a layer of
381-
// encryption from the encrypted error payload.
382-
encryptedData = onionEncrypt(
383-
AMMAG, &sharedSecret, encryptedData,
395+
// encryption from the encrypted failure and attribution
396+
// data.
397+
failData = onionEncrypt(AMMAG, &sharedSecret, failData)
398+
attrData = onionEncrypt(AMMAG_EXT, &sharedSecret, attrData)
399+
400+
payloads := o.payloads(attrData)
401+
hmacs := o.hmacs(attrData)
402+
403+
// Let's calculate the HMAC we expect for the corresponding
404+
// payloads.
405+
position := o.hopCount - i - 1
406+
expectedAttrHmac := o.calculateHmac(
407+
sharedSecret, position, failData, payloads, hmacs,
384408
)
385409

386-
// Next, we'll need to separate the data, from the MAC itself
387-
// so we can reconstruct and verify it.
388-
expectedMac := encryptedData[:sha256.Size]
389-
data := encryptedData[sha256.Size:]
410+
// Let's retrieve the actual HMAC from the correct position in
411+
// the HMACs array.
412+
actualAttrHmac := hmacs[i*o.hmacSize : (i+1)*o.hmacSize]
413+
414+
// If the hmac does not match up, exit with a nil message. This
415+
// is not done for the dummy iterations.
416+
if !bytes.Equal(actualAttrHmac, expectedAttrHmac) &&
417+
sender == 0 && i < len(o.circuit.PaymentPath) {
418+
419+
sender = i + 1
420+
msg = nil
421+
}
422+
423+
// Extract the payload and exit with a nil message if it is
424+
// invalid.
425+
holdTime := o.extractPayload(payloads)
426+
if sender == 0 {
427+
// Store hold time reported by this node.
428+
hopPayloads = append(hopPayloads, holdTime)
429+
430+
// Update the message.
431+
msg = failData[sha256.Size:]
432+
}
433+
434+
// Shift payloads and hmacs to the left to prepare for the next
435+
// iteration.
436+
o.shiftPayloadsLeft(payloads)
437+
o.shiftHmacsLeft(hmacs)
438+
439+
// Next, we'll need to separate the failure data, from the MAC
440+
// itself so we can reconstruct and verify it.
441+
expectedMac := failData[:sha256.Size]
442+
data := failData[sha256.Size:]
390443

391444
// With the data split, we'll now re-generate the MAC using its
392445
// specified key.
@@ -410,12 +463,55 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
410463
}
411464

412465
return &DecryptedError{
413-
SenderIdx: sender,
414466
Sender: o.circuit.PaymentPath[sender-1],
467+
SenderIdx: sender,
415468
Message: msg,
469+
HoldTimes: hopPayloads,
416470
}, nil
417471
}
418472

473+
// extractPayload extracts the payload and payload origin information from the
474+
// given byte slice.
475+
func (o *OnionErrorDecrypter) extractPayload(payloadBytes []byte) uint32 {
476+
// Extract payload.
477+
holdTime := binary.BigEndian.Uint32(payloadBytes[0:o.payloadLen()])
478+
479+
return holdTime
480+
}
481+
482+
func (o *OnionErrorDecrypter) shiftPayloadsLeft(payloads []byte) {
483+
copy(payloads, payloads[o.payloadLen():o.hopCount*o.payloadLen()])
484+
}
485+
486+
func (o *OnionErrorDecrypter) shiftHmacsLeft(hmacs []byte) {
487+
// Work from left to right to avoid overwriting data that is still
488+
// needed later on in the shift operation.
489+
srcIdx := o.hopCount
490+
destIdx := 0
491+
copyLen := o.hopCount - 1
492+
for i := 0; i < o.hopCount-1; i++ {
493+
// Clear first hmac slot. This slot is for the position farthest
494+
// away from the error source. Because we are shifting, this
495+
// cannot be relevant.
496+
copy(hmacs[destIdx*o.hmacSize:], o.zeroHmac)
497+
498+
// The hmacs of the downstream hop become the remaining hmacs
499+
// for the current hop.
500+
copy(
501+
hmacs[(destIdx+1)*o.hmacSize:],
502+
hmacs[srcIdx*o.hmacSize:(srcIdx+copyLen)*o.hmacSize],
503+
)
504+
505+
srcIdx += copyLen
506+
destIdx += copyLen + 1
507+
copyLen--
508+
}
509+
510+
// Clear the very last hmac slot. Because we just shifted, the most
511+
// downstream hop can never be the error source.
512+
copy(hmacs[destIdx*o.hmacSize:], o.zeroHmac)
513+
}
514+
419515
// EncryptError is used to make data obfuscation using the generated shared
420516
// secret.
421517
//

obfuscation.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,17 @@ func (c *Circuit) Encode(w io.Writer) error {
111111
// OnionErrorDecrypter is a struct that's used to decrypt onion errors in
112112
// response to failed HTLC routing attempts according to BOLT#4.
113113
type OnionErrorDecrypter struct {
114+
*AttrErrorStructure
114115
circuit *Circuit
115116
}
116117

117-
// NewOnionErrorDecrypter creates new instance of onion decrypter.
118-
func NewOnionErrorDecrypter(circuit *Circuit) *OnionErrorDecrypter {
118+
// NewOnionErrorDecrypter creates new instance of onion decrypter with the
119+
// provided circuit and attributable error structure.
120+
func NewOnionErrorDecrypter(circuit *Circuit,
121+
structure *AttrErrorStructure) *OnionErrorDecrypter {
122+
119123
return &OnionErrorDecrypter{
120-
circuit: circuit,
124+
circuit: circuit,
125+
AttrErrorStructure: structure,
121126
}
122127
}

0 commit comments

Comments
 (0)