Skip to content

Commit b175ee5

Browse files
committed
sphinx: add new NewHopPayload constructor
In this commit, we add a new constructor to abstract away the details of the hop payload framing from the caller. This new function accepts the hop data, as well as optional EOB bytes. We'll then map these two pieces of data into a single hop payload by appending the EOB bytes to the serialized hop data.
1 parent f2ff78b commit b175ee5

File tree

5 files changed

+95
-23
lines changed

5 files changed

+95
-23
lines changed

cmd/main.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,21 @@ func parseOnionSpec(spec OnionSpec) (*sphinx.PaymentPath, *btcec.PrivateKey, err
5959

6060
path[i].NodePub = *pubkey
6161

62-
path[i].HopPayload.Realm[0] = byte(hop.Realm)
63-
path[i].HopPayload.Payload, err = hex.DecodeString(hop.Payload)
62+
payload, err := hex.DecodeString(hop.Payload)
6463
if err != nil {
65-
log.Fatalf("%s is not a valid hex payload %s", hop.Payload, err)
64+
log.Fatalf("%s is not a valid hex payload %s",
65+
hop.Payload, err)
6666
}
6767

68+
hopPayload, err := sphinx.NewHopPayload(
69+
byte(hop.Realm), nil, payload,
70+
)
71+
if err != nil {
72+
log.Fatalf("unable to make payload: %v", err)
73+
}
74+
75+
path[i].HopPayload = hopPayload
76+
6877
fmt.Fprintf(os.Stderr, "Node %d pubkey %x\n", i, pubkey.SerializeCompressed())
6978
}
7079
return &path, sessionKey, nil

