Release v0.3.0

This commit is contained in:
Manu Herrera
2020-11-09 10:05:29 -03:00
parent 4e9aa7a3c5
commit 8107c4478b
1265 changed files with 440488 additions and 107809 deletions

View 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)

View File

@@ -0,0 +1,29 @@
package hop
import (
"github.com/lightningnetwork/lnd/lnwire"
)
// 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 Network
// 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
}

View File

@@ -0,0 +1,393 @@
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 {
// HopPayload 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. The payload will also contain any additional TLV fields provided
// by the sender.
HopPayload() (*Payload, error)
// 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)
}
// HopPayload 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. The payload will
// also contain any additional TLV fields provided by the sender.
//
// NOTE: Part of the HopIterator interface.
func (r *sphinxHopIterator) HopPayload() (*Payload, 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
return NewLegacyPayload(fwdInst), 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:
return NewPayloadFromReader(bytes.NewReader(
r.processedPacket.Payload.Payload,
))
default:
return nil, fmt.Errorf("unknown sphinx payload type: %v",
r.processedPacket.Payload.Type)
}
}
// 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
}
// ReconstructHopIterator 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) ReconstructHopIterator(r io.Reader, rHash []byte) (
Iterator, error) {
onionPkt := &sphinx.OnionPacket{}
if err := onionPkt.Decode(r); err != nil {
return nil, err
}
// 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.ReconstructOnionPacket(onionPkt, rHash)
if err != nil {
return nil, err
}
return makeSphinxHopIterator(onionPkt, sphinxPacket), nil
}
// 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
}

View 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
}

View File

@@ -0,0 +1,28 @@
package hop
// Network 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 Network uint8
const (
// BitcoinNetwork denotes that an HTLC is to be forwarded along the
// Bitcoin link with the specified short channel ID.
BitcoinNetwork Network = iota
// LitecoinNetwork denotes that an HTLC is to be forwarded along the
// Litecoin link with the specified short channel ID.
LitecoinNetwork
)
// String returns the string representation of the target Network.
func (c Network) String() string {
switch c {
case BitcoinNetwork:
return "Bitcoin"
case LitecoinNetwork:
return "Litecoin"
default:
return "Kekcoin"
}
}

View File

