Skip to content

Commit f34e9dc

Browse files
authored
Merge pull request #45 from joostjager/payload-length-check
Add payload length check
2 parents 850081b + 20a5267 commit f34e9dc

File tree

3 files changed

+73
-35
lines changed

3 files changed

+73
-35
lines changed

path.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func (hp *HopPayload) Decode(r io.Reader) error {
249249
case 0x00:
250250
// Our size is just the payload, without the HMAC. This means
251251
// that this is the legacy payload type.
252-
payloadSize = HopDataSize - HMACSize
252+
payloadSize = LegacyHopDataSize - HMACSize
253253
hp.Type = PayloadLegacy
254254

255255
default:
@@ -304,11 +304,15 @@ func (hp *HopPayload) HopData() (*HopData, error) {
304304
return &hd, nil
305305
}
306306

307-
// NumMaxHops is the maximum path length. This should be set to an estimate of
308-
// the upper limit of the diameter of the node graph.
309-
//
310-
// TODO(roasbeef): adjust due to var-payloads?
311-
const NumMaxHops = 20
307+
// NumMaxHops is the maximum path length. There is a maximum of 1300 bytes in
308+
// the routing info block. Legacy hop payloads are always 65 bytes, while tlv
309+
// payloads are at least 47 bytes (tlvlen 1, amt 2, timelock 2, nextchan 10,
310+
// hmac 32) for the intermediate hops and 37 bytes (tlvlen 1, amt 2, timelock 2,
311+
// hmac 32) for the exit hop. The maximum path length can therefore only be
312+
// reached by using tlv payloads only. With that, the maximum number of
313+
// intermediate hops is: Floor((1300 - 37) / 47) = 26. Including the exit hop,
314+
// the maximum path length is 27 hops.
315+
const NumMaxHops = 27
312316

313317
// PaymentPath represents a series of hops within the Lightning Network
314318
// starting at a sender and terminating at a receiver. Each hop contains a set

sphinx.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/ecdsa"
66
"crypto/hmac"
77
"crypto/sha256"
8+
"fmt"
89
"io"
910
"math/big"
1011

@@ -36,29 +37,26 @@ const (
3637
// utilize this space to pack in the unrolled bytes.
3738
NumPaddingBytes = 12
3839

39-
// HopDataSize is the fixed size of hop_data. BOLT 04 currently
40+
// LegacyHopDataSize is the fixed size of hop_data. BOLT 04 currently
4041
// specifies this to be 1 byte realm, 8 byte channel_id, 8 byte amount
41-
// to forward, 4 byte outgoing CLTV value, 12 bytes padding and 32
42-
// bytes HMAC for a total of 65 bytes per hop.
43-
HopDataSize = (RealmByteSize + AddressSize + AmtForwardSize +
42+
// to forward, 4 byte outgoing CLTV value, 12 bytes padding and 32 bytes
43+
// HMAC for a total of 65 bytes per hop.
44+
LegacyHopDataSize = (RealmByteSize + AddressSize + AmtForwardSize +
4445
OutgoingCLTVSize + NumPaddingBytes + HMACSize)
4546

46-
// MaxPayloadSize is the maximum size a payload for a single hop can
47-
// be. This is the worst case scenario of a single hop, consuming all
48-
// 20 frames. We need to know this in order to generate a sufficiently
49-
// long stream of pseudo-random bytes when encrypting/decrypting the
50-
// payload.
51-
MaxPayloadSize = NumMaxHops * HopDataSize
52-
53-
// sharedSecretSize is the size in bytes of the shared secrets.
54-
sharedSecretSize = 32
47+
// MaxPayloadSize is the maximum size a payload for a single hop can be.
48+
// This is the worst case scenario of a single hop, consuming all
49+
// available space. We need to know this in order to generate a
50+
// sufficiently long stream of pseudo-random bytes when
51+
// encrypting/decrypting the payload.
52+
MaxPayloadSize = routingInfoSize
5553

5654
// routingInfoSize is the fixed size of the the routing info. This
5755
// consists of a addressSize byte address and a HMACSize byte HMAC for
5856
// each hop of the route, the first pair in cleartext and the following
59-
// pairs increasingly obfuscated. In case fewer than numMaxHops are
60-
// used, then the remainder is padded with null-bytes, also obfuscated.
61-
routingInfoSize = NumMaxHops * HopDataSize
57+
// pairs increasingly obfuscated. If not all space is used up, the
58+
// remainder is padded with null-bytes, also obfuscated.
59+
routingInfoSize = 1300
6260

6361
// numStreamBytes is the number of bytes produced by our CSPRG for the
6462
// key stream implementing our stream cipher to encrypt/decrypt the mix
@@ -76,6 +74,11 @@ const (
7674
baseVersion = 0
7775
)
7876

77+
var (
78+
ErrMaxRoutingInfoSizeExceeded = fmt.Errorf(
79+
"max routing info size of %v bytes exceeded", routingInfoSize)
80+
)
81+
7982
// OnionPacket is the onion wrapped hop-to-hop routing information necessary to
8083
// propagate a message through the mix-net without intermediate nodes having
8184
// knowledge of their position within the route, the source, the destination,
@@ -189,6 +192,11 @@ func generateSharedSecrets(paymentPath []*btcec.PublicKey,
189192
func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
190193
assocData []byte) (*OnionPacket, error) {
191194

195+
// Check whether total payload size doesn't exceed the hard maximum.
196+
if paymentPath.TotalPayloadSize() > routingInfoSize {
197+
return nil, ErrMaxRoutingInfoSizeExceeded
198+
}
199+
192200
numHops := paymentPath.TrueRouteLength()
193201

194202
hopSharedSecrets := generateSharedSecrets(

sphinx_test.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ var (
8686
"2cf4f0731da13b8546d1d6d4f8d75b9fce6c2341a71b0ea6f780" +
8787
"df54bfdb0dd5cd9855179f602f917265f21f9190c70217774a6f" +
8888
"baaa7d63ad64199f4664813b955cff954949076dcf"
89+
90+
testLegacyRouteNumHops = 20
8991
)
9092

9193
func newTestRoute(numHops int) ([]*Router, *PaymentPath, *[]HopData, *OnionPacket, error) {
@@ -214,7 +216,7 @@ func TestBolt4Packet(t *testing.T) {
214216
}
215217

216218
func TestSphinxCorrectness(t *testing.T) {
217-
nodes, _, hopDatas, fwdMsg, err := newTestRoute(NumMaxHops)
219+
nodes, _, hopDatas, fwdMsg, err := newTestRoute(testLegacyRouteNumHops)
218220
if err != nil {
219221
t.Fatalf("unable to create random onion packet: %v", err)
220222
}
@@ -307,7 +309,7 @@ func TestSphinxSingleHop(t *testing.T) {
307309
func TestSphinxNodeRelpay(t *testing.T) {
308310
// We'd like to ensure that the sphinx node itself rejects all replayed
309311
// packets which share the same shared secret.
310-
nodes, _, _, fwdMsg, err := newTestRoute(NumMaxHops)
312+
nodes, _, _, fwdMsg, err := newTestRoute(testLegacyRouteNumHops)
311313
if err != nil {
312314
t.Fatalf("unable to create test route: %v", err)
313315
}
@@ -332,7 +334,7 @@ func TestSphinxNodeRelpay(t *testing.T) {
332334
func TestSphinxNodeRelpaySameBatch(t *testing.T) {
333335
// We'd like to ensure that the sphinx node itself rejects all replayed
334336
// packets which share the same shared secret.
335-
nodes, _, _, fwdMsg, err := newTestRoute(NumMaxHops)
337+
nodes, _, _, fwdMsg, err := newTestRoute(testLegacyRouteNumHops)
336338
if err != nil {
337339
t.Fatalf("unable to create test route: %v", err)
338340
}
@@ -378,7 +380,7 @@ func TestSphinxNodeRelpaySameBatch(t *testing.T) {
378380
func TestSphinxNodeRelpayLaterBatch(t *testing.T) {
379381
// We'd like to ensure that the sphinx node itself rejects all replayed
380382
// packets which share the same shared secret.
381-
nodes, _, _, fwdMsg, err := newTestRoute(NumMaxHops)
383+
nodes, _, _, fwdMsg, err := newTestRoute(testLegacyRouteNumHops)
382384
if err != nil {
383385
t.Fatalf("unable to create test route: %v", err)
384386
}
@@ -423,7 +425,7 @@ func TestSphinxNodeRelpayLaterBatch(t *testing.T) {
423425
func TestSphinxNodeReplayBatchIdempotency(t *testing.T) {
424426
// We'd like to ensure that the sphinx node itself rejects all replayed
425427
// packets which share the same shared secret.
426-
nodes, _, _, fwdMsg, err := newTestRoute(NumMaxHops)
428+
nodes, _, _, fwdMsg, err := newTestRoute(testLegacyRouteNumHops)
427429
if err != nil {
428430
t.Fatalf("unable to create test route: %v", err)
429431
}
@@ -563,8 +565,7 @@ func newEOBRoute(numHops uint32,
563565
)
564566
fwdMsg, err := NewOnionPacket(&route, sessionKey, nil)
565567
if err != nil {
566-
return nil, nil, fmt.Errorf("unable to create forwarding "+
567-
"message: %#v", err)
568+
return nil, nil, err
568569
}
569570

570571
return fwdMsg, nodes, nil
@@ -587,8 +588,9 @@ func TestSphinxHopVariableSizedPayloads(t *testing.T) {
587588
t.Parallel()
588589

589590
var testCases = []struct {
590-
numNodes uint32
591-
eobMapping map[int]HopPayload
591+
numNodes uint32
592+
eobMapping map[int]HopPayload
593+
expectedError error
592594
}{
593595
// A single hop route with a payload going to the last hop in
594596
// the route. The payload is enough to fit into what would be
@@ -598,7 +600,7 @@ func TestSphinxHopVariableSizedPayloads(t *testing.T) {
598600
eobMapping: map[int]HopPayload{
599601
0: HopPayload{
600602
Type: PayloadTLV,
601-
Payload: bytes.Repeat([]byte("a"), HopDataSize-HMACSize),
603+
Payload: bytes.Repeat([]byte("a"), LegacyHopDataSize-HMACSize),
602604
},
603605
},
604606
},
@@ -610,7 +612,7 @@ func TestSphinxHopVariableSizedPayloads(t *testing.T) {
610612
eobMapping: map[int]HopPayload{
611613
0: HopPayload{
612614
Type: PayloadTLV,
613-
Payload: bytes.Repeat([]byte("a"), HopDataSize*3),
615+
Payload: bytes.Repeat([]byte("a"), LegacyHopDataSize*3),
614616
},
615617
},
616618
},
@@ -631,7 +633,7 @@ func TestSphinxHopVariableSizedPayloads(t *testing.T) {
631633
}, nil),
632634
1: HopPayload{
633635
Type: PayloadTLV,
634-
Payload: bytes.Repeat([]byte("a"), HopDataSize*2),
636+
Payload: bytes.Repeat([]byte("a"), LegacyHopDataSize*2),
635637
},
636638
},
637639
},
@@ -678,16 +680,40 @@ func TestSphinxHopVariableSizedPayloads(t *testing.T) {
678680
},
679681
},
680682
},
683+
684+
// A 3 hop route (4 nodes) that carries more data then what fits
685+
// in the routing info.
686+
{
687+
numNodes: 3,
688+
eobMapping: map[int]HopPayload{
689+
0: HopPayload{
690+
Type: PayloadTLV,
691+
Payload: bytes.Repeat([]byte("a"), 500),
692+
},
693+
1: HopPayload{
694+
Type: PayloadTLV,
695+
Payload: bytes.Repeat([]byte("a"), 500),
696+
},
697+
2: HopPayload{
698+
Type: PayloadTLV,
699+
Payload: bytes.Repeat([]byte("a"), 500),
700+
},
701+
},
702+
expectedError: ErrMaxRoutingInfoSizeExceeded,
703+
},
681704
}
682705

683706
for testCaseNum, testCase := range testCases {
684707
nextPkt, routers, err := newEOBRoute(
685708
testCase.numNodes, testCase.eobMapping,
686709
)
687-
if err != nil {
710+
if testCase.expectedError != err {
688711
t.Fatalf("#%v: unable to create eob "+
689712
"route: %v", testCase, err)
690713
}
714+
if err != nil {
715+
continue
716+
}
691717

692718
// We'll now walk thru manually each actual hop within the
693719
// route. We use the size of the routers rather than the number

0 commit comments

Comments
 (0)