Skip to content

Commit bc94cdd

Browse files
committed
sphinx: create new crypto.go file
1 parent e280f21 commit bc94cdd

File tree

4 files changed

+267
-248
lines changed

4 files changed

+267
-248
lines changed

crypto.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
package sphinx
2+
3+
import (
4+
"bytes"
5+
"crypto/hmac"
6+
"crypto/sha256"
7+
"errors"
8+
"fmt"
9+
10+
"github.com/aead/chacha20"
11+
"github.com/btcsuite/btcd/btcec"
12+
)
13+
14+
const (
15+
// HMACSize is the length of the HMACs used to verify the integrity of
16+
// the onion. Any value lower than 32 will truncate the HMAC both
17+
// during onion creation as well as during the verification.
18+
HMACSize = 32
19+
)
20+
21+
// Hash256 is a statically sized, 32-byte array, typically containing
22+
// the output of a SHA256 hash.
23+
type Hash256 [sha256.Size]byte
24+
25+
// zeroHMAC is the special HMAC value that allows the final node to determine
26+
// if it is the payment destination or not.
27+
var zeroHMAC [HMACSize]byte
28+
29+
// calcMac calculates HMAC-SHA-256 over the message using the passed secret key
30+
// as input to the HMAC.
31+
func calcMac(key [keyLen]byte, msg []byte) [HMACSize]byte {
32+
hmac := hmac.New(sha256.New, key[:])
33+
hmac.Write(msg)
34+
h := hmac.Sum(nil)
35+
36+
var mac [HMACSize]byte
37+
copy(mac[:], h[:HMACSize])
38+
39+
return mac
40+
}
41+
42+
// xor computes the byte wise XOR of a and b, storing the result in dst. Only
43+
// the frist `min(len(a), len(b))` bytes will be xor'd.
44+
func xor(dst, a, b []byte) int {
45+
n := len(a)
46+
if len(b) < n {
47+
n = len(b)
48+
}
49+
for i := 0; i < n; i++ {
50+
dst[i] = a[i] ^ b[i]
51+
}
52+
return n
53+
}
54+
55+
// generateKey generates a new key for usage in Sphinx packet
56+
// construction/processing based off of the denoted keyType. Within Sphinx
57+
// various keys are used within the same onion packet for padding generation,
58+
// MAC generation, and encryption/decryption.
59+
func generateKey(keyType string, sharedKey *Hash256) [keyLen]byte {
60+
mac := hmac.New(sha256.New, []byte(keyType))
61+
mac.Write(sharedKey[:])
62+
h := mac.Sum(nil)
63+
64+
var key [keyLen]byte
65+
copy(key[:], h[:keyLen])
66+
67+
return key
68+
}
69+
70+
// generateCipherStream generates a stream of cryptographic psuedo-random bytes
71+
// intended to be used to encrypt a message using a one-time-pad like
72+
// construction.
73+
func generateCipherStream(key [keyLen]byte, numBytes uint) []byte {
74+
var (
75+
nonce [8]byte
76+
)
77+
cipher, err := chacha20.NewCipher(nonce[:], key[:])
78+
if err != nil {
79+
panic(err)
80+
}
81+
output := make([]byte, numBytes)
82+
cipher.XORKeyStream(output, output)
83+
84+
return output
85+
}
86+
87+
// computeBlindingFactor for the next hop given the ephemeral pubKey and
88+
// sharedSecret for this hop. The blinding factor is computed as the
89+
// sha-256(pubkey || sharedSecret).
90+
func computeBlindingFactor(hopPubKey *btcec.PublicKey,
91+
hopSharedSecret []byte) Hash256 {
92+
93+
sha := sha256.New()
94+
sha.Write(hopPubKey.SerializeCompressed())
95+
sha.Write(hopSharedSecret)
96+
97+
var hash Hash256
98+
copy(hash[:], sha.Sum(nil))
99+
return hash
100+
}
101+
102+
// blindGroupElement blinds the group element P by performing scalar
103+
// multiplication of the group element by blindingFactor: blindingFactor * P.
104+
func blindGroupElement(hopPubKey *btcec.PublicKey, blindingFactor []byte) *btcec.PublicKey {
105+
newX, newY := btcec.S256().ScalarMult(hopPubKey.X, hopPubKey.Y, blindingFactor[:])
106+
return &btcec.PublicKey{btcec.S256(), newX, newY}
107+
}
108+
109+
// blindBaseElement blinds the groups's generator G by performing scalar base
110+
// multiplication using the blindingFactor: blindingFactor * G.
111+
func blindBaseElement(blindingFactor []byte) *btcec.PublicKey {
112+
newX, newY := btcec.S256().ScalarBaseMult(blindingFactor)
113+
return &btcec.PublicKey{btcec.S256(), newX, newY}
114+
}
115+
116+
// sharedSecretGenerator is an interface that abstracts away exactly *how* the
117+
// shared secret for each hop is generated.
118+
//
119+
// TODO(roasbef): rename?
120+
type sharedSecretGenerator interface {
121+
// generateSharedSecret given a public key, generates a shared secret
122+
// using private data of the underlying sharedSecretGenerator.
123+
generateSharedSecret(dhKey *btcec.PublicKey) (Hash256, error)
124+
}
125+
126+
// generateSharedSecret generates the shared secret by given ephemeral key.
127+
func (r *Router) generateSharedSecret(dhKey *btcec.PublicKey) (Hash256, error) {
128+
var sharedSecret Hash256
129+
130+
// Ensure that the public key is on our curve.
131+
if !btcec.S256().IsOnCurve(dhKey.X, dhKey.Y) {
132+
return sharedSecret, ErrInvalidOnionKey
133+
}
134+
135+
// Compute our shared secret.
136+
sharedSecret = generateSharedSecret(dhKey, r.onionKey)
137+
return sharedSecret, nil
138+
}
139+
140+
// generateSharedSecret generates the shared secret for a particular hop. The
141+
// shared secret is generated by taking the group element contained in the
142+
// mix-header, and performing an ECDH operation with the node's long term onion
143+
// key. We then take the _entire_ point generated by the ECDH operation,
144+
// serialize that using a compressed format, then feed the raw bytes through a
145+
// single SHA256 invocation. The resulting value is the shared secret.
146+
func generateSharedSecret(pub *btcec.PublicKey, priv *btcec.PrivateKey) Hash256 {
147+
s := &btcec.PublicKey{}
148+
s.X, s.Y = btcec.S256().ScalarMult(pub.X, pub.Y, priv.D.Bytes())
149+
150+
return sha256.Sum256(s.SerializeCompressed())
151+
}
152+
153+
// onionEncrypt obfuscates the data with compliance with BOLT#4. As we use a
154+
// stream cipher, calling onionEncrypt on an already encrypted piece of data
155+
// will decrypt it.
156+
func onionEncrypt(sharedSecret *Hash256, data []byte) []byte {
157+
p := make([]byte, len(data))
158+
159+
ammagKey := generateKey("ammag", sharedSecret)
160+
streamBytes := generateCipherStream(ammagKey, uint(len(data)))
161+
xor(p, data, streamBytes)
162+
163+
return p
164+
}
165+
166+
// onionErrorLength is the expected length of the onion error message.
167+
// Including padding, all messages on the wire should be 256 bytes. We then add
168+
// the size of the sha256 HMAC as well.
169+
const onionErrorLength = 2 + 2 + 256 + sha256.Size
170+
171+
// DecryptError attempts to decrypt the passed encrypted error response. The
172+
// onion failure is encrypted in backward manner, starting from the node where
173+
// error have occurred. As a result, in order to decrypt the error we need get
174+
// all shared secret and apply decryption in the reverse order.
175+
func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (*btcec.PublicKey, []byte, error) {
176+
// Ensure the error message length is as expected.
177+
if len(encryptedData) != onionErrorLength {
178+
return nil, nil, fmt.Errorf("invalid error length: "+
179+
"expected %v got %v", onionErrorLength,
180+
len(encryptedData))
181+
}
182+
183+
sharedSecrets := generateSharedSecrets(
184+
o.circuit.PaymentPath,
185+
o.circuit.SessionKey,
186+
)
187+
188+
var (
189+
sender *btcec.PublicKey
190+
msg []byte
191+
dummySecret Hash256
192+
)
193+
copy(dummySecret[:], bytes.Repeat([]byte{1}, 32))
194+
195+
// We'll iterate a constant amount of hops to ensure that we don't give
196+
// away an timing information pertaining to the position in the route
197+
// that the error emanated from.
198+
for i := 0; i < NumMaxHops; i++ {
199+
var sharedSecret Hash256
200+
201+
// If we've already found the sender, then we'll use our dummy
202+
// secret to continue decryption attempts to fill out the rest
203+
// of the loop. Otherwise, we'll use the next shared secret in
204+
// line.
205+
if sender != nil || i > len(sharedSecrets)-1 {
206+
sharedSecret = dummySecret
207+
} else {
208+
sharedSecret = sharedSecrets[i]
209+
}
210+
211+
// With the shared secret, we'll now strip off a layer of
212+
// encryption from the encrypted error payload.
213+
encryptedData = onionEncrypt(&sharedSecret, encryptedData)
214+
215+
// Next, we'll need to separate the data, from the MAC itself
216+
// so we can reconstruct and verify it.
217+
expectedMac := encryptedData[:sha256.Size]
218+
data := encryptedData[sha256.Size:]
219+
220+
// With the data split, we'll now re-generate the MAC using its
221+
// specified key.
222+
umKey := generateKey("um", &sharedSecret)
223+
h := hmac.New(sha256.New, umKey[:])
224+
h.Write(data)
225+
226+
// If the MAC matches up, then we've found the sender of the
227+
// error and have also obtained the fully decrypted message.
228+
realMac := h.Sum(nil)
229+
if hmac.Equal(realMac, expectedMac) && sender == nil {
230+
sender = o.circuit.PaymentPath[i]
231+
msg = data
232+
}
233+
}
234+
235+
// If the sender pointer is still nil, then we haven't found the
236+
// sender, meaning we've failed to decrypt.
237+
if sender == nil {
238+
return nil, nil, errors.New("unable to retrieve onion failure")
239+
}
240+
241+
return sender, msg, nil
242+
}
243+
244+
// EncryptError is used to make data obfuscation using the generated shared
245+
// secret.
246+
//
247+
// In context of Lightning Network is either used by the nodes in order to make
248+
// initial obfuscation with the creation of the hmac or by the forwarding nodes
249+
// for backward failure obfuscation of the onion failure blob. By obfuscating
250+
// the onion failure on every node in the path we are adding additional step of
251+
// the security and barrier for malware nodes to retrieve valuable information.
252+
// The reason for using onion obfuscation is to not give
253+
// away to the nodes in the payment path the information about the exact
254+
// failure and its origin.
255+
func (o *OnionErrorEncrypter) EncryptError(initial bool, data []byte) []byte {
256+
if initial {
257+
umKey := generateKey("um", &o.sharedSecret)
258+
hash := hmac.New(sha256.New, umKey[:])
259+
hash.Write(data)
260+
h := hash.Sum(nil)
261+
data = append(h, data...)
262+
}
263+
264+
return onionEncrypt(&o.sharedSecret, data)
265+
}

