Skip to content

Commit 24f2b24

Browse files
htlcswitch/hop: add unit test for DecodeHopIterator
1 parent 760b60e commit 24f2b24

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

htlcswitch/hop/iterator_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,170 @@ func TestSphinxHopIteratorForwardingInstructions(t *testing.T) {
103103
}
104104
}
105105

106+
// TestDecodeHopIterator tests that DecodeHopIterator can successfully process
107+
// a real onion packet constructed by the sphinx library and return a valid hop
108+
// iterator with the correct forwarding information. It also tests various error
109+
// cases such as truncated packets and corrupted HMACs.
110+
func TestDecodeHopIterator(t *testing.T) {
111+
t.Parallel()
112+
113+
// Generate a fresh private key for our onion processor (the
114+
// "receiving" node).
115+
receiverPrivKey, err := btcec.NewPrivateKey()
116+
require.NoError(t, err)
117+
118+
sphinxRouter := sphinx.NewRouter(
119+
&sphinx.PrivKeyECDH{PrivKey: receiverPrivKey},
120+
sphinx.NewNoOpReplayLog(),
121+
)
122+
require.NoError(t, sphinxRouter.Start())
123+
defer sphinxRouter.Stop()
124+
125+
processor := NewOnionProcessor(sphinxRouter)
126+
127+
// Session key used by the "sender" to construct the onion.
128+
sessionKey, err := btcec.NewPrivateKey()
129+
require.NoError(t, err)
130+
131+
// Build a TLV payload for the final hop with amount and CLTV.
132+
var (
133+
fwdAmt uint64 = 500_000
134+
outgoingCltv uint32 = 144
135+
incomingCltv uint32 = 200
136+
incomingAmt lnwire.MilliSatoshi = 600_000
137+
noBlinding lnwire.BlindingPointRecord
138+
)
139+
var payloadBuf bytes.Buffer
140+
tlvRecords := []tlv.Record{
141+
record.NewAmtToFwdRecord(&fwdAmt),
142+
record.NewLockTimeRecord(&outgoingCltv),
143+
}
144+
tlvStream, err := tlv.NewStream(tlvRecords...)
145+
require.NoError(t, err)
146+
require.NoError(t, tlvStream.Encode(&payloadBuf))
147+
148+
// Build a one-hop payment path to the receiver.
149+
var path sphinx.PaymentPath
150+
path[0] = sphinx.OnionHop{
151+
NodePub: *receiverPrivKey.PubKey(),
152+
HopPayload: sphinx.HopPayload{
153+
Type: sphinx.PayloadTLV,
154+
Payload: payloadBuf.Bytes(),
155+
},
156+
}
157+
158+
// Create the onion packet.
159+
rHash := [32]byte{0xaa, 0xbb, 0xcc}
160+
onionPkt, err := sphinx.NewOnionPacket(
161+
&path, sessionKey, rHash[:],
162+
sphinx.DeterministicPacketFiller,
163+
)
164+
require.NoError(t, err)
165+
166+
// serializeOnion is a helper that encodes an onion packet to bytes.
167+
serializeOnion := func(pkt *sphinx.OnionPacket) []byte {
168+
var buf bytes.Buffer
169+
require.NoError(t, pkt.Encode(&buf))
170+
return buf.Bytes()
171+
}
172+
173+
validOnionBytes := serializeOnion(onionPkt)
174+
175+
tests := []struct {
176+
name string
177+
onionBytes []byte
178+
rHash []byte
179+
expectedFail lnwire.FailCode
180+
checkPayload bool
181+
}{
182+
{
183+
name: "valid onion",
184+
onionBytes: validOnionBytes,
185+
rHash: rHash[:],
186+
expectedFail: lnwire.CodeNone,
187+
checkPayload: true,
188+
},
189+
{
190+
name: "truncated packet",
191+
onionBytes: validOnionBytes[:10],
192+
rHash: rHash[:],
193+
expectedFail: lnwire.CodeInvalidOnionKey,
194+
},
195+
{
196+
name: "empty reader",
197+
onionBytes: []byte{},
198+
rHash: rHash[:],
199+
expectedFail: lnwire.CodeInvalidOnionKey,
200+
},
201+
{
202+
name: "corrupted HMAC",
203+
onionBytes: func() []byte {
204+
corrupted := make([]byte, len(validOnionBytes))
205+
copy(corrupted, validOnionBytes)
206+
// Flip a byte in the HMAC (last 32 bytes of
207+
// the packet).
208+
corrupted[len(corrupted)-1] ^= 0xff
209+
210+
return corrupted
211+
}(),
212+
rHash: rHash[:],
213+
expectedFail: lnwire.CodeInvalidOnionHmac,
214+
},
215+
{
216+
name: "wrong payment hash",
217+
onionBytes: validOnionBytes,
218+
rHash: bytes.Repeat([]byte{0xff}, 32),
219+
expectedFail: lnwire.CodeInvalidOnionHmac,
220+
},
221+
{
222+
name: "invalid version byte",
223+
onionBytes: func() []byte {
224+
corrupted := make([]byte, len(validOnionBytes))
225+
copy(corrupted, validOnionBytes)
226+
// Set an invalid version (first byte).
227+
corrupted[0] = 0xff
228+
229+
return corrupted
230+
}(),
231+
rHash: rHash[:],
232+
expectedFail: lnwire.CodeInvalidOnionVersion,
233+
},
234+
}
235+
236+
for _, tc := range tests {
237+
t.Run(tc.name, func(t *testing.T) {
238+
t.Parallel()
239+
240+
reader := bytes.NewReader(tc.onionBytes)
241+
iterator, failCode := processor.DecodeHopIterator(
242+
reader, tc.rHash, incomingCltv, incomingAmt,
243+
noBlinding,
244+
)
245+
246+
require.Equal(t, tc.expectedFail, failCode)
247+
248+
if !tc.checkPayload {
249+
return
250+
}
251+
252+
require.NotNil(t, iterator)
253+
254+
payload, role, err := iterator.HopPayload()
255+
require.NoError(t, err)
256+
require.Equal(t, RouteRoleCleartext, role)
257+
258+
fwdInfo := payload.ForwardingInfo()
259+
require.Equal(
260+
t, lnwire.MilliSatoshi(fwdAmt),
261+
fwdInfo.AmountToForward,
262+
)
263+
require.Equal(
264+
t, outgoingCltv, fwdInfo.OutgoingCTLV,
265+
)
266+
})
267+
}
268+
}
269+
106270
// TestForwardingAmountCalc tests calculation of forwarding amounts from the
107271
// hop's forwarding parameters.
108272
func TestForwardingAmountCalc(t *testing.T) {

0 commit comments

Comments
 (0)