Skip to content

Commit aeb7d71

Browse files
authored
Merge pull request #18 from cfromknecht/linear-packets
Optimization: Linear Speedup in Sphinx Shared-Secret Construction
2 parents d8e664f + 0b68cb3 commit aeb7d71

File tree

2 files changed

+57
-40
lines changed

2 files changed

+57
-40
lines changed

bench_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func BenchmarkPathPacketConstruction(b *testing.B) {
4040
}
4141

4242
d, _ := btcec.PrivKeyFromBytes(btcec.S256(), bytes.Repeat([]byte{'A'}, 32))
43+
b.ReportAllocs()
4344
b.StartTimer()
4445

4546
for i := 0; i < b.N; i++ {
@@ -58,6 +59,7 @@ func BenchmarkProcessPacket(b *testing.B) {
5859
if err != nil {
5960
b.Fatalf("unable to create test route: %v", err)
6061
}
62+
b.ReportAllocs()
6163
b.StartTimer()
6264

6365
var (

sphinx.go

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"encoding/binary"
99
"io"
1010
"io/ioutil"
11+
"math/big"
1112
"sync"
1213

1314
"github.com/aead/chacha20"
@@ -211,37 +212,60 @@ func generateSharedSecrets(paymentPath []*btcec.PublicKey,
211212
// we only need to transmit a single group element, and hops can't link
212213
// a session back to us if they have several nodes in the path.
213214
numHops := len(paymentPath)
214-
hopEphemeralPubKeys := make([]*btcec.PublicKey, numHops)
215215
hopSharedSecrets := make([][sha256.Size]byte, numHops)
216-
hopBlindingFactors := make([][sha256.Size]byte, numHops)
217216

218217
// Compute the triplet for the first hop outside of the main loop.
219218
// Within the loop each new triplet will be computed recursively based
220219
// off of the blinding factor of the last hop.
221-
hopEphemeralPubKeys[0] = sessionKey.PubKey()
220+
lastEphemeralPubKey := sessionKey.PubKey()
222221
hopSharedSecrets[0] = generateSharedSecret(paymentPath[0], sessionKey)
223-
hopBlindingFactors[0] = computeBlindingFactor(hopEphemeralPubKeys[0], hopSharedSecrets[0][:])
222+
lastBlindingFactor := computeBlindingFactor(lastEphemeralPubKey, hopSharedSecrets[0][:])
224223

225-
// Now recursively compute the ephemeral ECDH pub keys, the shared
226-
// secret, and blinding factor for each hop.
224+
// The cached blinding factor will contain the running product of the
225+
// session private key x and blinding factors b_i, computed as
226+
// c_0 = x
227+
// c_i = c_{i-1} * b_{i-1} (mod |F(G)|).
228+
// = x * b_0 * b_1 * ... * b_{i-1} (mod |F(G)|).
229+
//
230+
// We begin with just the session private key x, so that base case
231+
// c_0 = x. At the beginning of each iteration, the previous blinding
232+
// factor is aggregated into the modular product, and used as the scalar
233+
// value in deriving the hop ephemeral keys and shared secrets.
234+
var cachedBlindingFactor big.Int
235+
cachedBlindingFactor.SetBytes(sessionKey.D.Bytes())
236+
237+
// Now recursively compute the cached blinding factor, ephemeral ECDH
238+
// pub keys, and shared secret for each hop.
239+
var nextBlindingFactor big.Int
227240
for i := 1; i <= numHops-1; i++ {
228-
// a_{n} = a_{n-1} x c_{n-1} -> (Y_prev_pub_key x prevBlindingFactor)
229-
hopEphemeralPubKeys[i] = blindGroupElement(hopEphemeralPubKeys[i-1],
230-
hopBlindingFactors[i-1][:])
231-
232-
// s_{n} = sha256( y_{n} x c_{n-1} ) ->
241+
// Update the cached blinding factor with b_{i-1}.
242+
nextBlindingFactor.SetBytes(lastBlindingFactor[:])
243+
cachedBlindingFactor.Mul(&cachedBlindingFactor, &nextBlindingFactor)
244+
cachedBlindingFactor.Mod(&cachedBlindingFactor, btcec.S256().Params().N)
245+
246+
// a_i = g ^ c_i
247+
// = g^( x * b_0 * ... * b_{i-1} )
248+
// = X^( b_0 * ... * b_{i-1} )
249+
// X_our_session_pub_key x all prev blinding factors
250+
lastEphemeralPubKey = blindBaseElement(cachedBlindingFactor.Bytes())
251+
252+
// e_i = Y_i ^ c_i
253+
// = ( Y_i ^ x )^( b_0 * ... * b_{i-1} )
233254
// (Y_their_pub_key x x_our_priv) x all prev blinding factors
234-
yToX := blindGroupElement(paymentPath[i], sessionKey.D.Bytes())
235-
hopSharedSecrets[i] = sha256.Sum256(
236-
multiScalarMult(
237-
yToX,
238-
hopBlindingFactors[:i],
239-
).SerializeCompressed(),
240-
)
241-
242-
// TODO(roasbeef): prob don't need to store all blinding factors, only the prev...
243-
// b_{n} = sha256(a_{n} || s_{n})
244-
hopBlindingFactors[i] = computeBlindingFactor(hopEphemeralPubKeys[i],
255+
hopBlindedPubKey := blindGroupElement(
256+
paymentPath[i], cachedBlindingFactor.Bytes())
257+
258+
// s_i = sha256( e_i )
259+
// = sha256( Y_i ^ (x * b_0 * ... * b_{i-1} )
260+
hopSharedSecrets[i] = sha256.Sum256(hopBlindedPubKey.SerializeCompressed())
261+
262+
// Only need to evaluate up to the penultimate blinding factor.
263+
if i >= numHops-1 {
264+
break
265+
}
266+
267+
// b_i = sha256( a_i || s_i )
268+
lastBlindingFactor = computeBlindingFactor(lastEphemeralPubKey,
245269
hopSharedSecrets[i][:])
246270
}
247271

@@ -501,13 +525,20 @@ func computeBlindingFactor(hopPubKey *btcec.PublicKey, hopSharedSecret []byte) [
501525
return hash
502526
}
503527

504-
// blindGroupElement blinds the group element by performing scalar
505-
// multiplication of the group element by blindingFactor: G x blindingFactor.
528+
// blindGroupElement blinds the group element P by performing scalar
529+
// multiplication of the group element by blindingFactor: blindingFactor * P.
506530
func blindGroupElement(hopPubKey *btcec.PublicKey, blindingFactor []byte) *btcec.PublicKey {
507531
newX, newY := btcec.S256().ScalarMult(hopPubKey.X, hopPubKey.Y, blindingFactor[:])
508532
return &btcec.PublicKey{btcec.S256(), newX, newY}
509533
}
510534

535+
// blindBaseElement blinds the groups's generator G by performing scalar base
536+
// multiplication using the blindingFactor: blindingFactor * G.
537+
func blindBaseElement(blindingFactor []byte) *btcec.PublicKey {
538+
newX, newY := btcec.S256().ScalarBaseMult(blindingFactor)
539+
return &btcec.PublicKey{btcec.S256(), newX, newY}
540+
}
541+
511542
// generateSharedSecret generates the shared secret for a particular hop. The
512543
// shared secret is generated by taking the group element contained in the
513544
// mix-header, and performing an ECDH operation with the node's long term onion
@@ -523,22 +554,6 @@ func generateSharedSecret(pub *btcec.PublicKey, priv *btcec.PrivateKey) [32]byte
523554
return sha256.Sum256(s.SerializeCompressed())
524555
}
525556

526-
// multiScalarMult computes the cumulative product of the blinding factors
527-
// times the passed public key.
528-
//
529-
// TODO(roasbeef): optimize using totient?
530-
func multiScalarMult(hopPubKey *btcec.PublicKey,
531-
blindingFactors [][sha256.Size]byte) *btcec.PublicKey {
532-
533-
finalPubKey := hopPubKey
534-
535-
for _, blindingFactor := range blindingFactors {
536-
finalPubKey = blindGroupElement(finalPubKey, blindingFactor[:])
537-
}
538-
539-
return finalPubKey
540-
}
541-
542557
// ProcessCode is an enum-like type which describes to the high-level package
543558
// user which action should be taken after processing a Sphinx packet.
544559
type ProcessCode int

0 commit comments

Comments
 (0)