obfuscation.go

Lines changed: 0 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,11 @@
11
package sphinx
22

33
import (
4-
"bytes"
5-
"crypto/hmac"
6-
"crypto/sha256"
7-
"errors"
84
"io"
95

106
"github.com/btcsuite/btcd/btcec"
117
)
128

13-
// onionEncrypt obfuscates the data with compliance with BOLT#4. As we use a
14-
// stream cipher, calling onionEncrypt on an already encrypted piece of data
15-
// will decrypt it.
16-
func onionEncrypt(sharedSecret *Hash256, data []byte) []byte {
17-
18-
p := make([]byte, len(data))
19-
20-
ammagKey := generateKey("ammag", sharedSecret)
21-
streamBytes := generateCipherStream(ammagKey, uint(len(data)))
22-
xor(p, data, streamBytes)
23-
24-
return p
25-
}
26-
279
// OnionErrorEncrypter is a struct that's used to implement onion error
2810
// encryption as defined within BOLT0004.
2911
type OnionErrorEncrypter struct {
@@ -46,29 +28,6 @@ func NewOnionErrorEncrypter(router *Router,
4628
}, nil
4729
}
4830

49-
// EncryptError is used to make data obfuscation using the generated shared
50-
// secret.
51-
//
52-
// In context of Lightning Network is either used by the nodes in order to make
53-
// initial obfuscation with the creation of the hmac or by the forwarding nodes
54-
// for backward failure obfuscation of the onion failure blob. By obfuscating
55-
// the onion failure on every node in the path we are adding additional step of
56-
// the security and barrier for malware nodes to retrieve valuable information.
57-
// The reason for using onion obfuscation is to not give
58-
// away to the nodes in the payment path the information about the exact
59-
// failure and its origin.
60-
func (o *OnionErrorEncrypter) EncryptError(initial bool, data []byte) []byte {
61-
if initial {
62-
umKey := generateKey("um", &o.sharedSecret)
63-
hash := hmac.New(sha256.New, umKey[:])
64-
hash.Write(data)
65-
h := hash.Sum(nil)
66-
data = append(h, data...)
67-
}
68-
69-
return onionEncrypt(&o.sharedSecret, data)
70-
}
71-
7231
// Encode writes the encrypter's shared secret to the provided io.Writer.
7332
func (o *OnionErrorEncrypter) Encode(w io.Writer) error {
7433
_, err := w.Write(o.sharedSecret[:])
@@ -165,70 +124,3 @@ func NewOnionErrorDecrypter(circuit *Circuit) *OnionErrorDecrypter {
165124
circuit: circuit,
166125
}
167126
}
168-
169-
// DecryptError attempts to decrypt the passed encrypted error response. The
170-
// onion failure is encrypted in backward manner, starting from the node where
171-
// error have occurred. As a result, in order to decrypt the error we need get
172-
// all shared secret and apply decryption in the reverse order.
173-
func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (*btcec.PublicKey, []byte, error) {
174-
175-
sharedSecrets := generateSharedSecrets(
176-
o.circuit.PaymentPath,
177-
o.circuit.SessionKey,
178-
)
179-
180-
var (
181-
sender *btcec.PublicKey
182-
msg []byte
183-
dummySecret Hash256
184-
)
185-
copy(dummySecret[:], bytes.Repeat([]byte{1}, 32))
186-
187-
// We'll iterate a constant amount of hops to ensure that we don't give
188-
// away an timing information pertaining to the position in the route
189-
// that the error emanated from.
190-
for i := 0; i < NumMaxHops; i++ {
191-
var sharedSecret Hash256
192-
193-
// If we've already found the sender, then we'll use our dummy
194-
// secret to continue decryption attempts to fill out the rest
195-
// of the loop. Otherwise, we'll use the next shared secret in
196-
// line.
197-
if sender != nil || i > len(sharedSecrets)-1 {
198-
sharedSecret = dummySecret
199-
} else {
200-
sharedSecret = sharedSecrets[i]
201-
}
202-
203-
// With the shared secret, we'll now strip off a layer of
204-
// encryption from the encrypted error payload.
205-
encryptedData = onionEncrypt(&sharedSecret, encryptedData)
206-
207-
// Next, we'll need to separate the data, from the MAC itself
208-
// so we can reconstruct and verify it.
209-
expectedMac := encryptedData[:sha256.Size]
210-
data := encryptedData[sha256.Size:]
211-
212-
// With the data split, we'll now re-generate the MAC using its
213-
// specified key.
214-
umKey := generateKey("um", &sharedSecret)
215-
h := hmac.New(sha256.New, umKey[:])
216-
h.Write(data)
217-
218-
// If the MAC matches up, then we've found the sender of the
219-
// error and have also obtained the fully decrypted message.
220-
realMac := h.Sum(nil)
221-
if hmac.Equal(realMac, expectedMac) && sender == nil {
222-
sender = o.circuit.PaymentPath[i]
223-
msg = data
224-
}
225-
}
226-
227-
// If the sender pointer is still nil, then we haven't found the
228-
// sender, meaning we've failed to decrypt.
229-
if sender == nil {
230-
return nil, nil, errors.New("unable to retrieve onion failure")
231-
}
232-
233-
return sender, msg, nil
234-
}

0 commit comments

Comments
 (0)