243 lines
8.3 KiB
Go
243 lines
8.3 KiB
Go
package htlcswitch
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
|
|
"github.com/lightningnetwork/lightning-onion"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
)
|
|
|
|
// NetworkHop indicates the blockchain network that is intended to be the next
|
|
// hop for a forwarded HTLC. The existence of this field within the
|
|
// ForwardingInfo struct enables the ability for HTLC to cross chain-boundaries
|
|
// at will.
|
|
type NetworkHop uint8
|
|
|
|
const (
|
|
// BitcoinHop denotes that an HTLC is to be forwarded along the Bitcoin
|
|
// link with the specified short channel ID.
|
|
BitcoinHop NetworkHop = iota
|
|
|
|
// LitecoinHop denotes that an HTLC is to be forwarded along the
|
|
// Litecoin link with the specified short channel ID.
|
|
LitecoinHop
|
|
)
|
|
|
|
// String returns the string representation of the target NetworkHop.
|
|
func (c NetworkHop) String() string {
|
|
switch c {
|
|
case BitcoinHop:
|
|
return "Bitcoin"
|
|
case LitecoinHop:
|
|
return "Litecoin"
|
|
default:
|
|
return "Kekcoin"
|
|
}
|
|
}
|
|
|
|
var (
|
|
// exitHop is a special "hop" which denotes that an incoming HTLC is
|
|
// meant to pay finally to the receiving node.
|
|
exitHop lnwire.ShortChannelID
|
|
)
|
|
|
|
// ForwardingInfo contains all the information that is necessary to forward and
|
|
// incoming HTLC to the next hop encoded within a valid HopIterator instance.
|
|
// Forwarding links are to use this information to authenticate the information
|
|
// received within the incoming HTLC, to ensure that the prior hop didn't
|
|
// tamper with the end-to-end routing information at all.
|
|
type ForwardingInfo struct {
|
|
// Network is the target blockchain network that the HTLC will travel
|
|
// over next.
|
|
Network NetworkHop
|
|
|
|
// NextHop is the channel ID of the next hop. The received HTLC should
|
|
// be forwarded to this particular channel in order to continue the
|
|
// end-to-end route.
|
|
NextHop lnwire.ShortChannelID
|
|
|
|
// AmountToForward is the amount of milli-satoshis that the receiving
|
|
// node should forward to the next hop.
|
|
AmountToForward lnwire.MilliSatoshi
|
|
|
|
// OutgoingCTLV is the specified value of the CTLV timelock to be used
|
|
// in the outgoing HTLC.
|
|
OutgoingCTLV uint32
|
|
|
|
// TODO(roasbeef): modify sphinx logic to not just discard the
|
|
// remaining bytes, instead should include the rest as excess
|
|
}
|
|
|
|
// HopIterator 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 HopIterator 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
|
|
|
|
// EncodeNextHop encodes the onion packet destined for the next hop
|
|
// into the passed io.Writer.
|
|
EncodeNextHop(w io.Writer) error
|
|
}
|
|
|
|
// 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 {
|
|
// nextPacket is the decoded onion packet for the _next_ hop.
|
|
nextPacket *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
|
|
}
|
|
|
|
// A compile time check to ensure sphinxHopIterator implements the HopIterator
|
|
// interface.
|
|
var _ HopIterator = (*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.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 {
|
|
fwdInst := r.processedPacket.ForwardingInstructions
|
|
|
|
var nextHop lnwire.ShortChannelID
|
|
switch r.processedPacket.Action {
|
|
case sphinx.ExitNode:
|
|
nextHop = exitHop
|
|
case sphinx.MoreHops:
|
|
s := binary.BigEndian.Uint64(fwdInst.NextAddress[:])
|
|
nextHop = lnwire.NewShortChanIDFromInt(s)
|
|
}
|
|
|
|
return ForwardingInfo{
|
|
Network: BitcoinHop,
|
|
NextHop: nextHop,
|
|
AmountToForward: lnwire.MilliSatoshi(fwdInst.ForwardAmount),
|
|
OutgoingCTLV: fwdInst.OutgoingCltv,
|
|
}
|
|
}
|
|
|
|
// 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}
|
|
}
|
|
|
|
// 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) (HopIterator,
|
|
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)
|
|
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 &sphinxHopIterator{
|
|
nextPacket: sphinxPacket.NextPacket,
|
|
processedPacket: sphinxPacket,
|
|
}, lnwire.CodeNone
|
|
}
|
|
|
|
// 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(r io.Reader) (ErrorEncrypter, 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
|
|
}
|
|
}
|
|
|
|
onionObfuscator, err := sphinx.NewOnionErrorEncrypter(p.router,
|
|
onionPkt.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,
|
|
}, lnwire.CodeNone
|
|
}
|