From 6c28cc4d0b4f368a0d79a2e410ae99e0a8ba2ecb Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 27 Feb 2026 09:05:55 +0100 Subject: [PATCH] payments/migration1: freeze lnwire and record dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also freeze the lnwire and record packages used by the migration. Copy the minimal subset of lnwire files (16) into payments/db/migration1/lnwire/ and all record files (6) into payments/db/migration1/record/. Three lnwire files are trimmed to avoid pulling in the full message-type dispatch tree — all changes are purely subtractive and can be verified with: diff lnwire/message.go payments/db/migration1/lnwire/message.go diff lnwire/writer.go payments/db/migration1/lnwire/writer.go diff lnwire/lnwire.go payments/db/migration1/lnwire/lnwire.go All migration1 files now import only the frozen packages, removing the live dependency on lnwire and record so future changes to those packages cannot affect migration correctness. --- .golangci.yml | 4 +- payments/db/migration1/codec.go | 2 +- .../db/migration1/kv_duplicate_payments.go | 2 +- payments/db/migration1/kv_store.go | 4 +- payments/db/migration1/lnwire/channel_id.go | 127 ++ .../db/migration1/lnwire/channel_update.go | 422 +++++ .../db/migration1/lnwire/custom_records.go | 278 +++ payments/db/migration1/lnwire/error.go | 143 ++ payments/db/migration1/lnwire/extra_bytes.go | 309 ++++ payments/db/migration1/lnwire/features.go | 898 ++++++++++ payments/db/migration1/lnwire/interfaces.go | 174 ++ payments/db/migration1/lnwire/lnwire.go | 426 +++++ payments/db/migration1/lnwire/message.go | 323 ++++ payments/db/migration1/lnwire/msat.go | 92 + payments/db/migration1/lnwire/onion_error.go | 1546 +++++++++++++++++ .../db/migration1/lnwire/short_channel_id.go | 105 ++ payments/db/migration1/lnwire/signature.go | 292 ++++ payments/db/migration1/lnwire/typed_fee.go | 60 + .../db/migration1/lnwire/update_add_htlc.go | 226 +++ payments/db/migration1/lnwire/writer.go | 185 ++ .../db/migration1/migration_validation.go | 4 +- payments/db/migration1/payment.go | 2 +- payments/db/migration1/record/amp.go | 121 ++ payments/db/migration1/record/blinded_data.go | 440 +++++ .../db/migration1/record/custom_records.go | 31 + payments/db/migration1/record/experimental.go | 6 + payments/db/migration1/record/hop.go | 99 ++ payments/db/migration1/record/mpp.go | 112 ++ payments/db/migration1/route.go | 4 +- payments/db/migration1/sql_converters.go | 4 +- payments/db/migration1/sql_migration.go | 2 +- payments/db/migration1/sql_migration_test.go | 4 +- payments/db/migration1/sql_store.go | 2 +- 33 files changed, 6433 insertions(+), 16 deletions(-) create mode 100644 payments/db/migration1/lnwire/channel_id.go create mode 100644 payments/db/migration1/lnwire/channel_update.go create mode 100644 payments/db/migration1/lnwire/custom_records.go create mode 100644 payments/db/migration1/lnwire/error.go create mode 100644 payments/db/migration1/lnwire/extra_bytes.go create mode 100644 payments/db/migration1/lnwire/features.go create mode 100644 payments/db/migration1/lnwire/interfaces.go create mode 100644 payments/db/migration1/lnwire/lnwire.go create mode 100644 payments/db/migration1/lnwire/message.go create mode 100644 payments/db/migration1/lnwire/msat.go create mode 100644 payments/db/migration1/lnwire/onion_error.go create mode 100644 payments/db/migration1/lnwire/short_channel_id.go create mode 100644 payments/db/migration1/lnwire/signature.go create mode 100644 payments/db/migration1/lnwire/typed_fee.go create mode 100644 payments/db/migration1/lnwire/update_add_htlc.go create mode 100644 payments/db/migration1/lnwire/writer.go create mode 100644 payments/db/migration1/record/amp.go create mode 100644 payments/db/migration1/record/blinded_data.go create mode 100644 payments/db/migration1/record/custom_records.go create mode 100644 payments/db/migration1/record/experimental.go create mode 100644 payments/db/migration1/record/hop.go create mode 100644 payments/db/migration1/record/mpp.go diff --git a/.golangci.yml b/.golangci.yml index dc82da888..4cb893934 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 acc173bc4..64225d170 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 f72848da4..1454430bc 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 51bf005db..eda1945bc 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 000000000..5c9eca34f --- /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 000000000..09b8044bd --- /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 000000000..de5ff4a23 --- /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 000000000..382411999 --- /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 000000000..39228c101 --- /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 000000000..4e927e170 --- /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 000000000..115b07d7b --- /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 000000000..09057dae4 --- /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 000000000..def762f18 --- /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 "" + } +} + +// 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 000000000..7d6d581e1 --- /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 000000000..9cc115ff0 --- /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 "" + } +} + +// 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 000000000..e26575001 --- /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 000000000..35b039ed0 --- /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 0x02 r 0x02 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 , remaining: [0x02 r 0x02 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 , remaining: [r 0x02 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, , 0x20, + // , 0x20, , s, a total of 7 bytes. + if rLen <= 0 || rLen+7 > len(sig) { + return Sig{}, errBadRLength + } + + // Reading , 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, , 0x20, + // , 0x20, , 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 0x02 r 0x02 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 000000000..f9b6c8d01 --- /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 000000000..38ceeec21 --- /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 000000000..db575663e --- /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 fe43cf28f..f20c92783 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 53a3d814e..78599c3ed 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 000000000..f63c7a141 --- /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 "" + } + + 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 000000000..22c09672f --- /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 000000000..01952c24e --- /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 000000000..3aff0ff26 --- /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 000000000..e5c0884f1 --- /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 000000000..576ada776 --- /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 "" + } + + 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 dc9a22162..bdf8c6ff0 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 dbf88b2ea..50d129bf7 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 b20811760..2bda95f57 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 f19dd9179..ab0ee6cc1 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 2a2c97752..2129656d9 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" )