mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-08-29 23:21:12 +02:00
htlcswitch: move hop iterator into htlcswitch/hop package
Prepares for onion blob decoding outside of htlcswitch.
This commit is contained in:
205
htlcswitch/hop/error_encryptor.go
Normal file
205
htlcswitch/hop/error_encryptor.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package hop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// EncrypterType establishes an enum used in serialization to indicate how to
|
||||
// decode a concrete instance of the ErrorEncrypter interface.
|
||||
type EncrypterType byte
|
||||
|
||||
const (
|
||||
// EncrypterTypeNone signals that no error encyrpter is present, this
|
||||
// can happen if the htlc is originates in the switch.
|
||||
EncrypterTypeNone EncrypterType = 0
|
||||
|
||||
// EncrypterTypeSphinx is used to identify a sphinx onion error
|
||||
// encrypter instance.
|
||||
EncrypterTypeSphinx = 1
|
||||
|
||||
// EncrypterTypeMock is used to identify a mock obfuscator instance.
|
||||
EncrypterTypeMock = 2
|
||||
)
|
||||
|
||||
// ErrorEncrypterExtracter defines a function signature that extracts an
|
||||
// ErrorEncrypter from an sphinx OnionPacket.
|
||||
type ErrorEncrypterExtracter func(*btcec.PublicKey) (ErrorEncrypter,
|
||||
lnwire.FailCode)
|
||||
|
||||
// ErrorEncrypter is an interface that is used to encrypt HTLC related errors
|
||||
// at the source of the error, and also at each intermediate hop all the way
|
||||
// back to the source of the payment.
|
||||
type ErrorEncrypter interface {
|
||||
// EncryptFirstHop transforms a concrete failure message into an
|
||||
// encrypted opaque failure reason. This method will be used at the
|
||||
// source that the error occurs. It differs from IntermediateEncrypt
|
||||
// slightly, in that it computes a proper MAC over the error.
|
||||
EncryptFirstHop(lnwire.FailureMessage) (lnwire.OpaqueReason, error)
|
||||
|
||||
// EncryptMalformedError is similar to EncryptFirstHop (it adds the
|
||||
// MAC), but it accepts an opaque failure reason rather than a failure
|
||||
// message. This method is used when we receive an
|
||||
// UpdateFailMalformedHTLC from the remote peer and then need to
|
||||
// convert that into a proper error from only the raw bytes.
|
||||
EncryptMalformedError(lnwire.OpaqueReason) lnwire.OpaqueReason
|
||||
|
||||
// IntermediateEncrypt wraps an already encrypted opaque reason error
|
||||
// in an additional layer of onion encryption. This process repeats
|
||||
// until the error arrives at the source of the payment.
|
||||
IntermediateEncrypt(lnwire.OpaqueReason) lnwire.OpaqueReason
|
||||
|
||||
// Type returns an enum indicating the underlying concrete instance
|
||||
// backing this interface.
|
||||
Type() EncrypterType
|
||||
|
||||
// Encode serializes the encrypter's ephemeral public key to the given
|
||||
// io.Writer.
|
||||
Encode(io.Writer) error
|
||||
|
||||
// Decode deserializes the encrypter' ephemeral public key from the
|
||||
// given io.Reader.
|
||||
Decode(io.Reader) error
|
||||
|
||||
// Reextract rederives the encrypter using the extracter, performing an
|
||||
// ECDH with the sphinx router's key and the ephemeral public key.
|
||||
//
|
||||
// NOTE: This should be called shortly after Decode to properly
|
||||
// reinitialize the error encrypter.
|
||||
Reextract(ErrorEncrypterExtracter) error
|
||||
}
|
||||
|
||||
// SphinxErrorEncrypter is a concrete implementation of both the ErrorEncrypter
|
||||
// interface backed by an implementation of the Sphinx packet format. As a
|
||||
// result, all errors handled are themselves wrapped in layers of onion
|
||||
// encryption and must be treated as such accordingly.
|
||||
type SphinxErrorEncrypter struct {
|
||||
*sphinx.OnionErrorEncrypter
|
||||
|
||||
EphemeralKey *btcec.PublicKey
|
||||
}
|
||||
|
||||
// NewSphinxErrorEncrypter initializes a blank sphinx error encrypter, that
|
||||
// should be used to deserialize an encoded SphinxErrorEncrypter. Since the
|
||||
// actual encrypter is not stored in plaintext while at rest, reconstructing the
|
||||
// error encrypter requires:
|
||||
// 1) Decode: to deserialize the ephemeral public key.
|
||||
// 2) Reextract: to "unlock" the actual error encrypter using an active
|
||||
// OnionProcessor.
|
||||
func NewSphinxErrorEncrypter() *SphinxErrorEncrypter {
|
||||
return &SphinxErrorEncrypter{
|
||||
OnionErrorEncrypter: nil,
|
||||
EphemeralKey: &btcec.PublicKey{},
|
||||
}
|
||||
}
|
||||
|
||||
// EncryptFirstHop transforms a concrete failure message into an encrypted
|
||||
// opaque failure reason. This method will be used at the source that the error
|
||||
// occurs. It differs from BackwardObfuscate slightly, in that it computes a
|
||||
// proper MAC over the error.
|
||||
//
|
||||
// NOTE: Part of the ErrorEncrypter interface.
|
||||
func (s *SphinxErrorEncrypter) EncryptFirstHop(
|
||||
failure lnwire.FailureMessage) (lnwire.OpaqueReason, error) {
|
||||
|
||||
var b bytes.Buffer
|
||||
if err := lnwire.EncodeFailure(&b, failure, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We pass a true as the first parameter to indicate that a MAC should
|
||||
// be added.
|
||||
return s.EncryptError(true, b.Bytes()), nil
|
||||
}
|
||||
|
||||
// EncryptMalformedError is similar to EncryptFirstHop (it adds the MAC), but
|
||||
// it accepts an opaque failure reason rather than a failure message. This
|
||||
// method is used when we receive an UpdateFailMalformedHTLC from the remote
|
||||
// peer and then need to convert that into an proper error from only the raw
|
||||
// bytes.
|
||||
//
|
||||
// NOTE: Part of the ErrorEncrypter interface.
|
||||
func (s *SphinxErrorEncrypter) EncryptMalformedError(
|
||||
reason lnwire.OpaqueReason) lnwire.OpaqueReason {
|
||||
|
||||
return s.EncryptError(true, reason)
|
||||
}
|
||||
|
||||
// IntermediateEncrypt wraps an already encrypted opaque reason error in an
|
||||
// additional layer of onion encryption. This process repeats until the error
|
||||
// arrives at the source of the payment. We re-encrypt the message on the
|
||||
// backwards path to ensure that the error is indistinguishable from any other
|
||||
// error seen.
|
||||
//
|
||||
// NOTE: Part of the ErrorEncrypter interface.
|
||||
func (s *SphinxErrorEncrypter) IntermediateEncrypt(
|
||||
reason lnwire.OpaqueReason) lnwire.OpaqueReason {
|
||||
|
||||
return s.EncryptError(false, reason)
|
||||
}
|
||||
|
||||
// Type returns the identifier for a sphinx error encrypter.
|
||||
func (s *SphinxErrorEncrypter) Type() EncrypterType {
|
||||
return EncrypterTypeSphinx
|
||||
}
|
||||
|
||||
// Encode serializes the error encrypter' ephemeral public key to the provided
|
||||
// io.Writer.
|
||||
func (s *SphinxErrorEncrypter) Encode(w io.Writer) error {
|
||||
ephemeral := s.EphemeralKey.SerializeCompressed()
|
||||
_, err := w.Write(ephemeral)
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode reconstructs the error encrypter's ephemeral public key from the
|
||||
// provided io.Reader.
|
||||
func (s *SphinxErrorEncrypter) Decode(r io.Reader) error {
|
||||
var ephemeral [33]byte
|
||||
if _, err := io.ReadFull(r, ephemeral[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
s.EphemeralKey, err = btcec.ParsePubKey(ephemeral[:], btcec.S256())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reextract rederives the error encrypter from the currently held EphemeralKey.
|
||||
// This intended to be used shortly after Decode, to fully initialize a
|
||||
// SphinxErrorEncrypter.
|
||||
func (s *SphinxErrorEncrypter) Reextract(
|
||||
extract ErrorEncrypterExtracter) error {
|
||||
|
||||
obfuscator, failcode := extract(s.EphemeralKey)
|
||||
if failcode != lnwire.CodeNone {
|
||||
// This should never happen, since we already validated that
|
||||
// this obfuscator can be extracted when it was received in the
|
||||
// link.
|
||||
return fmt.Errorf("unable to reconstruct onion "+
|
||||
"obfuscator, got failcode: %d", failcode)
|
||||
}
|
||||
|
||||
sphinxEncrypter, ok := obfuscator.(*SphinxErrorEncrypter)
|
||||
if !ok {
|
||||
return fmt.Errorf("incorrect onion error extracter")
|
||||
}
|
||||
|
||||
// Copy the freshly extracted encrypter.
|
||||
s.OnionErrorEncrypter = sphinxEncrypter.OnionErrorEncrypter
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// A compile time check to ensure SphinxErrorEncrypter implements the
|
||||
// ErrorEncrypter interface.
|
||||
var _ ErrorEncrypter = (*SphinxErrorEncrypter)(nil)
|
386
htlcswitch/hop/iterator.go
Normal file
386
htlcswitch/hop/iterator.go
Normal file
@@ -0,0 +1,386 @@
|
||||
package hop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// Iterator is an interface that abstracts away the routing information
|
||||
// included in HTLC's which includes the entirety of the payment path of an
|
||||
// HTLC. This interface provides two basic method which carry out: how to
|
||||
// interpret the forwarding information encoded within the HTLC packet, and hop
|
||||
// to encode the forwarding information for the _next_ hop.
|
||||
type Iterator interface {
|
||||
// ForwardingInstructions returns the set of fields that detail exactly
|
||||
// _how_ this hop should forward the HTLC to the next hop.
|
||||
// Additionally, the information encoded within the returned
|
||||
// ForwardingInfo is to be used by each hop to authenticate the
|
||||
// information given to it by the prior hop.
|
||||
ForwardingInstructions() (ForwardingInfo, error)
|
||||
|
||||
// ExtraOnionBlob returns the additional EOB data (if available).
|
||||
ExtraOnionBlob() []byte
|
||||
|
||||
// EncodeNextHop encodes the onion packet destined for the next hop
|
||||
// into the passed io.Writer.
|
||||
EncodeNextHop(w io.Writer) error
|
||||
|
||||
// ExtractErrorEncrypter returns the ErrorEncrypter needed for this hop,
|
||||
// along with a failure code to signal if the decoding was successful.
|
||||
ExtractErrorEncrypter(ErrorEncrypterExtracter) (ErrorEncrypter,
|
||||
lnwire.FailCode)
|
||||
}
|
||||
|
||||
// sphinxHopIterator is the Sphinx implementation of hop iterator which uses
|
||||
// onion routing to encode the payment route in such a way so that node might
|
||||
// see only the next hop in the route..
|
||||
type sphinxHopIterator struct {
|
||||
// ogPacket is the original packet from which the processed packet is
|
||||
// derived.
|
||||
ogPacket *sphinx.OnionPacket
|
||||
|
||||
// processedPacket is the outcome of processing an onion packet. It
|
||||
// includes the information required to properly forward the packet to
|
||||
// the next hop.
|
||||
processedPacket *sphinx.ProcessedPacket
|
||||
}
|
||||
|
||||
// makeSphinxHopIterator converts a processed packet returned from a sphinx
|
||||
// router and converts it into an hop iterator for usage in the link.
|
||||
func makeSphinxHopIterator(ogPacket *sphinx.OnionPacket,
|
||||
packet *sphinx.ProcessedPacket) *sphinxHopIterator {
|
||||
|
||||
return &sphinxHopIterator{
|
||||
ogPacket: ogPacket,
|
||||
processedPacket: packet,
|
||||
}
|
||||
}
|
||||
|
||||
// A compile time check to ensure sphinxHopIterator implements the HopIterator
|
||||
// interface.
|
||||
var _ Iterator = (*sphinxHopIterator)(nil)
|
||||
|
||||
// Encode encodes iterator and writes it to the writer.
|
||||
//
|
||||
// NOTE: Part of the HopIterator interface.
|
||||
func (r *sphinxHopIterator) EncodeNextHop(w io.Writer) error {
|
||||
return r.processedPacket.NextPacket.Encode(w)
|
||||
}
|
||||
|
||||
// ForwardingInstructions returns the set of fields that detail exactly _how_
|
||||
// this hop should forward the HTLC to the next hop. Additionally, the
|
||||
// information encoded within the returned ForwardingInfo is to be used by each
|
||||
// hop to authenticate the information given to it by the prior hop.
|
||||
//
|
||||
// NOTE: Part of the HopIterator interface.
|
||||
func (r *sphinxHopIterator) ForwardingInstructions() (ForwardingInfo, error) {
|
||||
switch r.processedPacket.Payload.Type {
|
||||
// If this is the legacy payload, then we'll extract the information
|
||||
// directly from the pre-populated ForwardingInstructions field.
|
||||
case sphinx.PayloadLegacy:
|
||||
fwdInst := r.processedPacket.ForwardingInstructions
|
||||
p := NewLegacyPayload(fwdInst)
|
||||
|
||||
return p.ForwardingInfo(), nil
|
||||
|
||||
// Otherwise, if this is the TLV payload, then we'll make a new stream
|
||||
// to decode only what we need to make routing decisions.
|
||||
case sphinx.PayloadTLV:
|
||||
p, err := NewPayloadFromReader(bytes.NewReader(
|
||||
r.processedPacket.Payload.Payload,
|
||||
))
|
||||
if err != nil {
|
||||
return ForwardingInfo{}, err
|
||||
}
|
||||
|
||||
return p.ForwardingInfo(), nil
|
||||
|
||||
default:
|
||||
return ForwardingInfo{}, fmt.Errorf("unknown "+
|
||||
"sphinx payload type: %v",
|
||||
r.processedPacket.Payload.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtraOnionBlob returns the additional EOB data (if available).
|
||||
func (r *sphinxHopIterator) ExtraOnionBlob() []byte {
|
||||
if r.processedPacket.Payload.Type == sphinx.PayloadLegacy {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.processedPacket.Payload.Payload
|
||||
}
|
||||
|
||||
// ExtractErrorEncrypter decodes and returns the ErrorEncrypter for this hop,
|
||||
// along with a failure code to signal if the decoding was successful. The
|
||||
// ErrorEncrypter is used to encrypt errors back to the sender in the event that
|
||||
// a payment fails.
|
||||
//
|
||||
// NOTE: Part of the HopIterator interface.
|
||||
func (r *sphinxHopIterator) ExtractErrorEncrypter(
|
||||
extracter ErrorEncrypterExtracter) (ErrorEncrypter, lnwire.FailCode) {
|
||||
|
||||
return extracter(r.ogPacket.EphemeralKey)
|
||||
}
|
||||
|
||||
// OnionProcessor is responsible for keeping all sphinx dependent parts inside
|
||||
// and expose only decoding function. With such approach we give freedom for
|
||||
// subsystems which wants to decode sphinx path to not be dependable from
|
||||
// sphinx at all.
|
||||
//
|
||||
// NOTE: The reason for keeping decoder separated from hop iterator is too
|
||||
// maintain the hop iterator abstraction. Without it the structures which using
|
||||
// the hop iterator should contain sphinx router which makes their creations in
|
||||
// tests dependent from the sphinx internal parts.
|
||||
type OnionProcessor struct {
|
||||
router *sphinx.Router
|
||||
}
|
||||
|
||||
// NewOnionProcessor creates new instance of decoder.
|
||||
func NewOnionProcessor(router *sphinx.Router) *OnionProcessor {
|
||||
return &OnionProcessor{router}
|
||||
}
|
||||
|
||||
// Start spins up the onion processor's sphinx router.
|
||||
func (p *OnionProcessor) Start() error {
|
||||
return p.router.Start()
|
||||
}
|
||||
|
||||
// Stop shutsdown the onion processor's sphinx router.
|
||||
func (p *OnionProcessor) Stop() error {
|
||||
p.router.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeHopIterator attempts to decode a valid sphinx packet from the passed io.Reader
|
||||
// instance using the rHash as the associated data when checking the relevant
|
||||
// MACs during the decoding process.
|
||||
func (p *OnionProcessor) DecodeHopIterator(r io.Reader, rHash []byte,
|
||||
incomingCltv uint32) (Iterator, lnwire.FailCode) {
|
||||
|
||||
onionPkt := &sphinx.OnionPacket{}
|
||||
if err := onionPkt.Decode(r); err != nil {
|
||||
switch err {
|
||||
case sphinx.ErrInvalidOnionVersion:
|
||||
return nil, lnwire.CodeInvalidOnionVersion
|
||||
case sphinx.ErrInvalidOnionKey:
|
||||
return nil, lnwire.CodeInvalidOnionKey
|
||||
default:
|
||||
log.Errorf("unable to decode onion packet: %v", err)
|
||||
return nil, lnwire.CodeInvalidOnionKey
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to process the Sphinx packet. We include the payment hash of
|
||||
// the HTLC as it's authenticated within the Sphinx packet itself as
|
||||
// associated data in order to thwart attempts a replay attacks. In the
|
||||
// case of a replay, an attacker is *forced* to use the same payment
|
||||
// hash twice, thereby losing their money entirely.
|
||||
sphinxPacket, err := p.router.ProcessOnionPacket(
|
||||
onionPkt, rHash, incomingCltv,
|
||||
)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case sphinx.ErrInvalidOnionVersion:
|
||||
return nil, lnwire.CodeInvalidOnionVersion
|
||||
case sphinx.ErrInvalidOnionHMAC:
|
||||
return nil, lnwire.CodeInvalidOnionHmac
|
||||
case sphinx.ErrInvalidOnionKey:
|
||||
return nil, lnwire.CodeInvalidOnionKey
|
||||
default:
|
||||
log.Errorf("unable to process onion packet: %v", err)
|
||||
return nil, lnwire.CodeInvalidOnionKey
|
||||
}
|
||||
}
|
||||
|
||||
return makeSphinxHopIterator(onionPkt, sphinxPacket), lnwire.CodeNone
|
||||
}
|
||||
|
||||
// DecodeHopIteratorRequest encapsulates all date necessary to process an onion
|
||||
// packet, perform sphinx replay detection, and schedule the entry for garbage
|
||||
// collection.
|
||||
type DecodeHopIteratorRequest struct {
|
||||
OnionReader io.Reader
|
||||
RHash []byte
|
||||
IncomingCltv uint32
|
||||
}
|
||||
|
||||
// DecodeHopIteratorResponse encapsulates the outcome of a batched sphinx onion
|
||||
// processing.
|
||||
type DecodeHopIteratorResponse struct {
|
||||
HopIterator Iterator
|
||||
FailCode lnwire.FailCode
|
||||
}
|
||||
|
||||
// Result returns the (HopIterator, lnwire.FailCode) tuple, which should
|
||||
// correspond to the index of a particular DecodeHopIteratorRequest.
|
||||
//
|
||||
// NOTE: The HopIterator should be considered invalid if the fail code is
|
||||
// anything but lnwire.CodeNone.
|
||||
func (r *DecodeHopIteratorResponse) Result() (Iterator, lnwire.FailCode) {
|
||||
return r.HopIterator, r.FailCode
|
||||
}
|
||||
|
||||
// DecodeHopIterators performs batched decoding and validation of incoming
|
||||
// sphinx packets. For the same `id`, this method will return the same iterators
|
||||
// and failcodes upon subsequent invocations.
|
||||
//
|
||||
// NOTE: In order for the responses to be valid, the caller must guarantee that
|
||||
// the presented readers and rhashes *NEVER* deviate across invocations for the
|
||||
// same id.
|
||||
func (p *OnionProcessor) DecodeHopIterators(id []byte,
|
||||
reqs []DecodeHopIteratorRequest) ([]DecodeHopIteratorResponse, error) {
|
||||
|
||||
var (
|
||||
batchSize = len(reqs)
|
||||
onionPkts = make([]sphinx.OnionPacket, batchSize)
|
||||
resps = make([]DecodeHopIteratorResponse, batchSize)
|
||||
)
|
||||
|
||||
tx := p.router.BeginTxn(id, batchSize)
|
||||
|
||||
for i, req := range reqs {
|
||||
onionPkt := &onionPkts[i]
|
||||
resp := &resps[i]
|
||||
|
||||
err := onionPkt.Decode(req.OnionReader)
|
||||
switch err {
|
||||
case nil:
|
||||
// success
|
||||
|
||||
case sphinx.ErrInvalidOnionVersion:
|
||||
resp.FailCode = lnwire.CodeInvalidOnionVersion
|
||||
continue
|
||||
|
||||
case sphinx.ErrInvalidOnionKey:
|
||||
resp.FailCode = lnwire.CodeInvalidOnionKey
|
||||
continue
|
||||
|
||||
default:
|
||||
log.Errorf("unable to decode onion packet: %v", err)
|
||||
resp.FailCode = lnwire.CodeInvalidOnionKey
|
||||
continue
|
||||
}
|
||||
|
||||
err = tx.ProcessOnionPacket(
|
||||
uint16(i), onionPkt, req.RHash, req.IncomingCltv,
|
||||
)
|
||||
switch err {
|
||||
case nil:
|
||||
// success
|
||||
|
||||
case sphinx.ErrInvalidOnionVersion:
|
||||
resp.FailCode = lnwire.CodeInvalidOnionVersion
|
||||
continue
|
||||
|
||||
case sphinx.ErrInvalidOnionHMAC:
|
||||
resp.FailCode = lnwire.CodeInvalidOnionHmac
|
||||
continue
|
||||
|
||||
case sphinx.ErrInvalidOnionKey:
|
||||
resp.FailCode = lnwire.CodeInvalidOnionKey
|
||||
continue
|
||||
|
||||
default:
|
||||
log.Errorf("unable to process onion packet: %v", err)
|
||||
resp.FailCode = lnwire.CodeInvalidOnionKey
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// With that batch created, we will now attempt to write the shared
|
||||
// secrets to disk. This operation will returns the set of indices that
|
||||
// were detected as replays, and the computed sphinx packets for all
|
||||
// indices that did not fail the above loop. Only indices that are not
|
||||
// in the replay set should be considered valid, as they are
|
||||
// opportunistically computed.
|
||||
packets, replays, err := tx.Commit()
|
||||
if err != nil {
|
||||
log.Errorf("unable to process onion packet batch %x: %v",
|
||||
id, err)
|
||||
|
||||
// If we failed to commit the batch to the secret share log, we
|
||||
// will mark all not-yet-failed channels with a temporary
|
||||
// channel failure and exit since we cannot proceed.
|
||||
for i := range resps {
|
||||
resp := &resps[i]
|
||||
|
||||
// Skip any indexes that already failed onion decoding.
|
||||
if resp.FailCode != lnwire.CodeNone {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Errorf("unable to process onion packet %x-%v",
|
||||
id, i)
|
||||
resp.FailCode = lnwire.CodeTemporaryChannelFailure
|
||||
}
|
||||
|
||||
// TODO(conner): return real errors to caller so link can fail?
|
||||
return resps, err
|
||||
}
|
||||
|
||||
// Otherwise, the commit was successful. Now we will post process any
|
||||
// remaining packets, additionally failing any that were included in the
|
||||
// replay set.
|
||||
for i := range resps {
|
||||
resp := &resps[i]
|
||||
|
||||
// Skip any indexes that already failed onion decoding.
|
||||
if resp.FailCode != lnwire.CodeNone {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this index is contained in the replay set, mark it with a
|
||||
// temporary channel failure error code. We infer that the
|
||||
// offending error was due to a replayed packet because this
|
||||
// index was found in the replay set.
|
||||
if replays.Contains(uint16(i)) {
|
||||
log.Errorf("unable to process onion packet: %v",
|
||||
sphinx.ErrReplayedPacket)
|
||||
resp.FailCode = lnwire.CodeTemporaryChannelFailure
|
||||
continue
|
||||
}
|
||||
|
||||
// Finally, construct a hop iterator from our processed sphinx
|
||||
// packet, simultaneously caching the original onion packet.
|
||||
resp.HopIterator = makeSphinxHopIterator(&onionPkts[i], &packets[i])
|
||||
}
|
||||
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
// ExtractErrorEncrypter takes an io.Reader which should contain the onion
|
||||
// packet as original received by a forwarding node and creates an
|
||||
// ErrorEncrypter instance using the derived shared secret. In the case that en
|
||||
// error occurs, a lnwire failure code detailing the parsing failure will be
|
||||
// returned.
|
||||
func (p *OnionProcessor) ExtractErrorEncrypter(ephemeralKey *btcec.PublicKey) (
|
||||
ErrorEncrypter, lnwire.FailCode) {
|
||||
|
||||
onionObfuscator, err := sphinx.NewOnionErrorEncrypter(
|
||||
p.router, ephemeralKey,
|
||||
)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case sphinx.ErrInvalidOnionVersion:
|
||||
return nil, lnwire.CodeInvalidOnionVersion
|
||||
case sphinx.ErrInvalidOnionHMAC:
|
||||
return nil, lnwire.CodeInvalidOnionHmac
|
||||
case sphinx.ErrInvalidOnionKey:
|
||||
return nil, lnwire.CodeInvalidOnionKey
|
||||
default:
|
||||
log.Errorf("unable to process onion packet: %v", err)
|
||||
return nil, lnwire.CodeInvalidOnionKey
|
||||
}
|
||||
}
|
||||
|
||||
return &SphinxErrorEncrypter{
|
||||
OnionErrorEncrypter: onionObfuscator,
|
||||
EphemeralKey: ephemeralKey,
|
||||
}, lnwire.CodeNone
|
||||
}
|
100
htlcswitch/hop/iterator_test.go
Normal file
100
htlcswitch/hop/iterator_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package hop
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
sphinx "github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
// TestSphinxHopIteratorForwardingInstructions tests that we're able to
|
||||
// properly decode an onion payload, no matter the payload type, into the
|
||||
// original set of forwarding instructions.
|
||||
func TestSphinxHopIteratorForwardingInstructions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// First, we'll make the hop data that the sender would create to send
|
||||
// an HTLC through our imaginary route.
|
||||
hopData := sphinx.HopData{
|
||||
ForwardAmount: 100000,
|
||||
OutgoingCltv: 4343,
|
||||
}
|
||||
copy(hopData.NextAddress[:], bytes.Repeat([]byte("a"), 8))
|
||||
|
||||
// Next, we'll make the hop forwarding information that we should
|
||||
// extract each type, no matter the payload type.
|
||||
nextAddrInt := binary.BigEndian.Uint64(hopData.NextAddress[:])
|
||||
expectedFwdInfo := ForwardingInfo{
|
||||
NextHop: lnwire.NewShortChanIDFromInt(nextAddrInt),
|
||||
AmountToForward: lnwire.MilliSatoshi(hopData.ForwardAmount),
|
||||
OutgoingCTLV: hopData.OutgoingCltv,
|
||||
}
|
||||
|
||||
// For our TLV payload, we'll serialize the hop into into a TLV stream
|
||||
// as we would normally in the routing network.
|
||||
var b bytes.Buffer
|
||||
tlvRecords := []tlv.Record{
|
||||
record.NewAmtToFwdRecord(&hopData.ForwardAmount),
|
||||
record.NewLockTimeRecord(&hopData.OutgoingCltv),
|
||||
record.NewNextHopIDRecord(&nextAddrInt),
|
||||
}
|
||||
tlvStream, err := tlv.NewStream(tlvRecords...)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create stream: %v", err)
|
||||
}
|
||||
if err := tlvStream.Encode(&b); err != nil {
|
||||
t.Fatalf("unable to encode stream: %v", err)
|
||||
}
|
||||
|
||||
var testCases = []struct {
|
||||
sphinxPacket *sphinx.ProcessedPacket
|
||||
expectedFwdInfo ForwardingInfo
|
||||
}{
|
||||
// A regular legacy payload that signals more hops.
|
||||
{
|
||||
sphinxPacket: &sphinx.ProcessedPacket{
|
||||
Payload: sphinx.HopPayload{
|
||||
Type: sphinx.PayloadLegacy,
|
||||
},
|
||||
Action: sphinx.MoreHops,
|
||||
ForwardingInstructions: &hopData,
|
||||
},
|
||||
expectedFwdInfo: expectedFwdInfo,
|
||||
},
|
||||
// A TLV payload, we can leave off the action as we'll always
|
||||
// read the cid encoded.
|
||||
{
|
||||
sphinxPacket: &sphinx.ProcessedPacket{
|
||||
Payload: sphinx.HopPayload{
|
||||
Type: sphinx.PayloadTLV,
|
||||
Payload: b.Bytes(),
|
||||
},
|
||||
},
|
||||
expectedFwdInfo: expectedFwdInfo,
|
||||
},
|
||||
}
|
||||
|
||||
// Finally, we'll test that we get the same set of
|
||||
// ForwardingInstructions for each payload type.
|
||||
iterator := sphinxHopIterator{}
|
||||
for i, testCase := range testCases {
|
||||
iterator.processedPacket = testCase.sphinxPacket
|
||||
|
||||
fwdInfo, err := iterator.ForwardingInstructions()
|
||||
if err != nil {
|
||||
t.Fatalf("#%v: unable to extract forwarding "+
|
||||
"instructions: %v", i, err)
|
||||
}
|
||||
|
||||
if fwdInfo != testCase.expectedFwdInfo {
|
||||
t.Fatalf("#%v: wrong fwding info: expected %v, got %v",
|
||||
i, spew.Sdump(testCase.expectedFwdInfo),
|
||||
spew.Sdump(fwdInfo))
|
||||
}
|
||||
}
|
||||
}
|
16
htlcswitch/hop/log.go
Normal file
16
htlcswitch/hop/log.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package hop
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized with no output filters. This
|
||||
// means the package will not perform any logging by default until the caller
|
||||
// requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info. This
|
||||
// function is called from the parent package htlcswitch logger initialization.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
Reference in New Issue
Block a user