Skip to content

Commit 91a1fcd

Browse files
andrewshvvRoasbeef
authored andcommitted
sphinx: add specification onion error obfuscation
Add BOLT#4 data obfuscation, which will be used inside lnd in order to obfuscate the payment failures.
1 parent 9a8748f commit 91a1fcd

File tree

7 files changed

+550
-19
lines changed

7 files changed

+550
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
vendor/
2+
.idea

glide.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

glide.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import:
1010
- package: golang.org/x/crypto
1111
subpackages:
1212
- ripemd160
13+
- package: github.com/go-errors/errors
1314
testImport:
1415
- package: github.com/davecgh/go-spew
1516
version: ^1.1.0

obfuscation.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package sphinx
2+
3+
import (
4+
"bytes"
5+
"crypto/hmac"
6+
"crypto/sha256"
7+
"errors"
8+
"io"
9+
10+
"github.com/roasbeef/btcd/btcec"
11+
)
12+
13+
// onionObfuscation obfuscates the data with compliance with BOLT#4.
14+
// In context of Lightning Network this function is used by sender to obfuscate
15+
// the onion failure and by receiver to unwrap the failure data.
16+
func onionObfuscation(sharedSecret [sha256.Size]byte,
17+
data []byte) []byte {
18+
obfuscatedData := make([]byte, len(data))
19+
ammagKey := generateKey("ammag", sharedSecret)
20+
streamBytes := generateCipherStream(ammagKey, uint(len(data)))
21+
xor(obfuscatedData, data, streamBytes)
22+
return obfuscatedData
23+
}
24+
25+
// OnionObfuscator represent serializable object which is able to convert the
26+
// data to the obfuscated blob.
27+
// In context of Lightning Network the obfuscated data is usually a failure
28+
// which will be propagated back to payment sender, and obfuscated by the
29+
// forwarding nodes.
30+
type OnionObfuscator struct {
31+
sharedSecret [sha256.Size]byte
32+
}
33+
34+
// NewOnionObfuscator creates new instance of onion obfuscator.
35+
func NewOnionObfuscator(router *Router, ephemeralKey *btcec.PublicKey) (*OnionObfuscator,
36+
error) {
37+
38+
sharedSecret, err := router.generateSharedSecret(ephemeralKey)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
return &OnionObfuscator{
44+
sharedSecret: sharedSecret,
45+
}, nil
46+
}
47+
48+
// Obfuscate is used to make data obfuscation.
49+
// In context of Lightning Network is either used by the nodes in order to
50+
// make initial obfuscation with the creation of the hmac or by the forwarding
51+
// nodes for backward failure obfuscation of the onion failure blob. By
52+
// obfuscating the onion failure on every node in the path we are adding
53+
// additional step of the security and barrier for malware nodes to retrieve
54+
// valuable information. The reason for using onion obfuscation is to not give
55+
// away to the nodes in the payment path the information about the exact failure
56+
// and its origin.
57+
func (o *OnionObfuscator) Obfuscate(initial bool, data []byte) []byte {
58+
if initial {
59+
umKey := generateKey("um", o.sharedSecret)
60+
hash := hmac.New(sha256.New, umKey[:])
61+
hash.Write(data)
62+
h := hash.Sum(nil)
63+
data = append(h, data...)
64+
}
65+
66+
return onionObfuscation(o.sharedSecret, data)
67+
}
68+
69+
// Decode initializes the obfuscator from the byte stream.
70+
func (o *OnionObfuscator) Decode(r io.Reader) error {
71+
_, err := r.Read(o.sharedSecret[:])
72+
return err
73+
}
74+
75+
// Encode writes converted obfuscator in the byte stream.
76+
func (o *OnionObfuscator) Encode(w io.Writer) error {
77+
_, err := w.Write(o.sharedSecret[:])
78+
return err
79+
}
80+
81+
// OnionDeobfuscator represents the serializable object which encapsulate the
82+
// all necessary data to properly de-obfuscate previously obfuscated data.
83+
// In context of Lightning Network the data which have to be deobfuscated
84+
// usually is onion failure.
85+
type OnionDeobfuscator struct {
86+
sessionKey *btcec.PrivateKey
87+
paymentPath []*btcec.PublicKey
88+
}
89+
90+
// NewOnionDeobfuscator creates new instance of onion deobfuscator.
91+
func NewOnionDeobfuscator(sessionKey *btcec.PrivateKey,
92+
paymentPath []*btcec.PublicKey) (*OnionDeobfuscator, error) {
93+
return &OnionDeobfuscator{
94+
sessionKey: sessionKey,
95+
paymentPath: paymentPath,
96+
}, nil
97+
}
98+
99+
// Deobfuscate makes data deobfuscation. The onion failure is obfuscated in
100+
// backward manner, starting from the node where error have occurred, so in
101+
// order to deobfuscate the error we need get all shared secret and apply
102+
// obfuscation in reverse order.
103+
func (o *OnionDeobfuscator) Deobfuscate(obfuscatedData []byte) (*btcec.PublicKey,
104+
[]byte, error) {
105+
for i, sharedSecret := range generateSharedSecrets(o.paymentPath,
106+
o.sessionKey) {
107+
obfuscatedData = onionObfuscation(sharedSecret, obfuscatedData)
108+
umKey := generateKey("um", sharedSecret)
109+
110+
// Split the data and hmac.
111+
expectedMac := obfuscatedData[:sha256.Size]
112+
data := obfuscatedData[sha256.Size:]
113+
114+
// Calculate the real hmac.
115+
h := hmac.New(sha256.New, umKey[:])
116+
h.Write(data)
117+
realMac := h.Sum(nil)
118+
119+
if bytes.Equal(realMac, expectedMac) {
120+
return o.paymentPath[i], data, nil
121+
}
122+
}
123+
124+
return nil, nil, errors.New("unable to retrieve onion failure")
125+
}
126+
127+
// Decode initializes the deobfuscator from the byte stream.
128+
func (o *OnionDeobfuscator) Decode(r io.Reader) error {
129+
var keyLength [1]byte
130+
if _, err := r.Read(keyLength[:]); err != nil {
131+
return err
132+
}
133+
134+
sessionKeyData := make([]byte, uint8(keyLength[0]))
135+
if _, err := r.Read(sessionKeyData[:]); err != nil {
136+
return err
137+
}
138+
139+
o.sessionKey, _ = btcec.PrivKeyFromBytes(btcec.S256(), sessionKeyData)
140+
var pathLength [1]byte
141+
if _, err := r.Read(pathLength[:]); err != nil {
142+
return err
143+
}
144+
o.paymentPath = make([]*btcec.PublicKey, uint8(pathLength[0]))
145+
146+
for i := 0; i < len(o.paymentPath); i++ {
147+
var pubKeyData [btcec.PubKeyBytesLenCompressed]byte
148+
if _, err := r.Read(pubKeyData[:]); err != nil {
149+
return err
150+
}
151+
152+
pubKey, err := btcec.ParsePubKey(pubKeyData[:], btcec.S256())
153+
if err != nil {
154+
return err
155+
}
156+
o.paymentPath[i] = pubKey
157+
}
158+
159+
return nil
160+
}
161+
162+
// Encode writes converted deobfuscator in the byte stream.
163+
func (o *OnionDeobfuscator) Encode(w io.Writer) error {
164+
var keyLength [1]byte
165+
keyLength[0] = uint8(len(o.sessionKey.Serialize()))
166+
if _, err := w.Write(keyLength[:]); err != nil {
167+
return err
168+
}
169+
170+
if _, err := w.Write(o.sessionKey.Serialize()); err != nil {
171+
return err
172+
}
173+
174+
var pathLength [1]byte
175+
pathLength[0] = uint8(len(o.paymentPath))
176+
if _, err := w.Write(pathLength[:]); err != nil {
177+
return err
178+
}
179+
180+
for _, pubKey := range o.paymentPath {
181+
if _, err := w.Write(pubKey.SerializeCompressed()); err != nil {
182+
return err
183+
}
184+
}
185+
186+
return nil
187+
}

0 commit comments

Comments
 (0)