path.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,67 @@ func (hd *HopData) Decode(r io.Reader) error {
9999

100100
// HopPayload is a slice of bytes and associated payload-type that are destined
101101
// for a specific hop in the PaymentPath. The payload itself is treated as an
102-
// opaque datafield by the onion router, while the Realm is modified to
102+
// opaque data field by the onion router, while the Realm is modified to
103103
// indicate how many hops are to be read by the processing node. The 4 MSB in
104104
// the realm indicate how many additional hops are to be processed to collect
105105
// the entire payload.
106106
type HopPayload struct {
107-
Realm [1]byte
107+
// realm denotes the "real" of target chain of the next hop. For
108+
// bitcoin, this value will be 0x00.
109+
realm [RealmByteSize]byte
108110

111+
// Payload is the raw bytes of the per-hop payload for this hop.
112+
// Depending on the realm, this pay be the regular legacy hop data, or
113+
// a set of opaque blobs to be parsed by higher layers.
109114
Payload []byte
110115

116+
// HMAC is an HMAC computed over the entire per-hop payload that also
117+
// includes the higher-level (optional) associated data bytes.
111118
HMAC [HMACSize]byte
112119
}
113120

121+
// NewHopPayload creates a new hop payload given an optional set of forwarding
122+
// instructions for a hop, and a set of optional opaque extra onion bytes to
123+
// drop off at the target hop. If both values are not specified, then an error
124+
// is returned.
125+
func NewHopPayload(realm byte, hopData *HopData, eob []byte) (HopPayload, error) {
126+
127+
var (
128+
h HopPayload
129+
b bytes.Buffer
130+
)
131+
132+
// We can't proceed if neither the hop data or the EOB has been
133+
// specified by the caller.
134+
if hopData == nil && len(eob) == 0 {
135+
return h, fmt.Errorf("either hop data or eob must " +
136+
"be specified")
137+
}
138+
139+
// If the hop data is specified, then we'll write that now, as it
140+
// should proceed the EOB portion of the payload.
141+
if hopData != nil {
142+
if err := hopData.Encode(&b); err != nil {
143+
return h, nil
144+
}
145+
}
146+
147+
// Finally, we'll write out the EOB portion to the same buffer to
148+
// ensure it comes after mandatory hop payload.
149+
if _, err := b.Write(eob); err != nil {
150+
return h, nil
151+
}
152+
153+
h.realm = [RealmByteSize]byte{realm}
154+
h.Payload = b.Bytes()
155+
156+
return h, nil
157+
}
158+
159+
// Realm returns the context specific representation of the realm for a hop.
160+
func (hp *HopPayload) Realm() byte {
161+
return hp.realm[0] & RealmMaskBytes
162+
}
114163
// NumFrames returns the total number of frames it'll take to pack the target
115164
// HopPayload into a Sphinx packet.
116165
func (hp *HopPayload) NumFrames() int {

path_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@ func TestHopPayloadSizes(t *testing.T) {
2121
}
2222

2323
for _, tt := range tests {
24-
hp := HopPayload{
25-
Realm: [1]byte{1},
26-
Payload: bytes.Repeat([]byte{0x00}, tt.size),
24+
hp, err := NewHopPayload(
25+
1, nil, bytes.Repeat([]byte{0x00}, tt.size),
26+
)
27+
if err != nil {
28+
t.Fatalf("unable to make hop payload: %v", err)
2729
}
2830

2931
actual := hp.NumFrames()
3032
if actual != tt.expected {
31-
t.Errorf("Wrong number of hops returned: expected %d, actual %d", tt.expected, actual)
33+
t.Errorf("wrong number of hops returned: expected "+
34+
"%d, actual %d", tt.expected, actual)
3235
}
3336

3437
hp.CalculateRealm()

sphinx.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,6 @@ func NewOnionPacket(paymentPath *PaymentPath, sessionKey *btcec.PrivateKey,
268268
packet := append(mixHeader[:], assocData...)
269269
nextHmac = calcMac(muKey, packet)
270270

271-
hopDataBuf.Reset()
272271
hopPayloadBuf.Reset()
273272
}
274273

sphinx_test.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,23 +120,31 @@ func newTestVarSizeRoute(numHops int, extraPayloadSize []int) ([]*Router, *Payme
120120

121121
copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8))
122122

123+
var extraData []byte
124+
125+
// If we were told to increase the payload with some extra data
126+
// do it now.
127+
if extraPayloadSize[i] > 0 {
128+
extraData = bytes.Repeat([]byte{'A'}, extraPayloadSize[i])
129+
}
130+
131+
hopPayload, err := NewHopPayload(0, &hopData, extraData)
132+
if err != nil {
133+
return nil, nil, nil, nil, fmt.Errorf("unable to "+
134+
"create new hop payload: %v", err)
135+
}
136+
123137
route[i] = OnionHop{
124-
NodePub: *nodes[i].onionKey.PubKey(),
125-
HopData: hopData,
138+
NodePub: *nodes[i].onionKey.PubKey(),
139+
HopPayload: hopPayload,
126140
}
127141
}
128142

129-
// If we were told to increase the payload with some extra data do it
130-
// now:
131143
for i := 0; i < route.TrueRouteLength(); i++ {
132-
if extraPayloadSize[i] > 0 {
133-
extra := bytes.Repeat([]byte{'A'}, extraPayloadSize[i])
134-
route[i].HopPayload.Payload = append(route[i].HopPayload.Payload, extra...)
135-
}
136144
}
137145

138146
// Generate a forwarding message to route to the final node via the
139-
// generated intermdiates nodes above. Destination should be Hash160,
147+
// generated intermediate nodes above. Destination should be Hash160,
140148
// adding padding so parsing still works.
141149
sessionKey, _ := btcec.PrivKeyFromBytes(
142150
btcec.S256(), bytes.Repeat([]byte{'A'}, 32),
@@ -172,18 +180,22 @@ func TestBolt4Packet(t *testing.T) {
172180
}
173181

174182
hopData := HopData{
175-
Realm: [1]byte{0x00},
176183
ForwardAmount: uint64(i),
177184
OutgoingCltv: uint32(i),
178185
}
179186
copy(hopData.NextAddress[:], bytes.Repeat([]byte{byte(i)}, 8))
180187
hopsData = append(hopsData, hopData)
181188

189+
hopPayload, err := NewHopPayload(0, &hopData, nil)
190+
if err != nil {
191+
t.Fatalf("unable to make hop payload: %v", err)
192+
}
193+
182194
pubKey.Curve = nil
183195

184196
route[i] = OnionHop{
185-
NodePub: *pubKey,
186-
HopData: hopData,
197+
NodePub: *pubKey,
198+
HopPayload: hopPayload,
187199
}
188200
}
189201

@@ -548,7 +560,7 @@ func TestMultiFrameEncodeDecode(t *testing.T) {
548560
t.Logf("Processing at hop: %v \n", i)
549561
onionPacket, err := hop.ProcessOnionPacket(fwdMsg, nil, uint32(i)+1)
550562
if err != nil {
551-
t.Fatalf("Node %v was unable to process the "+
563+
t.Fatalf("node %v was unable to process the "+
552564
"forwarding message: %v", i, err)
553565
}
554566

0 commit comments

Comments
 (0)