lnd/record/blinded_data.go

305 lines
7.9 KiB
Go
Raw Normal View History

package record
import (
"bytes"
"encoding/binary"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv"
)
// 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 {
// ShortChannelID is the channel ID of the next hop.
ShortChannelID tlv.RecordT[tlv.TlvType2, lnwire.ShortChannelID]
// 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.RecordT[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]
}
// NewBlindedRouteData creates the data that's provided for hops within a
// blinded route.
func NewBlindedRouteData(chanID lnwire.ShortChannelID,
blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo,
constraints *PaymentConstraints,
features *lnwire.FeatureVector) *BlindedRouteData {
info := &BlindedRouteData{
ShortChannelID: tlv.NewRecordT[tlv.TlvType2](chanID),
RelayInfo: 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
}
// DecodeBlindedRouteData decodes the data provided within a blinded route.
func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) {
var (
d BlindedRouteData
blindingOverride = d.NextBlindingOverride.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(
&d.ShortChannelID,
&blindingOverride, &d.RelayInfo.Val, &constraints,
&features,
)
if err != nil {
return nil, err
}
val, ok := typeMap[d.NextBlindingOverride.TlvType()]
if ok && val == nil {
d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride)
}
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)
)
recordProducers = append(recordProducers, &data.ShortChannelID)
data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8,
*btcec.PublicKey]) {
recordProducers = append(recordProducers, &pk)
})
recordProducers = append(recordProducers, &data.RelayInfo.Val)
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
}
// 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.
BaseFee uint32
}
// newPaymentRelayRecord 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(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
}
// We can safely reuse buf here because we overwrite its
// contents.
return tlv.ETUint32(w, &relayInfo.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:])
return tlv.DTUint32(b, &relayInfo.BaseFee, buf, l-6)
}
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)
}