@@ -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.
108272func TestForwardingAmountCalc (t * testing.T ) {
0 commit comments