@@ -0,0 +1,291 @@
package hop
import (
"encoding/binary"
"fmt"
"io"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
// PayloadViolation is an enum encapsulating the possible invalid payload
// violations that can occur when processing or validating a payload.
type PayloadViolation byte
const (
// OmittedViolation indicates that a type was expected to be found the
// payload but was absent.
OmittedViolation PayloadViolation = iota
// IncludedViolation indicates that a type was expected to be omitted
// from the payload but was present.
IncludedViolation
// RequiredViolation indicates that an unknown even type was found in
// the payload that we could not process.
RequiredViolation
)
// String returns a human-readable description of the violation as a verb.
func (v PayloadViolation) String() string {
switch v {
case OmittedViolation:
return "omitted"
case IncludedViolation:
return "included"
case RequiredViolation:
return "required"
default:
return "unknown violation"
}
}
// ErrInvalidPayload is an error returned when a parsed onion payload either
// included or omitted incorrect records for a particular hop type.
type ErrInvalidPayload struct {
// Type the record's type that cause the violation.
Type tlv.Type
// Violation is an enum indicating the type of violation detected in
// processing Type.
Violation PayloadViolation
// FinalHop if true, indicates that the violation is for the final hop
// in the route (identified by next hop id), otherwise the violation is
// for an intermediate hop.
FinalHop bool
}
// Error returns a human-readable description of the invalid payload error.
func (e ErrInvalidPayload) Error() string {
hopType := "intermediate"
if e.FinalHop {
hopType = "final"
}
return fmt.Sprintf("onion payload for %s hop %v record with type %d",
hopType, e.Violation, e.Type)
}
// Payload encapsulates all information delivered to a hop in an onion payload.
// A Hop can represent either a TLV or legacy payload. The primary forwarding
// instruction can be accessed via ForwardingInfo, and additional records can be
// accessed by other member functions.
type Payload struct {
// FwdInfo holds the basic parameters required for HTLC forwarding, e.g.
// amount, cltv, and next hop.
FwdInfo ForwardingInfo
// MPP holds the info provided in an option_mpp record when parsed from
// a TLV onion payload.
MPP *record.MPP
// customRecords are user-defined records in the custom type range that
// were included in the payload.
customRecords record.CustomSet
}
// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
// parameters provided by leegacy onion payloads.
func NewLegacyPayload(f *sphinx.HopData) *Payload {
nextHop := binary.BigEndian.Uint64(f.NextAddress[:])
return &Payload{
FwdInfo: ForwardingInfo{
Network: BitcoinNetwork,
NextHop: lnwire.NewShortChanIDFromInt(nextHop),
AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount),
OutgoingCTLV: f.OutgoingCltv,
},
customRecords: make(record.CustomSet),
}
}
// NewPayloadFromReader builds a new Hop from the passed io.Reader. The reader
// should correspond to the bytes encapsulated in a TLV onion payload.
func NewPayloadFromReader(r io.Reader) (*Payload, error) {
var (
cid uint64
amt uint64
cltv uint32
mpp = &record.MPP{}
)
tlvStream, err := tlv.NewStream(
record.NewAmtToFwdRecord(&amt),
record.NewLockTimeRecord(&cltv),
record.NewNextHopIDRecord(&cid),
mpp.Record(),
)
if err != nil {
return nil, err
}
parsedTypes, err := tlvStream.DecodeWithParsedTypes(r)
if err != nil {
return nil, err
}
// Validate whether the sender properly included or omitted tlv records
// in accordance with BOLT 04.
nextHop := lnwire.NewShortChanIDFromInt(cid)
err = ValidateParsedPayloadTypes(parsedTypes, nextHop)
if err != nil {
return nil, err
}
// Check for violation of the rules for mandatory fields.
violatingType := getMinRequiredViolation(parsedTypes)
if violatingType != nil {
return nil, ErrInvalidPayload{
Type: *violatingType,
Violation: RequiredViolation,
FinalHop: nextHop == Exit,
}
}
// If no MPP field was parsed, set the MPP field on the resulting
// payload to nil.
if _, ok := parsedTypes[record.MPPOnionType]; !ok {
mpp = nil
}
// Filter out the custom records.
customRecords := NewCustomRecords(parsedTypes)
return &Payload{
FwdInfo: ForwardingInfo{
Network: BitcoinNetwork,
NextHop: nextHop,
AmountToForward: lnwire.MilliSatoshi(amt),
OutgoingCTLV: cltv,
},
MPP: mpp,
customRecords: customRecords,
}, nil
}
// ForwardingInfo returns the basic parameters required for HTLC forwarding,
// e.g. amount, cltv, and next hop.
func (h *Payload) ForwardingInfo() ForwardingInfo {
return h.FwdInfo
}
// NewCustomRecords filters the types parsed from the tlv stream for custom
// records.
func NewCustomRecords(parsedTypes tlv.TypeMap) record.CustomSet {
customRecords := make(record.CustomSet)
for t, parseResult := range parsedTypes {
if parseResult == nil || t < record.CustomTypeStart {
continue
}
customRecords[uint64(t)] = parseResult
}
return customRecords
}
// ValidateParsedPayloadTypes checks the types parsed from a hop payload to
// ensure that the proper fields are either included or omitted. The finalHop
// boolean should be true if the payload was parsed for an exit hop. The
// requirements for this method are described in BOLT 04.
func ValidateParsedPayloadTypes(parsedTypes tlv.TypeMap,
nextHop lnwire.ShortChannelID) error {
isFinalHop := nextHop == Exit
_, hasAmt := parsedTypes[record.AmtOnionType]
_, hasLockTime := parsedTypes[record.LockTimeOnionType]
_, hasNextHop := parsedTypes[record.NextHopOnionType]
_, hasMPP := parsedTypes[record.MPPOnionType]
switch {
// All hops must include an amount to forward.
case !hasAmt:
return ErrInvalidPayload{
Type: record.AmtOnionType,
Violation: OmittedViolation,
FinalHop: isFinalHop,
}
// All hops must include a cltv expiry.
case !hasLockTime:
return ErrInvalidPayload{
Type: record.LockTimeOnionType,
Violation: OmittedViolation,
FinalHop: isFinalHop,
}
// The exit hop should omit the next hop id. If nextHop != Exit, the
// sender must have included a record, so we don't need to test for its
// inclusion at intermediate hops directly.
case isFinalHop && hasNextHop:
return ErrInvalidPayload{
Type: record.NextHopOnionType,
Violation: IncludedViolation,
FinalHop: true,
}
// Intermediate nodes should never receive MPP fields.
case !isFinalHop && hasMPP:
return ErrInvalidPayload{
Type: record.MPPOnionType,
Violation: IncludedViolation,
FinalHop: isFinalHop,
}
}
return nil
}
// MultiPath returns the record corresponding the option_mpp parsed from the
// onion payload.
func (h *Payload) MultiPath() *record.MPP {
return h.MPP
}
// CustomRecords returns the custom tlv type records that were parsed from the
// payload.
func (h *Payload) CustomRecords() record.CustomSet {
return h.customRecords
}
// getMinRequiredViolation checks for unrecognized required (even) fields in the
// standard range and returns the lowest required type. Always returning the
// lowest required type allows a failure message to be deterministic.
func getMinRequiredViolation(set tlv.TypeMap) *tlv.Type {
var (
requiredViolation bool
minRequiredViolationType tlv.Type
)
for t, parseResult := range set {
// If a type is even but not known to us, we cannot process the
// payload. We are required to understand a field that we don't
// support.
//
// We always accept custom fields, because a higher level
// application may understand them.
if parseResult == nil || t%2 != 0 ||
t >= record.CustomTypeStart {
continue
}
if !requiredViolation || t < minRequiredViolationType {
minRequiredViolationType = t
}
requiredViolation = true
}
if requiredViolation {
return &minRequiredViolationType
}
return nil
}

View File

@@ -0,0 +1,13 @@
package hop
import "github.com/lightningnetwork/lnd/lnwire"
var (
// Exit is a special "hop" denoting that an incoming HTLC is meant to
// pay finally to the receiving node.
Exit lnwire.ShortChannelID
// Source is a sentinel "hop" denoting that an incoming HTLC is
// initiated by our own switch.
Source lnwire.ShortChannelID
)