Skip to content

Commit 25954be

Browse files
authored
Merge pull request #40 from Roasbeef/random-starting-bytes
sphinx: pad out the starting packet with random bytes
2 parents f34e9dc + 6bbfac5 commit 25954be

File tree

5 files changed

+97
-9
lines changed

5 files changed

+97
-9
lines changed

bench_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ func BenchmarkPathPacketConstruction(b *testing.B) {
5151
b.StartTimer()
5252

5353
for i := 0; i < b.N; i++ {
54-
sphinxPacket, err = NewOnionPacket(&route, d, nil)
54+
sphinxPacket, err = NewOnionPacket(
55+
&route, d, nil, BlankPacketFiller,
56+
)
5557
if err != nil {
5658
b.Fatalf("unable to create packet: %v", err)
5759
}

cmd/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ func main() {
105105
log.Fatalf("could not parse onion spec: %v", err)
106106
}
107107

108-
msg, err := sphinx.NewOnionPacket(path, sessionKey, assocData)
108+
msg, err := sphinx.NewOnionPacket(
109+
path, sessionKey, assocData,
110+
sphinx.DeterministicPacketFiller,
111+
)
109112
if err != nil {
110113
log.Fatalf("Error creating message: %v", err)
111114
}

packetfiller.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package sphinx
2+
3+
import (
4+
"crypto/rand"
5+
6+
"github.com/aead/chacha20"
7+
"github.com/btcsuite/btcd/btcec"
8+
)
9+
10+
// PacketFiller is a function type to be specified by the caller to provide a
11+
// stream of random bytes derived from a CSPRNG to fill out the starting packet
12+
// in order to ensure we don't leak information on the true route length to the
13+
// receiver. The packet filler may also use the session key to generate a set
14+
// of filler bytes if it wishes to be deterministic.
15+
type PacketFiller func(*btcec.PrivateKey, *[routingInfoSize]byte) error
16+
17+
// RandPacketFiller is a packet filler that reads a set of random bytes from a
18+
// CSPRNG.
19+
func RandPacketFiller(_ *btcec.PrivateKey, mixHeader *[routingInfoSize]byte) error {
20+
// Read out random bytes to fill out the rest of the starting packet
21+
// after the hop payload for the final node. This mitigates a privacy
22+
// leak that may reveal a lower bound on the true path length to the
23+
// receiver.
24+
if _, err := rand.Read(mixHeader[:]); err != nil {
25+
return err
26+
}
27+
28+
return nil
29+
}
30+
31+
// BlankPacketFiller is a packet filler that doesn't attempt to fill out the
32+
// packet at all. It should ONLY be used for generating test vectors or other
33+
// instances that required deterministic packet generation.
34+
func BlankPacketFiller(_ *btcec.PrivateKey, _ *[routingInfoSize]byte) error {
35+
return nil
36+
}
37+
38+
// DeterministicPacketFiller is a packet filler that generates a deterministic
39+
// set of filler bytes by using chacha20 with a key derived from the session
40+
// key.
41+
func DeterministicPacketFiller(sessionKey *btcec.PrivateKey,
42+
mixHeader *[routingInfoSize]byte) error {
43+
44+
// First, we'll generate a new key that'll be used to generate some
45+
// random bytes for our padding purposes. To derive this new key, we
46+
// essentially calculate: HMAC("pad", sessionKey).
47+
var sessionKeyBytes Hash256
48+
copy(sessionKeyBytes[:], sessionKey.Serialize())
49+
paddingKey := generateKey("pad", &sessionKeyBytes)
50+
51+
// Now that we have our target key, we'll use chacha20 to generate a
52+
// series of random bytes directly into the passed mixHeader packet.
53+
var nonce [8]byte
54+
padCipher, err := chacha20.NewCipher(nonce[:], paddingKey[:])
55+
if err != nil {
56+
return err
57+
}
58+
padCipher.XORKeyStream(mixHeader[:], mixHeader[:])
59+
60+
return nil
61+
}

sphinx.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,26 @@ func generateSharedSecrets(paymentPath []*btcec.PublicKey,
190190
// NewOnionPacket creates a new onion packet which is capable of obliviously
191191
// routing a message through the mix-net path outline by 'paymentPath'.
192192
func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
193-
assocData []byte) (*OnionPacket, error) {
193+
assocData []byte, pktFiller PacketFiller) (*OnionPacket, error) {
194194

195195
// Check whether total payload size doesn't exceed the hard maximum.
196196
if paymentPath.TotalPayloadSize() > routingInfoSize {
197197
return nil, ErrMaxRoutingInfoSizeExceeded
198198
}
199199

200+
// If we don't actually have a partially populated route, then we'll
201+
// exit early.
200202
numHops := paymentPath.TrueRouteLength()
203+
if numHops == 0 {
204+
return nil, fmt.Errorf("route of length zero passed in")
205+
}
206+
207+
// We'll force the caller to provide a packet filler, as otherwise we
208+
// may default to an insecure filling method (which should only really
209+
// be used to generate test vectors).
210+
if pktFiller == nil {
211+
return nil, fmt.Errorf("packet filler must be specified")
212+
}
201213

202214
hopSharedSecrets := generateSharedSecrets(
203215
paymentPath.NodeKeys(), sessionKey,
@@ -214,6 +226,11 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
214226
hopPayloadBuf bytes.Buffer
215227
)
216228

229+
// Fill the packet using the caller specified methodology.
230+
if err := pktFiller(sessionKey, &mixHeader); err != nil {
231+
return nil, err
232+
}
233+
217234
// Now we compute the routing information for each hop, along with a
218235
// MAC of the routing info using the shared key for that hop.
219236
for i := numHops - 1; i >= 0; i-- {
@@ -232,7 +249,6 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
232249
// generate enough bytes to obfuscate this layer of the onion
233250
// packet.
234251
streamBytes := generateCipherStream(rhoKey, routingInfoSize)
235-
236252
payload := paymentPath[i].HopPayload
237253

238254
// Before we assemble the packet, we'll shift the current

sphinx_test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacke
135135
sessionKey, _ := btcec.PrivKeyFromBytes(
136136
btcec.S256(), bytes.Repeat([]byte{'A'}, 32),
137137
)
138-
fwdMsg, err := NewOnionPacket(&route, sessionKey, nil)
138+
fwdMsg, err := NewOnionPacket(
139+
&route, sessionKey, nil, DeterministicPacketFiller,
140+
)
139141
if err != nil {
140142
return nil, nil, nil, nil, fmt.Errorf("unable to create "+
141143
"forwarding message: %#v", err)
@@ -198,7 +200,7 @@ func TestBolt4Packet(t *testing.T) {
198200
}
199201

200202
sessionKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), bolt4SessionKey)
201-
pkt, err := NewOnionPacket(&route, sessionKey, bolt4AssocData)
203+
pkt, err := NewOnionPacket(&route, sessionKey, bolt4AssocData, BlankPacketFiller)
202204
if err != nil {
203205
t.Fatalf("unable to construct onion packet: %v", err)
204206
}
@@ -558,12 +560,14 @@ func newEOBRoute(numHops uint32,
558560
}
559561

560562
// Generate a forwarding message to route to the final node via the
561-
// generated intermdiates nodes above. Destination should be Hash160,
563+
// generated intermediate nodes above. Destination should be Hash160,
562564
// adding padding so parsing still works.
563565
sessionKey, _ := btcec.PrivKeyFromBytes(
564566
btcec.S256(), bytes.Repeat([]byte{'A'}, 32),
565567
)
566-
fwdMsg, err := NewOnionPacket(&route, sessionKey, nil)
568+
fwdMsg, err := NewOnionPacket(
569+
&route, sessionKey, nil, DeterministicPacketFiller,
570+
)
567571
if err != nil {
568572
return nil, nil, err
569573
}
@@ -920,7 +924,9 @@ func TestVariablePayloadOnion(t *testing.T) {
920924

921925
// With all the required data assembled, we'll craft a new packet.
922926
sessionKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), sessionKeyBytes)
923-
pkt, err := NewOnionPacket(&route, sessionKey, associatedData)
927+
pkt, err := NewOnionPacket(
928+
&route, sessionKey, associatedData, BlankPacketFiller,
929+
)
924930
if err != nil {
925931
t.Fatalf("unable to construct onion packet: %v", err)
926932
}

0 commit comments

Comments
 (0)