Skip to content

Commit fadc953

Browse files
committed
sphinx: ensure error decryption uses a constant number of iterations
This commit modifies the way we do decryption slightly to ensure that decryption on the receiver side always uses a constant number of iterations. We implement this by using a dummy hard coded secret after we’ve found the actual secret to continue decryption attempts. With this in place, we’ll also iterate 20 times, meaning that we don’t give away any timing data that would expose who in the route sent the actual error.
1 parent 1d52214 commit fadc953

File tree

1 file changed

+49
-9
lines changed

1 file changed

+49
-9
lines changed

obfuscation.go

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package sphinx
22

33
import (
4+
"bytes"
45
"crypto/hmac"
56
"crypto/sha256"
67
"errors"
@@ -175,26 +176,65 @@ func NewOnionDeobfuscator(circuit *Circuit) *OnionDeobfuscator {
175176
// obfuscation in reverse order.
176177
func (o *OnionDeobfuscator) Deobfuscate(obfuscatedData []byte) (*btcec.PublicKey, []byte, error) {
177178

178-
for i, sharedSecret := range generateSharedSecrets(o.circuit.PaymentPath,
179-
o.circuit.SessionKey) {
179+
sharedSecrets := generateSharedSecrets(
180+
o.circuit.PaymentPath,
181+
o.circuit.SessionKey,
182+
)
183+
184+
var (
185+
sender *btcec.PublicKey
186+
msg []byte
187+
dummySecret [sha256.Size]byte
188+
)
189+
copy(dummySecret[:], bytes.Repeat([]byte{1}, 32))
190+
191+
// We'll iterate a constant amount of hops to ensure that we don't give
192+
// away an timing information pertaining to the position in the route
193+
// that the error emanated from.
194+
for i := 0; i < NumMaxHops; i++ {
195+
var sharedSecret [sha256.Size]byte
196+
197+
// If we've already found the sender, then we'll use our dummy
198+
// secret to continue decryption attempts to fill out the rest
199+
// of the loop. Otherwise, we'll use the next shared secret in
200+
// line.
201+
if sender != nil {
202+
sharedSecret = dummySecret
203+
} else {
204+
sharedSecret = sharedSecrets[i]
205+
}
206+
207+
// With the shared secret, we'll now strip off a layer of
208+
// encryption from the encrypted error payload.
180209
obfuscatedData = onionObfuscation(sharedSecret, obfuscatedData)
181-
umKey := generateKey("um", sharedSecret)
182210

183-
// Split the data and hmac.
211+
// Next, we'll need to separate the data, from the MAC itself
212+
// so we can reconstruct and verify it.
184213
expectedMac := obfuscatedData[:sha256.Size]
185214
data := obfuscatedData[sha256.Size:]
186215

187-
// Calculate the real hmac.
216+
// With the data split, we'll now re-generate the MAC using its
217+
// specified key.
218+
umKey := generateKey("um", sharedSecret)
188219
h := hmac.New(sha256.New, umKey[:])
189220
h.Write(data)
190-
realMac := h.Sum(nil)
191221

192-
if hmac.Equal(realMac, expectedMac) {
193-
return o.circuit.PaymentPath[i], data, nil
222+
// If the MAC matches up, then we've found the sender of the
223+
// error and have also obtained the fully decrypted message.
224+
realMac := h.Sum(nil)
225+
if hmac.Equal(realMac, expectedMac) && sender == nil {
226+
sender = o.circuit.PaymentPath[i]
227+
msg = data
194228
}
195229
}
196230

197-
return nil, nil, errors.New("unable to retrieve onion failure")
231+
// If the sender pointer is still nil, then we haven't found the
232+
// sender, meaning we've failed to decrypt.
233+
if sender == nil {
234+
return nil, nil, errors.New("unable to retrieve onion failure")
235+
}
236+
237+
return sender, msg, nil
198238
}
199239

200240
// Decode writes converted deobfucator in the byte stream.

0 commit comments

Comments
 (0)