mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
c48841a38b
This commit adds encoding and decoding for blinded route data blobs. TLV fields such as path_id (which are only used for the final hop) are omitted to minimize the change size.
305 lines
7.9 KiB
Go
305 lines
7.9 KiB
Go
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)
|
|
}
|