payments/migration1: freeze lnwire and record dependency
What changed, and why it matters
This commit is a defensive code-hygiene change. It copies a small, frozen snapshot of two shared code packages (lnwire and record) into an old database migration folder so that future updates to the live versions of those packages cannot accidentally change how past payment data is decoded. There is no direct vulnerability being fixed; instead, the change prevents a class of future migration-correctness bugs.
Treat this as a hardening patch. Reviewers should verify that the copied files are byte-for-byte or subtractively identical to the originals at the commit point, especially the three trimmed files (message.go, writer.go, lnwire.go), and confirm that no live lnwire/record imports remain in migration1. No urgent deployment is required, but it should ride along with normal releases.
Security signals we found
Defensive dependency freezing for a database migration
Copied wire/record serialization code now lives under the migration package
No functional wire-format changes claimed; three files are explicitly subtractive trims
Linter exclusions added for the vendored copies
Evidence from the diff
The patch vendors a minimal subset of lnwire (16 files, three trimmed) and all record files (6) into payments/db/migration1/lnwire and payments/db/migration1/record. Existing migration1 files are updated to import these frozen copies instead of github.com/lightningnetwork/lnd/lnwire and github.com/lightningnetwork/lnd/record. .golangci.yml is updated to skip linting the copied code. The stated goal is to remove the live dependency so that future wire-format or record-format changes cannot affect the correctness of this historical migration.
Changed components
payments/db/migration1payments/db/migration1/lnwirepayments/db/migration1/record.golangci.ymlInspect captured patch +6433 / −16
diff --git a/.golangci.yml b/.golangci.yml
index dc82da8..4cb8939 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -302,7 +302,9 @@ linters:
- "internal\\/musig2v040"
- channeldb/migration_01_to_11
- channeldb/migration/lnwire21
-
+ - payments/db/migration1/lnwire
+ - payments/db/migration1/record
+
issues:
# Only show newly introduced problems.
new-from-rev: 03eab4db64540aa5f789c617793e4459f4ba9e78
diff --git a/payments/db/migration1/codec.go b/payments/db/migration1/codec.go
index acc173b..64225d1 100644
--- a/payments/db/migration1/codec.go
+++ b/payments/db/migration1/codec.go
@@ -7,7 +7,7 @@ import (
"time"
"github.com/btcsuite/btcd/wire"
- "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
)
// Big endian is the preferred byte order, due to cursor scans over
diff --git a/payments/db/migration1/kv_duplicate_payments.go b/payments/db/migration1/kv_duplicate_payments.go
index f72848d..1454430 100644
--- a/payments/db/migration1/kv_duplicate_payments.go
+++ b/payments/db/migration1/kv_duplicate_payments.go
@@ -10,7 +10,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
)
var (
diff --git a/payments/db/migration1/kv_store.go b/payments/db/migration1/kv_store.go
index 51bf005..eda1945 100644
--- a/payments/db/migration1/kv_store.go
+++ b/payments/db/migration1/kv_store.go
@@ -17,8 +17,8 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
- "github.com/lightningnetwork/lnd/record"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/record"
"github.com/lightningnetwork/lnd/tlv"
)
diff --git a/payments/db/migration1/lnwire/channel_id.go b/payments/db/migration1/lnwire/channel_id.go
new file mode 100644
index 0000000..5c9eca3
--- /dev/null
+++ b/payments/db/migration1/lnwire/channel_id.go
@@ -0,0 +1,127 @@
+package lnwire
+
+import (
+ "encoding/binary"
+ "encoding/hex"
+ "io"
+ "math"
+
+ "github.com/btcsuite/btcd/chaincfg/chainhash"
+ "github.com/btcsuite/btcd/wire"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+const (
+ // MaxFundingTxOutputs is the maximum number of allowed outputs on a
+ // funding transaction within the protocol. This is due to the fact
+ // that we use 2-bytes to encode the index within the funding output
+ // during the funding workflow. Funding transaction with more outputs
+ // than this are considered invalid within the protocol.
+ MaxFundingTxOutputs = math.MaxUint16
+)
+
+// ChannelID is a series of 32-bytes that uniquely identifies all channels
+// within the network. The ChannelID is computed using the outpoint of the
+// funding transaction (the txid, and output index). Given a funding output the
+// ChannelID can be calculated by XOR'ing the big-endian serialization of the
+// txid and the big-endian serialization of the output index, truncated to
+// 2 bytes.
+type ChannelID [32]byte
+
+// ConnectionWideID is an all-zero ChannelID, which is used to represent a
+// message intended for all channels to specific peer.
+var ConnectionWideID = ChannelID{}
+
+// String returns the string representation of the ChannelID. This is just the
+// hex string encoding of the ChannelID itself.
+func (c ChannelID) String() string {
+ return hex.EncodeToString(c[:])
+}
+
+// Record returns a TLV record that can be used to encode/decode a ChannelID
+// to/from a TLV stream.
+func (c *ChannelID) Record() tlv.Record {
+ return tlv.MakeStaticRecord(0, c, 32, encodeChannelID, decodeChannelID)
+}
+
+func encodeChannelID(w io.Writer, val interface{}, buf *[8]byte) error {
+ if v, ok := val.(*ChannelID); ok {
+ bigSize := [32]byte(*v)
+
+ return tlv.EBytes32(w, &bigSize, buf)
+ }
+
+ return tlv.NewTypeForEncodingErr(val, "lnwire.ChannelID")
+}
+
+func decodeChannelID(r io.Reader, val interface{}, buf *[8]byte,
+ l uint64) error {
+
+ if v, ok := val.(*ChannelID); ok {
+ var id [32]byte
+ err := tlv.DBytes32(r, &id, buf, l)
+ if err != nil {
+ return err
+ }
+
+ *v = id
+
+ return nil
+ }
+
+ return tlv.NewTypeForDecodingErr(val, "lnwire.ChannelID", l, l)
+}
+
+// NewChanIDFromOutPoint converts a target OutPoint into a ChannelID that is
+// usable within the network. In order to convert the OutPoint into a ChannelID,
+// we XOR the lower 2-bytes of the txid within the OutPoint with the big-endian
+// serialization of the Index of the OutPoint, truncated to 2-bytes.
+func NewChanIDFromOutPoint(op wire.OutPoint) ChannelID {
+ // First we'll copy the txid of the outpoint into our channel ID slice.
+ var cid ChannelID
+ copy(cid[:], op.Hash[:])
+
+ // With the txid copied over, we'll now XOR the lower 2-bytes of the
+ // partial channelID with big-endian serialization of output index.
+ xorTxid(&cid, uint16(op.Index))
+
+ return cid
+}
+
+// xorTxid performs the transformation needed to transform an OutPoint into a
+// ChannelID. To do this, we expect the cid parameter to contain the txid
+// unaltered and the outputIndex to be the output index
+func xorTxid(cid *ChannelID, outputIndex uint16) {
+ var buf [2]byte
+ binary.BigEndian.PutUint16(buf[:], outputIndex)
+
+ cid[30] ^= buf[0]
+ cid[31] ^= buf[1]
+}
+
+// GenPossibleOutPoints generates all the possible outputs given a channel ID.
+// In order to generate these possible outpoints, we perform a brute-force
+// search through the candidate output index space, performing a reverse
+// mapping from channelID back to OutPoint.
+func (c *ChannelID) GenPossibleOutPoints() [MaxFundingTxOutputs]wire.OutPoint {
+ var possiblePoints [MaxFundingTxOutputs]wire.OutPoint
+ for i := uint16(0); i < MaxFundingTxOutputs; i++ {
+ cidCopy := *c
+ xorTxid(&cidCopy, i)
+
+ possiblePoints[i] = wire.OutPoint{
+ Hash: chainhash.Hash(cidCopy),
+ Index: uint32(i),
+ }
+ }
+
+ return possiblePoints
+}
+
+// IsChanPoint returns true if the OutPoint passed corresponds to the target
+// ChannelID.
+func (c ChannelID) IsChanPoint(op *wire.OutPoint) bool {
+ candidateCid := NewChanIDFromOutPoint(*op)
+
+ return candidateCid == c
+}
diff --git a/payments/db/migration1/lnwire/channel_update.go b/payments/db/migration1/lnwire/channel_update.go
new file mode 100644
index 0000000..09b8044
--- /dev/null
+++ b/payments/db/migration1/lnwire/channel_update.go
@@ -0,0 +1,422 @@
+package lnwire
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "github.com/btcsuite/btcd/chaincfg/chainhash"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+// ChanUpdateMsgFlags is a bitfield that signals whether optional fields are
+// present in the ChannelUpdate.
+type ChanUpdateMsgFlags uint8
+
+const (
+ // ChanUpdateRequiredMaxHtlc is a bit that indicates whether the
+ // required htlc_maximum_msat field is present in this ChannelUpdate.
+ ChanUpdateRequiredMaxHtlc ChanUpdateMsgFlags = 1 << iota
+)
+
+// String returns the bitfield flags as a string.
+func (c ChanUpdateMsgFlags) String() string {
+ return fmt.Sprintf("%08b", c)
+}
+
+// HasMaxHtlc returns true if the htlc_maximum_msat option bit is set in the
+// message flags.
+func (c ChanUpdateMsgFlags) HasMaxHtlc() bool {
+ return c&ChanUpdateRequiredMaxHtlc != 0
+}
+
+// ChanUpdateChanFlags is a bitfield that signals various options concerning a
+// particular channel edge. Each bit is to be examined in order to determine
+// how the ChannelUpdate message is to be interpreted.
+type ChanUpdateChanFlags uint8
+
+const (
+ // ChanUpdateDirection indicates the direction of a channel update. If
+ // this bit is set to 0 if Node1 (the node with the "smaller" Node ID)
+ // is updating the channel, and to 1 otherwise.
+ ChanUpdateDirection ChanUpdateChanFlags = 1 << iota
+
+ // ChanUpdateDisabled is a bit that indicates if the channel edge
+ // selected by the ChanUpdateDirection bit is to be treated as being
+ // disabled.
+ ChanUpdateDisabled
+)
+
+// IsDisabled determines whether the channel flags has the disabled bit set.
+func (c ChanUpdateChanFlags) IsDisabled() bool {
+ return c&ChanUpdateDisabled == ChanUpdateDisabled
+}
+
+// String returns the bitfield flags as a string.
+func (c ChanUpdateChanFlags) String() string {
+ return fmt.Sprintf("%08b", c)
+}
+
+// ChannelUpdate1 message is used after channel has been initially announced.
+// Each side independently announces its fees and minimum expiry for HTLCs and
+// other parameters. Also this message is used to redeclare initially set
+// channel parameters.
+type ChannelUpdate1 struct {
+ // Signature is used to validate the announced data and prove the
+ // ownership of node id.
+ Signature Sig
+
+ // ChainHash denotes the target chain that this channel was opened
+ // within. This value should be the genesis hash of the target chain.
+ // Along with the short channel ID, this uniquely identifies the
+ // channel globally in a blockchain.
+ ChainHash chainhash.Hash
+
+ // ShortChannelID is the unique description of the funding transaction.
+ ShortChannelID ShortChannelID
+
+ // Timestamp allows ordering in the case of multiple announcements. We
+ // should ignore the message if timestamp is not greater than
+ // the last-received.
+ Timestamp uint32
+
+ // MessageFlags is a bitfield that describes whether optional fields
+ // are present in this update. Currently, the least-significant bit
+ // must be set to 1 if the optional field MaxHtlc is present.
+ MessageFlags ChanUpdateMsgFlags
+
+ // ChannelFlags is a bitfield that describes additional meta-data
+ // concerning how the update is to be interpreted. Currently, the
+ // least-significant bit must be set to 0 if the creating node
+ // corresponds to the first node in the previously sent channel
+ // announcement and 1 otherwise. If the second bit is set, then the
+ // channel is set to be disabled.
+ ChannelFlags ChanUpdateChanFlags
+
+ // TimeLockDelta is the minimum number of blocks this node requires to
+ // be added to the expiry of HTLCs. This is a security parameter
+ // determined by the node operator. This value represents the required
+ // gap between the time locks of the incoming and outgoing HTLC's set
+ // to this node.
+ TimeLockDelta uint16
+
+ // HtlcMinimumMsat is the minimum HTLC value which will be accepted.
+ HtlcMinimumMsat MilliSatoshi
+
+ // BaseFee is the base fee that must be used for incoming HTLC's to
+ // this particular channel. This value will be tacked onto the required
+ // for a payment independent of the size of the payment.
+ BaseFee uint32
+
+ // FeeRate is the fee rate that will be charged per millionth of a
+ // satoshi.
+ FeeRate uint32
+
+ // HtlcMaximumMsat is the maximum HTLC value which will be accepted.
+ HtlcMaximumMsat MilliSatoshi
+
+ // InboundFee is an optional TLV record that contains the fee
+ // information for incoming HTLCs.
+ InboundFee tlv.OptionalRecordT[tlv.TlvType55555, Fee]
+
+ // ExtraData is the set of data that was appended to this message to
+ // fill out the full maximum transport message size. These fields can
+ // be used to specify optional data such as custom TLV fields.
+ ExtraOpaqueData ExtraOpaqueData
+}
+
+// A compile time check to ensure ChannelUpdate implements the lnwire.Message
+// interface.
+var _ Message = (*ChannelUpdate1)(nil)
+
+// A compile time check to ensure ChannelUpdate1 implements the
+// lnwire.SizeableMessage interface.
+var _ SizeableMessage = (*ChannelUpdate1)(nil)
+
+// Decode deserializes a serialized ChannelUpdate stored in the passed
+// io.Reader observing the specified protocol version.
+//
+// This is part of the lnwire.Message interface.
+func (a *ChannelUpdate1) Decode(r io.Reader, _ uint32) error {
+ err := ReadElements(r,
+ &a.Signature,
+ a.ChainHash[:],
+ &a.ShortChannelID,
+ &a.Timestamp,
+ &a.MessageFlags,
+ &a.ChannelFlags,
+ &a.TimeLockDelta,
+ &a.HtlcMinimumMsat,
+ &a.BaseFee,
+ &a.FeeRate,
+ )
+ if err != nil {
+ return err
+ }
+
+ // Now check whether the max HTLC field is present and read it if so.
+ if a.MessageFlags.HasMaxHtlc() {
+ if err := ReadElements(r, &a.HtlcMaximumMsat); err != nil {
+ return err
+ }
+ }
+
+ var tlvRecords ExtraOpaqueData
+ if err := ReadElements(r, &tlvRecords); err != nil {
+ return err
+ }
+
+ var inboundFee = a.InboundFee.Zero()
+ typeMap, err := tlvRecords.ExtractRecords(&inboundFee)
+ if err != nil {
+ return fmt.Errorf("%w: %w", ErrParsingExtraTLVBytes, err)
+ }
+
+ val, ok := typeMap[a.InboundFee.TlvType()]
+ if ok && val == nil {
+ a.InboundFee = tlv.SomeRecordT(inboundFee)
+ }
+
+ if len(tlvRecords) != 0 {
+ a.ExtraOpaqueData = tlvRecords
+ }
+
+ return nil
+}
+
+// Encode serializes the target ChannelUpdate into the passed io.Writer
+// observing the protocol version specified.
+//
+// This is part of the lnwire.Message interface.
+func (a *ChannelUpdate1) Encode(w *bytes.Buffer, pver uint32) error {
+ if err := WriteSig(w, a.Signature); err != nil {
+ return err
+ }
+
+ if err := WriteBytes(w, a.ChainHash[:]); err != nil {
+ return err
+ }
+
+ if err := WriteShortChannelID(w, a.ShortChannelID); err != nil {
+ return err
+ }
+
+ if err := WriteUint32(w, a.Timestamp); err != nil {
+ return err
+ }
+
+ if err := WriteChanUpdateMsgFlags(w, a.MessageFlags); err != nil {
+ return err
+ }
+
+ if err := WriteChanUpdateChanFlags(w, a.ChannelFlags); err != nil {
+ return err
+ }
+
+ if err := WriteUint16(w, a.TimeLockDelta); err != nil {
+ return err
+ }
+
+ if err := WriteMilliSatoshi(w, a.HtlcMinimumMsat); err != nil {
+ return err
+ }
+
+ if err := WriteUint32(w, a.BaseFee); err != nil {
+ return err
+ }
+
+ if err := WriteUint32(w, a.FeeRate); err != nil {
+ return err
+ }
+
+ // Now append optional fields if they are set. Currently, the only
+ // optional field is max HTLC.
+ if a.MessageFlags.HasMaxHtlc() {
+ err := WriteMilliSatoshi(w, a.HtlcMaximumMsat)
+ if err != nil {
+ return err
+ }
+ }
+
+ recordProducers := make([]tlv.RecordProducer, 0, 1)
+ a.InboundFee.WhenSome(func(fee tlv.RecordT[tlv.TlvType55555, Fee]) {
+ recordProducers = append(recordProducers, &fee)
+ })
+
+ err := EncodeMessageExtraData(&a.ExtraOpaqueData, recordProducers...)
+ if err != nil {
+ return err
+ }
+
+ // Finally, append any extra opaque data.
+ return WriteBytes(w, a.ExtraOpaqueData)
+}
+
+// MsgType returns the integer uniquely identifying this message type on the
+// wire.
+//
+// This is part of the lnwire.Message interface.
+func (a *ChannelUpdate1) MsgType() MessageType {
+ return MsgChannelUpdate
+}
+
+// DataToSign is used to retrieve part of the announcement message which should
+// be signed.
+func (a *ChannelUpdate1) DataToSign() ([]byte, error) {
+ // We should not include the signatures itself.
+ b := make([]byte, 0, MaxMsgBody)
+ buf := bytes.NewBuffer(b)
+ if err := WriteBytes(buf, a.ChainHash[:]); err != nil {
+ return nil, err
+ }
+
+ if err := WriteShortChannelID(buf, a.ShortChannelID); err != nil {
+ return nil, err
+ }
+
+ if err := WriteUint32(buf, a.Timestamp); err != nil {
+ return nil, err
+ }
+
+ if err := WriteChanUpdateMsgFlags(buf, a.MessageFlags); err != nil {
+ return nil, err
+ }
+
+ if err := WriteChanUpdateChanFlags(buf, a.ChannelFlags); err != nil {
+ return nil, err
+ }
+
+ if err := WriteUint16(buf, a.TimeLockDelta); err != nil {
+ return nil, err
+ }
+
+ if err := WriteMilliSatoshi(buf, a.HtlcMinimumMsat); err != nil {
+ return nil, err
+ }
+
+ if err := WriteUint32(buf, a.BaseFee); err != nil {
+ return nil, err
+ }
+
+ if err := WriteUint32(buf, a.FeeRate); err != nil {
+ return nil, err
+ }
+
+ // Now append optional fields if they are set. Currently, the only
+ // optional field is max HTLC.
+ if a.MessageFlags.HasMaxHtlc() {
+ err := WriteMilliSatoshi(buf, a.HtlcMaximumMsat)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Finally, append any extra opaque data.
+ if err := WriteBytes(buf, a.ExtraOpaqueData); err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+// SCID returns the ShortChannelID of the channel that the update applies to.
+//
+// NOTE: this is part of the ChannelUpdate interface.
+func (a *ChannelUpdate1) SCID() ShortChannelID {
+ return a.ShortChannelID
+}
+
+// IsNode1 is true if the update was produced by node 1 of the channel peers.
+// Node 1 is the node with the lexicographically smaller public key.
+//
+// NOTE: this is part of the ChannelUpdate interface.
+func (a *ChannelUpdate1) IsNode1() bool {
+ return a.ChannelFlags&ChanUpdateDirection == 0
+}
+
+// IsDisabled is true if the update is announcing that the channel should be
+// considered disabled.
+//
+// NOTE: this is part of the ChannelUpdate interface.
+func (a *ChannelUpdate1) IsDisabled() bool {
+ return a.ChannelFlags&ChanUpdateDisabled == ChanUpdateDisabled
+}
+
+// GetChainHash returns the hash of the chain that the message is referring to.
+//
+// NOTE: this is part of the ChannelUpdate interface.
+func (a *ChannelUpdate1) GetChainHash() chainhash.Hash {
+ return a.ChainHash
+}
+
+// ForwardingPolicy returns the set of forwarding constraints of the update.
+//
+// NOTE: this is part of the ChannelUpdate interface.
+func (a *ChannelUpdate1) ForwardingPolicy() *ForwardingPolicy {
+ return &ForwardingPolicy{
+ TimeLockDelta: a.TimeLockDelta,
+ BaseFee: MilliSatoshi(a.BaseFee),
+ FeeRate: MilliSatoshi(a.FeeRate),
+ MinHTLC: a.HtlcMinimumMsat,
+ HasMaxHTLC: a.MessageFlags.HasMaxHtlc(),
+ MaxHTLC: a.HtlcMaximumMsat,
+ }
+}
+
+// GossipVersion returns the gossip version that this message is part of.
+//
+// NOTE: this is part of the GossipMessage interface.
+func (a *ChannelUpdate1) GossipVersion() GossipVersion {
+ return GossipVersion1
+}
+
+// CmpAge can be used to determine if the update is older or newer than the
+// passed update. It returns 1 if this update is newer, -1 if it is older, and
+// 0 if they are the same age.
+//
+// NOTE: this is part of the ChannelUpdate interface.
+func (a *ChannelUpdate1) CmpAge(update ChannelUpdate) (CompareResult, error) {
+ other, ok := update.(*ChannelUpdate1)
+ if !ok {
+ return 0, fmt.Errorf("expected *ChannelUpdate1, got: %T",
+ update)
+ }
+
+ switch {
+ case a.Timestamp > other.Timestamp:
+ return GreaterThan, nil
+ case a.Timestamp < other.Timestamp:
+ return LessThan, nil
+ default:
+ return EqualTo, nil
+ }
+}
+
+// SetDisabledFlag can be used to adjust the disabled flag of an update.
+//
+// NOTE: this is part of the ChannelUpdate interface.
+func (a *ChannelUpdate1) SetDisabledFlag(disabled bool) {
+ if disabled {
+ a.ChannelFlags |= ChanUpdateDisabled
+ } else {
+ a.ChannelFlags &= ^ChanUpdateDisabled
+ }
+}
+
+// SetSCID can be used to overwrite the SCID of the update.
+//
+// NOTE: this is part of the ChannelUpdate interface.
+func (a *ChannelUpdate1) SetSCID(scid ShortChannelID) {
+ a.ShortChannelID = scid
+}
+
+// A compile time assertion to ensure ChannelUpdate1 implements the
+// ChannelUpdate interface.
+var _ ChannelUpdate = (*ChannelUpdate1)(nil)
+
+// SerializedSize returns the serialized size of the message in bytes.
+//
+// This is part of the lnwire.SizeableMessage interface.
+func (a *ChannelUpdate1) SerializedSize() (uint32, error) {
+ return MessageSerializedSize(a)
+}
diff --git a/payments/db/migration1/lnwire/custom_records.go b/payments/db/migration1/lnwire/custom_records.go
new file mode 100644
index 0000000..de5ff4a
--- /dev/null
+++ b/payments/db/migration1/lnwire/custom_records.go
@@ -0,0 +1,278 @@
+package lnwire
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "maps"
+ "sort"
+
+ "github.com/lightningnetwork/lnd/fn/v2"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+const (
+ // MinCustomRecordsTlvType is the minimum custom records TLV type as
+ // defined in BOLT 01.
+ MinCustomRecordsTlvType = 65536
+)
+
+// CustomRecords stores a set of custom key/value pairs. Map keys are TLV types
+// which must be greater than or equal to MinCustomRecordsTlvType.
+type CustomRecords map[uint64][]byte
+
+// NewCustomRecords creates a new CustomRecords instance from a
+// tlv.TypeMap.
+func NewCustomRecords(tlvMap tlv.TypeMap) (CustomRecords, error) {
+ // Make comparisons in unit tests easy by returning nil if the map is
+ // empty.
+ if len(tlvMap) == 0 {
+ return nil, nil
+ }
+
+ customRecords := make(CustomRecords, len(tlvMap))
+ for k, v := range tlvMap {
+ customRecords[uint64(k)] = v
+ }
+
+ // Validate the custom records.
+ err := customRecords.Validate()
+ if err != nil {
+ return nil, fmt.Errorf("custom records from tlv map "+
+ "validation error: %w", err)
+ }
+
+ return customRecords, nil
+}
+
+// ParseCustomRecords creates a new CustomRecords instance from a tlv.Blob.
+func ParseCustomRecords(b tlv.Blob) (CustomRecords, error) {
+ return ParseCustomRecordsFrom(bytes.NewReader(b))
+}
+
+// ParseCustomRecordsFrom creates a new CustomRecords instance from a reader.
+func ParseCustomRecordsFrom(r io.Reader) (CustomRecords, error) {
+ typeMap, err := DecodeRecords(r)
+ if err != nil {
+ return nil, fmt.Errorf("error decoding HTLC record: %w", err)
+ }
+
+ return NewCustomRecords(typeMap)
+}
+
+// Validate checks that all custom records are in the custom type range.
+func (c CustomRecords) Validate() error {
+ if c == nil {
+ return nil
+ }
+
+ for key := range c {
+ if key < MinCustomRecordsTlvType {
+ return fmt.Errorf("custom records entry with TLV "+
+ "type below min: %d", MinCustomRecordsTlvType)
+ }
+ }
+
+ return nil
+}
+
+// Copy returns a copy of the custom records.
+func (c CustomRecords) Copy() CustomRecords {
+ if c == nil {
+ return nil
+ }
+
+ customRecords := make(CustomRecords, len(c))
+ for k, v := range c {
+ customRecords[k] = v
+ }
+
+ return customRecords
+}
+
+// MergedCopy creates a copy of the records and merges them with the given
+// records. If the same key is present in both sets, the value from the other
+// records will be used.
+func (c CustomRecords) MergedCopy(other CustomRecords) CustomRecords {
+ copiedRecords := make(CustomRecords, len(c))
+ maps.Copy(copiedRecords, c)
+ maps.Copy(copiedRecords, other)
+
+ return copiedRecords
+}
+
+// ExtendRecordProducers extends the given records slice with the custom
+// records. The resultant records slice will be sorted if the given records
+// slice contains TLV types greater than or equal to MinCustomRecordsTlvType.
+func (c CustomRecords) ExtendRecordProducers(
+ producers []tlv.RecordProducer) ([]tlv.RecordProducer, error) {
+
+ // If the custom records are nil or empty, there is nothing to do.
+ if len(c) == 0 {
+ return producers, nil
+ }
+
+ // Validate the custom records.
+ err := c.Validate()
+ if err != nil {
+ return nil, err
+ }
+
+ // Ensure that the existing records slice TLV types are not also present
+ // in the custom records. If they are, the resultant extended records
+ // slice would erroneously contain duplicate TLV types.
+ for _, rp := range producers {
+ record := rp.Record()
+ recordTlvType := uint64(record.Type())
+
+ _, foundDuplicateTlvType := c[recordTlvType]
+ if foundDuplicateTlvType {
+ return nil, fmt.Errorf("custom records contains a TLV "+
+ "type that is already present in the "+
+ "existing records: %d", recordTlvType)
+ }
+ }
+
+ // Convert the custom records map to a TLV record producer slice and
+ // append them to the exiting records slice.
+ customRecordProducers := RecordsAsProducers(tlv.MapToRecords(c))
+ producers = append(producers, customRecordProducers...)
+
+ // If the records slice which was given as an argument included TLV
+ // values greater than or equal to the minimum custom records TLV type
+ // we will sort the extended records slice to ensure that it is ordered
+ // correctly.
+ SortProducers(producers)
+
+ return producers, nil
+}
+
+// RecordProducers returns a slice of record producers for the custom records.
+func (c CustomRecords) RecordProducers() []tlv.RecordProducer {
+ // If the custom records are nil or empty, return an empty slice.
+ if len(c) == 0 {
+ return nil
+ }
+
+ // Convert the custom records map to a TLV record producer slice.
+ records := tlv.MapToRecords(c)
+
+ return RecordsAsProducers(records)
+}
+
+// Serialize serializes the custom records into a byte slice.
+func (c CustomRecords) Serialize() ([]byte, error) {
+ records := tlv.MapToRecords(c)
+ return EncodeRecords(records)
+}
+
+// SerializeTo serializes the custom records into the given writer.
+func (c CustomRecords) SerializeTo(w io.Writer) error {
+ records := tlv.MapToRecords(c)
+ return EncodeRecordsTo(w, records)
+}
+
+// ProduceRecordsSorted converts a slice of record producers into a slice of
+// records and then sorts it by type.
+func ProduceRecordsSorted(recordProducers ...tlv.RecordProducer) []tlv.Record {
+ records := fn.Map(
+ recordProducers,
+ func(producer tlv.RecordProducer) tlv.Record {
+ return producer.Record()
+ },
+ )
+
+ // Ensure that the set of records are sorted before we attempt to
+ // decode from the stream, to ensure they're canonical.
+ tlv.SortRecords(records)
+
+ return records
+}
+
+// SortProducers sorts the given record producers by their type.
+func SortProducers(producers []tlv.RecordProducer) {
+ sort.Slice(producers, func(i, j int) bool {
+ recordI := producers[i].Record()
+ recordJ := producers[j].Record()
+ return recordI.Type() < recordJ.Type()
+ })
+}
+
+// TlvMapToRecords converts a TLV map into a slice of records.
+func TlvMapToRecords(tlvMap tlv.TypeMap) []tlv.Record {
+ tlvMapGeneric := make(map[uint64][]byte)
+ for k, v := range tlvMap {
+ tlvMapGeneric[uint64(k)] = v
+ }
+
+ return tlv.MapToRecords(tlvMapGeneric)
+}
+
+// RecordsAsProducers converts a slice of records into a slice of record
+// producers.
+func RecordsAsProducers(records []tlv.Record) []tlv.RecordProducer {
+ return fn.Map(records, func(record tlv.Record) tlv.RecordProducer {
+ return &record
+ })
+}
+
+// EncodeRecords encodes the given records into a byte slice.
+func EncodeRecords(records []tlv.Record) ([]byte, error) {
+ var buf bytes.Buffer
+ if err := EncodeRecordsTo(&buf, records); err != nil {
+ return nil, err
+ }
+
+ return buf.Bytes(), nil
+}
+
+// EncodeRecordsTo encodes the given records into the given writer.
+func EncodeRecordsTo(w io.Writer, records []tlv.Record) error {
+ tlvStream, err := tlv.NewStream(records...)
+ if err != nil {
+ return err
+ }
+
+ return tlvStream.Encode(w)
+}
+
+// DecodeRecords decodes the given byte slice into the given records and returns
+// the rest as a TLV type map.
+func DecodeRecords(r io.Reader,
+ records ...tlv.Record) (tlv.TypeMap, error) {
+
+ tlvStream, err := tlv.NewStream(records...)
+ if err != nil {
+ return nil, err
+ }
+
+ return tlvStream.DecodeWithParsedTypes(r)
+}
+
+// DecodeRecordsP2P decodes the given byte slice into the given records and
+// returns the rest as a TLV type map. This function is identical to
+// DecodeRecords except that the record size is capped at 65535.
+func DecodeRecordsP2P(r *bytes.Reader,
+ records ...tlv.Record) (tlv.TypeMap, error) {
+
+ tlvStream, err := tlv.NewStream(records...)
+ if err != nil {
+ return nil, err
+ }
+
+ return tlvStream.DecodeWithParsedTypesP2P(r)
+}
+
+// AssertUniqueTypes asserts that the given records have unique types.
+func AssertUniqueTypes(r []tlv.Record) error {
+ seen := make(fn.Set[tlv.Type], len(r))
+ for _, record := range r {
+ t := record.Type()
+ if seen.Contains(t) {
+ return fmt.Errorf("duplicate record type: %d", t)
+ }
+ seen.Add(t)
+ }
+
+ return nil
+}
diff --git a/payments/db/migration1/lnwire/error.go b/payments/db/migration1/lnwire/error.go
new file mode 100644
index 0000000..3824119
--- /dev/null
+++ b/payments/db/migration1/lnwire/error.go
@@ -0,0 +1,143 @@
+package lnwire
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+)
+
+var (
+ // ErrParsingExtraTLVBytes is returned when we attempt to parse
+ // extra opaque bytes as a TLV stream, but the parsing fails due to
+ // and invalid TLV stream.
+ ErrParsingExtraTLVBytes = fmt.Errorf("error parsing extra TLV bytes")
+)
+
+// FundingError represents a set of errors that can be encountered and sent
+// during the funding workflow.
+type FundingError uint8
+
+const (
+ // ErrMaxPendingChannels is returned by remote peer when the number of
+ // active pending channels exceeds their maximum policy limit.
+ ErrMaxPendingChannels FundingError = 1
+
+ // ErrChanTooLarge is returned by a remote peer that receives a
+ // FundingOpen request for a channel that is above their current
+ // soft-limit.
+ ErrChanTooLarge FundingError = 2
+)
+
+// String returns a human readable version of the target FundingError.
+func (e FundingError) String() string {
+ switch e {
+ case ErrMaxPendingChannels:
+ return "Number of pending channels exceed maximum"
+ case ErrChanTooLarge:
+ return "channel too large"
+ default:
+ return "unknown error"
+ }
+}
+
+// Error returns the human readable version of the target FundingError.
+//
+// NOTE: Satisfies the Error interface.
+func (e FundingError) Error() string {
+ return e.String()
+}
+
+// ErrorData is a set of bytes associated with a particular sent error. A
+// receiving node SHOULD only print out data verbatim if the string is composed
+// solely of printable ASCII characters. For reference, the printable character
+// set includes byte values 32 through 127 inclusive.
+type ErrorData []byte
+
+// Error represents a generic error bound to an exact channel. The message
+// format is purposefully general in order to allow expression of a wide array
+// of possible errors. Each Error message is directed at a particular open
+// channel referenced by ChannelPoint.
+type Error struct {
+ // ChanID references the active channel in which the error occurred
+ // within. If the ChanID is all zeros, then this error applies to the
+ // entire established connection.
+ ChanID ChannelID
+
+ // Data is the attached error data that describes the exact failure
+ // which caused the error message to be sent.
+ Data ErrorData
+}
+
+// NewError creates a new Error message.
+func NewError() *Error {
+ return &Error{}
+}
+
+// A compile time check to ensure Error implements the lnwire.Message
+// interface.
+var _ Message = (*Error)(nil)
+
+// A compile time check to ensure Error implements the lnwire.SizeableMessage
+// interface.
+var _ SizeableMessage = (*Error)(nil)
+
+// Error returns the string representation to Error.
+//
+// NOTE: Satisfies the error interface.
+func (c *Error) Error() string {
+ errMsg := "non-ascii data"
+ if isASCII(c.Data) {
+ errMsg = string(c.Data)
+ }
+
+ return fmt.Sprintf("chan_id=%v, err=%v", c.ChanID, errMsg)
+}
+
+// Decode deserializes a serialized Error message stored in the passed
+// io.Reader observing the specified protocol version.
+//
+// This is part of the lnwire.Message interface.
+func (c *Error) Decode(r io.Reader, pver uint32) error {
+ return ReadElements(r,
+ &c.ChanID,
+ &c.Data,
+ )
+}
+
+// Encode serializes the target Error into the passed io.Writer observing the
+// protocol version specified.
+//
+// This is part of the lnwire.Message interface.
+func (c *Error) Encode(w *bytes.Buffer, pver uint32) error {
+ if err := WriteBytes(w, c.ChanID[:]); err != nil {
+ return err
+ }
+
+ return WriteErrorData(w, c.Data)
+}
+
+// MsgType returns the integer uniquely identifying an Error message on the
+// wire.
+//
+// This is part of the lnwire.Message interface.
+func (c *Error) MsgType() MessageType {
+ return MsgError
+}
+
+// SerializedSize returns the serialized size of the message in bytes.
+//
+// This is part of the lnwire.SizeableMessage interface.
+func (c *Error) SerializedSize() (uint32, error) {
+ return MessageSerializedSize(c)
+}
+
+// isASCII is a helper method that checks whether all bytes in `data` would be
+// printable ASCII characters if interpreted as a string.
+func isASCII(data []byte) bool {
+ for _, c := range data {
+ if c < 32 || c > 126 {
+ return false
+ }
+ }
+ return true
+}
diff --git a/payments/db/migration1/lnwire/extra_bytes.go b/payments/db/migration1/lnwire/extra_bytes.go
new file mode 100644
index 0000000..39228c1
--- /dev/null
+++ b/payments/db/migration1/lnwire/extra_bytes.go
@@ -0,0 +1,309 @@
+package lnwire
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "github.com/lightningnetwork/lnd/fn/v2"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+// ExtraOpaqueData is the set of data that was appended to this message, some
+// of which we may not actually know how to iterate or parse. By holding onto
+// this data, we ensure that we're able to properly validate the set of
+// signatures that cover these new fields, and ensure we're able to make
+// upgrades to the network in a forwards compatible manner.
+type ExtraOpaqueData []byte
+
+// NewExtraOpaqueData creates a new ExtraOpaqueData instance from a tlv.TypeMap.
+func NewExtraOpaqueData(tlvMap tlv.TypeMap) (ExtraOpaqueData, error) {
+ // If the tlv map is empty, we'll want to mirror the behavior of
+ // decoding an empty extra opaque data field (see Decode method).
+ if len(tlvMap) == 0 {
+ return make([]byte, 0), nil
+ }
+
+ // Convert the TLV map into a slice of records.
+ records := TlvMapToRecords(tlvMap)
+
+ // Encode the records into the extra data byte slice.
+ return EncodeRecords(records)
+}
+
+// Encode attempts to encode the raw extra bytes into the passed io.Writer.
+func (e *ExtraOpaqueData) Encode(w *bytes.Buffer) error {
+ eBytes := []byte((*e)[:])
+ if err := WriteBytes(w, eBytes); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Decode attempts to unpack the raw bytes encoded in the passed-in io.Reader as
+// a set of extra opaque data.
+func (e *ExtraOpaqueData) Decode(r io.Reader) error {
+ // First, we'll attempt to read a set of bytes contained within the
+ // passed io.Reader (if any exist).
+ rawBytes, err := io.ReadAll(r)
+ if err != nil {
+ return err
+ }
+
+ // If we _do_ have some bytes, then we'll swap out our backing pointer.
+ // This ensures that any struct that embeds this type will properly
+ // store the bytes once this method exits.
+ if len(rawBytes) > 0 {
+ *e = rawBytes
+ } else {
+ *e = make([]byte, 0)
+ }
+
+ return nil
+}
+
+// ValidateTLV checks that the raw bytes that make up the ExtraOpaqueData
+// instance are a valid TLV stream.
+func (e *ExtraOpaqueData) ValidateTLV() error {
+ // There is nothing to validate if the ExtraOpaqueData is nil or empty.
+ if e == nil || len(*e) == 0 {
+ return nil
+ }
+
+ tlvStream, err := tlv.NewStream()
+ if err != nil {
+ return err
+ }
+
+ // Ensure that the TLV stream is valid by attempting to decode it.
+ _, err = tlvStream.DecodeWithParsedTypesP2P(bytes.NewReader(*e))
+ if err != nil {
+ return fmt.Errorf("invalid TLV stream: %w: %v", err, *e)
+ }
+
+ return nil
+}
+
+// PackRecords attempts to encode the set of tlv records into the target
+// ExtraOpaqueData instance. The records will be encoded as a raw TLV stream
+// and stored within the backing slice pointer.
+func (e *ExtraOpaqueData) PackRecords(
+ recordProducers ...tlv.RecordProducer) error {
+
+ // Assemble all the records passed in series, then encode them.
+ records := ProduceRecordsSorted(recordProducers...)
+ encoded, err := EncodeRecords(records)
+ if err != nil {
+ return err
+ }
+
+ *e = encoded
+
+ return nil
+}
+
+// ExtractRecords attempts to decode any types in the internal raw bytes as if
+// it were a tlv stream. The set of raw parsed types is returned, and any
+// passed records (if found in the stream) will be parsed into the proper
+// tlv.Record.
+func (e *ExtraOpaqueData) ExtractRecords(
+ recordProducers ...tlv.RecordProducer) (tlv.TypeMap, error) {
+
+ // First, assemble all the records passed in series.
+ records := ProduceRecordsSorted(recordProducers...)
+ extraBytesReader := bytes.NewReader(*e)
+
+ // Since ExtraOpaqueData is provided by a potentially malicious peer,
+ // pass it into the P2P decoding variant.
+ return DecodeRecordsP2P(extraBytesReader, records...)
+}
+
+// RecordProducers parses ExtraOpaqueData into a slice of TLV record producers
+// by interpreting it as a TLV map.
+func (e *ExtraOpaqueData) RecordProducers() ([]tlv.RecordProducer, error) {
+ var recordProducers []tlv.RecordProducer
+
+ // If the instance is nil or empty, return an empty slice.
+ if e == nil || len(*e) == 0 {
+ return recordProducers, nil
+ }
+
+ // Parse the extra opaque data as a TLV map.
+ tlvMap, err := e.ExtractRecords()
+ if err != nil {
+ return nil, err
+ }
+
+ // Convert the TLV map into a slice of record producers.
+ records := TlvMapToRecords(tlvMap)
+
+ return RecordsAsProducers(records), nil
+}
+
+// EncodeMessageExtraData encodes the given recordProducers into the given
+// extraData.
+func EncodeMessageExtraData(extraData *ExtraOpaqueData,
+ recordProducers ...tlv.RecordProducer) error {
+
+ // Treat extraData as a mutable reference.
+ if extraData == nil {
+ return fmt.Errorf("extra data cannot be nil")
+ }
+
+ // Pack in the series of TLV records into this message. The order we
+ // pass them in doesn't matter, as the method will ensure that things
+ // are all properly sorted.
+ return extraData.PackRecords(recordProducers...)
+}
+
+// ParseAndExtractCustomRecords parses the given extra data into the passed-in
+// records, then returns any remaining records split into custom records and
+// extra data.
+func ParseAndExtractCustomRecords(allExtraData ExtraOpaqueData,
+ knownRecords ...tlv.RecordProducer) (CustomRecords,
+ fn.Set[tlv.Type], ExtraOpaqueData, error) {
+
+ extraDataTlvMap, err := allExtraData.ExtractRecords(knownRecords...)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ // Remove the known and now extracted records from the leftover extra
+ // data map.
+ parsedKnownRecords := make(fn.Set[tlv.Type], len(knownRecords))
+ for _, producer := range knownRecords {
+ r := producer.Record()
+
+ // Only remove the records if it was parsed (remainder is nil).
+ // We'll just store the type so we can tell the caller which
+ // records were actually parsed fully.
+ val, ok := extraDataTlvMap[r.Type()]
+ if ok && val == nil {
+ parsedKnownRecords.Add(r.Type())
+ delete(extraDataTlvMap, r.Type())
+ }
+ }
+
+ // Any records from the extra data TLV map which are in the custom
+ // records TLV type range will be included in the custom records field
+ // and removed from the extra data field.
+ customRecordsTlvMap := make(tlv.TypeMap, len(extraDataTlvMap))
+ for k, v := range extraDataTlvMap {
+ // Skip records that are not in the custom records TLV type
+ // range.
+ if k < MinCustomRecordsTlvType {
+ continue
+ }
+
+ // Include the record in the custom records map.
+ customRecordsTlvMap[k] = v
+
+ // Now that the record is included in the custom records map,
+ // we can remove it from the extra data TLV map.
+ delete(extraDataTlvMap, k)
+ }
+
+ // Set the custom records field to the custom records specific TLV
+ // record map.
+ customRecords, err := NewCustomRecords(customRecordsTlvMap)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ // Encode the remaining records back into the extra data field. These
+ // records are not in the custom records TLV type range and do not
+ // have associated fields in the struct that produced the records.
+ extraData, err := NewExtraOpaqueData(extraDataTlvMap)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ // Help with unit testing where we might have the empty value (nil) for
+ // the extra data instead of the default that's returned by the
+ // constructor (empty slice).
+ if len(extraData) == 0 {
+ extraData = nil
+ }
+
+ return customRecords, parsedKnownRecords, extraData, nil
+}
+
+// MergeAndEncode merges the known records with the extra data and custom
+// records, then encodes the merged records into raw bytes.
+func MergeAndEncode(knownRecords []tlv.RecordProducer,
+ extraData ExtraOpaqueData, customRecords CustomRecords) ([]byte,
+ error) {
+
+ // Construct a slice of all the records that we should include in the
+ // message extra data field. We will start by including any records from
+ // the extra data field.
+ mergedRecords, err := extraData.RecordProducers()
+ if err != nil {
+ return nil, err
+ }
+
+ // Merge the known and extra data records.
+ mergedRecords = append(mergedRecords, knownRecords...)
+
+ // Include custom records in the extra data wire field if they are
+ // present. Ensure that the custom records are validated before encoding
+ // them.
+ if err := customRecords.Validate(); err != nil {
+ return nil, fmt.Errorf("custom records validation error: %w",
+ err)
+ }
+
+ // Extend the message extra data records slice with TLV records from the
+ // custom records field.
+ mergedRecords = append(
+ mergedRecords, customRecords.RecordProducers()...,
+ )
+
+ // Now we can sort the records and make sure there are no records with
+ // the same type that would collide when encoding.
+ sortedRecords := ProduceRecordsSorted(mergedRecords...)
+ if err := AssertUniqueTypes(sortedRecords); err != nil {
+ return nil, err
+ }
+
+ return EncodeRecords(sortedRecords)
+}
+
+// ParseAndExtractExtraData parses the given extra data into the passed-in
+// records, then returns any remaining records as extra data.
+func ParseAndExtractExtraData(allTlvData ExtraOpaqueData,
+ knownRecords ...tlv.RecordProducer) (fn.Set[tlv.Type],
+ ExtraOpaqueData, error) {
+
+ extraDataTlvMap, err := allTlvData.ExtractRecords(knownRecords...)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Remove the known and now extracted records from the leftover extra
+ // data map.
+ parsedKnownRecords := make(fn.Set[tlv.Type], len(knownRecords))
+ for _, producer := range knownRecords {
+ r := producer.Record()
+
+ // Only remove the records if it was parsed (remainder is nil).
+ // We'll just store the type so we can tell the caller which
+ // records were actually parsed fully.
+ val, ok := extraDataTlvMap[r.Type()]
+ if ok && val == nil {
+ parsedKnownRecords.Add(r.Type())
+ delete(extraDataTlvMap, r.Type())
+ }
+ }
+
+ // Encode the remaining records back into the extra data field. These
+ // records are not in the custom records TLV type range and do not
+ // have associated fields in the struct that produced the records.
+ extraData, err := NewExtraOpaqueData(extraDataTlvMap)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return parsedKnownRecords, extraData, nil
+}
diff --git a/payments/db/migration1/lnwire/features.go b/payments/db/migration1/lnwire/features.go
new file mode 100644
index 0000000..4e927e1
--- /dev/null
+++ b/payments/db/migration1/lnwire/features.go
@@ -0,0 +1,898 @@
+package lnwire
+
+import (
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+var (
+ // ErrFeaturePairExists signals an error in feature vector construction
+ // where the opposing bit in a feature pair has already been set.
+ ErrFeaturePairExists = errors.New("feature pair exists")
+
+ // ErrFeatureStandard is returned when attempts to modify LND's known
+ // set of features are made.
+ ErrFeatureStandard = errors.New("feature is used in standard " +
+ "protocol set")
+
+ // ErrFeatureBitMaximum is returned when a feature bit exceeds the
+ // maximum allowable value.
+ ErrFeatureBitMaximum = errors.New("feature bit exceeds allowed maximum")
+)
+
+// FeatureBit represents a feature that can be enabled in either a local or
+// global feature vector at a specific bit position. Feature bits follow the
+// "it's OK to be odd" rule, where features at even bit positions must be known
+// to a node receiving them from a peer while odd bits do not. In accordance,
+// feature bits are usually assigned in pairs, first being assigned an odd bit
+// position which may later be changed to the preceding even position once
+// knowledge of the feature becomes required on the network.
+type FeatureBit uint16
+
+const (
+ // DataLossProtectRequired is a feature bit that indicates that a peer
+ // *requires* the other party know about the data-loss-protect optional
+ // feature. If the remote peer does not know of such a feature, then
+ // the sending peer SHOULD disconnect them. The data-loss-protect
+ // feature allows a peer that's lost partial data to recover their
+ // settled funds of the latest commitment state.
+ DataLossProtectRequired FeatureBit = 0
+
+ // DataLossProtectOptional is an optional feature bit that indicates
+ // that the sending peer knows of this new feature and can activate it
+ // it. The data-loss-protect feature allows a peer that's lost partial
+ // data to recover their settled funds of the latest commitment state.
+ DataLossProtectOptional FeatureBit = 1
+
+ // InitialRoutingSync is a local feature bit meaning that the receiving
+ // node should send a complete dump of routing information when a new
+ // connection is established.
+ InitialRoutingSync FeatureBit = 3
+
+ // UpfrontShutdownScriptRequired is a feature bit which indicates that a
+ // peer *requires* that the remote peer accept an upfront shutdown script to
+ // which payout is enforced on cooperative closes.
+ UpfrontShutdownScriptRequired FeatureBit = 4
+
+ // UpfrontShutdownScriptOptional is an optional feature bit which indicates
+ // that the peer will accept an upfront shutdown script to which payout is
+ // enforced on cooperative closes.
+ UpfrontShutdownScriptOptional FeatureBit = 5
+
+ // GossipQueriesRequired is a feature bit that indicates that the
+ // receiving peer MUST know of the set of features that allows nodes to
+ // more efficiently query the network view of peers on the network for
+ // reconciliation purposes.
+ GossipQueriesRequired FeatureBit = 6
+
+ // GossipQueriesOptional is an optional feature bit that signals that
+ // the setting peer knows of the set of features that allows more
+ // efficient network view reconciliation.
+ GossipQueriesOptional FeatureBit = 7
+
+ // TLVOnionPayloadRequired is a feature bit that indicates a node is
+ // able to decode the new TLV information included in the onion packet.
+ TLVOnionPayloadRequired FeatureBit = 8
+
+ // TLVOnionPayloadOptional is an optional feature bit that indicates a
+ // node is able to decode the new TLV information included in the onion
+ // packet.
+ TLVOnionPayloadOptional FeatureBit = 9
+
+ // StaticRemoteKeyRequired is a required feature bit that signals that
+ // within one's commitment transaction, the key used for the remote
+ // party's non-delay output should not be tweaked.
+ StaticRemoteKeyRequired FeatureBit = 12
+
+ // StaticRemoteKeyOptional is an optional feature bit that signals that
+ // within one's commitment transaction, the key used for the remote
+ // party's non-delay output should not be tweaked.
+ StaticRemoteKeyOptional FeatureBit = 13
+
+ // PaymentAddrRequired is a required feature bit that signals that a
+ // node requires payment addresses, which are used to mitigate probing
+ // attacks on the receiver of a payment.
+ PaymentAddrRequired FeatureBit = 14
+
+ // PaymentAddrOptional is an optional feature bit that signals that a
+ // node supports payment addresses, which are used to mitigate probing
+ // attacks on the receiver of a payment.
+ PaymentAddrOptional FeatureBit = 15
+
+ // MPPRequired is a required feature bit that signals that the receiver
+ // of a payment requires settlement of an invoice with more than one
+ // HTLC.
+ MPPRequired FeatureBit = 16
+
+ // MPPOptional is an optional feature bit that signals that the receiver
+ // of a payment supports settlement of an invoice with more than one
+ // HTLC.
+ MPPOptional FeatureBit = 17
+
+ // WumboChannelsRequired is a required feature bit that signals that a
+ // node is willing to accept channels larger than 2^24 satoshis.
+ WumboChannelsRequired FeatureBit = 18
+
+ // WumboChannelsOptional is an optional feature bit that signals that a
+ // node is willing to accept channels larger than 2^24 satoshis.
+ WumboChannelsOptional FeatureBit = 19
+
+ // AnchorsRequired is a required feature bit that signals that the node
+ // requires channels to be made using commitments having anchor
+ // outputs.
+ AnchorsRequired FeatureBit = 20
+
+ // AnchorsOptional is an optional feature bit that signals that the
+ // node supports channels to be made using commitments having anchor
+ // outputs.
+ AnchorsOptional FeatureBit = 21
+
+ // AnchorsZeroFeeHtlcTxRequired is a required feature bit that signals
+ // that the node requires channels having zero-fee second-level HTLC
+ // transactions, which also imply anchor commitments.
+ AnchorsZeroFeeHtlcTxRequired FeatureBit = 22
+
+ // AnchorsZeroFeeHtlcTxOptional is an optional feature bit that signals
+ // that the node supports channels having zero-fee second-level HTLC
+ // transactions, which also imply anchor commitments.
+ AnchorsZeroFeeHtlcTxOptional FeatureBit = 23
+
+ // RouteBlindingRequired is a required feature bit that signals that
+ // the node supports blinded payments.
+ RouteBlindingRequired FeatureBit = 24
+
+ // RouteBlindingOptional is an optional feature bit that signals that
+ // the node supports blinded payments.
+ RouteBlindingOptional FeatureBit = 25
+
+ // ShutdownAnySegwitRequired is an required feature bit that signals
+ // that the sender is able to properly handle/parse segwit witness
+ // programs up to version 16. This enables utilization of Taproot
+ // addresses for cooperative closure addresses.
+ ShutdownAnySegwitRequired FeatureBit = 26
+
+ // ShutdownAnySegwitOptional is an optional feature bit that signals
+ // that the sender is able to properly handle/parse segwit witness
+ // programs up to version 16. This enables utilization of Taproot
+ // addresses for cooperative closure addresses.
+ ShutdownAnySegwitOptional FeatureBit = 27
+
+ // AMPRequired is a required feature bit that signals that the receiver
+ // of a payment supports accepts spontaneous payments, i.e.
+ // sender-generated preimages according to BOLT XX.
+ AMPRequired FeatureBit = 30
+
+ // AMPOptional is an optional feature bit that signals that the receiver
+ // of a payment supports accepts spontaneous payments, i.e.
+ // sender-generated preimages according to BOLT XX.
+ AMPOptional FeatureBit = 31
+
+ // QuiescenceRequired is a required feature bit that denotes that a
+ // connection established with this node must support the quiescence
+ // protocol if it wants to have a channel relationship.
+ QuiescenceRequired FeatureBit = 34
+
+ // QuiescenceOptional is an optional feature bit that denotes that a
+ // connection established with this node is permitted to use the
+ // quiescence protocol.
+ QuiescenceOptional FeatureBit = 35
+
+ // ExplicitChannelTypeRequired is a required bit that denotes that a
+ // connection established with this node is to use explicit channel
+ // commitment types for negotiation instead of the existing implicit
+ // negotiation methods. With this bit, there is no longer a "default"
+ // implicit channel commitment type, allowing a connection to
+ // open/maintain types of several channels over its lifetime.
+ ExplicitChannelTypeRequired = 44
+
+ // ExplicitChannelTypeOptional is an optional bit that denotes that a
+ // connection established with this node is to use explicit channel
+ // commitment types for negotiation instead of the existing implicit
+ // negotiation methods. With this bit, there is no longer a "default"
+ // implicit channel commitment type, allowing a connection to
+ // TODO: Decide on actual feature bit value.
+ ExplicitChannelTypeOptional = 45
+
+ // ScidAliasRequired is a required feature bit that signals that the
+ // node requires understanding of ShortChannelID aliases in the TLV
+ // segment of the channel_ready message.
+ ScidAliasRequired FeatureBit = 46
+
+ // ScidAliasOptional is an optional feature bit that signals that the
+ // node understands ShortChannelID aliases in the TLV segment of the
+ // channel_ready message.
+ ScidAliasOptional FeatureBit = 47
+
+ // PaymentMetadataRequired is a required bit that denotes that if an
+ // invoice contains metadata, it must be passed along with the payment
+ // htlc(s).
+ PaymentMetadataRequired = 48
+
+ // PaymentMetadataOptional is an optional bit that denotes that if an
+ // invoice contains metadata, it may be passed along with the payment
+ // htlc(s).
+ PaymentMetadataOptional = 49
+
+ // ZeroConfRequired is a required feature bit that signals that the
+ // node requires understanding of the zero-conf channel_type.
+ ZeroConfRequired FeatureBit = 50
+
+ // ZeroConfOptional is an optional feature bit that signals that the
+ // node understands the zero-conf channel type.
+ ZeroConfOptional FeatureBit = 51
+
+ // KeysendRequired is a required bit that indicates that the node is
+ // able and willing to accept keysend payments.
+ KeysendRequired = 54
+
+ // KeysendOptional is an optional bit that indicates that the node is
+ // able and willing to accept keysend payments.
+ KeysendOptional = 55
+
+ // RbfCoopCloseRequired is a required feature bit that signals that
+ // the new RBF-based co-op close protocol is supported.
+ RbfCoopCloseRequired = 60
+
+ // RbfCoopCloseOptional is an optional feature bit that signals that the
+ // new RBF-based co-op close protocol is supported.
+ RbfCoopCloseOptional = 61
+
+ // RbfCoopCloseRequiredStaging is a required feature bit that signals
+ // that the new RBF-based co-op close protocol is supported.
+ RbfCoopCloseRequiredStaging = 160
+
+ // RbfCoopCloseOptionalStaging is an optional feature bit that signals
+ // that the new RBF-based co-op close protocol is supported.
+ RbfCoopCloseOptionalStaging = 161
+
+ // ScriptEnforcedLeaseRequired is a required feature bit that signals
+ // that the node requires channels having zero-fee second-level HTLC
+ // transactions, which also imply anchor commitments, along with an
+ // additional CLTV constraint of a channel lease's expiration height
+ // applied to all outputs that pay directly to the channel initiator.
+ //
+ // TODO: Decide on actual feature bit value.
+ ScriptEnforcedLeaseRequired FeatureBit = 2022
+
+ // ScriptEnforcedLeaseOptional is an optional feature bit that signals
+ // that the node requires channels having zero-fee second-level HTLC
+ // transactions, which also imply anchor commitments, along with an
+ // additional CLTV constraint of a channel lease's expiration height
+ // applied to all outputs that pay directly to the channel initiator.
+ //
+ // TODO: Decide on actual feature bit value.
+ ScriptEnforcedLeaseOptional FeatureBit = 2023
+
+ // SimpleTaprootChannelsRequiredFinal is a required bit that indicates
+ // the node is able to create taproot-native channels. This is the
+ // final feature bit to be used once the channel type is finalized.
+ SimpleTaprootChannelsRequiredFinal = 80
+
+ // SimpleTaprootChannelsOptionalFinal is an optional bit that indicates
+ // the node is able to create taproot-native channels. This is the
+ // final feature bit to be used once the channel type is finalized.
+ SimpleTaprootChannelsOptionalFinal = 81
+
+ // SimpleTaprootChannelsRequiredStaging is a required bit that indicates
+ // the node is able to create taproot-native channels. This is a
+ // feature bit used in the wild while the channel type is still being
+ // finalized.
+ SimpleTaprootChannelsRequiredStaging = 180
+
+ // SimpleTaprootChannelsOptionalStaging is an optional bit that
+ // indicates the node is able to create taproot-native channels. This
+ // is a feature bit used in the wild while the channel type is still
+ // being finalized.
+ SimpleTaprootChannelsOptionalStaging = 181
+
+ // ExperimentalAccountabilityRequired is a required feature bit that
+ // indicates that the node will relay experimental accountability
+ // signals.
+ ExperimentalAccountabilityRequired FeatureBit = 260
+
+ // ExperimentalAccountabilityOptional is an optional feature bit that
+ // indicates that the node will relay experimental accountability
+ // signals.
+ ExperimentalAccountabilityOptional FeatureBit = 261
+
+ // Bolt11BlindedPathsRequired is a required feature bit that indicates
+ // that the node is able to understand the blinded path tagged field in
+ // a BOLT 11 invoice.
+ Bolt11BlindedPathsRequired = 262
+
+ // Bolt11BlindedPathsOptional is an optional feature bit that indicates
+ // that the node is able to understand the blinded path tagged field in
+ // a BOLT 11 invoice.
+ Bolt11BlindedPathsOptional = 263
+
+ // SimpleTaprootOverlayChansRequired is a required bit that indicates
+ // support for the special custom taproot overlay channel.
+ SimpleTaprootOverlayChansOptional = 2025
+
+ // SimpleTaprootOverlayChansRequired is a required bit that indicates
+ // support for the special custom taproot overlay channel.
+ SimpleTaprootOverlayChansRequired = 2026
+
+ // MaxBolt11Feature is the maximum feature bit value allowed in bolt 11
+ // invoices.
+ //
+ // The base 32 encoded tagged fields in invoices are limited to 10 bits
+ // to express the length of the field's data.
+ //nolint:ll
+ // See: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md#tagged-fields
+ //
+ // With a maximum length field of 1023 (2^10 -1) and 5 bit encoding,
+ // the highest feature bit that can be expressed is:
+ // 1023 * 5 - 1 = 5114.
+ MaxBolt11Feature = 5114
+)
+
+// IsRequired returns true if the feature bit is even, and false otherwise.
+func (b FeatureBit) IsRequired() bool {
+ return b&0x01 == 0x00
+}
+
+// Features is a mapping of known feature bits to a descriptive name. All known
+// feature bits must be assigned a name in this mapping, and feature bit pairs
+// must be assigned together for correct behavior.
+var Features = map[FeatureBit]string{
+ DataLossProtectRequired: "data-loss-protect",
+ DataLossProtectOptional: "data-loss-protect",
+ InitialRoutingSync: "initial-routing-sync",
+ UpfrontShutdownScriptRequired: "upfront-shutdown-script",
+ UpfrontShutdownScriptOptional: "upfront-shutdown-script",
+ GossipQueriesRequired: "gossip-queries",
+ GossipQueriesOptional: "gossip-queries",
+ TLVOnionPayloadRequired: "tlv-onion",
+ TLVOnionPayloadOptional: "tlv-onion",
+ StaticRemoteKeyOptional: "static-remote-key",
+ StaticRemoteKeyRequired: "static-remote-key",
+ PaymentAddrOptional: "payment-addr",
+ PaymentAddrRequired: "payment-addr",
+ MPPOptional: "multi-path-payments",
+ MPPRequired: "multi-path-payments",
+ AnchorsRequired: "anchor-commitments",
+ AnchorsOptional: "anchor-commitments",
+ AnchorsZeroFeeHtlcTxRequired: "anchors-zero-fee-htlc-tx",
+ AnchorsZeroFeeHtlcTxOptional: "anchors-zero-fee-htlc-tx",
+ WumboChannelsRequired: "wumbo-channels",
+ WumboChannelsOptional: "wumbo-channels",
+ AMPRequired: "amp",
+ AMPOptional: "amp",
+ QuiescenceRequired: "quiescence",
+ QuiescenceOptional: "quiescence",
+ PaymentMetadataOptional: "payment-metadata",
+ PaymentMetadataRequired: "payment-metadata",
+ ExplicitChannelTypeOptional: "explicit-commitment-type",
+ ExplicitChannelTypeRequired: "explicit-commitment-type",
+ KeysendOptional: "keysend",
+ KeysendRequired: "keysend",
+ ScriptEnforcedLeaseRequired: "script-enforced-lease",
+ ScriptEnforcedLeaseOptional: "script-enforced-lease",
+ ScidAliasRequired: "scid-alias",
+ ScidAliasOptional: "scid-alias",
+ ZeroConfRequired: "zero-conf",
+ ZeroConfOptional: "zero-conf",
+ RouteBlindingRequired: "route-blinding",
+ RouteBlindingOptional: "route-blinding",
+ ShutdownAnySegwitRequired: "shutdown-any-segwit",
+ ShutdownAnySegwitOptional: "shutdown-any-segwit",
+ SimpleTaprootChannelsRequiredFinal: "simple-taproot-chans",
+ SimpleTaprootChannelsOptionalFinal: "simple-taproot-chans",
+ SimpleTaprootChannelsRequiredStaging: "simple-taproot-chans-x",
+ SimpleTaprootChannelsOptionalStaging: "simple-taproot-chans-x",
+ SimpleTaprootOverlayChansOptional: "taproot-overlay-chans",
+ SimpleTaprootOverlayChansRequired: "taproot-overlay-chans",
+ ExperimentalAccountabilityRequired: "accountable-x",
+ ExperimentalAccountabilityOptional: "accountable-x",
+ Bolt11BlindedPathsOptional: "bolt-11-blinded-paths",
+ Bolt11BlindedPathsRequired: "bolt-11-blinded-paths",
+ RbfCoopCloseOptional: "rbf-coop-close",
+ RbfCoopCloseRequired: "rbf-coop-close",
+ RbfCoopCloseOptionalStaging: "rbf-coop-close-x",
+ RbfCoopCloseRequiredStaging: "rbf-coop-close-x",
+}
+
+// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
+// RawFeatureVector itself just stores a set of bit flags but can be used to
+// construct a FeatureVector which binds meaning to each bit. Feature vectors
+// can be serialized and deserialized to/from a byte representation that is
+// transmitted in Lightning network messages.
+type RawFeatureVector struct {
+ features map[FeatureBit]struct{}
+}
+
+// NewRawFeatureVector creates a feature vector with all of the feature bits
+// given as arguments enabled.
+func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector {
+ fv := &RawFeatureVector{features: make(map[FeatureBit]struct{})}
+ for _, bit := range bits {
+ fv.Set(bit)
+ }
+ return fv
+}
+
+// IsEmpty returns whether the feature vector contains any feature bits.
+func (fv RawFeatureVector) IsEmpty() bool {
+ return len(fv.features) == 0
+}
+
+// OnlyContains determines whether only the specified feature bits are found.
+func (fv RawFeatureVector) OnlyContains(bits ...FeatureBit) bool {
+ if len(bits) != len(fv.features) {
+ return false
+ }
+ for _, bit := range bits {
+ if !fv.IsSet(bit) {
+ return false
+ }
+ }
+ return true
+}
+
+// Equals determines whether two features vectors contain exactly the same
+// features.
+func (fv RawFeatureVector) Equals(other *RawFeatureVector) bool {
+ if len(fv.features) != len(other.features) {
+ return false
+ }
+ for bit := range fv.features {
+ if _, ok := other.features[bit]; !ok {
+ return false
+ }
+ }
+ return true
+}
+
+// Merge sets all feature bits in other on the receiver's feature vector.
+func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error {
+ for bit := range other.features {
+ err := fv.SafeSet(bit)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// ValidateUpdate checks whether a feature vector can safely be updated to the
+// new feature vector provided, checking that it does not alter any of the
+// "standard" features that are defined by LND. The new feature vector should
+// be inclusive of all features in the original vector that it still wants to
+// advertise, setting and unsetting updates as desired. Features in the vector
+// are also checked against a maximum inclusive value, as feature vectors in
+// different contexts have different maximum values.
+func (fv *RawFeatureVector) ValidateUpdate(other *RawFeatureVector,
+ maximumValue FeatureBit) error {
+
+ // Run through the new set of features and check that we're not adding
+ // any feature bits that are defined but not set in LND.
+ for feature := range other.features {
+ if fv.IsSet(feature) {
+ continue
+ }
+
+ if feature > maximumValue {
+ return fmt.Errorf("can't set feature bit %d: %w %v",
+ feature, ErrFeatureBitMaximum,
+ maximumValue)
+ }
+
+ if name, known := Features[feature]; known {
+ return fmt.Errorf("can't set feature "+
+ "bit %d (%v): %w", feature, name,
+ ErrFeatureStandard)
+ }
+ }
+
+ // Check that the new feature vector for this set does not unset any
+ // features that are standard in LND by comparing the features in our
+ // current set to the omitted values in the new set.
+ for feature := range fv.features {
+ if other.IsSet(feature) {
+ continue
+ }
+
+ if name, known := Features[feature]; known {
+ return fmt.Errorf("can't unset feature "+
+ "bit %d (%v): %w", feature, name,
+ ErrFeatureStandard)
+ }
+ }
+
+ return nil
+}
+
+// ValidatePairs checks each feature bit in a raw vector to ensure that the
+// opposing bit is not set, validating that the vector has either the optional
+// or required bit set, not both.
+func (fv *RawFeatureVector) ValidatePairs() error {
+ for feature := range fv.features {
+ if _, ok := fv.features[feature^1]; ok {
+ return ErrFeaturePairExists
+ }
+ }
+
+ return nil
+}
+
+// Clone makes a copy of a feature vector.
+func (fv *RawFeatureVector) Clone() *RawFeatureVector {
+ newFeatures := NewRawFeatureVector()
+ for bit := range fv.features {
+ newFeatures.Set(bit)
+ }
+ return newFeatures
+}
+
+// IsSet returns whether a particular feature bit is enabled in the vector.
+func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool {
+ _, ok := fv.features[feature]
+ return ok
+}
+
+// Set marks a feature as enabled in the vector.
+func (fv *RawFeatureVector) Set(feature FeatureBit) {
+ fv.features[feature] = struct{}{}
+}
+
+// SafeSet sets the chosen feature bit in the feature vector, but returns an
+// error if the opposing feature bit is already set. This ensures both that we
+// are creating properly structured feature vectors, and in some cases, that
+// peers are sending properly encoded ones, i.e. it can't be both optional and
+// required.
+func (fv *RawFeatureVector) SafeSet(feature FeatureBit) error {
+ if _, ok := fv.features[feature^1]; ok {
+ return ErrFeaturePairExists
+ }
+
+ fv.Set(feature)
+ return nil
+}
+
+// Unset marks a feature as disabled in the vector.
+func (fv *RawFeatureVector) Unset(feature FeatureBit) {
+ delete(fv.features, feature)
+}
+
+// SerializeSize returns the number of bytes needed to represent feature vector
+// in byte format.
+func (fv *RawFeatureVector) SerializeSize() int {
+ // We calculate byte-length via the largest bit index.
+ return fv.serializeSize(8)
+}
+
+// SerializeSize32 returns the number of bytes needed to represent feature
+// vector in base32 format.
+func (fv *RawFeatureVector) SerializeSize32() int {
+ // We calculate base32-length via the largest bit index.
+ return fv.serializeSize(5)
+}
+
+// serializeSize returns the number of bytes required to encode the feature
+// vector using at most width bits per encoded byte.
+func (fv *RawFeatureVector) serializeSize(width int) int {
+ // Find the largest feature bit index
+ max := -1
+ for feature := range fv.features {
+ index := int(feature)
+ if index > max {
+ max = index
+ }
+ }
+ if max == -1 {
+ return 0
+ }
+
+ return max/width + 1
+}
+
+// Encode writes the feature vector in byte representation. Every feature
+// encoded as a bit, and the bit vector is serialized using the least number of
+// bytes. Since the bit vector length is variable, the first two bytes of the
+// serialization represent the length.
+func (fv *RawFeatureVector) Encode(w io.Writer) error {
+ // Write length of feature vector.
+ var l [2]byte
+ length := fv.SerializeSize()
+ binary.BigEndian.PutUint16(l[:], uint16(length))
+ if _, err := w.Write(l[:]); err != nil {
+ return err
+ }
+
+ return fv.encode(w, length, 8)
+}
+
+// EncodeBase256 writes the feature vector in base256 representation. Every
+// feature is encoded as a bit, and the bit vector is serialized using the least
+// number of bytes.
+func (fv *RawFeatureVector) EncodeBase256(w io.Writer) error {
+ length := fv.SerializeSize()
+ return fv.encode(w, length, 8)
+}
+
+// EncodeBase32 writes the feature vector in base32 representation. Every feature
+// is encoded as a bit, and the bit vector is serialized using the least number of
+// bytes.
+func (fv *RawFeatureVector) EncodeBase32(w io.Writer) error {
+ length := fv.SerializeSize32()
+ return fv.encode(w, length, 5)
+}
+
+// encode writes the feature vector
+func (fv *RawFeatureVector) encode(w io.Writer, length, width int) error {
+ // Generate the data and write it.
+ data := make([]byte, length)
+ for feature := range fv.features {
+ byteIndex := int(feature) / width
+ bitIndex := int(feature) % width
+ data[length-byteIndex-1] |= 1 << uint(bitIndex)
+ }
+
+ _, err := w.Write(data)
+ return err
+}
+
+// Decode reads the feature vector from its byte representation. Every feature
+// is encoded as a bit, and the bit vector is serialized using the least number
+// of bytes. Since the bit vector length is variable, the first two bytes of the
+// serialization represent the length.
+func (fv *RawFeatureVector) Decode(r io.Reader) error {
+ // Read the length of the feature vector.
+ var l [2]byte
+ if _, err := io.ReadFull(r, l[:]); err != nil {
+ return err
+ }
+ length := binary.BigEndian.Uint16(l[:])
+
+ return fv.decode(r, int(length), 8)
+}
+
+// DecodeBase256 reads the feature vector from its base256 representation. Every
+// feature encoded as a bit, and the bit vector is serialized using the least
+// number of bytes.
+func (fv *RawFeatureVector) DecodeBase256(r io.Reader, length int) error {
+ return fv.decode(r, length, 8)
+}
+
+// DecodeBase32 reads the feature vector from its base32 representation. Every
+// feature encoded as a bit, and the bit vector is serialized using the least
+// number of bytes.
+func (fv *RawFeatureVector) DecodeBase32(r io.Reader, length int) error {
+ return fv.decode(r, length, 5)
+}
+
+// decode reads a feature vector from the next length bytes of the io.Reader,
+// assuming each byte has width feature bits encoded per byte.
+func (fv *RawFeatureVector) decode(r io.Reader, length, width int) error {
+ // Read the feature vector data.
+ data := make([]byte, length)
+ if _, err := io.ReadFull(r, data); err != nil {
+ return err
+ }
+
+ // Set feature bits from parsed data.
+ bitsNumber := len(data) * width
+ for i := 0; i < bitsNumber; i++ {
+ byteIndex := int(i / width)
+ bitIndex := uint(i % width)
+ if (data[length-byteIndex-1]>>bitIndex)&1 == 1 {
+ fv.Set(FeatureBit(i))
+ }
+ }
+
+ return nil
+}
+
+// sizeFunc returns the length required to encode the feature vector.
+func (fv *RawFeatureVector) sizeFunc() uint64 {
+ return uint64(fv.SerializeSize())
+}
+
+// Record returns a TLV record that can be used to encode/decode raw feature
+// vectors. Note that the length of the feature vector is not included, because
+// it is covered by the TLV record's length field.
+func (fv *RawFeatureVector) Record() tlv.Record {
+ return tlv.MakeDynamicRecord(
+ 0, fv, fv.sizeFunc, rawFeatureEncoder, rawFeatureDecoder,
+ )
+}
+
+// rawFeatureEncoder is a custom TLV encoder for raw feature vectors.
+func rawFeatureEncoder(w io.Writer, val interface{}, _ *[8]byte) error {
+ if v, ok := val.(*RawFeatureVector); ok {
+ // Encode the feature bits as a byte slice without its length
+ // prepended, as that's already taken care of by the TLV record.
+ fv := *v
+ return fv.encode(w, fv.SerializeSize(), 8)
+ }
+
+ return tlv.NewTypeForEncodingErr(val, "lnwire.RawFeatureVector")
+}
+
+// rawFeatureDecoder is a custom TLV decoder for raw feature vectors.
+func rawFeatureDecoder(r io.Reader, val interface{}, _ *[8]byte,
+ l uint64) error {
+
+ if v, ok := val.(*RawFeatureVector); ok {
+ fv := NewRawFeatureVector()
+ if err := fv.decode(r, int(l), 8); err != nil {
+ return err
+ }
+ *v = *fv
+
+ return nil
+ }
+
+ return tlv.NewTypeForEncodingErr(val, "lnwire.RawFeatureVector")
+}
+
+// FeatureVector represents a set of enabled features. The set stores
+// information on enabled flags and metadata about the feature names. A feature
+// vector is serializable to a compact byte representation that is included in
+// Lightning network messages.
+type FeatureVector struct {
+ *RawFeatureVector
+ featureNames map[FeatureBit]string
+}
+
+// NewFeatureVector constructs a new FeatureVector from a raw feature vector
+// and mapping of feature definitions. If the feature vector argument is nil, a
+// new one will be constructed with no enabled features.
+func NewFeatureVector(featureVector *RawFeatureVector,
+ featureNames map[FeatureBit]string) *FeatureVector {
+
+ if featureVector == nil {
+ featureVector = NewRawFeatureVector()
+ }
+ return &FeatureVector{
+ RawFeatureVector: featureVector,
+ featureNames: featureNames,
+ }
+}
+
+// EmptyFeatureVector returns a feature vector with no bits set.
+func EmptyFeatureVector() *FeatureVector {
+ return NewFeatureVector(nil, Features)
+}
+
+// Record implements the RecordProducer interface for FeatureVector. Note that
+// it uses a zero-value type is used to produce the record, as we expect this
+// type value to be overwritten when used in generic TLV record production.
+// This allows a single Record function to serve in the many different contexts
+// in which feature vectors are encoded. This record wraps the encoding/
+// decoding for our raw feature vectors so that we can directly parse fully
+// formed feature vector types.
+func (fv *FeatureVector) Record() tlv.Record {
+ return tlv.MakeDynamicRecord(0, fv, fv.sizeFunc,
+ func(w io.Writer, val interface{}, buf *[8]byte) error {
+ if f, ok := val.(*FeatureVector); ok {
+ return rawFeatureEncoder(
+ w, f.RawFeatureVector, buf,
+ )
+ }
+
+ return tlv.NewTypeForEncodingErr(
+ val, "*lnwire.FeatureVector",
+ )
+ },
+ func(r io.Reader, val interface{}, buf *[8]byte,
+ l uint64) error {
+
+ if f, ok := val.(*FeatureVector); ok {
+ features := NewFeatureVector(nil, Features)
+ err := rawFeatureDecoder(
+ r, features.RawFeatureVector, buf, l,
+ )
+ if err != nil {
+ return err
+ }
+
+ *f = *features
+
+ return nil
+ }
+
+ return tlv.NewTypeForDecodingErr(
+ val, "*lnwire.FeatureVector", l, l,
+ )
+ },
+ )
+}
+
+// HasFeature returns whether a particular feature is included in the set. The
+// feature can be seen as set either if the bit is set directly OR the queried
+// bit has the same meaning as its corresponding even/odd bit, which is set
+// instead. The second case is because feature bits are generally assigned in
+// pairs where both the even and odd position represent the same feature.
+func (fv *FeatureVector) HasFeature(feature FeatureBit) bool {
+ return fv.IsSet(feature) ||
+ (fv.isFeatureBitPair(feature) && fv.IsSet(feature^1))
+}
+
+// RequiresFeature returns true if the referenced feature vector *requires*
+// that the given required bit be set. This method can be used with both
+// optional and required feature bits as a parameter.
+func (fv *FeatureVector) RequiresFeature(feature FeatureBit) bool {
+ // If we weren't passed a required feature bit, then we'll flip the
+ // lowest bit to query for the required version of the feature. This
+ // lets callers pass in both the optional and required bits.
+ if !feature.IsRequired() {
+ feature ^= 1
+ }
+
+ return fv.IsSet(feature)
+}
+
+// UnknownRequiredFeatures returns a list of feature bits set in the vector
+// that are unknown and in an even bit position. Feature bits with an even
+// index must be known to a node receiving the feature vector in a message.
+func (fv *FeatureVector) UnknownRequiredFeatures() []FeatureBit {
+ var unknown []FeatureBit
+ for feature := range fv.features {
+ if feature%2 == 0 && !fv.IsKnown(feature) {
+ unknown = append(unknown, feature)
+ }
+ }
+ return unknown
+}
+
+// UnknownFeatures returns a boolean if a feature vector contains *any*
+// unknown features (even if they are odd).
+func (fv *FeatureVector) UnknownFeatures() bool {
+ for feature := range fv.features {
+ if !fv.IsKnown(feature) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Name returns a string identifier for the feature represented by this bit. If
+// the bit does not represent a known feature, this returns a string indicating
+// as such.
+func (fv *FeatureVector) Name(bit FeatureBit) string {
+ name, known := fv.featureNames[bit]
+ if !known {
+ return "unknown"
+ }
+ return name
+}
+
+// IsKnown returns whether this feature bit represents a known feature.
+func (fv *FeatureVector) IsKnown(bit FeatureBit) bool {
+ _, known := fv.featureNames[bit]
+ return known
+}
+
+// isFeatureBitPair returns whether this feature bit and its corresponding
+// even/odd bit both represent the same feature. This may often be the case as
+// bits are generally assigned in pairs, first being assigned an odd bit
+// position then being promoted to an even bit position once the network is
+// ready.
+func (fv *FeatureVector) isFeatureBitPair(bit FeatureBit) bool {
+ name1, known1 := fv.featureNames[bit]
+ name2, known2 := fv.featureNames[bit^1]
+ return known1 && known2 && name1 == name2
+}
+
+// Features returns the set of raw features contained in the feature vector.
+func (fv *FeatureVector) Features() map[FeatureBit]struct{} {
+ fs := make(map[FeatureBit]struct{}, len(fv.RawFeatureVector.features))
+ for b := range fv.RawFeatureVector.features {
+ fs[b] = struct{}{}
+ }
+ return fs
+}
+
+// Clone copies a feature vector, carrying over its feature bits. The feature
+// names are not copied.
+func (fv *FeatureVector) Clone() *FeatureVector {
+ features := fv.RawFeatureVector.Clone()
+ return NewFeatureVector(features, fv.featureNames)
+}
diff --git a/payments/db/migration1/lnwire/interfaces.go b/payments/db/migration1/lnwire/interfaces.go
new file mode 100644
index 0000000..115b07d
--- /dev/null
+++ b/payments/db/migration1/lnwire/interfaces.go
@@ -0,0 +1,174 @@
+package lnwire
+
+import (
+ "fmt"
+
+ "github.com/btcsuite/btcd/chaincfg/chainhash"
+)
+
+// GossipVersion is a version number that describes the version of the
+// gossip protocol that a gossip message was gossiped on.
+type GossipVersion uint8
+
+const (
+ // GossipVersion1 is the initial version of the gossip protocol as
+ // defined in BOLT 7. This version of the protocol can only gossip P2WSH
+ // channels and makes use of ECDSA signatures.
+ GossipVersion1 GossipVersion = 1
+
+ // GossipVersion2 is the newest version of the gossip protocol. This
+ // version adds support for P2TR channels and makes use of Schnorr
+ // signatures. The BOLT number is TBD.
+ GossipVersion2 GossipVersion = 2
+)
+
+// String returns a string representation of the protocol version.
+func (v GossipVersion) String() string {
+ return fmt.Sprintf("V%d", v)
+}
+
+// GossipMessage is an interface that must be satisfied by all messages that are
+// part of the gossip protocol.
+type GossipMessage interface {
+ // GossipVersion returns the version of the gossip protocol that a
+ // message is part of.
+ GossipVersion() GossipVersion
+}
+
+// AnnounceSignatures is an interface that represents a message used to
+// exchange signatures of a ChannelAnnouncment message during the funding flow.
+type AnnounceSignatures interface {
+ // SCID returns the ShortChannelID of the channel.
+ SCID() ShortChannelID
+
+ // ChanID returns the ChannelID identifying the channel.
+ ChanID() ChannelID
+
+ Message
+ GossipMessage
+}
+
+// ChannelAnnouncement is an interface that must be satisfied by any message
+// used to announce and prove the existence of a channel.
+type ChannelAnnouncement interface {
+ // SCID returns the short channel ID of the channel.
+ SCID() ShortChannelID
+
+ // GetChainHash returns the hash of the chain which this channel's
+ // funding transaction is confirmed in.
+ GetChainHash() chainhash.Hash
+
+ // Node1KeyBytes returns the bytes representing the public key of node
+ // 1 in the channel.
+ Node1KeyBytes() [33]byte
+
+ // Node2KeyBytes returns the bytes representing the public key of node
+ // 2 in the channel.
+ Node2KeyBytes() [33]byte
+
+ Message
+ GossipMessage
+}
+
+// CompareResult represents the result after comparing two things.
+type CompareResult uint8
+
+const (
+ // LessThan indicates that base object is less than the object it was
+ // compared to.
+ LessThan CompareResult = iota
+
+ // EqualTo indicates that the base object is equal to the object it was
+ // compared to.
+ EqualTo
+
+ // GreaterThan indicates that base object is greater than the object it
+ // was compared to.
+ GreaterThan
+)
+
+// ChannelUpdate is an interface that describes a message used to update the
+// forwarding rules of a channel.
+type ChannelUpdate interface {
+ // SCID returns the ShortChannelID of the channel that the update
+ // applies to.
+ SCID() ShortChannelID
+
+ // IsNode1 is true if the update was produced by node 1 of the channel
+ // peers. Node 1 is the node with the lexicographically smaller public
+ // key.
+ IsNode1() bool
+
+ // IsDisabled is true if the update is announcing that the channel
+ // should be considered disabled.
+ IsDisabled() bool
+
+ // GetChainHash returns the hash of the chain that the message is
+ // referring to.
+ GetChainHash() chainhash.Hash
+
+ // ForwardingPolicy returns the set of forwarding constraints of the
+ // update.
+ ForwardingPolicy() *ForwardingPolicy
+
+ // CmpAge can be used to determine if the update is older or newer than
+ // the passed update. It returns LessThan if this update is older than
+ // the passed update, GreaterThan if it is newer and EqualTo if they are
+ // the same age.
+ CmpAge(update ChannelUpdate) (CompareResult, error)
+
+ // SetDisabledFlag can be used to adjust the disabled flag of an update.
+ SetDisabledFlag(bool)
+
+ // SetSCID can be used to overwrite the SCID of the update.
+ SetSCID(scid ShortChannelID)
+
+ Message
+ GossipMessage
+}
+
+// NodeAnnouncement is an interface that must be satisfied by any message used
+// to announce the existence of a node.
+type NodeAnnouncement interface {
+ // NodePub returns the identity public key of the node.
+ NodePub() [33]byte
+
+ // NodeFeatures returns the set of features supported by the node.
+ NodeFeatures() *FeatureVector
+
+ // TimestampDesc returns a human-readable description of the
+ // timestamp of the announcement.
+ TimestampDesc() string
+
+ Message
+ GossipMessage
+}
+
+// ForwardingPolicy defines the set of forwarding constraints advertised in a
+// ChannelUpdate message.
+type ForwardingPolicy struct {
+ // TimeLockDelta is the minimum number of blocks that the node requires
+ // to be added to the expiry of HTLCs. This is a security parameter
+ // determined by the node operator. This value represents the required
+ // gap between the time locks of the incoming and outgoing HTLC's set
+ // to this node.
+ TimeLockDelta uint16
+
+ // BaseFee is the base fee that must be used for incoming HTLC's to
+ // this particular channel. This value will be tacked onto the required
+ // for a payment independent of the size of the payment.
+ BaseFee MilliSatoshi
+
+ // FeeRate is the fee rate that will be charged per millionth of a
+ // satoshi.
+ FeeRate MilliSatoshi
+
+ // HtlcMinimumMsat is the minimum HTLC value which will be accepted.
+ MinHTLC MilliSatoshi
+
+ // HasMaxHTLC is true if the MaxHTLC field is provided in the update.
+ HasMaxHTLC bool
+
+ // HtlcMaximumMsat is the maximum HTLC value which will be accepted.
+ MaxHTLC MilliSatoshi
+}
diff --git a/payments/db/migration1/lnwire/lnwire.go b/payments/db/migration1/lnwire/lnwire.go
new file mode 100644
index 0000000..09057da
--- /dev/null
+++ b/payments/db/migration1/lnwire/lnwire.go
@@ -0,0 +1,426 @@
+package lnwire
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/btcsuite/btcd/btcutil"
+)
+
+const (
+ // MaxSliceLength is the maximum allowed length for any opaque byte
+ // slices in the wire protocol.
+ MaxSliceLength = 65535
+
+ // MaxMsgBody is the largest payload any message is allowed to provide.
+ // This is two less than the MaxSliceLength as each message has a 2
+ // byte type that precedes the message body.
+ MaxMsgBody = 65533
+)
+
+// PkScript is simple type definition which represents a raw serialized public
+// key script.
+type PkScript []byte
+
+// WriteElement is a one-stop shop to write the big endian representation of
+// any element which is to be serialized for the wire protocol.
+//
+// TODO(yy): rm this method once we finish dereferencing it from other
+// packages.
+func WriteElement(w *bytes.Buffer, element interface{}) error {
+ switch e := element.(type) {
+ case uint8:
+ var b [1]byte
+ b[0] = e
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case uint16:
+ var b [2]byte
+ binary.BigEndian.PutUint16(b[:], e)
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case ChanUpdateMsgFlags:
+ var b [1]byte
+ b[0] = uint8(e)
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case ChanUpdateChanFlags:
+ var b [1]byte
+ b[0] = uint8(e)
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case MilliSatoshi:
+ var b [8]byte
+ binary.BigEndian.PutUint64(b[:], uint64(e))
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case btcutil.Amount:
+ var b [8]byte
+ binary.BigEndian.PutUint64(b[:], uint64(e))
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case uint32:
+ var b [4]byte
+ binary.BigEndian.PutUint32(b[:], e)
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case uint64:
+ var b [8]byte
+ binary.BigEndian.PutUint64(b[:], e)
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case *btcec.PublicKey:
+ if e == nil {
+ return fmt.Errorf("cannot write nil pubkey")
+ }
+
+ var b [33]byte
+ serializedPubkey := e.SerializeCompressed()
+ copy(b[:], serializedPubkey)
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case []Sig:
+ var b [2]byte
+ numSigs := uint16(len(e))
+ binary.BigEndian.PutUint16(b[:], numSigs)
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ for _, sig := range e {
+ if err := WriteElement(w, sig); err != nil {
+ return err
+ }
+ }
+
+ case Sig:
+ // Write buffer
+ if _, err := w.Write(e.bytes[:]); err != nil {
+ return err
+ }
+
+ case ErrorData:
+ var l [2]byte
+ binary.BigEndian.PutUint16(l[:], uint16(len(e)))
+ if _, err := w.Write(l[:]); err != nil {
+ return err
+ }
+
+ if _, err := w.Write(e[:]); err != nil {
+ return err
+ }
+
+ case [33]byte:
+ if _, err := w.Write(e[:]); err != nil {
+ return err
+ }
+
+ case []byte:
+ if _, err := w.Write(e[:]); err != nil {
+ return err
+ }
+
+ case *RawFeatureVector:
+ if e == nil {
+ return fmt.Errorf("cannot write nil feature vector")
+ }
+
+ if err := e.Encode(w); err != nil {
+ return err
+ }
+
+ case ChannelID:
+ if _, err := w.Write(e[:]); err != nil {
+ return err
+ }
+
+ case FailCode:
+ if err := WriteElement(w, uint16(e)); err != nil {
+ return err
+ }
+
+ case ShortChannelID:
+ // Check that field fit in 3 bytes and write the blockHeight
+ if e.BlockHeight > ((1 << 24) - 1) {
+ return errors.New("block height should fit in 3 bytes")
+ }
+
+ var blockHeight [4]byte
+ binary.BigEndian.PutUint32(blockHeight[:], e.BlockHeight)
+
+ if _, err := w.Write(blockHeight[1:]); err != nil {
+ return err
+ }
+
+ // Check that field fit in 3 bytes and write the txIndex
+ if e.TxIndex > ((1 << 24) - 1) {
+ return errors.New("tx index should fit in 3 bytes")
+ }
+
+ var txIndex [4]byte
+ binary.BigEndian.PutUint32(txIndex[:], e.TxIndex)
+ if _, err := w.Write(txIndex[1:]); err != nil {
+ return err
+ }
+
+ // Write the txPosition
+ var txPosition [2]byte
+ binary.BigEndian.PutUint16(txPosition[:], e.TxPosition)
+ if _, err := w.Write(txPosition[:]); err != nil {
+ return err
+ }
+
+ case bool:
+ var b [1]byte
+ if e {
+ b[0] = 1
+ }
+ if _, err := w.Write(b[:]); err != nil {
+ return err
+ }
+
+ case ExtraOpaqueData:
+ return e.Encode(w)
+
+ default:
+ return fmt.Errorf("unknown type in WriteElement: %T", e)
+ }
+
+ return nil
+}
+
+// WriteElements is writes each element in the elements slice to the passed
+// buffer using WriteElement.
+//
+// TODO(yy): rm this method once we finish dereferencing it from other
+// packages.
+func WriteElements(buf *bytes.Buffer, elements ...interface{}) error {
+ for _, element := range elements {
+ err := WriteElement(buf, element)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// ReadElement is a one-stop utility function to deserialize any datastructure
+// encoded using the serialization format of lnwire.
+func ReadElement(r io.Reader, element interface{}) error {
+ var err error
+ switch e := element.(type) {
+ case *bool:
+ var b [1]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+
+ if b[0] == 1 {
+ *e = true
+ }
+
+ case *uint8:
+ var b [1]uint8
+ if _, err := r.Read(b[:]); err != nil {
+ return err
+ }
+ *e = b[0]
+
+ case *uint16:
+ var b [2]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+ *e = binary.BigEndian.Uint16(b[:])
+
+ case *ChanUpdateMsgFlags:
+ var b [1]uint8
+ if _, err := r.Read(b[:]); err != nil {
+ return err
+ }
+ *e = ChanUpdateMsgFlags(b[0])
+
+ case *ChanUpdateChanFlags:
+ var b [1]uint8
+ if _, err := r.Read(b[:]); err != nil {
+ return err
+ }
+ *e = ChanUpdateChanFlags(b[0])
+
+ case *uint32:
+ var b [4]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+ *e = binary.BigEndian.Uint32(b[:])
+
+ case *uint64:
+ var b [8]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+ *e = binary.BigEndian.Uint64(b[:])
+
+ case *MilliSatoshi:
+ var b [8]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+ *e = MilliSatoshi(int64(binary.BigEndian.Uint64(b[:])))
+
+ case *btcutil.Amount:
+ var b [8]byte
+ if _, err := io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+ *e = btcutil.Amount(int64(binary.BigEndian.Uint64(b[:])))
+
+ case **btcec.PublicKey:
+ var b [btcec.PubKeyBytesLenCompressed]byte
+ if _, err = io.ReadFull(r, b[:]); err != nil {
+ return err
+ }
+
+ pubKey, err := btcec.ParsePubKey(b[:])
+ if err != nil {
+ return err
+ }
+ *e = pubKey
+
+ case *RawFeatureVector:
+ f := NewRawFeatureVector()
+ err = f.Decode(r)
+ if err != nil {
+ return err
+ }
+ *e = *f
+
+ case **RawFeatureVector:
+ f := NewRawFeatureVector()
+ err = f.Decode(r)
+ if err != nil {
+ return err
+ }
+ *e = f
+
+ case *[]Sig:
+ var l [2]byte
+ if _, err := io.ReadFull(r, l[:]); err != nil {
+ return err
+ }
+ numSigs := binary.BigEndian.Uint16(l[:])
+
+ var sigs []Sig
+ if numSigs > 0 {
+ sigs = make([]Sig, numSigs)
+ for i := 0; i < int(numSigs); i++ {
+ if err := ReadElement(r, &sigs[i]); err != nil {
+ return err
+ }
+ }
+ }
+ *e = sigs
+
+ case *Sig:
+ if _, err := io.ReadFull(r, e.bytes[:]); err != nil {
+ return err
+ }
+
+ case *ErrorData:
+ var l [2]byte
+ if _, err := io.ReadFull(r, l[:]); err != nil {
+ return err
+ }
+ errorLen := binary.BigEndian.Uint16(l[:])
+
+ *e = ErrorData(make([]byte, errorLen))
+ if _, err := io.ReadFull(r, *e); err != nil {
+ return err
+ }
+
+ case *[33]byte:
+ if _, err := io.ReadFull(r, e[:]); err != nil {
+ return err
+ }
+
+ case []byte:
+ if _, err := io.ReadFull(r, e); err != nil {
+ return err
+ }
+
+ case *FailCode:
+ if err := ReadElement(r, (*uint16)(e)); err != nil {
+ return err
+ }
+
+ case *ChannelID:
+ if _, err := io.ReadFull(r, e[:]); err != nil {
+ return err
+ }
+
+ case *ShortChannelID:
+ var blockHeight [4]byte
+ if _, err = io.ReadFull(r, blockHeight[1:]); err != nil {
+ return err
+ }
+
+ var txIndex [4]byte
+ if _, err = io.ReadFull(r, txIndex[1:]); err != nil {
+ return err
+ }
+
+ var txPosition [2]byte
+ if _, err = io.ReadFull(r, txPosition[:]); err != nil {
+ return err
+ }
+
+ *e = ShortChannelID{
+ BlockHeight: binary.BigEndian.Uint32(blockHeight[:]),
+ TxIndex: binary.BigEndian.Uint32(txIndex[:]),
+ TxPosition: binary.BigEndian.Uint16(txPosition[:]),
+ }
+
+ case *ExtraOpaqueData:
+ return e.Decode(r)
+
+ default:
+ return fmt.Errorf("unknown type in ReadElement: %T", e)
+ }
+
+ return nil
+}
+
+// ReadElements deserializes a variable number of elements into the passed
+// io.Reader, with each element being deserialized according to the ReadElement
+// function.
+func ReadElements(r io.Reader, elements ...interface{}) error {
+ for _, element := range elements {
+ err := ReadElement(r, element)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/payments/db/migration1/lnwire/message.go b/payments/db/migration1/lnwire/message.go
new file mode 100644
index 0000000..def762f
--- /dev/null
+++ b/payments/db/migration1/lnwire/message.go
@@ -0,0 +1,323 @@
+// Copyright (c) 2013-2017 The btcsuite developers
+// Copyright (c) 2015-2016 The Decred developers
+// code derived from https://github .com/btcsuite/btcd/blob/master/wire/message.go
+// Copyright (C) 2015-2022 The Lightning Network Developers
+
+package lnwire
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "io"
+)
+
+// MessageTypeSize is the size in bytes of the message type field in the header
+// of all messages.
+const MessageTypeSize = 2
+
+// MessageType is the unique 2 byte big-endian integer that indicates the type
+// of message on the wire. All messages have a very simple header which
+// consists simply of 2-byte message type. We omit a length field, and checksum
+// as the Lightning Protocol is intended to be encapsulated within a
+// confidential+authenticated cryptographic messaging protocol.
+type MessageType uint16
+
+// The currently defined message types within this current version of the
+// Lightning protocol.
+const (
+ MsgWarning MessageType = 1
+ MsgStfu = 2
+ MsgInit = 16
+ MsgError = 17
+ MsgPing = 18
+ MsgPong = 19
+ MsgOpenChannel = 32
+ MsgAcceptChannel = 33
+ MsgFundingCreated = 34
+ MsgFundingSigned = 35
+ MsgChannelReady = 36
+ MsgShutdown = 38
+ MsgClosingSigned = 39
+ MsgClosingComplete = 40
+ MsgClosingSig = 41
+ MsgDynPropose = 111
+ MsgDynAck = 113
+ MsgDynReject = 115
+ MsgDynCommit = 117
+ MsgUpdateAddHTLC = 128
+ MsgUpdateFulfillHTLC = 130
+ MsgUpdateFailHTLC = 131
+ MsgCommitSig = 132
+ MsgRevokeAndAck = 133
+ MsgUpdateFee = 134
+ MsgUpdateFailMalformedHTLC = 135
+ MsgChannelReestablish = 136
+ MsgChannelAnnouncement = 256
+ MsgNodeAnnouncement = 257
+ MsgChannelUpdate = 258
+ MsgAnnounceSignatures = 259
+ MsgAnnounceSignatures2 = 260
+ MsgQueryShortChanIDs = 261
+ MsgReplyShortChanIDsEnd = 262
+ MsgQueryChannelRange = 263
+ MsgReplyChannelRange = 264
+ MsgGossipTimestampRange = 265
+ MsgChannelAnnouncement2 = 267
+ MsgNodeAnnouncement2 = 269
+ MsgChannelUpdate2 = 271
+ MsgOnionMessage = 513
+ MsgKickoffSig = 777
+
+ // MsgEnd defines the end of the official message range of the protocol.
+ // If a new message is added beyond this message, then this should be
+ // modified.
+ MsgEnd = 778
+)
+
+// IsChannelUpdate is a filter function that discerns channel update messages
+// from the other messages in the Lightning Network Protocol.
+func (t MessageType) IsChannelUpdate() bool {
+ switch t {
+ case MsgUpdateAddHTLC:
+ return true
+ case MsgUpdateFulfillHTLC:
+ return true
+ case MsgUpdateFailHTLC:
+ return true
+ case MsgUpdateFailMalformedHTLC:
+ return true
+ case MsgUpdateFee:
+ return true
+ default:
+ return false
+ }
+}
+
+// ErrorEncodeMessage is used when failed to encode the message payload.
+func ErrorEncodeMessage(err error) error {
+ return fmt.Errorf("failed to encode message to buffer, got %w", err)
+}
+
+// ErrorWriteMessageType is used when failed to write the message type.
+func ErrorWriteMessageType(err error) error {
+ return fmt.Errorf("failed to write message type, got %w", err)
+}
+
+// ErrorPayloadTooLarge is used when the payload size exceeds the
+// MaxMsgBody.
+func ErrorPayloadTooLarge(size int) error {
+ return fmt.Errorf(
+ "message payload is too large - encoded %d bytes, "+
+ "but maximum message payload is %d bytes",
+ size, MaxMsgBody,
+ )
+}
+
+// String return the string representation of message type.
+func (t MessageType) String() string {
+ switch t {
+ case MsgWarning:
+ return "Warning"
+ case MsgStfu:
+ return "Stfu"
+ case MsgInit:
+ return "Init"
+ case MsgOpenChannel:
+ return "MsgOpenChannel"
+ case MsgAcceptChannel:
+ return "MsgAcceptChannel"
+ case MsgFundingCreated:
+ return "MsgFundingCreated"
+ case MsgFundingSigned:
+ return "MsgFundingSigned"
+ case MsgChannelReady:
+ return "ChannelReady"
+ case MsgShutdown:
+ return "Shutdown"
+ case MsgClosingSigned:
+ return "ClosingSigned"
+ case MsgDynPropose:
+ return "DynPropose"
+ case MsgDynAck:
+ return "DynAck"
+ case MsgDynReject:
+ return "DynReject"
+ case MsgDynCommit:
+ return "DynCommit"
+ case MsgKickoffSig:
+ return "KickoffSig"
+ case MsgUpdateAddHTLC:
+ return "UpdateAddHTLC"
+ case MsgUpdateFailHTLC:
+ return "UpdateFailHTLC"
+ case MsgUpdateFulfillHTLC:
+ return "UpdateFulfillHTLC"
+ case MsgCommitSig:
+ return "CommitSig"
+ case MsgRevokeAndAck:
+ return "RevokeAndAck"
+ case MsgUpdateFailMalformedHTLC:
+ return "UpdateFailMalformedHTLC"
+ case MsgChannelReestablish:
+ return "ChannelReestablish"
+ case MsgError:
+ return "Error"
+ case MsgChannelAnnouncement:
+ return "ChannelAnnouncement"
+ case MsgChannelUpdate:
+ return "ChannelUpdate"
+ case MsgNodeAnnouncement:
+ return "NodeAnnouncement1"
+ case MsgPing:
+ return "Ping"
+ case MsgAnnounceSignatures:
+ return "AnnounceSignatures"
+ case MsgPong:
+ return "Pong"
+ case MsgUpdateFee:
+ return "UpdateFee"
+ case MsgQueryShortChanIDs:
+ return "QueryShortChanIDs"
+ case MsgReplyShortChanIDsEnd:
+ return "ReplyShortChanIDsEnd"
+ case MsgQueryChannelRange:
+ return "QueryChannelRange"
+ case MsgReplyChannelRange:
+ return "ReplyChannelRange"
+ case MsgGossipTimestampRange:
+ return "GossipTimestampRange"
+ case MsgClosingComplete:
+ return "ClosingComplete"
+ case MsgClosingSig:
+ return "ClosingSig"
+ case MsgAnnounceSignatures2:
+ return "MsgAnnounceSignatures2"
+ case MsgChannelAnnouncement2:
+ return "ChannelAnnouncement2"
+ case MsgNodeAnnouncement2:
+ return "NodeAnnouncement2"
+ case MsgChannelUpdate2:
+ return "ChannelUpdate2"
+ case MsgOnionMessage:
+ return "OnionMessage"
+ default:
+ return "<unknown>"
+ }
+}
+
+// UnknownMessage is an implementation of the error interface that allows the
+// creation of an error in response to an unknown message.
+type UnknownMessage struct {
+ messageType MessageType
+}
+
+// Error returns a human readable string describing the error.
+//
+// This is part of the error interface.
+func (u *UnknownMessage) Error() string {
+ return fmt.Sprintf("unable to parse message of unknown type: %v",
+ u.messageType)
+}
+
+// Serializable is an interface which defines a lightning wire serializable
+// object.
+type Serializable interface {
+ // Decode reads the bytes stream and converts it to the object.
+ Decode(io.Reader, uint32) error
+
+ // Encode converts object to the bytes stream and write it into the
+ // write buffer.
+ Encode(*bytes.Buffer, uint32) error
+}
+
+// Message is an interface that defines a lightning wire protocol message. The
+// interface is general in order to allow implementing types full control over
+// the representation of its data.
+type Message interface {
+ Serializable
+ MsgType() MessageType
+}
+
+// LinkUpdater is an interface implemented by most messages in BOLT 2 that are
+// allowed to update the channel state.
+type LinkUpdater interface {
+ // All LinkUpdater messages are messages and so we embed the interface
+ // so that we can treat it as a message if all we know about it is that
+ // it is a LinkUpdater message.
+ Message
+
+ // TargetChanID returns the channel id of the link for which this
+ // message is intended.
+ TargetChanID() ChannelID
+}
+
+// SizeableMessage is an interface that extends the base Message interface with
+// a method to calculate the serialized size of a message.
+type SizeableMessage interface {
+ Message
+
+ // SerializedSize returns the serialized size of the message in bytes.
+ // The returned size includes the message type header bytes.
+ SerializedSize() (uint32, error)
+}
+
+// MessageSerializedSize calculates the serialized size of a message in bytes.
+// This is a helper function that can be used by all message types to implement
+// the SerializedSize method.
+func MessageSerializedSize(msg Message) (uint32, error) {
+ var buf bytes.Buffer
+
+ // Encode the message to the buffer.
+ if err := msg.Encode(&buf, 0); err != nil {
+ return 0, err
+ }
+
+ // Add the size of the message type.
+ return uint32(buf.Len()) + MessageTypeSize, nil
+}
+
+// WriteMessage writes a lightning Message to a buffer including the necessary
+// header information and returns the number of bytes written. If any error is
+// encountered, the buffer passed will be reset to its original state since we
+// don't want any broken bytes left. In other words, no bytes will be written
+// if there's an error. Either all or none of the message bytes will be written
+// to the buffer.
+//
+// NOTE: this method is not concurrent safe.
+func WriteMessage(buf *bytes.Buffer, msg Message, pver uint32) (int, error) {
+ // Record the size of the bytes already written in buffer.
+ oldByteSize := buf.Len()
+
+ // cleanBrokenBytes is a helper closure that helps reset the buffer to
+ // its original state. It truncates all the bytes written in current
+ // scope.
+ var cleanBrokenBytes = func(b *bytes.Buffer) int {
+ b.Truncate(oldByteSize)
+ return 0
+ }
+
+ // Write the message type.
+ var mType [2]byte
+ binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType()))
+ msgTypeBytes, err := buf.Write(mType[:])
+ if err != nil {
+ return cleanBrokenBytes(buf), ErrorWriteMessageType(err)
+ }
+
+ // Use the write buffer to encode our message.
+ if err := msg.Encode(buf, pver); err != nil {
+ return cleanBrokenBytes(buf), ErrorEncodeMessage(err)
+ }
+
+ // Enforce maximum overall message payload. The write buffer now has
+ // the size of len(originalBytes) + len(payload) + len(type). We want
+ // to enforce the payload here, so we subtract it by the length of the
+ // type and old bytes.
+ lenp := buf.Len() - oldByteSize - msgTypeBytes
+ if lenp > MaxMsgBody {
+ return cleanBrokenBytes(buf), ErrorPayloadTooLarge(lenp)
+ }
+
+ return buf.Len() - oldByteSize, nil
+}
diff --git a/payments/db/migration1/lnwire/msat.go b/payments/db/migration1/lnwire/msat.go
new file mode 100644
index 0000000..7d6d581
--- /dev/null
+++ b/payments/db/migration1/lnwire/msat.go
@@ -0,0 +1,92 @@
+package lnwire
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/btcsuite/btcd/btcutil"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+const (
+ // mSatScale is a value that's used to scale satoshis to milli-satoshis, and
+ // the other way around.
+ mSatScale uint64 = 1000
+
+ // MaxMilliSatoshi is the maximum number of msats that can be expressed
+ // in this data type.
+ MaxMilliSatoshi = ^MilliSatoshi(0)
+)
+
+// MilliSatoshi are the native unit of the Lightning Network. A milli-satoshi
+// is simply 1/1000th of a satoshi. There are 1000 milli-satoshis in a single
+// satoshi. Within the network, all HTLC payments are denominated in
+// milli-satoshis. As milli-satoshis aren't deliverable on the native
+// blockchain, before settling to broadcasting, the values are rounded down to
+// the nearest satoshi.
+type MilliSatoshi uint64
+
+// NewMSatFromSatoshis creates a new MilliSatoshi instance from a target amount
+// of satoshis.
+func NewMSatFromSatoshis(sat btcutil.Amount) MilliSatoshi {
+ return MilliSatoshi(uint64(sat) * mSatScale)
+}
+
+// ToBTC converts the target MilliSatoshi amount to its corresponding value
+// when expressed in BTC.
+func (m MilliSatoshi) ToBTC() float64 {
+ sat := m.ToSatoshis()
+ return sat.ToBTC()
+}
+
+// ToSatoshis converts the target MilliSatoshi amount to satoshis. Simply, this
+// sheds a factor of 1000 from the mSAT amount in order to convert it to SAT.
+func (m MilliSatoshi) ToSatoshis() btcutil.Amount {
+ return btcutil.Amount(uint64(m) / mSatScale)
+}
+
+// String returns the string representation of the mSAT amount.
+func (m MilliSatoshi) String() string {
+ return fmt.Sprintf("%v mSAT", uint64(m))
+}
+
+// TODO(roasbeef): extend with arithmetic operations?
+
+// Record returns a TLV record that can be used to encode/decode a MilliSatoshi
+// to/from a TLV stream.
+func (m *MilliSatoshi) Record() tlv.Record {
+ msat := uint64(*m)
+
+ return tlv.MakeDynamicRecord(
+ 0, m, tlv.SizeBigSize(&msat), encodeMilliSatoshis,
+ decodeMilliSatoshis,
+ )
+}
+
+func encodeMilliSatoshis(w io.Writer, val interface{}, buf *[8]byte) error {
+ if v, ok := val.(*MilliSatoshi); ok {
+ bigSize := uint64(*v)
+
+ return tlv.EBigSize(w, &bigSize, buf)
+ }
+
+ return tlv.NewTypeForEncodingErr(val, "lnwire.MilliSatoshi")
+}
+
+func decodeMilliSatoshis(r io.Reader, val interface{}, buf *[8]byte,
+ l uint64) error {
+
+ if v, ok := val.(*MilliSatoshi); ok {
+ var bigSize uint64
+ err := tlv.DBigSize(r, &bigSize, buf, l)
+ if err != nil {
+ return err
+ }
+
+ *v = MilliSatoshi(bigSize)
+
+ return nil
+ }
+
+ return tlv.NewTypeForDecodingErr(val, "lnwire.MilliSatoshi", l, l)
+}
diff --git a/payments/db/migration1/lnwire/onion_error.go b/payments/db/migration1/lnwire/onion_error.go
new file mode 100644
index 0000000..9cc115f
--- /dev/null
+++ b/payments/db/migration1/lnwire/onion_error.go
@@ -0,0 +1,1546 @@
+package lnwire
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/sha256"
+ "encoding/binary"
+ "fmt"
+ "io"
+
+ "github.com/davecgh/go-spew/spew"
+ "github.com/lightningnetwork/lnd/fn/v2"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+// FailureMessage represents the onion failure object identified by its unique
+// failure code.
+type FailureMessage interface {
+ // Code returns a failure code describing the exact nature of the
+ // error.
+ Code() FailCode
+
+ // Error returns a human readable string describing the error. With
+ // this method, the FailureMessage interface meets the built-in error
+ // interface.
+ Error() string
+}
+
+// FailureMessageLength is the size of the failure message plus the size of
+// padding. The FailureMessage message should always be EXACTLY this size.
+const FailureMessageLength = 256
+
+const (
+ // FlagBadOnion error flag describes an unparsable, encrypted by
+ // previous node.
+ FlagBadOnion FailCode = 0x8000
+
+ // FlagPerm error flag indicates a permanent failure.
+ FlagPerm FailCode = 0x4000
+
+ // FlagNode error flag indicates a node failure.
+ FlagNode FailCode = 0x2000
+
+ // FlagUpdate error flag indicates a new channel update is enclosed
+ // within the error.
+ FlagUpdate FailCode = 0x1000
+)
+
+// FailCode specifies the precise reason that an upstream HTLC was canceled.
+// Each UpdateFailHTLC message carries a FailCode which is to be passed
+// backwards, encrypted at each step back to the source of the HTLC within the
+// route.
+type FailCode uint16
+
+// The currently defined onion failure types within this current version of the
+// Lightning protocol.
+const (
+ CodeNone FailCode = 0
+ CodeInvalidRealm = FlagBadOnion | 1
+ CodeTemporaryNodeFailure = FlagNode | 2
+ CodePermanentNodeFailure = FlagPerm | FlagNode | 2
+ CodeRequiredNodeFeatureMissing = FlagPerm | FlagNode | 3
+ CodeInvalidOnionVersion = FlagBadOnion | FlagPerm | 4
+ CodeInvalidOnionHmac = FlagBadOnion | FlagPerm | 5
+ CodeInvalidOnionKey = FlagBadOnion | FlagPerm | 6
+ CodeTemporaryChannelFailure = FlagUpdate | 7
+ CodePermanentChannelFailure = FlagPerm | 8
+ CodeRequiredChannelFeatureMissing = FlagPerm | 9
+ CodeUnknownNextPeer = FlagPerm | 10
+ CodeAmountBelowMinimum = FlagUpdate | 11
+ CodeFeeInsufficient = FlagUpdate | 12
+ CodeIncorrectCltvExpiry = FlagUpdate | 13
+ CodeExpiryTooSoon = FlagUpdate | 14
+ CodeChannelDisabled = FlagUpdate | 20
+ CodeIncorrectOrUnknownPaymentDetails = FlagPerm | 15
+ CodeIncorrectPaymentAmount = FlagPerm | 16
+ CodeFinalExpiryTooSoon FailCode = 17
+ CodeFinalIncorrectCltvExpiry FailCode = 18
+ CodeFinalIncorrectHtlcAmount FailCode = 19
+ CodeExpiryTooFar FailCode = 21
+ CodeInvalidOnionPayload = FlagPerm | 22
+ CodeMPPTimeout FailCode = 23
+ CodeInvalidBlinding = FlagBadOnion | FlagPerm | 24 //nolint:ll
+)
+
+// String returns the string representation of the failure code.
+func (c FailCode) String() string {
+ switch c {
+ case CodeInvalidRealm:
+ return "InvalidRealm"
+
+ case CodeTemporaryNodeFailure:
+ return "TemporaryNodeFailure"
+
+ case CodePermanentNodeFailure:
+ return "PermanentNodeFailure"
+
+ case CodeRequiredNodeFeatureMissing:
+ return "RequiredNodeFeatureMissing"
+
+ case CodeInvalidOnionVersion:
+ return "InvalidOnionVersion"
+
+ case CodeInvalidOnionHmac:
+ return "InvalidOnionHmac"
+
+ case CodeInvalidOnionKey:
+ return "InvalidOnionKey"
+
+ case CodeTemporaryChannelFailure:
+ return "TemporaryChannelFailure"
+
+ case CodePermanentChannelFailure:
+ return "PermanentChannelFailure"
+
+ case CodeRequiredChannelFeatureMissing:
+ return "RequiredChannelFeatureMissing"
+
+ case CodeUnknownNextPeer:
+ return "UnknownNextPeer"
+
+ case CodeAmountBelowMinimum:
+ return "AmountBelowMinimum"
+
+ case CodeFeeInsufficient:
+ return "FeeInsufficient"
+
+ case CodeIncorrectCltvExpiry:
+ return "IncorrectCltvExpiry"
+
+ case CodeIncorrectPaymentAmount:
+ return "IncorrectPaymentAmount"
+
+ case CodeExpiryTooSoon:
+ return "ExpiryTooSoon"
+
+ case CodeChannelDisabled:
+ return "ChannelDisabled"
+
+ case CodeIncorrectOrUnknownPaymentDetails:
+ return "IncorrectOrUnknownPaymentDetails"
+
+ case CodeFinalExpiryTooSoon:
+ return "FinalExpiryTooSoon"
+
+ case CodeFinalIncorrectCltvExpiry:
+ return "FinalIncorrectCltvExpiry"
+
+ case CodeFinalIncorrectHtlcAmount:
+ return "FinalIncorrectHtlcAmount"
+
+ case CodeExpiryTooFar:
+ return "ExpiryTooFar"
+
+ case CodeInvalidOnionPayload:
+ return "InvalidOnionPayload"
+
+ case CodeMPPTimeout:
+ return "MPPTimeout"
+
+ case CodeInvalidBlinding:
+ return "InvalidBlinding"
+
+ default:
+ return "<unknown>"
+ }
+}
+
+// FailInvalidRealm is returned if the realm byte is unknown.
+//
+// NOTE: May be returned by any node in the payment route.
+type FailInvalidRealm struct{}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailInvalidRealm) Error() string {
+ return f.Code().String()
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailInvalidRealm) Code() FailCode {
+ return CodeInvalidRealm
+}
+
+// FailTemporaryNodeFailure is returned if an otherwise unspecified transient
+// error occurs for the entire node.
+//
+// NOTE: May be returned by any node in the payment route.
+type FailTemporaryNodeFailure struct{}
+
+// Code returns the failure unique code.
+// NOTE: Part of the FailureMessage interface.
+func (f *FailTemporaryNodeFailure) Code() FailCode {
+ return CodeTemporaryNodeFailure
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailTemporaryNodeFailure) Error() string {
+ return f.Code().String()
+}
+
+// FailPermanentNodeFailure is returned if an otherwise unspecified permanent
+// error occurs for the entire node.
+//
+// NOTE: May be returned by any node in the payment route.
+type FailPermanentNodeFailure struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailPermanentNodeFailure) Code() FailCode {
+ return CodePermanentNodeFailure
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailPermanentNodeFailure) Error() string {
+ return f.Code().String()
+}
+
+// FailRequiredNodeFeatureMissing is returned if a node has requirement
+// advertised in its node_announcement features which were not present in the
+// onion.
+//
+// NOTE: May be returned by any node in the payment route.
+type FailRequiredNodeFeatureMissing struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailRequiredNodeFeatureMissing) Code() FailCode {
+ return CodeRequiredNodeFeatureMissing
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailRequiredNodeFeatureMissing) Error() string {
+ return f.Code().String()
+}
+
+// FailPermanentChannelFailure is return if an otherwise unspecified permanent
+// error occurs for the outgoing channel (eg. channel (recently).
+//
+// NOTE: May be returned by any node in the payment route.
+type FailPermanentChannelFailure struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailPermanentChannelFailure) Code() FailCode {
+ return CodePermanentChannelFailure
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailPermanentChannelFailure) Error() string {
+ return f.Code().String()
+}
+
+// FailRequiredChannelFeatureMissing is returned if the outgoing channel has a
+// requirement advertised in its channel announcement features which were not
+// present in the onion.
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailRequiredChannelFeatureMissing struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailRequiredChannelFeatureMissing) Code() FailCode {
+ return CodeRequiredChannelFeatureMissing
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailRequiredChannelFeatureMissing) Error() string {
+ return f.Code().String()
+}
+
+// FailUnknownNextPeer is returned if the next peer specified by the onion is
+// not known.
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailUnknownNextPeer struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailUnknownNextPeer) Code() FailCode {
+ return CodeUnknownNextPeer
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailUnknownNextPeer) Error() string {
+ return f.Code().String()
+}
+
+// FailIncorrectPaymentAmount is returned if the amount paid is less than the
+// amount expected, the final node MUST fail the HTLC. If the amount paid is
+// more than twice the amount expected, the final node SHOULD fail the HTLC.
+// This allows the sender to reduce information leakage by altering the amount,
+// without allowing accidental gross overpayment.
+//
+// NOTE: May only be returned by the final node in the path.
+type FailIncorrectPaymentAmount struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailIncorrectPaymentAmount) Code() FailCode {
+ return CodeIncorrectPaymentAmount
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailIncorrectPaymentAmount) Error() string {
+ return f.Code().String()
+}
+
+// FailIncorrectDetails is returned for two reasons:
+//
+// 1) if the payment hash has already been paid, the final node MAY treat the
+// payment hash as unknown, or may succeed in accepting the HTLC. If the
+// payment hash is unknown, the final node MUST fail the HTLC.
+//
+// 2) if the amount paid is less than the amount expected, the final node MUST
+// fail the HTLC. If the amount paid is more than twice the amount expected,
+// the final node SHOULD fail the HTLC. This allows the sender to reduce
+// information leakage by altering the amount, without allowing accidental
+// gross overpayment.
+//
+// NOTE: May only be returned by the final node in the path.
+type FailIncorrectDetails struct {
+ // amount is the value of the extended HTLC.
+ amount MilliSatoshi
+
+ // height is the block height when the htlc was received.
+ height uint32
+
+ // extraOpaqueData contains additional failure message tlv data.
+ extraOpaqueData ExtraOpaqueData
+}
+
+// NewFailIncorrectDetails makes a new instance of the FailIncorrectDetails
+// error bound to the specified HTLC amount and acceptance height.
+func NewFailIncorrectDetails(amt MilliSatoshi,
+ height uint32) *FailIncorrectDetails {
+
+ return &FailIncorrectDetails{
+ amount: amt,
+ height: height,
+ extraOpaqueData: []byte{},
+ }
+}
+
+// Amount is the value of the extended HTLC.
+func (f *FailIncorrectDetails) Amount() MilliSatoshi {
+ return f.amount
+}
+
+// Height is the block height when the htlc was received.
+func (f *FailIncorrectDetails) Height() uint32 {
+ return f.height
+}
+
+// ExtraOpaqueData returns additional failure message tlv data.
+func (f *FailIncorrectDetails) ExtraOpaqueData() ExtraOpaqueData {
+ return f.extraOpaqueData
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailIncorrectDetails) Code() FailCode {
+ return CodeIncorrectOrUnknownPaymentDetails
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailIncorrectDetails) Error() string {
+ return fmt.Sprintf(
+ "%v(amt=%v, height=%v)", CodeIncorrectOrUnknownPaymentDetails,
+ f.amount, f.height,
+ )
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailIncorrectDetails) Decode(r io.Reader, pver uint32) error {
+ err := ReadElement(r, &f.amount)
+ switch {
+ // This is an optional tack on that was added later in the protocol. As
+ // a result, older nodes may not include this value. We'll account for
+ // this by checking for io.EOF here which means that no bytes were read
+ // at all.
+ case err == io.EOF:
+ return nil
+
+ case err != nil:
+ return err
+ }
+
+ // At a later stage, the height field was also tacked on. We need to
+ // check for io.EOF here as well.
+ err = ReadElement(r, &f.height)
+ switch {
+ case err == io.EOF:
+ return nil
+
+ case err != nil:
+ return err
+ }
+
+ return f.extraOpaqueData.Decode(r)
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailIncorrectDetails) Encode(w *bytes.Buffer, pver uint32) error {
+ if err := WriteMilliSatoshi(w, f.amount); err != nil {
+ return err
+ }
+
+ if err := WriteUint32(w, f.height); err != nil {
+ return err
+ }
+
+ return f.extraOpaqueData.Encode(w)
+}
+
+// FailFinalExpiryTooSoon is returned if the cltv_expiry is too low, the final
+// node MUST fail the HTLC.
+//
+// NOTE: May only be returned by the final node in the path.
+type FailFinalExpiryTooSoon struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailFinalExpiryTooSoon) Code() FailCode {
+ return CodeFinalExpiryTooSoon
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailFinalExpiryTooSoon) Error() string {
+ return f.Code().String()
+}
+
+// NewFinalExpiryTooSoon creates new instance of the FailFinalExpiryTooSoon.
+func NewFinalExpiryTooSoon() *FailFinalExpiryTooSoon {
+ return &FailFinalExpiryTooSoon{}
+}
+
+// FailInvalidOnionVersion is returned if the onion version byte is unknown.
+//
+// NOTE: May be returned only by intermediate nodes.
+type FailInvalidOnionVersion struct {
+ // OnionSHA256 hash of the onion blob which haven't been proceeded.
+ OnionSHA256 [sha256.Size]byte
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailInvalidOnionVersion) Error() string {
+ return fmt.Sprintf("InvalidOnionVersion(onion_sha=%x)", f.OnionSHA256[:])
+}
+
+// NewInvalidOnionVersion creates new instance of the FailInvalidOnionVersion.
+func NewInvalidOnionVersion(onion []byte) *FailInvalidOnionVersion {
+ return &FailInvalidOnionVersion{OnionSHA256: sha256.Sum256(onion)}
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailInvalidOnionVersion) Code() FailCode {
+ return CodeInvalidOnionVersion
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailInvalidOnionVersion) Decode(r io.Reader, pver uint32) error {
+ return ReadElement(r, f.OnionSHA256[:])
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailInvalidOnionVersion) Encode(w *bytes.Buffer, pver uint32) error {
+ return WriteBytes(w, f.OnionSHA256[:])
+}
+
+// FailInvalidOnionHmac is return if the onion HMAC is incorrect.
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailInvalidOnionHmac struct {
+ // OnionSHA256 hash of the onion blob which haven't been proceeded.
+ OnionSHA256 [sha256.Size]byte
+}
+
+// NewInvalidOnionHmac creates new instance of the FailInvalidOnionHmac.
+func NewInvalidOnionHmac(onion []byte) *FailInvalidOnionHmac {
+ return &FailInvalidOnionHmac{OnionSHA256: sha256.Sum256(onion)}
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailInvalidOnionHmac) Code() FailCode {
+ return CodeInvalidOnionHmac
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailInvalidOnionHmac) Decode(r io.Reader, pver uint32) error {
+ return ReadElement(r, f.OnionSHA256[:])
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailInvalidOnionHmac) Encode(w *bytes.Buffer, pver uint32) error {
+ return WriteBytes(w, f.OnionSHA256[:])
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailInvalidOnionHmac) Error() string {
+ return fmt.Sprintf("InvalidOnionHMAC(onion_sha=%x)", f.OnionSHA256[:])
+}
+
+// FailInvalidOnionKey is return if the ephemeral key in the onion is
+// unparsable.
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailInvalidOnionKey struct {
+ // OnionSHA256 hash of the onion blob which haven't been proceeded.
+ OnionSHA256 [sha256.Size]byte
+}
+
+// NewInvalidOnionKey creates new instance of the FailInvalidOnionKey.
+func NewInvalidOnionKey(onion []byte) *FailInvalidOnionKey {
+ return &FailInvalidOnionKey{OnionSHA256: sha256.Sum256(onion)}
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailInvalidOnionKey) Code() FailCode {
+ return CodeInvalidOnionKey
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailInvalidOnionKey) Decode(r io.Reader, pver uint32) error {
+ return ReadElement(r, f.OnionSHA256[:])
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailInvalidOnionKey) Encode(w *bytes.Buffer, pver uint32) error {
+ return WriteBytes(w, f.OnionSHA256[:])
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailInvalidOnionKey) Error() string {
+ return fmt.Sprintf("InvalidOnionKey(onion_sha=%x)", f.OnionSHA256[:])
+}
+
+// parseChannelUpdateCompatibilityMode will attempt to parse a channel updated
+// encoded into an onion error payload in two ways. First, we'll try the
+// compatibility oriented version wherein we'll _skip_ the length prefixing on
+// the channel update message. Older versions of c-lighting do this so we'll
+// attempt to parse these messages in order to retain compatibility. If we're
+// unable to pull out a fully valid version, then we'll fall back to the
+// regular parsing mechanism which includes the length prefix an NO type byte.
+func parseChannelUpdateCompatibilityMode(reader io.Reader, length uint16,
+ chanUpdate *ChannelUpdate1, pver uint32) error {
+
+ // Instantiate a LimitReader because there may be additional data
+ // present after the channel update. Without limiting the stream, the
+ // additional data would be interpreted as channel update tlv data.
+ limitReader := io.LimitReader(reader, int64(length))
+
+ r := bufio.NewReader(limitReader)
+
+ // We'll peek out two bytes from the buffer without advancing the
+ // buffer so we can decide how to parse the remainder of it.
+ maybeTypeBytes, err := r.Peek(2)
+ if err != nil {
+ return err
+ }
+
+ // Some nodes well prefix an additional set of bytes in front of their
+ // channel updates. These bytes will _almost_ always be 258 or the type
+ // of the ChannelUpdate message.
+ typeInt := binary.BigEndian.Uint16(maybeTypeBytes)
+ if typeInt == MsgChannelUpdate {
+ // At this point it's likely the case that this is a channel
+ // update message with its type prefixed, so we'll snip off the
+ // first two bytes and parse it as normal.
+ var throwAwayTypeBytes [2]byte
+ _, err := r.Read(throwAwayTypeBytes[:])
+ if err != nil {
+ return err
+ }
+ }
+
+ // At this pint, we've either decided to keep the entire thing, or snip
+ // off the first two bytes. In either case, we can just read it as
+ // normal.
+ return chanUpdate.Decode(r, pver)
+}
+
+// FailTemporaryChannelFailure is if an otherwise unspecified transient error
+// occurs for the outgoing channel (eg. channel capacity reached, too many
+// in-flight htlcs)
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailTemporaryChannelFailure struct {
+ // Update is used to update information about state of the channel
+ // which caused the failure.
+ //
+ // NOTE: This field is optional.
+ Update *ChannelUpdate1
+}
+
+// NewTemporaryChannelFailure creates new instance of the FailTemporaryChannelFailure.
+func NewTemporaryChannelFailure(
+ update *ChannelUpdate1) *FailTemporaryChannelFailure {
+
+ return &FailTemporaryChannelFailure{Update: update}
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailTemporaryChannelFailure) Code() FailCode {
+ return CodeTemporaryChannelFailure
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailTemporaryChannelFailure) Error() string {
+ if f.Update == nil {
+ return f.Code().String()
+ }
+
+ return fmt.Sprintf("TemporaryChannelFailure(update=%v)",
+ spew.Sdump(f.Update))
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailTemporaryChannelFailure) Decode(r io.Reader, pver uint32) error {
+ var length uint16
+ err := ReadElement(r, &length)
+ if err != nil {
+ return err
+ }
+
+ if length != 0 {
+ f.Update = &ChannelUpdate1{}
+
+ return parseChannelUpdateCompatibilityMode(
+ r, length, f.Update, pver,
+ )
+ }
+
+ return nil
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailTemporaryChannelFailure) Encode(w *bytes.Buffer,
+ pver uint32) error {
+
+ if f.Update != nil {
+ return writeOnionErrorChanUpdate(w, f.Update, pver)
+ }
+
+ // Write zero length to indicate no channel_update is present.
+ return WriteUint16(w, 0)
+}
+
+// FailAmountBelowMinimum is returned if the HTLC does not reach the current
+// minimum amount, we tell them the amount of the incoming HTLC and the current
+// channel setting for the outgoing channel.
+//
+// NOTE: May only be returned by the intermediate nodes in the path.
+type FailAmountBelowMinimum struct {
+ // HtlcMsat is the wrong amount of the incoming HTLC.
+ HtlcMsat MilliSatoshi
+
+ // Update is used to update information about state of the channel
+ // which caused the failure.
+ Update ChannelUpdate1
+}
+
+// NewAmountBelowMinimum creates new instance of the FailAmountBelowMinimum.
+func NewAmountBelowMinimum(htlcMsat MilliSatoshi,
+ update ChannelUpdate1) *FailAmountBelowMinimum {
+
+ return &FailAmountBelowMinimum{
+ HtlcMsat: htlcMsat,
+ Update: update,
+ }
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailAmountBelowMinimum) Code() FailCode {
+ return CodeAmountBelowMinimum
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailAmountBelowMinimum) Error() string {
+ return fmt.Sprintf("AmountBelowMinimum(amt=%v, update=%v", f.HtlcMsat,
+ spew.Sdump(f.Update))
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailAmountBelowMinimum) Decode(r io.Reader, pver uint32) error {
+ if err := ReadElement(r, &f.HtlcMsat); err != nil {
+ return err
+ }
+
+ var length uint16
+ if err := ReadElement(r, &length); err != nil {
+ return err
+ }
+
+ f.Update = ChannelUpdate1{}
+
+ return parseChannelUpdateCompatibilityMode(
+ r, length, &f.Update, pver,
+ )
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailAmountBelowMinimum) Encode(w *bytes.Buffer, pver uint32) error {
+ if err := WriteMilliSatoshi(w, f.HtlcMsat); err != nil {
+ return err
+ }
+
+ return writeOnionErrorChanUpdate(w, &f.Update, pver)
+}
+
+// FailFeeInsufficient is returned if the HTLC does not pay sufficient fee, we
+// tell them the amount of the incoming HTLC and the current channel setting
+// for the outgoing channel.
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailFeeInsufficient struct {
+ // HtlcMsat is the wrong amount of the incoming HTLC.
+ HtlcMsat MilliSatoshi
+
+ // Update is used to update information about state of the channel
+ // which caused the failure.
+ Update ChannelUpdate1
+}
+
+// NewFeeInsufficient creates new instance of the FailFeeInsufficient.
+func NewFeeInsufficient(htlcMsat MilliSatoshi,
+ update ChannelUpdate1) *FailFeeInsufficient {
+ return &FailFeeInsufficient{
+ HtlcMsat: htlcMsat,
+ Update: update,
+ }
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailFeeInsufficient) Code() FailCode {
+ return CodeFeeInsufficient
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailFeeInsufficient) Error() string {
+ return fmt.Sprintf("FeeInsufficient(htlc_amt==%v, update=%v", f.HtlcMsat,
+ spew.Sdump(f.Update))
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailFeeInsufficient) Decode(r io.Reader, pver uint32) error {
+ if err := ReadElement(r, &f.HtlcMsat); err != nil {
+ return err
+ }
+
+ var length uint16
+ if err := ReadElement(r, &length); err != nil {
+ return err
+ }
+
+ f.Update = ChannelUpdate1{}
+
+ return parseChannelUpdateCompatibilityMode(
+ r, length, &f.Update, pver,
+ )
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailFeeInsufficient) Encode(w *bytes.Buffer, pver uint32) error {
+ if err := WriteMilliSatoshi(w, f.HtlcMsat); err != nil {
+ return err
+ }
+
+ return writeOnionErrorChanUpdate(w, &f.Update, pver)
+}
+
+// FailIncorrectCltvExpiry is returned if outgoing cltv value does not match
+// the update add htlc's cltv expiry minus cltv expiry delta for the outgoing
+// channel, we tell them the cltv expiry and the current channel setting for
+// the outgoing channel.
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailIncorrectCltvExpiry struct {
+ // CltvExpiry is the wrong absolute timeout in blocks, after which
+ // outgoing HTLC expires.
+ CltvExpiry uint32
+
+ // Update is used to update information about state of the channel
+ // which caused the failure.
+ Update ChannelUpdate1
+}
+
+// NewIncorrectCltvExpiry creates new instance of the FailIncorrectCltvExpiry.
+func NewIncorrectCltvExpiry(cltvExpiry uint32,
+ update ChannelUpdate1) *FailIncorrectCltvExpiry {
+
+ return &FailIncorrectCltvExpiry{
+ CltvExpiry: cltvExpiry,
+ Update: update,
+ }
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailIncorrectCltvExpiry) Code() FailCode {
+ return CodeIncorrectCltvExpiry
+}
+
+func (f *FailIncorrectCltvExpiry) Error() string {
+ return fmt.Sprintf("IncorrectCltvExpiry(expiry=%v, update=%v",
+ f.CltvExpiry, spew.Sdump(f.Update))
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error {
+ if err := ReadElement(r, &f.CltvExpiry); err != nil {
+ return err
+ }
+
+ var length uint16
+ if err := ReadElement(r, &length); err != nil {
+ return err
+ }
+
+ f.Update = ChannelUpdate1{}
+
+ return parseChannelUpdateCompatibilityMode(
+ r, length, &f.Update, pver,
+ )
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailIncorrectCltvExpiry) Encode(w *bytes.Buffer, pver uint32) error {
+ if err := WriteUint32(w, f.CltvExpiry); err != nil {
+ return err
+ }
+
+ return writeOnionErrorChanUpdate(w, &f.Update, pver)
+}
+
+// FailExpiryTooSoon is returned if the ctlv-expiry is too near, we tell them
+// the current channel setting for the outgoing channel.
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailExpiryTooSoon struct {
+ // Update is used to update information about state of the channel
+ // which caused the failure.
+ Update ChannelUpdate1
+}
+
+// NewExpiryTooSoon creates new instance of the FailExpiryTooSoon.
+func NewExpiryTooSoon(update ChannelUpdate1) *FailExpiryTooSoon {
+ return &FailExpiryTooSoon{
+ Update: update,
+ }
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailExpiryTooSoon) Code() FailCode {
+ return CodeExpiryTooSoon
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailExpiryTooSoon) Error() string {
+ return fmt.Sprintf("ExpiryTooSoon(update=%v", spew.Sdump(f.Update))
+}
+
+// Decode decodes the failure from l stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailExpiryTooSoon) Decode(r io.Reader, pver uint32) error {
+ var length uint16
+ if err := ReadElement(r, &length); err != nil {
+ return err
+ }
+
+ f.Update = ChannelUpdate1{}
+
+ return parseChannelUpdateCompatibilityMode(
+ r, length, &f.Update, pver,
+ )
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailExpiryTooSoon) Encode(w *bytes.Buffer, pver uint32) error {
+ return writeOnionErrorChanUpdate(w, &f.Update, pver)
+}
+
+// FailChannelDisabled is returned if the channel is disabled, we tell them the
+// current channel setting for the outgoing channel.
+//
+// NOTE: May only be returned by intermediate nodes.
+type FailChannelDisabled struct {
+ // Flags least-significant bit must be set to 0 if the creating node
+ // corresponds to the first node in the previously sent channel
+ // announcement and 1 otherwise.
+ Flags uint16
+
+ // Update is used to update information about state of the channel
+ // which caused the failure.
+ Update ChannelUpdate1
+}
+
+// NewChannelDisabled creates new instance of the FailChannelDisabled.
+func NewChannelDisabled(flags uint16,
+ update ChannelUpdate1) *FailChannelDisabled {
+
+ return &FailChannelDisabled{
+ Flags: flags,
+ Update: update,
+ }
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailChannelDisabled) Code() FailCode {
+ return CodeChannelDisabled
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailChannelDisabled) Error() string {
+ return fmt.Sprintf("ChannelDisabled(flags=%v, update=%v", f.Flags,
+ spew.Sdump(f.Update))
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailChannelDisabled) Decode(r io.Reader, pver uint32) error {
+ if err := ReadElement(r, &f.Flags); err != nil {
+ return err
+ }
+
+ var length uint16
+ if err := ReadElement(r, &length); err != nil {
+ return err
+ }
+
+ f.Update = ChannelUpdate1{}
+
+ return parseChannelUpdateCompatibilityMode(
+ r, length, &f.Update, pver,
+ )
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailChannelDisabled) Encode(w *bytes.Buffer, pver uint32) error {
+ if err := WriteUint16(w, f.Flags); err != nil {
+ return err
+ }
+
+ return writeOnionErrorChanUpdate(w, &f.Update, pver)
+}
+
+// FailFinalIncorrectCltvExpiry is returned if the outgoing_cltv_value does not
+// match the ctlv_expiry of the HTLC at the final hop.
+//
+// NOTE: might be returned by final node only.
+type FailFinalIncorrectCltvExpiry struct {
+ // CltvExpiry is the wrong absolute timeout in blocks, after which
+ // outgoing HTLC expires.
+ CltvExpiry uint32
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailFinalIncorrectCltvExpiry) Error() string {
+ return fmt.Sprintf("FinalIncorrectCltvExpiry(expiry=%v)", f.CltvExpiry)
+}
+
+// NewFinalIncorrectCltvExpiry creates new instance of the
+// FailFinalIncorrectCltvExpiry.
+func NewFinalIncorrectCltvExpiry(cltvExpiry uint32) *FailFinalIncorrectCltvExpiry {
+ return &FailFinalIncorrectCltvExpiry{
+ CltvExpiry: cltvExpiry,
+ }
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailFinalIncorrectCltvExpiry) Code() FailCode {
+ return CodeFinalIncorrectCltvExpiry
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailFinalIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error {
+ return ReadElement(r, &f.CltvExpiry)
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailFinalIncorrectCltvExpiry) Encode(w *bytes.Buffer,
+ pver uint32) error {
+
+ return WriteUint32(w, f.CltvExpiry)
+}
+
+// FailFinalIncorrectHtlcAmount is returned if the amt_to_forward is higher
+// than incoming_htlc_amt of the HTLC at the final hop.
+//
+// NOTE: May only be returned by the final node.
+type FailFinalIncorrectHtlcAmount struct {
+ // IncomingHTLCAmount is the wrong forwarded htlc amount.
+ IncomingHTLCAmount MilliSatoshi
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailFinalIncorrectHtlcAmount) Error() string {
+ return fmt.Sprintf("FinalIncorrectHtlcAmount(amt=%v)",
+ f.IncomingHTLCAmount)
+}
+
+// NewFinalIncorrectHtlcAmount creates new instance of the
+// FailFinalIncorrectHtlcAmount.
+func NewFinalIncorrectHtlcAmount(amount MilliSatoshi) *FailFinalIncorrectHtlcAmount {
+ return &FailFinalIncorrectHtlcAmount{
+ IncomingHTLCAmount: amount,
+ }
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailFinalIncorrectHtlcAmount) Code() FailCode {
+ return CodeFinalIncorrectHtlcAmount
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailFinalIncorrectHtlcAmount) Decode(r io.Reader, pver uint32) error {
+ return ReadElement(r, &f.IncomingHTLCAmount)
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailFinalIncorrectHtlcAmount) Encode(w *bytes.Buffer,
+ pver uint32) error {
+
+ return WriteMilliSatoshi(w, f.IncomingHTLCAmount)
+}
+
+// FailExpiryTooFar is returned if the CLTV expiry in the HTLC is too far in the
+// future.
+//
+// NOTE: May be returned by any node in the payment route.
+type FailExpiryTooFar struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailExpiryTooFar) Code() FailCode {
+ return CodeExpiryTooFar
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailExpiryTooFar) Error() string {
+ return f.Code().String()
+}
+
+// InvalidOnionPayload is returned if the hop could not process the TLV payload
+// enclosed in the onion.
+type InvalidOnionPayload struct {
+ // Type is the TLV type that caused the specific failure.
+ Type uint64
+
+ // Offset is the byte offset within the payload where the failure
+ // occurred.
+ Offset uint16
+}
+
+// NewInvalidOnionPayload initializes a new InvalidOnionPayload failure.
+func NewInvalidOnionPayload(typ uint64, offset uint16) *InvalidOnionPayload {
+ return &InvalidOnionPayload{
+ Type: typ,
+ Offset: offset,
+ }
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *InvalidOnionPayload) Code() FailCode {
+ return CodeInvalidOnionPayload
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *InvalidOnionPayload) Error() string {
+ return fmt.Sprintf("%v(type=%v, offset=%d)",
+ f.Code(), f.Type, f.Offset)
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *InvalidOnionPayload) Decode(r io.Reader, pver uint32) error {
+ var buf [8]byte
+ typ, err := tlv.ReadVarInt(r, &buf)
+ if err != nil {
+ return err
+ }
+ f.Type = typ
+
+ return ReadElements(r, &f.Offset)
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *InvalidOnionPayload) Encode(w *bytes.Buffer, pver uint32) error {
+ var buf [8]byte
+ if err := tlv.WriteVarInt(w, f.Type, &buf); err != nil {
+ return err
+ }
+
+ return WriteUint16(w, f.Offset)
+}
+
+// FailMPPTimeout is returned if the complete amount for a multi part payment
+// was not received within a reasonable time.
+//
+// NOTE: May only be returned by the final node in the path.
+type FailMPPTimeout struct{}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailMPPTimeout) Code() FailCode {
+ return CodeMPPTimeout
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailMPPTimeout) Error() string {
+ return f.Code().String()
+}
+
+// FailInvalidBlinding is returned if there has been a route blinding related
+// error.
+type FailInvalidBlinding struct {
+ OnionSHA256 [sha256.Size]byte
+}
+
+// Code returns the failure unique code.
+//
+// NOTE: Part of the FailureMessage interface.
+func (f *FailInvalidBlinding) Code() FailCode {
+ return CodeInvalidBlinding
+}
+
+// Returns a human readable string describing the target FailureMessage.
+//
+// NOTE: Implements the error interface.
+func (f *FailInvalidBlinding) Error() string {
+ return f.Code().String()
+}
+
+// Decode decodes the failure from bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailInvalidBlinding) Decode(r io.Reader, _ uint32) error {
+ return ReadElement(r, f.OnionSHA256[:])
+}
+
+// Encode writes the failure in bytes stream.
+//
+// NOTE: Part of the Serializable interface.
+func (f *FailInvalidBlinding) Encode(w *bytes.Buffer, _ uint32) error {
+ return WriteBytes(w, f.OnionSHA256[:])
+}
+
+// NewInvalidBlinding creates new instance of FailInvalidBlinding.
+func NewInvalidBlinding(
+ onion fn.Option[[OnionPacketSize]byte]) *FailInvalidBlinding {
+ // The spec allows empty onion hashes for invalid blinding, so we only
+ // include our onion hash if it's provided.
+ if onion.IsNone() {
+ return &FailInvalidBlinding{}
+ }
+
+ shaSum := fn.MapOptionZ(onion, func(o [OnionPacketSize]byte) [32]byte {
+ return sha256.Sum256(o[:])
+ })
+
+ return &FailInvalidBlinding{OnionSHA256: shaSum}
+}
+
+// DecodeFailure decodes, validates, and parses the lnwire onion failure, for
+// the provided protocol version.
+func DecodeFailure(r io.Reader, pver uint32) (FailureMessage, error) {
+ // First, we'll parse out the encapsulated failure message itself. This
+ // is a 2 byte length followed by the payload itself.
+ var failureLength uint16
+ if err := ReadElement(r, &failureLength); err != nil {
+ return nil, fmt.Errorf("unable to read failure len: %w", err)
+ }
+
+ failureData := make([]byte, failureLength)
+ if _, err := io.ReadFull(r, failureData); err != nil {
+ return nil, fmt.Errorf("unable to full read payload of "+
+ "%v: %w", failureLength, err)
+ }
+
+ // Read the padding.
+ var padLength uint16
+ if err := ReadElement(r, &padLength); err != nil {
+ return nil, fmt.Errorf("unable to read pad len: %w", err)
+ }
+
+ if _, err := io.CopyN(io.Discard, r, int64(padLength)); err != nil {
+ return nil, fmt.Errorf("unable to read padding %w", err)
+ }
+
+ // Verify that we are at the end of the stream now.
+ scratch := make([]byte, 1)
+ _, err := r.Read(scratch)
+ if err != io.EOF {
+ return nil, fmt.Errorf("unexpected failure bytes")
+ }
+
+ // Check the total length. Convert to 32 bits to prevent overflow.
+ totalLength := uint32(padLength) + uint32(failureLength)
+ if totalLength < FailureMessageLength {
+ return nil, fmt.Errorf("failure message too short: "+
+ "msg=%v, pad=%v, total=%v",
+ failureLength, padLength, totalLength)
+ }
+
+ // Decode the failure message.
+ dataReader := bytes.NewReader(failureData)
+
+ return DecodeFailureMessage(dataReader, pver)
+}
+
+// DecodeFailureMessage decodes just the failure message, ignoring any padding
+// that may be present at the end.
+func DecodeFailureMessage(r io.Reader, pver uint32) (FailureMessage, error) {
+ // Once we have the failure data, we can obtain the failure code from
+ // the first two bytes of the buffer.
+ var codeBytes [2]byte
+ if _, err := io.ReadFull(r, codeBytes[:]); err != nil {
+ return nil, fmt.Errorf("unable to read failure code: %w", err)
+ }
+ failCode := FailCode(binary.BigEndian.Uint16(codeBytes[:]))
+
+ // Create the empty failure by given code and populate the failure with
+ // additional data if needed.
+ failure, err := makeEmptyOnionError(failCode)
+ if err != nil {
+ return nil, fmt.Errorf("unable to make empty error: %w", err)
+ }
+
+ // Finally, if this failure has a payload, then we'll read that now as
+ // well.
+ switch f := failure.(type) {
+ case Serializable:
+ if err := f.Decode(r, pver); err != nil {
+ return nil, fmt.Errorf("unable to decode error "+
+ "update (type=%T): %w", failure, err)
+ }
+ }
+
+ return failure, nil
+}
+
+// EncodeFailure encodes, including the necessary onion failure header
+// information.
+func EncodeFailure(w *bytes.Buffer, failure FailureMessage, pver uint32) error {
+ var failureMessageBuffer bytes.Buffer
+
+ err := EncodeFailureMessage(&failureMessageBuffer, failure, pver)
+ if err != nil {
+ return err
+ }
+
+ // The combined size of this message must be below the max allowed
+ // failure message length.
+ failureMessage := failureMessageBuffer.Bytes()
+ if len(failureMessage) > FailureMessageLength {
+ return fmt.Errorf("failure message exceed max "+
+ "available size: %v", len(failureMessage))
+ }
+
+ // Finally, we'll add some padding in order to ensure that all failure
+ // messages are fixed size.
+ pad := make([]byte, FailureMessageLength-len(failureMessage))
+
+ if err := WriteUint16(w, uint16(len(failureMessage))); err != nil {
+ return err
+ }
+
+ if err := WriteBytes(w, failureMessage); err != nil {
+ return err
+ }
+ if err := WriteUint16(w, uint16(len(pad))); err != nil {
+ return err
+ }
+
+ return WriteBytes(w, pad)
+}
+
+// EncodeFailureMessage encodes just the failure message without adding a length
+// and padding the message for the onion protocol.
+func EncodeFailureMessage(w *bytes.Buffer,
+ failure FailureMessage, pver uint32) error {
+
+ // First, we'll write out the error code itself into the failure
+ // buffer.
+ var codeBytes [2]byte
+ code := uint16(failure.Code())
+ binary.BigEndian.PutUint16(codeBytes[:], code)
+ _, err := w.Write(codeBytes[:])
+ if err != nil {
+ return err
+ }
+
+ // Next, some message have an additional message payload, if this is
+ // one of those types, then we'll also encode the error payload as
+ // well.
+ switch failure := failure.(type) {
+ case Serializable:
+ if err := failure.Encode(w, pver); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// makeEmptyOnionError creates a new empty onion error of the proper concrete
+// type based on the passed failure code.
+func makeEmptyOnionError(code FailCode) (FailureMessage, error) {
+ switch code {
+ case CodeInvalidRealm:
+ return &FailInvalidRealm{}, nil
+
+ case CodeTemporaryNodeFailure:
+ return &FailTemporaryNodeFailure{}, nil
+
+ case CodePermanentNodeFailure:
+ return &FailPermanentNodeFailure{}, nil
+
+ case CodeRequiredNodeFeatureMissing:
+ return &FailRequiredNodeFeatureMissing{}, nil
+
+ case CodePermanentChannelFailure:
+ return &FailPermanentChannelFailure{}, nil
+
+ case CodeRequiredChannelFeatureMissing:
+ return &FailRequiredChannelFeatureMissing{}, nil
+
+ case CodeUnknownNextPeer:
+ return &FailUnknownNextPeer{}, nil
+
+ case CodeIncorrectOrUnknownPaymentDetails:
+ return &FailIncorrectDetails{}, nil
+
+ case CodeIncorrectPaymentAmount:
+ return &FailIncorrectPaymentAmount{}, nil
+
+ case CodeFinalExpiryTooSoon:
+ return &FailFinalExpiryTooSoon{}, nil
+
+ case CodeInvalidOnionVersion:
+ return &FailInvalidOnionVersion{}, nil
+
+ case CodeInvalidOnionHmac:
+ return &FailInvalidOnionHmac{}, nil
+
+ case CodeInvalidOnionKey:
+ return &FailInvalidOnionKey{}, nil
+
+ case CodeTemporaryChannelFailure:
+ return &FailTemporaryChannelFailure{}, nil
+
+ case CodeAmountBelowMinimum:
+ return &FailAmountBelowMinimum{}, nil
+
+ case CodeFeeInsufficient:
+ return &FailFeeInsufficient{}, nil
+
+ case CodeIncorrectCltvExpiry:
+ return &FailIncorrectCltvExpiry{}, nil
+
+ case CodeExpiryTooSoon:
+ return &FailExpiryTooSoon{}, nil
+
+ case CodeChannelDisabled:
+ return &FailChannelDisabled{}, nil
+
+ case CodeFinalIncorrectCltvExpiry:
+ return &FailFinalIncorrectCltvExpiry{}, nil
+
+ case CodeFinalIncorrectHtlcAmount:
+ return &FailFinalIncorrectHtlcAmount{}, nil
+
+ case CodeExpiryTooFar:
+ return &FailExpiryTooFar{}, nil
+
+ case CodeInvalidOnionPayload:
+ return &InvalidOnionPayload{}, nil
+
+ case CodeMPPTimeout:
+ return &FailMPPTimeout{}, nil
+
+ case CodeInvalidBlinding:
+ return &FailInvalidBlinding{}, nil
+
+ default:
+ return nil, fmt.Errorf("unknown error code: %v", code)
+ }
+}
+
+// writeOnionErrorChanUpdate writes out a ChannelUpdate using the onion error
+// format. The format is that we first write out the true serialized length of
+// the channel update, followed by the serialized channel update itself.
+func writeOnionErrorChanUpdate(w *bytes.Buffer, chanUpdate *ChannelUpdate1,
+ pver uint32) error {
+
+ // First, we encode the channel update in a temporary buffer in order
+ // to get the exact serialized size.
+ var b bytes.Buffer
+ updateLen, err := WriteMessage(&b, chanUpdate, pver)
+ if err != nil {
+ return err
+ }
+
+ // Now that we know the size, we can write the length out in the main
+ // writer.
+ if err := WriteUint16(w, uint16(updateLen)); err != nil {
+ return err
+ }
+
+ // With the length written, we'll then write out the serialized channel
+ // update.
+ if _, err := w.Write(b.Bytes()); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/payments/db/migration1/lnwire/short_channel_id.go b/payments/db/migration1/lnwire/short_channel_id.go
new file mode 100644
index 0000000..e265750
--- /dev/null
+++ b/payments/db/migration1/lnwire/short_channel_id.go
@@ -0,0 +1,105 @@
+package lnwire
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+const (
+ // AliasScidRecordType is the type of the experimental record to denote
+ // the alias being used in an option_scid_alias channel.
+ AliasScidRecordType tlv.Type = 1
+)
+
+// ShortChannelID represents the set of data which is needed to retrieve all
+// necessary data to validate the channel existence.
+type ShortChannelID struct {
+ // BlockHeight is the height of the block where funding transaction
+ // located.
+ //
+ // NOTE: This field is limited to 3 bytes.
+ BlockHeight uint32
+
+ // TxIndex is a position of funding transaction within a block.
+ //
+ // NOTE: This field is limited to 3 bytes.
+ TxIndex uint32
+
+ // TxPosition indicating transaction output which pays to the channel.
+ TxPosition uint16
+}
+
+// NewShortChanIDFromInt returns a new ShortChannelID which is the decoded
+// version of the compact channel ID encoded within the uint64. The format of
+// the compact channel ID is as follows: 3 bytes for the block height, 3 bytes
+// for the transaction index, and 2 bytes for the output index.
+func NewShortChanIDFromInt(chanID uint64) ShortChannelID {
+ return ShortChannelID{
+ BlockHeight: uint32(chanID >> 40),
+ TxIndex: uint32(chanID>>16) & 0xFFFFFF,
+ TxPosition: uint16(chanID),
+ }
+}
+
+// ToUint64 converts the ShortChannelID into a compact format encoded within a
+// uint64 (8 bytes).
+func (c ShortChannelID) ToUint64() uint64 {
+ // TODO(roasbeef): explicit error on overflow?
+ return ((uint64(c.BlockHeight) << 40) | (uint64(c.TxIndex) << 16) |
+ (uint64(c.TxPosition)))
+}
+
+// String generates a human-readable representation of the channel ID.
+func (c ShortChannelID) String() string {
+ return fmt.Sprintf("%d:%d:%d", c.BlockHeight, c.TxIndex, c.TxPosition)
+}
+
+// AltString generates a human-readable representation of the channel ID
+// with 'x' as a separator.
+func (c ShortChannelID) AltString() string {
+ return fmt.Sprintf("%dx%dx%d", c.BlockHeight, c.TxIndex, c.TxPosition)
+}
+
+// Record returns a TLV record that can be used to encode/decode a
+// ShortChannelID to/from a TLV stream.
+func (c *ShortChannelID) Record() tlv.Record {
+ return tlv.MakeStaticRecord(
+ AliasScidRecordType, c, 8, EShortChannelID, DShortChannelID,
+ )
+}
+
+// IsDefault returns true if the ShortChannelID represents the zero value for
+// its type.
+func (c ShortChannelID) IsDefault() bool {
+ return c == ShortChannelID{}
+}
+
+// EShortChannelID is an encoder for ShortChannelID. It is exported so other
+// packages can use the encoding scheme.
+func EShortChannelID(w io.Writer, val interface{}, buf *[8]byte) error {
+ if v, ok := val.(*ShortChannelID); ok {
+ return tlv.EUint64T(w, v.ToUint64(), buf)
+ }
+ return tlv.NewTypeForEncodingErr(val, "lnwire.ShortChannelID")
+}
+
+// DShortChannelID is a decoder for ShortChannelID. It is exported so other
+// packages can use the decoding scheme.
+func DShortChannelID(r io.Reader, val interface{}, buf *[8]byte,
+ l uint64) error {
+
+ if v, ok := val.(*ShortChannelID); ok {
+ var scid uint64
+ // tlv.DUint64 forces the length to be 8 bytes.
+ err := tlv.DUint64(r, &scid, buf, l)
+ if err != nil {
+ return err
+ }
+
+ *v = NewShortChanIDFromInt(scid)
+ return nil
+ }
+ return tlv.NewTypeForDecodingErr(val, "lnwire.ShortChannelID", l, 8)
+}
diff --git a/payments/db/migration1/lnwire/signature.go b/payments/db/migration1/lnwire/signature.go
new file mode 100644
index 0000000..35b039e
--- /dev/null
+++ b/payments/db/migration1/lnwire/signature.go
@@ -0,0 +1,292 @@
+package lnwire
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/btcsuite/btcd/btcec/v2/ecdsa"
+ "github.com/btcsuite/btcd/btcec/v2/schnorr"
+ "github.com/lightningnetwork/lnd/input"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+var (
+ errSigTooShort = errors.New("malformed signature: too short")
+ errBadLength = errors.New("malformed signature: bad length")
+ errBadRLength = errors.New("malformed signature: bogus R length")
+ errBadSLength = errors.New("malformed signature: bogus S length")
+ errRTooLong = errors.New("R is over 32 bytes long without padding")
+ errSTooLong = errors.New("S is over 32 bytes long without padding")
+)
+
+// sigType represents the type of signature that is carried within the Sig.
+// Today this can either be an ECDSA sig or a schnorr sig. Both of these can
+// fit cleanly into 64 bytes.
+type sigType uint
+
+const (
+ // sigTypeECDSA represents an ECDSA signature.
+ sigTypeECDSA sigType = iota
+
+ // sigTypeSchnorr represents a schnorr signature.
+ sigTypeSchnorr
+)
+
+// Sig is a fixed-sized ECDSA signature or 64-byte schnorr signature. For the
+// ECDSA sig, unlike Bitcoin, we use fixed sized signatures on the wire,
+// instead of DER encoded signatures. This type provides several methods to
+// convert to/from a regular Bitcoin DER encoded signature (raw bytes and
+// *ecdsa.Signature).
+type Sig struct {
+ bytes [64]byte
+
+ sigType sigType
+}
+
+// ForceSchnorr forces the signature to be interpreted as a schnorr signature.
+// This is useful when reading an HTLC sig off the wire for a taproot channel.
+// In this case, in order to obtain an input.Signature, we need to know that
+// the sig is a schnorr sig.
+func (s *Sig) ForceSchnorr() {
+ s.sigType = sigTypeSchnorr
+}
+
+// RawBytes returns the raw bytes of signature.
+func (s *Sig) RawBytes() []byte {
+ return s.bytes[:]
+}
+
+// Copy copies the signature into a new Sig instance.
+func (s *Sig) Copy() Sig {
+ var sCopy Sig
+ copy(sCopy.bytes[:], s.bytes[:])
+ sCopy.sigType = s.sigType
+
+ return sCopy
+}
+
+// Record returns a Record that can be used to encode or decode the backing
+// object.
+//
+// This returns a record that serializes the sig as a 64-byte fixed size
+// signature.
+func (s *Sig) Record() tlv.Record {
+ // We set a type here as zero as it isn't needed when used as a
+ // RecordT.
+ return tlv.MakePrimitiveRecord(0, &s.bytes)
+}
+
+// NewSigFromWireECDSA returns a Sig instance based on an ECDSA signature
+// that's already in the 64-byte format we expect.
+func NewSigFromWireECDSA(sig []byte) (Sig, error) {
+ if len(sig) != 64 {
+ return Sig{}, fmt.Errorf("%w: %v bytes", errSigTooShort,
+ len(sig))
+ }
+
+ var s Sig
+ copy(s.bytes[:], sig)
+
+ return s, nil
+}
+
+// NewSigFromECDSARawSignature returns a Sig from a Bitcoin raw signature
+// encoded in the canonical DER encoding.
+func NewSigFromECDSARawSignature(sig []byte) (Sig, error) {
+ var b [64]byte
+
+ // Check the total length is above the minimal.
+ if len(sig) < ecdsa.MinSigLen {
+ return Sig{}, errSigTooShort
+ }
+
+ // The DER representation is laid out as:
+ // 0x30 <length> 0x02 <length r> r 0x02 <length s> s
+ // which means the length of R is the 4th byte and the length of S is
+ // the second byte after R ends. 0x02 signifies a length-prefixed,
+ // zero-padded, big-endian bigint. 0x30 signifies a DER signature.
+ // See the Serialize() method for ecdsa.Signature for details.
+
+ // Reading <length>, remaining: [0x02 <length r> r 0x02 <length s> s]
+ sigLen := int(sig[1])
+
+ // siglen should be less than the entire message and greater than
+ // the minimal message size.
+ if sigLen+2 > len(sig) || sigLen+2 < ecdsa.MinSigLen {
+ return Sig{}, errBadLength
+ }
+
+ // Reading <length r>, remaining: [r 0x02 <length s> s]
+ rLen := int(sig[3])
+
+ // rLen must be positive and must be able to fit in other elements.
+ // Assuming s is one byte, then we have 0x30, <length>, 0x20,
+ // <length r>, 0x20, <length s>, s, a total of 7 bytes.
+ if rLen <= 0 || rLen+7 > len(sig) {
+ return Sig{}, errBadRLength
+ }
+
+ // Reading <length s>, remaining: [s]
+ sLen := int(sig[5+rLen])
+
+ // S should be the rest of the string.
+ // sLen must be positive and must be able to fit in other elements.
+ // We know r is rLen bytes, and we have 0x30, <length>, 0x20,
+ // <length r>, 0x20, <length s>, a total of rLen+6 bytes.
+ if sLen <= 0 || sLen+rLen+6 > len(sig) {
+ return Sig{}, errBadSLength
+ }
+
+ // Check to make sure R and S can both fit into their intended buffers.
+ // We check S first because these code blocks decrement sLen and rLen
+ // in the case of a 33-byte 0-padded integer returned from Serialize()
+ // and rLen is used in calculating array indices for S. We can track
+ // this with additional variables, but it's more efficient to just
+ // check S first.
+ if sLen > 32 {
+ if (sLen > 33) || (sig[6+rLen] != 0x00) {
+ return Sig{}, errSTooLong
+ }
+ sLen--
+ copy(b[64-sLen:], sig[7+rLen:])
+ } else {
+ copy(b[64-sLen:], sig[6+rLen:])
+ }
+
+ // Do the same for R as we did for S
+ if rLen > 32 {
+ if (rLen > 33) || (sig[4] != 0x00) {
+ return Sig{}, errRTooLong
+ }
+ rLen--
+ copy(b[32-rLen:], sig[5:5+rLen])
+ } else {
+ copy(b[32-rLen:], sig[4:4+rLen])
+ }
+
+ return Sig{
+ bytes: b,
+ sigType: sigTypeECDSA,
+ }, nil
+}
+
+// NewSigFromSchnorrRawSignature converts a raw schnorr signature into an
+// lnwire.Sig.
+func NewSigFromSchnorrRawSignature(sig []byte) (Sig, error) {
+ var s Sig
+ copy(s.bytes[:], sig)
+ s.sigType = sigTypeSchnorr
+
+ return s, nil
+}
+
+// NewSigFromSignature creates a new signature as used on the wire, from an
+// existing ecdsa.Signature or schnorr.Signature.
+func NewSigFromSignature(e input.Signature) (Sig, error) {
+ if e == nil {
+ return Sig{}, fmt.Errorf("cannot decode empty signature")
+ }
+
+ // Nil is still a valid interface, apparently. So we need a more
+ // explicit check here.
+ if ecsig, ok := e.(*ecdsa.Signature); ok && ecsig == nil {
+ return Sig{}, fmt.Errorf("cannot decode empty signature")
+ }
+
+ switch ecSig := e.(type) {
+ // If this is a schnorr signature, then we can just pack it as normal,
+ // since the default encoding is already 64 bytes.
+ case *schnorr.Signature:
+ return NewSigFromSchnorrRawSignature(e.Serialize())
+
+ // For ECDSA signatures, we'll need to do a bit more work to map the
+ // signature into a compact 64 byte form.
+ case *ecdsa.Signature:
+ // Serialize the signature with all the checks that entails.
+ return NewSigFromECDSARawSignature(e.Serialize())
+
+ default:
+ return Sig{}, fmt.Errorf("unknown wire sig type: %T", ecSig)
+ }
+}
+
+// ToSignature converts the fixed-sized signature to a input.Signature which
+// can be used for signature validation checks.
+func (s *Sig) ToSignature() (input.Signature, error) {
+ switch s.sigType {
+ case sigTypeSchnorr:
+ return schnorr.ParseSignature(s.bytes[:])
+
+ case sigTypeECDSA:
+ // Parse the signature with strict checks.
+ sigBytes := s.ToSignatureBytes()
+ sig, err := ecdsa.ParseDERSignature(sigBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ return sig, nil
+
+ default:
+ return nil, fmt.Errorf("unknown sig type: %v", s.sigType)
+ }
+}
+
+// ToSignatureBytes serializes the target fixed-sized signature into the
+// encoding of the primary domain for the signature. For ECDSA signatures, this
+// is the raw bytes of a DER encoding.
+func (s *Sig) ToSignatureBytes() []byte {
+ switch s.sigType {
+ // For ECDSA signatures, we'll convert to DER encoding.
+ case sigTypeECDSA:
+ // Extract canonically-padded bigint representations from buffer
+ r := extractCanonicalPadding(s.bytes[0:32])
+ s := extractCanonicalPadding(s.bytes[32:64])
+ rLen := uint8(len(r))
+ sLen := uint8(len(s))
+
+ // Create a canonical serialized signature. DER format is:
+ // 0x30 <length> 0x02 <length r> r 0x02 <length s> s
+ sigBytes := make([]byte, 6+rLen+sLen)
+ sigBytes[0] = 0x30 // DER signature magic value
+ sigBytes[1] = 4 + rLen + sLen // Length of rest of signature
+ sigBytes[2] = 0x02 // Big integer magic value
+ sigBytes[3] = rLen // Length of R
+ sigBytes[rLen+4] = 0x02 // Big integer magic value
+ sigBytes[rLen+5] = sLen // Length of S
+ copy(sigBytes[4:], r) // Copy R
+ copy(sigBytes[rLen+6:], s) // Copy S
+
+ return sigBytes
+
+ // For schnorr signatures, we can use the same internal 64 bytes.
+ case sigTypeSchnorr:
+ // We'll make a copy of the signature so we don't return a
+ // reference into the raw slice.
+ var sig [64]byte
+ copy(sig[:], s.bytes[:])
+ return sig[:]
+
+ default:
+ // TODO(roasbeef): can only be called via public methods so
+ // never reachable?
+ panic("sig type not set")
+ }
+}
+
+// extractCanonicalPadding is a utility function to extract the canonical
+// padding of a big-endian integer from the wire encoding (a 0-padded
+// big-endian integer) such that it passes btcec.canonicalPadding test.
+func extractCanonicalPadding(b []byte) []byte {
+ for i := 0; i < len(b); i++ {
+ // Found first non-zero byte.
+ if b[i] > 0 {
+ // If the MSB is set, we need zero padding.
+ if b[i]&0x80 == 0x80 {
+ return append([]byte{0x00}, b[i:]...)
+ }
+ return b[i:]
+ }
+ }
+ return []byte{0x00}
+}
diff --git a/payments/db/migration1/lnwire/typed_fee.go b/payments/db/migration1/lnwire/typed_fee.go
new file mode 100644
index 0000000..f9b6c8d
--- /dev/null
+++ b/payments/db/migration1/lnwire/typed_fee.go
@@ -0,0 +1,60 @@
+package lnwire
+
+import (
+ "io"
+
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+const (
+ FeeRecordType tlv.Type = 55555
+)
+
+// Fee represents a fee schedule.
+type Fee struct {
+ BaseFee int32
+ FeeRate int32
+}
+
+// Record returns a TLV record that can be used to encode/decode the fee
+// type from a given TLV stream.
+func (l *Fee) Record() tlv.Record {
+ return tlv.MakeStaticRecord(
+ FeeRecordType, l, 8, feeEncoder, feeDecoder,
+ )
+}
+
+// feeEncoder is a custom TLV encoder for the fee record.
+func feeEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
+ v, ok := val.(*Fee)
+ if !ok {
+ return tlv.NewTypeForEncodingErr(val, "lnwire.Fee")
+ }
+
+ if err := tlv.EUint32T(w, uint32(v.BaseFee), buf); err != nil {
+ return err
+ }
+
+ return tlv.EUint32T(w, uint32(v.FeeRate), buf)
+}
+
+// feeDecoder is a custom TLV decoder for the fee record.
+func feeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
+ v, ok := val.(*Fee)
+ if !ok || l != 8 {
+ return tlv.NewTypeForDecodingErr(val, "lnwire.Fee", l, 8)
+ }
+
+ var baseFee, feeRate uint32
+ if err := tlv.DUint32(r, &baseFee, buf, 4); err != nil {
+ return err
+ }
+ if err := tlv.DUint32(r, &feeRate, buf, 4); err != nil {
+ return err
+ }
+
+ v.FeeRate = int32(feeRate)
+ v.BaseFee = int32(baseFee)
+
+ return nil
+}
diff --git a/payments/db/migration1/lnwire/update_add_htlc.go b/payments/db/migration1/lnwire/update_add_htlc.go
new file mode 100644
index 0000000..38ceeec
--- /dev/null
+++ b/payments/db/migration1/lnwire/update_add_htlc.go
@@ -0,0 +1,226 @@
+package lnwire
+
+import (
+ "bytes"
+ "io"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+const (
+ // OnionPacketSize is the size of the serialized Sphinx onion packet
+ // included in each UpdateAddHTLC message. The breakdown of the onion
+ // packet is as follows: 1-byte version, 33-byte ephemeral public key
+ // (for ECDH), 1300-bytes of per-hop data, and a 32-byte HMAC over the
+ // entire packet.
+ OnionPacketSize = 1366
+
+ // ExperimentalAccountableType is the TLV type used for a custom
+ // record that sets an experimental accountable value.
+ ExperimentalAccountableType tlv.Type = 106823
+
+ // ExperimentalUnaccountable is the value that the experimental
+ // accountable field contains when a htlc is not accountable.
+ ExperimentalUnaccountable = 0
+
+ // ExperimentalAccountable is the value that the experimental
+ // accountable field contains when a htlc is accountable. We're using a
+ // single byte to represent our accountable value, but limit the value
+ // to using the first three bits (max value = 00000111). Interpreted as
+ // a uint8 (an alias for byte in go), we can just define this constant
+ // as 7.
+ ExperimentalAccountable = 7
+)
+
+type (
+ // BlindingPointTlvType is the type for ephemeral pubkeys used in
+ // route blinding.
+ BlindingPointTlvType = tlv.TlvType0
+
+ // BlindingPointRecord holds an optional blinding point on update add
+ // htlc.
+ //nolint:ll
+ BlindingPointRecord = tlv.OptionalRecordT[BlindingPointTlvType, *btcec.PublicKey]
+)
+
+// UpdateAddHTLC is the message sent by Alice to Bob when she wishes to add an
+// HTLC to his remote commitment transaction. In addition to information
+// detailing the value, the ID, expiry, and the onion blob is also included
+// which allows Bob to derive the next hop in the route. The HTLC added by this
+// message is to be added to the remote node's "pending" HTLCs. A subsequent
+// CommitSig message will move the pending HTLC to the newly created commitment
+// transaction, marking them as "staged".
+type UpdateAddHTLC struct {
+ // ChanID is the particular active channel that this UpdateAddHTLC is
+ // bound to.
+ ChanID ChannelID
+
+ // ID is the identification server for this HTLC. This value is
+ // explicitly included as it allows nodes to survive single-sided
+ // restarts. The ID value for this sides starts at zero, and increases
+ // with each offered HTLC.
+ ID uint64
+
+ // Amount is the amount of millisatoshis this HTLC is worth.
+ Amount MilliSatoshi
+
+ // PaymentHash is the payment hash to be included in the HTLC this
+ // request creates. The pre-image to this HTLC must be revealed by the
+ // upstream peer in order to fully settle the HTLC.
+ PaymentHash [32]byte
+
+ // Expiry is the number of blocks after which this HTLC should expire.
+ // It is the receiver's duty to ensure that the outgoing HTLC has a
+ // sufficient expiry value to allow her to redeem the incoming HTLC.
+ Expiry uint32
+
+ // OnionBlob is the raw serialized mix header used to route an HTLC in
+ // a privacy-preserving manner. The mix header is defined currently to
+ // be parsed as a 4-tuple: (groupElement, routingInfo, headerMAC,
+ // body). First the receiving node should use the groupElement, and
+ // its current onion key to derive a shared secret with the source.
+ // Once the shared secret has been derived, the headerMAC should be
+ // checked FIRST. Note that the MAC only covers the routingInfo field.
+ // If the MAC matches, and the shared secret is fresh, then the node
+ // should strip off a layer of encryption, exposing the next hop to be
+ // used in the subsequent UpdateAddHTLC message.
+ OnionBlob [OnionPacketSize]byte
+
+ // BlindingPoint is the ephemeral pubkey used to optionally blind the
+ // next hop for this htlc.
+ BlindingPoint BlindingPointRecord
+
+ // CustomRecords maps TLV types to byte slices, storing arbitrary data
+ // intended for inclusion in the ExtraData field of the UpdateAddHTLC
+ // message.
+ CustomRecords CustomRecords
+
+ // ExtraData is the set of data that was appended to this message to
+ // fill out the full maximum transport message size. These fields can
+ // be used to specify optional data such as custom TLV fields.
+ ExtraData ExtraOpaqueData
+}
+
+// NewUpdateAddHTLC returns a new empty UpdateAddHTLC message.
+func NewUpdateAddHTLC() *UpdateAddHTLC {
+ return &UpdateAddHTLC{}
+}
+
+// A compile time check to ensure UpdateAddHTLC implements the lnwire.Message
+// interface.
+var _ Message = (*UpdateAddHTLC)(nil)
+
+// Decode deserializes a serialized UpdateAddHTLC message stored in the passed
+// io.Reader observing the specified protocol version.
+//
+// This is part of the lnwire.Message interface.
+func (c *UpdateAddHTLC) Decode(r io.Reader, pver uint32) error {
+ // msgExtraData is a temporary variable used to read the message extra
+ // data field from the reader.
+ var msgExtraData ExtraOpaqueData
+
+ if err := ReadElements(r,
+ &c.ChanID,
+ &c.ID,
+ &c.Amount,
+ c.PaymentHash[:],
+ &c.Expiry,
+ c.OnionBlob[:],
+ &msgExtraData,
+ ); err != nil {
+ return err
+ }
+
+ // Extract TLV records from the extra data field.
+ blindingRecord := c.BlindingPoint.Zero()
+
+ customRecords, parsed, extraData, err := ParseAndExtractCustomRecords(
+ msgExtraData, &blindingRecord,
+ )
+ if err != nil {
+ return err
+ }
+
+ // Assign the parsed records back to the message.
+ if parsed.Contains(blindingRecord.TlvType()) {
+ c.BlindingPoint = tlv.SomeRecordT(blindingRecord)
+ }
+
+ c.CustomRecords = customRecords
+ c.ExtraData = extraData
+
+ return nil
+}
+
+// Encode serializes the target UpdateAddHTLC into the passed io.Writer
+// observing the protocol version specified.
+//
+// This is part of the lnwire.Message interface.
+func (c *UpdateAddHTLC) Encode(w *bytes.Buffer, pver uint32) error {
+ if err := WriteChannelID(w, c.ChanID); err != nil {
+ return err
+ }
+
+ if err := WriteUint64(w, c.ID); err != nil {
+ return err
+ }
+
+ if err := WriteMilliSatoshi(w, c.Amount); err != nil {
+ return err
+ }
+
+ if err := WriteBytes(w, c.PaymentHash[:]); err != nil {
+ return err
+ }
+
+ if err := WriteUint32(w, c.Expiry); err != nil {
+ return err
+ }
+
+ if err := WriteBytes(w, c.OnionBlob[:]); err != nil {
+ return err
+ }
+
+ // Only include blinding point in extra data if present.
+ var records []tlv.RecordProducer
+ c.BlindingPoint.WhenSome(
+ func(b tlv.RecordT[BlindingPointTlvType, *btcec.PublicKey]) {
+ records = append(records, &b)
+ },
+ )
+
+ extraData, err := MergeAndEncode(records, c.ExtraData, c.CustomRecords)
+ if err != nil {
+ return err
+ }
+
+ return WriteBytes(w, extraData)
+}
+
+// MsgType returns the integer uniquely identifying this message type on the
+// wire.
+//
+// This is part of the lnwire.Message interface.
+func (c *UpdateAddHTLC) MsgType() MessageType {
+ return MsgUpdateAddHTLC
+}
+
+// TargetChanID returns the channel id of the link for which this message is
+// intended.
+//
+// NOTE: Part of peer.LinkUpdater interface.
+func (c *UpdateAddHTLC) TargetChanID() ChannelID {
+ return c.ChanID
+}
+
+// SerializedSize returns the serialized size of the message in bytes.
+//
+// This is part of the lnwire.SizeableMessage interface.
+func (c *UpdateAddHTLC) SerializedSize() (uint32, error) {
+ return MessageSerializedSize(c)
+}
+
+// A compile time check to ensure UpdateAddHTLC implements the
+// lnwire.SizeableMessage interface.
+var _ SizeableMessage = (*UpdateAddHTLC)(nil)
diff --git a/payments/db/migration1/lnwire/writer.go b/payments/db/migration1/lnwire/writer.go
new file mode 100644
index 0000000..db57566
--- /dev/null
+++ b/payments/db/migration1/lnwire/writer.go
@@ -0,0 +1,185 @@
+package lnwire
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/btcsuite/btcd/btcutil"
+)
+
+var (
+ // ErrNilFeatureVector is returned when the supplied feature is nil.
+ ErrNilFeatureVector = errors.New("cannot write nil feature vector")
+
+ // ErrNilPublicKey is returned when a nil pubkey is used.
+ ErrNilPublicKey = errors.New("cannot write nil pubkey")
+)
+
+// WriteBytes appends the given bytes to the provided buffer.
+func WriteBytes(buf *bytes.Buffer, b []byte) error {
+ _, err := buf.Write(b)
+ return err
+}
+
+// WriteUint8 appends the uint8 to the provided buffer.
+func WriteUint8(buf *bytes.Buffer, n uint8) error {
+ _, err := buf.Write([]byte{n})
+ return err
+}
+
+// WriteUint16 appends the uint16 to the provided buffer. It encodes the
+// integer using big endian byte order.
+func WriteUint16(buf *bytes.Buffer, n uint16) error {
+ var b [2]byte
+ binary.BigEndian.PutUint16(b[:], n)
+ _, err := buf.Write(b[:])
+ return err
+}
+
+// WriteUint32 appends the uint32 to the provided buffer. It encodes the
+// integer using big endian byte order.
+func WriteUint32(buf *bytes.Buffer, n uint32) error {
+ var b [4]byte
+ binary.BigEndian.PutUint32(b[:], n)
+ _, err := buf.Write(b[:])
+ return err
+}
+
+// WriteUint64 appends the uint64 to the provided buffer. It encodes the
+// integer using big endian byte order.
+func WriteUint64(buf *bytes.Buffer, n uint64) error {
+ var b [8]byte
+ binary.BigEndian.PutUint64(b[:], n)
+ _, err := buf.Write(b[:])
+ return err
+}
+
+// WriteSatoshi appends the Satoshi value to the provided buffer.
+func WriteSatoshi(buf *bytes.Buffer, amount btcutil.Amount) error {
+ return WriteUint64(buf, uint64(amount))
+}
+
+// WriteMilliSatoshi appends the MilliSatoshi value to the provided buffer.
+func WriteMilliSatoshi(buf *bytes.Buffer, amount MilliSatoshi) error {
+ return WriteUint64(buf, uint64(amount))
+}
+
+// WritePublicKey appends the compressed public key to the provided buffer.
+func WritePublicKey(buf *bytes.Buffer, pub *btcec.PublicKey) error {
+ if pub == nil {
+ return ErrNilPublicKey
+ }
+
+ serializedPubkey := pub.SerializeCompressed()
+ return WriteBytes(buf, serializedPubkey)
+}
+
+// WriteChannelID appends the ChannelID to the provided buffer.
+func WriteChannelID(buf *bytes.Buffer, channelID ChannelID) error {
+ return WriteBytes(buf, channelID[:])
+}
+
+// WriteShortChannelID appends the ShortChannelID to the provided buffer. It
+// encodes the BlockHeight and TxIndex each using 3 bytes with big endian byte
+// order, and encodes txPosition using 2 bytes with big endian byte order.
+func WriteShortChannelID(buf *bytes.Buffer, shortChanID ShortChannelID) error {
+ // Check that field fit in 3 bytes and write the blockHeight
+ if shortChanID.BlockHeight > ((1 << 24) - 1) {
+ return errors.New("block height should fit in 3 bytes")
+ }
+
+ var blockHeight [4]byte
+ binary.BigEndian.PutUint32(blockHeight[:], shortChanID.BlockHeight)
+
+ if _, err := buf.Write(blockHeight[1:]); err != nil {
+ return err
+ }
+
+ // Check that field fit in 3 bytes and write the txIndex
+ if shortChanID.TxIndex > ((1 << 24) - 1) {
+ return errors.New("tx index should fit in 3 bytes")
+ }
+
+ var txIndex [4]byte
+ binary.BigEndian.PutUint32(txIndex[:], shortChanID.TxIndex)
+ if _, err := buf.Write(txIndex[1:]); err != nil {
+ return err
+ }
+
+ // Write the TxPosition
+ return WriteUint16(buf, shortChanID.TxPosition)
+}
+
+// WriteSig appends the signature to the provided buffer.
+func WriteSig(buf *bytes.Buffer, sig Sig) error {
+ return WriteBytes(buf, sig.bytes[:])
+}
+
+// WriteSigs appends the slice of signatures to the provided buffer with its
+// length.
+func WriteSigs(buf *bytes.Buffer, sigs []Sig) error {
+ // Write the length of the sigs.
+ if err := WriteUint16(buf, uint16(len(sigs))); err != nil {
+ return err
+ }
+
+ for _, sig := range sigs {
+ if err := WriteSig(buf, sig); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// WriteFailCode appends the FailCode to the provided buffer.
+func WriteFailCode(buf *bytes.Buffer, e FailCode) error {
+ return WriteUint16(buf, uint16(e))
+}
+
+// WriteRawFeatureVector encodes the feature using the feature's Encode method
+// and appends the data to the provided buffer. An error will return if the
+// passed feature is nil.
+func WriteRawFeatureVector(buf *bytes.Buffer, feature *RawFeatureVector) error {
+ if feature == nil {
+ return ErrNilFeatureVector
+ }
+
+ return feature.Encode(buf)
+}
+
+// WriteChanUpdateMsgFlags appends the update flag to the provided buffer.
+func WriteChanUpdateMsgFlags(buf *bytes.Buffer, f ChanUpdateMsgFlags) error {
+ return WriteUint8(buf, uint8(f))
+}
+
+// WriteChanUpdateChanFlags appends the update flag to the provided buffer.
+func WriteChanUpdateChanFlags(buf *bytes.Buffer, f ChanUpdateChanFlags) error {
+ return WriteUint8(buf, uint8(f))
+}
+
+// WriteErrorData appends the data to the provided buffer.
+func WriteErrorData(buf *bytes.Buffer, data ErrorData) error {
+ return writeDataWithLength(buf, data)
+}
+
+// WriteBool appends the boolean to the provided buffer.
+func WriteBool(buf *bytes.Buffer, b bool) error {
+ if b {
+ return WriteBytes(buf, []byte{1})
+ }
+ return WriteBytes(buf, []byte{0})
+}
+
+// writeDataWithLength writes the data and its length to the buffer.
+func writeDataWithLength(buf *bytes.Buffer, data []byte) error {
+ var l [2]byte
+ binary.BigEndian.PutUint16(l[:], uint16(len(data)))
+ if _, err := buf.Write(l[:]); err != nil {
+ return err
+ }
+
+ _, err := buf.Write(data)
+ return err
+}
diff --git a/payments/db/migration1/migration_validation.go b/payments/db/migration1/migration_validation.go
index fe43cf2..f20c927 100644
--- a/payments/db/migration1/migration_validation.go
+++ b/payments/db/migration1/migration_validation.go
@@ -12,9 +12,9 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/record"
"github.com/lightningnetwork/lnd/payments/db/migration1/sqlc"
- "github.com/lightningnetwork/lnd/record"
"github.com/pmezard/go-difflib/difflib"
)
diff --git a/payments/db/migration1/payment.go b/payments/db/migration1/payment.go
index 53a3d81..78599c3 100644
--- a/payments/db/migration1/payment.go
+++ b/payments/db/migration1/payment.go
@@ -8,7 +8,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
)
// FailureReason encodes the reason a payment ultimately failed.
diff --git a/payments/db/migration1/record/amp.go b/payments/db/migration1/record/amp.go
new file mode 100644
index 0000000..f63c7a1
--- /dev/null
+++ b/payments/db/migration1/record/amp.go
@@ -0,0 +1,121 @@
+package record
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+// AMPOnionType is the type used in the onion to reference the AMP fields:
+// root_share, set_id, and child_index.
+const AMPOnionType tlv.Type = 14
+
+// AMP is a record that encodes the fields necessary for atomic multi-path
+// payments.
+type AMP struct {
+ rootShare [32]byte
+ setID [32]byte
+ childIndex uint32
+}
+
+// MaxAmpPayLoadSize is an AMP Record which when serialized to a tlv record uses
+// the maximum payload size. The `childIndex` is created randomly and is a
+// 4 byte `varint` type so we make sure we use an index which will be encoded in
+// 4 bytes.
+var MaxAmpPayLoadSize = AMP{
+ rootShare: [32]byte{},
+ setID: [32]byte{},
+ childIndex: 0x80000000,
+}
+
+// NewAMP generate a new AMP record with the given root_share, set_id, and
+// child_index.
+func NewAMP(rootShare, setID [32]byte, childIndex uint32) *AMP {
+ return &{
+ rootShare: rootShare,
+ setID: setID,
+ childIndex: childIndex,
+ }
+}
+
+// RootShare returns the root share contained in the AMP record.
+func (a *AMP) RootShare() [32]byte {
+ return a.rootShare
+}
+
+// SetID returns the set id contained in the AMP record.
+func (a *AMP) SetID() [32]byte {
+ return a.setID
+}
+
+// ChildIndex returns the child index contained in the AMP record.
+func (a *AMP) ChildIndex() uint32 {
+ return a.childIndex
+}
+
+// AMPEncoder writes the AMP record to the provided io.Writer.
+func AMPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
+ if v, ok := val.(*AMP); ok {
+ if err := tlv.EBytes32(w, &v.rootShare, buf); err != nil {
+ return err
+ }
+
+ if err := tlv.EBytes32(w, &v.setID, buf); err != nil {
+ return err
+ }
+
+ return tlv.ETUint32T(w, v.childIndex, buf)
+ }
+ return tlv.NewTypeForEncodingErr(val, "AMP")
+}
+
+const (
+ // minAMPLength is the minimum length of a serialized AMP TLV record,
+ // which occurs when the truncated encoding of child_index takes 0
+ // bytes, leaving only the root_share and set_id.
+ minAMPLength = 64
+
+ // maxAMPLength is the maximum length of a serialized AMP TLV record,
+ // which occurs when the truncated encoding of a child_index takes 2
+ // bytes.
+ maxAMPLength = 68
+)
+
+// AMPDecoder reads the AMP record from the provided io.Reader.
+func AMPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
+ if v, ok := val.(*AMP); ok && minAMPLength <= l && l <= maxAMPLength {
+ if err := tlv.DBytes32(r, &v.rootShare, buf, 32); err != nil {
+ return err
+ }
+
+ if err := tlv.DBytes32(r, &v.setID, buf, 32); err != nil {
+ return err
+ }
+
+ return tlv.DTUint32(r, &v.childIndex, buf, l-minAMPLength)
+ }
+ return tlv.NewTypeForDecodingErr(val, "AMP", l, maxAMPLength)
+}
+
+// Record returns a tlv.Record that can be used to encode or decode this record.
+func (a *AMP) Record() tlv.Record {
+ return tlv.MakeDynamicRecord(
+ AMPOnionType, a, a.PayloadSize, AMPEncoder, AMPDecoder,
+ )
+}
+
+// PayloadSize returns the size this record takes up in encoded form.
+func (a *AMP) PayloadSize() uint64 {
+ return 32 + 32 + tlv.SizeTUint32(a.childIndex)
+}
+
+// String returns a human-readable description of the amp payload fields.
+func (a *AMP) String() string {
+ if a == nil {
+ return "<nil>"
+ }
+
+ return fmt.Sprintf("root_share=%x set_id=%x child_index=%d",
+ a.rootShare, a.setID, a.childIndex)
+}
diff --git a/payments/db/migration1/record/blinded_data.go b/payments/db/migration1/record/blinded_data.go
new file mode 100644
index 0000000..22c0967
--- /dev/null
+++ b/payments/db/migration1/record/blinded_data.go
@@ -0,0 +1,440 @@
+package record
+
+import (
+ "bytes"
+ "encoding/binary"
+ "io"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+// AverageDummyHopPayloadSize is the size of a standard blinded path dummy hop
+// payload. In most cases, this is larger than the other payload types and so
+// to make sure that a sender cannot use this fact to know if a dummy hop is
+// present or not, we'll make sure to always pad all payloads to at least this
+// size.
+const AverageDummyHopPayloadSize = 51
+
+// BlindedRouteData contains the information that is included in a blinded
+// route encrypted data blob that is created by the recipient to provide
+// forwarding information.
+type BlindedRouteData struct {
+ // Padding is an optional set of bytes that a recipient can use to pad
+ // the data so that the encrypted recipient data blobs are all the same
+ // length.
+ Padding tlv.OptionalRecordT[tlv.TlvType1, []byte]
+
+ // ShortChannelID is the channel ID of the next hop.
+ ShortChannelID tlv.OptionalRecordT[tlv.TlvType2, lnwire.ShortChannelID]
+
+ // NextNodeID is the node ID of the next node on the path. In the
+ // context of blinded path payments, this is used to indicate the
+ // presence of dummy hops that need to be peeled from the onion.
+ NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey]
+
+ // PathID is a secret set of bytes that the blinded path creator will
+ // set so that they can check the value on decryption to ensure that the
+ // path they created was used for the intended purpose.
+ PathID tlv.OptionalRecordT[tlv.TlvType6, []byte]
+
+ // NextBlindingOverride is a blinding point that should be switched
+ // in for the next hop. This is used to combine two blinded paths into
+ // one (which primarily is used in onion messaging, but in theory
+ // could be used for payments as well).
+ NextBlindingOverride tlv.OptionalRecordT[tlv.TlvType8, *btcec.PublicKey]
+
+ // RelayInfo provides the relay parameters for the hop.
+ RelayInfo tlv.OptionalRecordT[tlv.TlvType10, PaymentRelayInfo]
+
+ // Constraints provides the payment relay constraints for the hop.
+ Constraints tlv.OptionalRecordT[tlv.TlvType12, PaymentConstraints]
+
+ // Features is the set of features the payment requires.
+ Features tlv.OptionalRecordT[tlv.TlvType14, lnwire.FeatureVector]
+}
+
+// NewNonFinalBlindedRouteData creates the data that's provided for hops within
+// a blinded route.
+func NewNonFinalBlindedRouteData(chanID lnwire.ShortChannelID,
+ blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo,
+ constraints *PaymentConstraints,
+ features *lnwire.FeatureVector) *BlindedRouteData {
+
+ info := &BlindedRouteData{
+ ShortChannelID: tlv.SomeRecordT(
+ tlv.NewRecordT[tlv.TlvType2](chanID),
+ ),
+ RelayInfo: tlv.SomeRecordT(
+ tlv.NewRecordT[tlv.TlvType10](relayInfo),
+ ),
+ }
+
+ if blindingOverride != nil {
+ info.NextBlindingOverride = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride))
+ }
+
+ if constraints != nil {
+ info.Constraints = tlv.SomeRecordT(
+ tlv.NewRecordT[tlv.TlvType12](*constraints))
+ }
+
+ if features != nil {
+ info.Features = tlv.SomeRecordT(
+ tlv.NewRecordT[tlv.TlvType14](*features),
+ )
+ }
+
+ return info
+}
+
+// NewFinalHopBlindedRouteData creates the data that's provided for the final
+// hop in a blinded route.
+func NewFinalHopBlindedRouteData(constraints *PaymentConstraints,
+ pathID []byte) *BlindedRouteData {
+
+ var data BlindedRouteData
+ if pathID != nil {
+ data.PathID = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType6](pathID),
+ )
+ }
+
+ if constraints != nil {
+ data.Constraints = tlv.SomeRecordT(
+ tlv.NewRecordT[tlv.TlvType12](*constraints))
+ }
+
+ return &data
+}
+
+// NewDummyHopRouteData creates the data that's provided for any hop preceding
+// a dummy hop. The presence of such a payload indicates to the reader that
+// they are the intended recipient and should peel the remainder of the onion.
+func NewDummyHopRouteData(ourPubKey *btcec.PublicKey,
+ relayInfo PaymentRelayInfo,
+ constraints PaymentConstraints) *BlindedRouteData {
+
+ return &BlindedRouteData{
+ NextNodeID: tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType4](ourPubKey),
+ ),
+ RelayInfo: tlv.SomeRecordT(
+ tlv.NewRecordT[tlv.TlvType10](relayInfo),
+ ),
+ Constraints: tlv.SomeRecordT(
+ tlv.NewRecordT[tlv.TlvType12](constraints),
+ ),
+ }
+}
+
+// DecodeBlindedRouteData decodes the data provided within a blinded route.
+func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
+ var (
+ d BlindedRouteData
+
+ padding = d.Padding.Zero()
+ scid = d.ShortChannelID.Zero()
+ nextNodeID = d.NextNodeID.Zero()
+ pathID = d.PathID.Zero()
+ blindingOverride = d.NextBlindingOverride.Zero()
+ relayInfo = d.RelayInfo.Zero()
+ constraints = d.Constraints.Zero()
+ features = d.Features.Zero()
+ )
+
+ var tlvRecords lnwire.ExtraOpaqueData
+ if err := lnwire.ReadElements(r, &tlvRecords); err != nil {
+ return nil, err
+ }
+
+ typeMap, err := tlvRecords.ExtractRecords(
+ &padding, &scid, &nextNodeID, &pathID, &blindingOverride,
+ &relayInfo, &constraints, &features,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ val, ok := typeMap[d.Padding.TlvType()]
+ if ok && val == nil {
+ d.Padding = tlv.SomeRecordT(padding)
+ }
+
+ if val, ok := typeMap[d.ShortChannelID.TlvType()]; ok && val == nil {
+ d.ShortChannelID = tlv.SomeRecordT(scid)
+ }
+
+ if val, ok := typeMap[d.NextNodeID.TlvType()]; ok && val == nil {
+ d.NextNodeID = tlv.SomeRecordT(nextNodeID)
+ }
+
+ if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil {
+ d.PathID = tlv.SomeRecordT(pathID)
+ }
+
+ val, ok = typeMap[d.NextBlindingOverride.TlvType()]
+ if ok && val == nil {
+ d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride)
+ }
+
+ if val, ok := typeMap[d.RelayInfo.TlvType()]; ok && val == nil {
+ d.RelayInfo = tlv.SomeRecordT(relayInfo)
+ }
+
+ if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil {
+ d.Constraints = tlv.SomeRecordT(constraints)
+ }
+
+ if val, ok := typeMap[d.Features.TlvType()]; ok && val == nil {
+ d.Features = tlv.SomeRecordT(features)
+ }
+
+ return &d, nil
+}
+
+// EncodeBlindedRouteData encodes the blinded route data provided.
+func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) {
+ var (
+ e lnwire.ExtraOpaqueData
+ recordProducers = make([]tlv.RecordProducer, 0, 5)
+ )
+
+ data.Padding.WhenSome(func(p tlv.RecordT[tlv.TlvType1, []byte]) {
+ recordProducers = append(recordProducers, &p)
+ })
+
+ data.ShortChannelID.WhenSome(func(scid tlv.RecordT[tlv.TlvType2,
+ lnwire.ShortChannelID]) {
+
+ recordProducers = append(recordProducers, &scid)
+ })
+
+ data.NextNodeID.WhenSome(func(f tlv.RecordT[tlv.TlvType4,
+ *btcec.PublicKey]) {
+
+ recordProducers = append(recordProducers, &f)
+ })
+
+ data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) {
+ recordProducers = append(recordProducers, &pathID)
+ })
+
+ data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8,
+ *btcec.PublicKey]) {
+
+ recordProducers = append(recordProducers, &pk)
+ })
+
+ data.RelayInfo.WhenSome(func(r tlv.RecordT[tlv.TlvType10,
+ PaymentRelayInfo]) {
+
+ recordProducers = append(recordProducers, &r)
+ })
+
+ data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12,
+ PaymentConstraints]) {
+
+ recordProducers = append(recordProducers, &cs)
+ })
+
+ data.Features.WhenSome(func(f tlv.RecordT[tlv.TlvType14,
+ lnwire.FeatureVector]) {
+
+ recordProducers = append(recordProducers, &f)
+ })
+
+ if err := e.PackRecords(recordProducers...); err != nil {
+ return nil, err
+ }
+
+ return e[:], nil
+}
+
+// PadBy adds "n" padding bytes to the BlindedRouteData using the Padding field.
+// Callers should be aware that the total payload size will change by more than
+// "n" since the "n" bytes will be prefixed by BigSize type and length fields.
+// Callers may need to call PadBy iteratively until each encrypted data packet
+// is the same size and so each call will overwrite the Padding record.
+// Note that calling PadBy with an n value of 0 will still result in a zero
+// length TLV entry being added.
+func (b *BlindedRouteData) PadBy(n int) {
+ b.Padding = tlv.SomeRecordT(
+ tlv.NewPrimitiveRecord[tlv.TlvType1](make([]byte, n)),
+ )
+}
+
+// PaymentRelayInfo describes the relay policy for a blinded path.
+type PaymentRelayInfo struct {
+ // CltvExpiryDelta is the expiry delta for the payment.
+ CltvExpiryDelta uint16
+
+ // FeeRate is the fee rate that will be charged per millionth of a
+ // satoshi.
+ FeeRate uint32
+
+ // BaseFee is the per-htlc fee charged in milli-satoshis.
+ BaseFee lnwire.MilliSatoshi
+}
+
+// Record creates a tlv.Record that encodes the payment relay (type 10) type for
+// an encrypted blob payload.
+func (i *PaymentRelayInfo) Record() tlv.Record {
+ return tlv.MakeDynamicRecord(
+ 10, &i, func() uint64 {
+ // uint16 + uint32 + tuint32
+ return 2 + 4 + tlv.SizeTUint32(uint32(i.BaseFee))
+ }, encodePaymentRelay, decodePaymentRelay,
+ )
+}
+
+func encodePaymentRelay(w io.Writer, val interface{}, buf *[8]byte) error {
+ if t, ok := val.(**PaymentRelayInfo); ok {
+ relayInfo := *t
+
+ // Just write our first 6 bytes directly.
+ binary.BigEndian.PutUint16(buf[:2], relayInfo.CltvExpiryDelta)
+ binary.BigEndian.PutUint32(buf[2:6], relayInfo.FeeRate)
+ if _, err := w.Write(buf[0:6]); err != nil {
+ return err
+ }
+
+ baseFee := uint32(relayInfo.BaseFee)
+
+ // We can safely reuse buf here because we overwrite its
+ // contents.
+ return tlv.ETUint32(w, &baseFee, buf)
+ }
+
+ return tlv.NewTypeForEncodingErr(val, "**hop.PaymentRelayInfo")
+}
+
+func decodePaymentRelay(r io.Reader, val interface{}, buf *[8]byte,
+ l uint64) error {
+
+ if t, ok := val.(**PaymentRelayInfo); ok && l <= 10 {
+ scratch := make([]byte, l)
+
+ n, err := io.ReadFull(r, scratch)
+ if err != nil {
+ return err
+ }
+
+ // We expect at least 6 bytes, because we have 2 bytes for
+ // cltv delta and 4 bytes for fee rate.
+ if n < 6 {
+ return tlv.NewTypeForDecodingErr(val,
+ "*hop.paymentRelayInfo", uint64(n), 6)
+ }
+
+ relayInfo := *t
+
+ relayInfo.CltvExpiryDelta = binary.BigEndian.Uint16(
+ scratch[0:2],
+ )
+ relayInfo.FeeRate = binary.BigEndian.Uint32(scratch[2:6])
+
+ // To be able to re-use the DTUint32 function we create a
+ // buffer with just the bytes holding the variable length u32.
+ // If the base fee is zero, this will be an empty buffer, which
+ // is okay.
+ b := bytes.NewBuffer(scratch[6:])
+
+ var baseFee uint32
+ err = tlv.DTUint32(b, &baseFee, buf, l-6)
+ if err != nil {
+ return err
+ }
+
+ relayInfo.BaseFee = lnwire.MilliSatoshi(baseFee)
+
+ return nil
+ }
+
+ return tlv.NewTypeForDecodingErr(val, "*hop.paymentRelayInfo", l, 10)
+}
+
+// PaymentConstraints is a set of restrictions on a payment.
+type PaymentConstraints struct {
+ // MaxCltvExpiry is the maximum expiry height for the payment.
+ MaxCltvExpiry uint32
+
+ // HtlcMinimumMsat is the minimum htlc size for the payment.
+ HtlcMinimumMsat lnwire.MilliSatoshi
+}
+
+func (p *PaymentConstraints) Record() tlv.Record {
+ return tlv.MakeDynamicRecord(
+ 12, &p, func() uint64 {
+ // uint32 + tuint64.
+ return 4 + tlv.SizeTUint64(uint64(
+ p.HtlcMinimumMsat,
+ ))
+ },
+ encodePaymentConstraints, decodePaymentConstraints,
+ )
+}
+
+func encodePaymentConstraints(w io.Writer, val interface{},
+ buf *[8]byte) error {
+
+ if c, ok := val.(**PaymentConstraints); ok {
+ constraints := *c
+
+ binary.BigEndian.PutUint32(buf[:4], constraints.MaxCltvExpiry)
+ if _, err := w.Write(buf[:4]); err != nil {
+ return err
+ }
+
+ // We can safely re-use buf here because we overwrite its
+ // contents.
+ htlcMsat := uint64(constraints.HtlcMinimumMsat)
+
+ return tlv.ETUint64(w, &htlcMsat, buf)
+ }
+
+ return tlv.NewTypeForEncodingErr(val, "**PaymentConstraints")
+}
+
+func decodePaymentConstraints(r io.Reader, val interface{}, buf *[8]byte,
+ l uint64) error {
+
+ if c, ok := val.(**PaymentConstraints); ok && l <= 12 {
+ scratch := make([]byte, l)
+
+ n, err := io.ReadFull(r, scratch)
+ if err != nil {
+ return err
+ }
+
+ // We expect at least 4 bytes for our uint32.
+ if n < 4 {
+ return tlv.NewTypeForDecodingErr(val,
+ "*paymentConstraints", uint64(n), 4)
+ }
+
+ payConstraints := *c
+
+ payConstraints.MaxCltvExpiry = binary.BigEndian.Uint32(
+ scratch[:4],
+ )
+
+ // This could be empty if our minimum is zero, that's okay.
+ var (
+ b = bytes.NewBuffer(scratch[4:])
+ minHtlc uint64
+ )
+
+ err = tlv.DTUint64(b, &minHtlc, buf, l-4)
+ if err != nil {
+ return err
+ }
+ payConstraints.HtlcMinimumMsat = lnwire.MilliSatoshi(minHtlc)
+
+ return nil
+ }
+
+ return tlv.NewTypeForDecodingErr(val, "**PaymentConstraints", l, l)
+}
diff --git a/payments/db/migration1/record/custom_records.go b/payments/db/migration1/record/custom_records.go
new file mode 100644
index 0000000..01952c2
--- /dev/null
+++ b/payments/db/migration1/record/custom_records.go
@@ -0,0 +1,31 @@
+package record
+
+import (
+ "fmt"
+)
+
+const (
+ // CustomTypeStart is the start of the custom tlv type range as defined
+ // in BOLT 01.
+ CustomTypeStart = 65536
+)
+
+// CustomSet stores a set of custom key/value pairs.
+type CustomSet map[uint64][]byte
+
+// Validate checks that all custom records are in the custom type range.
+func (c CustomSet) Validate() error {
+ for key := range c {
+ if key < CustomTypeStart {
+ return fmt.Errorf("no custom records with types "+
+ "below %v allowed", CustomTypeStart)
+ }
+ }
+
+ return nil
+}
+
+// IsKeysend checks if the custom records contain the key send type.
+func (c CustomSet) IsKeysend() bool {
+ return c[KeySendType] != nil
+}
diff --git a/payments/db/migration1/record/experimental.go b/payments/db/migration1/record/experimental.go
new file mode 100644
index 0000000..3aff0ff
--- /dev/null
+++ b/payments/db/migration1/record/experimental.go
@@ -0,0 +1,6 @@
+package record
+
+const (
+ // KeySendType is the custom record identifier for keysend preimages.
+ KeySendType uint64 = 5482373484
+)
diff --git a/payments/db/migration1/record/hop.go b/payments/db/migration1/record/hop.go
new file mode 100644
index 0000000..e5c0884
--- /dev/null
+++ b/payments/db/migration1/record/hop.go
@@ -0,0 +1,99 @@
+package record
+
+import (
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+const (
+ // AmtOnionType is the type used in the onion to reference the amount to
+ // send to the next hop.
+ AmtOnionType tlv.Type = 2
+
+ // LockTimeTLV is the type used in the onion to reference the CLTV
+ // value that should be used for the next hop's HTLC.
+ LockTimeOnionType tlv.Type = 4
+
+ // NextHopOnionType is the type used in the onion to reference the ID
+ // of the next hop.
+ NextHopOnionType tlv.Type = 6
+
+ // EncryptedDataOnionType is the type used to include encrypted data
+ // provided by the receiver in the onion for use in blinded paths.
+ EncryptedDataOnionType tlv.Type = 10
+
+ // BlindingPointOnionType is the type used to include receiver provided
+ // ephemeral keys in the onion that are used in blinded paths.
+ BlindingPointOnionType tlv.Type = 12
+
+ // MetadataOnionType is the type used in the onion for the payment
+ // metadata.
+ MetadataOnionType tlv.Type = 16
+
+ // TotalAmtMsatBlindedType is the type used in the onion for the total
+ // amount field that is included in the final hop for blinded payments.
+ TotalAmtMsatBlindedType tlv.Type = 18
+)
+
+// NewAmtToFwdRecord creates a tlv.Record that encodes the amount_to_forward
+// (type 2) for an onion payload.
+func NewAmtToFwdRecord(amt *uint64) tlv.Record {
+ return tlv.MakeDynamicRecord(
+ AmtOnionType, amt, func() uint64 {
+ return tlv.SizeTUint64(*amt)
+ },
+ tlv.ETUint64, tlv.DTUint64,
+ )
+}
+
+// NewLockTimeRecord creates a tlv.Record that encodes the outgoing_cltv_value
+// (type 4) for an onion payload.
+func NewLockTimeRecord(lockTime *uint32) tlv.Record {
+ return tlv.MakeDynamicRecord(
+ LockTimeOnionType, lockTime, func() uint64 {
+ return tlv.SizeTUint32(*lockTime)
+ },
+ tlv.ETUint32, tlv.DTUint32,
+ )
+}
+
+// NewNextHopIDRecord creates a tlv.Record that encodes the short_channel_id
+// (type 6) for an onion payload.
+func NewNextHopIDRecord(cid *uint64) tlv.Record {
+ return tlv.MakePrimitiveRecord(NextHopOnionType, cid)
+}
+
+// NewEncryptedDataRecord creates a tlv.Record that encodes the encrypted_data
+// (type 10) record for an onion payload.
+func NewEncryptedDataRecord(data *[]byte) tlv.Record {
+ return tlv.MakePrimitiveRecord(EncryptedDataOnionType, data)
+}
+
+// NewBlindingPointRecord creates a tlv.Record that encodes the blinding_point
+// (type 12) record for an onion payload.
+func NewBlindingPointRecord(point **btcec.PublicKey) tlv.Record {
+ return tlv.MakePrimitiveRecord(BlindingPointOnionType, point)
+}
+
+// NewMetadataRecord creates a tlv.Record that encodes the metadata (type 10)
+// for an onion payload.
+func NewMetadataRecord(metadata *[]byte) tlv.Record {
+ return tlv.MakeDynamicRecord(
+ MetadataOnionType, metadata,
+ func() uint64 {
+ return uint64(len(*metadata))
+ },
+ tlv.EVarBytes, tlv.DVarBytes,
+ )
+}
+
+// NewTotalAmtMsatBlinded creates a tlv.Record that encodes the
+// total_amount_msat for the final an onion payload within a blinded route.
+func NewTotalAmtMsatBlinded(amt *uint64) tlv.Record {
+ return tlv.MakeDynamicRecord(
+ TotalAmtMsatBlindedType, amt, func() uint64 {
+ return tlv.SizeTUint64(*amt)
+ },
+ tlv.ETUint64, tlv.DTUint64,
+ )
+}
diff --git a/payments/db/migration1/record/mpp.go b/payments/db/migration1/record/mpp.go
new file mode 100644
index 0000000..576ada7
--- /dev/null
+++ b/payments/db/migration1/record/mpp.go
@@ -0,0 +1,112 @@
+package record
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
+ "github.com/lightningnetwork/lnd/tlv"
+)
+
+// MPPOnionType is the type used in the onion to reference the MPP fields:
+// total_amt and payment_addr.
+const MPPOnionType tlv.Type = 8
+
+// MPP is a record that encodes the fields necessary for multi-path payments.
+type MPP struct {
+ // paymentAddr is a random, receiver-generated value used to avoid
+ // collisions with concurrent payers.
+ paymentAddr [32]byte
+
+ // totalMsat is the total value of the payment, potentially spread
+ // across more than one HTLC.
+ totalMsat lnwire.MilliSatoshi
+}
+
+// NewMPP generates a new MPP record with the given total and payment address.
+func NewMPP(total lnwire.MilliSatoshi, addr [32]byte) *MPP {
+ return &MPP{
+ paymentAddr: addr,
+ totalMsat: total,
+ }
+}
+
+// PaymentAddr returns the payment address contained in the MPP record.
+func (r *MPP) PaymentAddr() [32]byte {
+ return r.paymentAddr
+}
+
+// TotalMsat returns the total value of an MPP payment in msats.
+func (r *MPP) TotalMsat() lnwire.MilliSatoshi {
+ return r.totalMsat
+}
+
+// MPPEncoder writes the MPP record to the provided io.Writer.
+func MPPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
+ if v, ok := val.(*MPP); ok {
+ err := tlv.EBytes32(w, &v.paymentAddr, buf)
+ if err != nil {
+ return err
+ }
+
+ return tlv.ETUint64T(w, uint64(v.totalMsat), buf)
+ }
+ return tlv.NewTypeForEncodingErr(val, "MPP")
+}
+
+const (
+ // minMPPLength is the minimum length of a serialized MPP TLV record,
+ // which occurs when the truncated encoding of total_amt_msat takes 0
+ // bytes, leaving only the payment_addr.
+ minMPPLength = 32
+
+ // maxMPPLength is the maximum length of a serialized MPP TLV record,
+ // which occurs when the truncated encoding of total_amt_msat takes 8
+ // bytes.
+ maxMPPLength = 40
+)
+
+// MPPDecoder reads the MPP record to the provided io.Reader.
+func MPPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
+ if v, ok := val.(*MPP); ok && minMPPLength <= l && l <= maxMPPLength {
+ if err := tlv.DBytes32(r, &v.paymentAddr, buf, 32); err != nil {
+ return err
+ }
+
+ var total uint64
+ if err := tlv.DTUint64(r, &total, buf, l-32); err != nil {
+ return err
+ }
+ v.totalMsat = lnwire.MilliSatoshi(total)
+
+ return nil
+ }
+ return tlv.NewTypeForDecodingErr(val, "MPP", l, maxMPPLength)
+}
+
+// Record returns a tlv.Record that can be used to encode or decode this record.
+func (r *MPP) Record() tlv.Record {
+ // Fixed-size, 32 byte payment address followed by truncated 64-bit
+ // total msat.
+ size := func() uint64 {
+ return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
+ }
+
+ return tlv.MakeDynamicRecord(
+ MPPOnionType, r, size, MPPEncoder, MPPDecoder,
+ )
+}
+
+// PayloadSize returns the size this record takes up in encoded form.
+func (r *MPP) PayloadSize() uint64 {
+ return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
+}
+
+// String returns a human-readable representation of the mpp payload field.
+func (r *MPP) String() string {
+ if r == nil {
+ return "<nil>"
+ }
+
+ return fmt.Sprintf("total=%v, addr=%x", r.totalMsat, r.paymentAddr)
+}
diff --git a/payments/db/migration1/route.go b/payments/db/migration1/route.go
index dc9a221..bdf8c6f 100644
--- a/payments/db/migration1/route.go
+++ b/payments/db/migration1/route.go
@@ -4,8 +4,8 @@ import (
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
- "github.com/lightningnetwork/lnd/lnwire"
- "github.com/lightningnetwork/lnd/record"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/record"
"github.com/lightningnetwork/lnd/tlv"
)
diff --git a/payments/db/migration1/sql_converters.go b/payments/db/migration1/sql_converters.go
index dbf88b2..50d129b 100644
--- a/payments/db/migration1/sql_converters.go
+++ b/payments/db/migration1/sql_converters.go
@@ -8,9 +8,9 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/record"
"github.com/lightningnetwork/lnd/payments/db/migration1/sqlc"
- "github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
diff --git a/payments/db/migration1/sql_migration.go b/payments/db/migration1/sql_migration.go
index b208117..2bda95f 100644
--- a/payments/db/migration1/sql_migration.go
+++ b/payments/db/migration1/sql_migration.go
@@ -10,7 +10,7 @@ import (
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
"github.com/lightningnetwork/lnd/payments/db/migration1/sqlc"
"golang.org/x/time/rate"
)
diff --git a/payments/db/migration1/sql_migration_test.go b/payments/db/migration1/sql_migration_test.go
index f19dd91..ab0ee6c 100644
--- a/payments/db/migration1/sql_migration_test.go
+++ b/payments/db/migration1/sql_migration_test.go
@@ -15,9 +15,9 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/record"
"github.com/lightningnetwork/lnd/payments/db/migration1/sqlc"
- "github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/sqldb"
"github.com/stretchr/testify/require"
)
diff --git a/payments/db/migration1/sql_store.go b/payments/db/migration1/sql_store.go
index 2a2c977..2129656 100644
--- a/payments/db/migration1/sql_store.go
+++ b/payments/db/migration1/sql_store.go
@@ -11,7 +11,7 @@ import (
"time"
"github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
"github.com/lightningnetwork/lnd/payments/db/migration1/sqlc"
"github.com/lightningnetwork/lnd/sqldb"
)
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.