Skip to content

Commit 2a7048e

Browse files
committed
sphinx: update encrypter for attr errors
We enhance the existing onion error encrypter to now include the attribution data. This will be needed by upstream peers in order to help verify the error source and hold times.
1 parent 1444273 commit 2a7048e

File tree

2 files changed

+158
-12
lines changed

2 files changed

+158
-12
lines changed

crypto.go

Lines changed: 150 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"crypto/hmac"
66
"crypto/sha256"
7+
"encoding/binary"
78
"errors"
89
"fmt"
910

@@ -18,6 +19,14 @@ const (
1819
// the onion. Any value lower than 32 will truncate the HMAC both
1920
// during onion creation as well as during the verification.
2021
HMACSize = 32
22+
23+
// AMMAG is the string representation for the ammag key type. Used in
24+
// cypher stream generation.
25+
AMMAG = "ammag"
26+
27+
// AMMAG_EXT is the string representation for the extended ammag key
28+
// type. user in cypher stream generation.
29+
AMMAG_EXT = "ammagext"
2130
)
2231

2332
// chaChaPolyZeroNonce is a slice of zero bytes used in the chacha20poly1305
@@ -301,10 +310,10 @@ func sharedSecret(priv SingleKeyECDH, pub *btcec.PublicKey) (Hash256, error) {
301310
// onionEncrypt obfuscates the data with compliance with BOLT#4. As we use a
302311
// stream cipher, calling onionEncrypt on an already encrypted piece of data
303312
// will decrypt it.
304-
func onionEncrypt(sharedSecret *Hash256, data []byte) []byte {
313+
func onionEncrypt(keyType string, sharedSecret *Hash256, data []byte) []byte {
305314
p := make([]byte, len(data))
306315

307-
ammagKey := generateKey("ammag", sharedSecret)
316+
ammagKey := generateKey(keyType, sharedSecret)
308317
streamBytes := generateCipherStream(ammagKey, uint(len(data)))
309318
xor(p, data, streamBytes)
310319

@@ -370,7 +379,9 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
370379

371380
// With the shared secret, we'll now strip off a layer of
372381
// encryption from the encrypted error payload.
373-
encryptedData = onionEncrypt(&sharedSecret, encryptedData)
382+
encryptedData = onionEncrypt(
383+
AMMAG, &sharedSecret, encryptedData,
384+
)
374385

375386
// Next, we'll need to separate the data, from the MAC itself
376387
// so we can reconstruct and verify it.
@@ -413,17 +424,146 @@ func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (
413424
// for backward failure obfuscation of the onion failure blob. By obfuscating
414425
// the onion failure on every node in the path we are adding additional step of
415426
// the security and barrier for malware nodes to retrieve valuable information.
416-
// The reason for using onion obfuscation is to not give
417-
// away to the nodes in the payment path the information about the exact
418-
// failure and its origin.
419-
func (o *OnionErrorEncrypter) EncryptError(initial bool, data []byte) []byte {
427+
// The reason for using onion obfuscation is to not give away to the nodes in
428+
// the payment path the information about the exact failure and its origin.
429+
// Every node down the error path reports the recorded hold times for the HTLC,
430+
// so this is also passed as an argument to this function in order for this node
431+
// to append its own value. The attribution data is a structure which helps with
432+
// identifying malicious intermediate hops that may have modified the failure
433+
// data.
434+
func (o *OnionErrorEncrypter) EncryptError(initial bool, legacyData []byte,
435+
attrData []byte, holdTime uint32) ([]byte, []byte, error) {
436+
437+
if initial && attrData != nil {
438+
return nil, nil, fmt.Errorf("unable to encrypt, cannot " +
439+
"initialize error with existing attribution data")
440+
}
441+
442+
if attrData == nil {
443+
attrData = o.initializePayload(holdTime)
444+
}
445+
420446
if initial {
447+
if len(legacyData) < minPaddedOnionErrorLength {
448+
return nil, nil, fmt.Errorf("initial data size less "+
449+
"than %v", minPaddedOnionErrorLength)
450+
}
451+
421452
umKey := generateKey("um", &o.sharedSecret)
422453
hash := hmac.New(sha256.New, umKey[:])
423-
hash.Write(data)
454+
hash.Write(legacyData)
424455
h := hash.Sum(nil)
425-
data = append(h, data...)
456+
legacyData = append(h, legacyData...)
457+
} else {
458+
if len(attrData) < o.hmacsAndPayloadsLen() {
459+
return nil, nil, fmt.Errorf("invalid attribution data"+
460+
"length, have %v expected %v", len(attrData),
461+
o.hmacsAndPayloadsLen())
462+
}
463+
464+
// Add our hold time.
465+
o.addIntermediatePayload(attrData, holdTime)
466+
467+
// Shift hmacs to create space for the new hmacs.
468+
o.shiftHmacsRight(o.hmacs(attrData))
469+
}
470+
471+
// Update hmac block.
472+
o.addHmacs(attrData, legacyData)
473+
474+
legacy := onionEncrypt(AMMAG, &o.sharedSecret, legacyData)
475+
attrError := onionEncrypt(AMMAG_EXT, &o.sharedSecret, attrData)
476+
477+
return legacy, attrError, nil
478+
}
479+
480+
func (o *OnionErrorEncrypter) shiftHmacsRight(hmacs []byte) {
481+
totalHmacs := (o.hopCount * (o.hopCount + 1)) / 2
482+
483+
// Work from right to left to avoid overwriting data that is still
484+
// needed.
485+
srcIdx := totalHmacs - 2
486+
destIdx := totalHmacs - 1
487+
488+
// The variable copyLen contains the number of hmacs to copy for the
489+
// current hop.
490+
copyLen := 1
491+
for i := 0; i < o.hopCount-1; i++ {
492+
// Shift the hmacs to the right for the current hop. The hmac
493+
// corresponding to the assumed position that is farthest away
494+
// from the error source is discarded.
495+
copy(
496+
hmacs[destIdx*o.hmacSize:],
497+
hmacs[srcIdx*o.hmacSize:(srcIdx+copyLen)*o.hmacSize],
498+
)
499+
500+
// The number of hmacs to copy increases by one for each
501+
// iteration. The further away from the error source, the more
502+
// downstream hmacs exist that are relevant.
503+
copyLen++
504+
505+
// Update indices backwards for the next iteration.
506+
srcIdx -= copyLen + 1
507+
destIdx -= copyLen
426508
}
427509

428-
return onionEncrypt(&o.sharedSecret, data)
510+
// Zero out the hmac slots corresponding to every possible position
511+
// relative to the error source for the current hop. This is not
512+
// strictly necessary as these slots are overwritten anyway, but we
513+
// clear them for cleanliness.
514+
for i := 0; i < o.hopCount; i++ {
515+
copy(hmacs[i*o.hmacSize:], o.zeroHmac)
516+
}
517+
}
518+
519+
// addHmacs updates the failure data with a series of hmacs corresponding to all
520+
// possible positions in the path for the current node.
521+
func (o *OnionErrorEncrypter) addHmacs(data []byte, message []byte) {
522+
payloads := o.payloads(data)
523+
hmacs := o.hmacs(data)
524+
525+
for i := 0; i < o.hopCount; i++ {
526+
position := o.hopCount - i - 1
527+
hmac := o.calculateHmac(
528+
o.sharedSecret, position, message, payloads, hmacs,
529+
)
530+
531+
copy(hmacs[i*o.hmacSize:], hmac)
532+
}
533+
}
534+
535+
func (o *OnionErrorEncrypter) initializePayload(holdTime uint32) []byte {
536+
537+
// Add space for payloads and hmacs.
538+
data := make([]byte, o.hmacsAndPayloadsLen())
539+
540+
payloads := o.payloads(data)
541+
542+
// Signal final hops in the payload.
543+
addPayload(payloads, holdTime)
544+
545+
return data
546+
}
547+
548+
func (o *OnionErrorEncrypter) addIntermediatePayload(data []byte,
549+
holdTime uint32) {
550+
551+
payloads := o.payloads(data)
552+
553+
// Shift payloads to create space for the new payload.
554+
o.shiftPayloadsRight(payloads)
555+
556+
// Signal intermediate hop in the payload.
557+
addPayload(payloads, holdTime)
558+
}
559+
560+
func (o *OnionErrorEncrypter) shiftPayloadsRight(payloads []byte) {
561+
copy(payloads[o.payloadLen():], payloads)
562+
}
563+
564+
func addPayload(payloads []byte, holdTime uint32) {
565+
566+
payload := make([]byte, 4)
567+
binary.BigEndian.PutUint32(payload, holdTime)
568+
copy(payloads, payload)
429569
}

obfuscation.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ import (
99
// OnionErrorEncrypter is a struct that's used to implement onion error
1010
// encryption as defined within BOLT0004.
1111
type OnionErrorEncrypter struct {
12+
*AttrErrorStructure
1213
sharedSecret Hash256
1314
}
1415

15-
func NewOnionErrorEncrypter(sharedSecret Hash256) *OnionErrorEncrypter {
16+
// NewOnionErrorEncrypter creates a new encrypter with the provided shared
17+
// secret and attributable error structure.
18+
func NewOnionErrorEncrypter(sharedSecret Hash256,
19+
structure *AttrErrorStructure) *OnionErrorEncrypter {
20+
1621
return &OnionErrorEncrypter{
17-
sharedSecret: sharedSecret,
22+
sharedSecret: sharedSecret,
23+
AttrErrorStructure: structure,
1824
}
1925
}
2026

0 commit comments

Comments
 (0)