Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sphinx: pad out the starting packet with random bytes #40

Merged
merged 1 commit into from Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion bench_test.go
Expand Up @@ -51,7 +51,9 @@ func BenchmarkPathPacketConstruction(b *testing.B) {
b.StartTimer()

for i := 0; i < b.N; i++ {
sphinxPacket, err = NewOnionPacket(&route, d, nil)
sphinxPacket, err = NewOnionPacket(
&route, d, nil, BlankPacketFiller,
)
if err != nil {
b.Fatalf("unable to create packet: %v", err)
}
Expand Down
5 changes: 4 additions & 1 deletion cmd/main.go
Expand Up @@ -105,7 +105,10 @@ func main() {
log.Fatalf("could not parse onion spec: %v", err)
}

msg, err := sphinx.NewOnionPacket(path, sessionKey, assocData)
msg, err := sphinx.NewOnionPacket(
path, sessionKey, assocData,
sphinx.DeterministicPacketFiller,
)
if err != nil {
log.Fatalf("Error creating message: %v", err)
}
Expand Down
61 changes: 61 additions & 0 deletions packetfiller.go
@@ -0,0 +1,61 @@
package sphinx

import (
"crypto/rand"

"github.com/aead/chacha20"
"github.com/btcsuite/btcd/btcec"
)

// PacketFiller is a function type to be specified by the caller to provide a
// stream of random bytes derived from a CSPRNG to fill out the starting packet
// in order to ensure we don't leak information on the true route length to the
// receiver. The packet filler may also use the session key to generate a set
// of filler bytes if it wishes to be deterministic.
type PacketFiller func(*btcec.PrivateKey, *[routingInfoSize]byte) error

// RandPacketFiller is a packet filler that reads a set of random bytes from a
// CSPRNG.
func RandPacketFiller(_ *btcec.PrivateKey, mixHeader *[routingInfoSize]byte) error {
// Read out random bytes to fill out the rest of the starting packet
// after the hop payload for the final node. This mitigates a privacy
// leak that may reveal a lower bound on the true path length to the
// receiver.
if _, err := rand.Read(mixHeader[:]); err != nil {
return err
}

return nil
}

// BlankPacketFiller is a packet filler that doesn't attempt to fill out the
// packet at all. It should ONLY be used for generating test vectors or other
// instances that required deterministic packet generation.
func BlankPacketFiller(_ *btcec.PrivateKey, _ *[routingInfoSize]byte) error {
return nil
}

// DeterministicPacketFiller is a packet filler that generates a deterministic
// set of filler bytes by using chacha20 with a key derived from the session
// key.
func DeterministicPacketFiller(sessionKey *btcec.PrivateKey,
mixHeader *[routingInfoSize]byte) error {

// First, we'll generate a new key that'll be used to generate some
// random bytes for our padding purposes. To derive this new key, we
// essentially calculate: HMAC("pad", sessionKey).
var sessionKeyBytes Hash256
copy(sessionKeyBytes[:], sessionKey.Serialize())
paddingKey := generateKey("pad", &sessionKeyBytes)

// Now that we have our target key, we'll use chacha20 to generate a
// series of random bytes directly into the passed mixHeader packet.
var nonce [8]byte
padCipher, err := chacha20.NewCipher(nonce[:], paddingKey[:])
if err != nil {
return err
}
padCipher.XORKeyStream(mixHeader[:], mixHeader[:])

return nil
}
20 changes: 18 additions & 2 deletions sphinx.go
Expand Up @@ -190,14 +190,26 @@ func generateSharedSecrets(paymentPath []*btcec.PublicKey,
// NewOnionPacket creates a new onion packet which is capable of obliviously
// routing a message through the mix-net path outline by 'paymentPath'.
func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
assocData []byte) (*OnionPacket, error) {
assocData []byte, pktFiller PacketFiller) (*OnionPacket, error) {

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

// If we don't actually have a partially populated route, then we'll
// exit early.
numHops := paymentPath.TrueRouteLength()
if numHops == 0 {
return nil, fmt.Errorf("route of length zero passed in")
}

// We'll force the caller to provide a packet filler, as otherwise we
// may default to an insecure filling method (which should only really
// be used to generate test vectors).
if pktFiller == nil {
return nil, fmt.Errorf("packet filler must be specified")
}

hopSharedSecrets := generateSharedSecrets(
paymentPath.NodeKeys(), sessionKey,
Expand All @@ -214,6 +226,11 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
hopPayloadBuf bytes.Buffer
)

// Fill the packet using the caller specified methodology.
if err := pktFiller(sessionKey, &mixHeader); err != nil {
return nil, err
}

// Now we compute the routing information for each hop, along with a
// MAC of the routing info using the shared key for that hop.
for i := numHops - 1; i >= 0; i-- {
Expand All @@ -232,7 +249,6 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
// generate enough bytes to obfuscate this layer of the onion
// packet.
streamBytes := generateCipherStream(rhoKey, routingInfoSize)

payload := paymentPath[i].HopPayload

// Before we assemble the packet, we'll shift the current
Expand Down
16 changes: 11 additions & 5 deletions sphinx_test.go
Expand Up @@ -135,7 +135,9 @@ func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacke
sessionKey, _ := btcec.PrivKeyFromBytes(
btcec.S256(), bytes.Repeat([]byte{'A'}, 32),
)
fwdMsg, err := NewOnionPacket(&route, sessionKey, nil)
fwdMsg, err := NewOnionPacket(
&route, sessionKey, nil, DeterministicPacketFiller,
)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("unable to create "+
"forwarding message: %#v", err)
Expand Down Expand Up @@ -198,7 +200,7 @@ func TestBolt4Packet(t *testing.T) {
}

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

// Generate a forwarding message to route to the final node via the
// generated intermdiates nodes above. Destination should be Hash160,
// generated intermediate nodes above. Destination should be Hash160,
// adding padding so parsing still works.
sessionKey, _ := btcec.PrivKeyFromBytes(
btcec.S256(), bytes.Repeat([]byte{'A'}, 32),
)
fwdMsg, err := NewOnionPacket(&route, sessionKey, nil)
fwdMsg, err := NewOnionPacket(
&route, sessionKey, nil, DeterministicPacketFiller,
)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -920,7 +924,9 @@ func TestVariablePayloadOnion(t *testing.T) {

// With all the required data assembled, we'll craft a new packet.
sessionKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), sessionKeyBytes)
pkt, err := NewOnionPacket(&route, sessionKey, associatedData)
pkt, err := NewOnionPacket(
&route, sessionKey, associatedData, BlankPacketFiller,
)
if err != nil {
t.Fatalf("unable to construct onion packet: %v", err)
}
Expand Down