blinded_data.go
1 package record 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "io" 7 8 "github.com/btcsuite/btcd/btcec/v2" 9 "github.com/lightningnetwork/lnd/fn/v2" 10 "github.com/lightningnetwork/lnd/lnwire" 11 "github.com/lightningnetwork/lnd/tlv" 12 ) 13 14 // AverageDummyHopPayloadSize is the size of a standard blinded path dummy hop 15 // payload. In most cases, this is larger than the other payload types and so 16 // to make sure that a sender cannot use this fact to know if a dummy hop is 17 // present or not, we'll make sure to always pad all payloads to at least this 18 // size. 19 const AverageDummyHopPayloadSize = 51 20 21 // BlindedRouteData contains the information that is included in a blinded 22 // route encrypted data blob that is created by the recipient to provide 23 // forwarding information. 24 type BlindedRouteData struct { 25 // Padding is an optional set of bytes that a recipient can use to pad 26 // the data so that the encrypted recipient data blobs are all the same 27 // length. 28 Padding tlv.OptionalRecordT[tlv.TlvType1, []byte] 29 30 // ShortChannelID is the channel ID of the next hop. 31 ShortChannelID tlv.OptionalRecordT[tlv.TlvType2, lnwire.ShortChannelID] 32 33 // NextNodeID is the node ID of the next node on the path. In the 34 // context of blinded path payments, this is used to indicate the 35 // presence of dummy hops that need to be peeled from the onion. 36 NextNodeID tlv.OptionalRecordT[tlv.TlvType4, *btcec.PublicKey] 37 38 // PathID is a secret set of bytes that the blinded path creator will 39 // set so that they can check the value on decryption to ensure that the 40 // path they created was used for the intended purpose. 41 PathID tlv.OptionalRecordT[tlv.TlvType6, []byte] 42 43 // NextBlindingOverride is a blinding point that should be switched 44 // in for the next hop. This is used to combine two blinded paths into 45 // one (which primarily is used in onion messaging, but in theory 46 // could be used for payments as well). 47 NextBlindingOverride tlv.OptionalRecordT[tlv.TlvType8, *btcec.PublicKey] 48 49 // RelayInfo provides the relay parameters for the hop. 50 RelayInfo tlv.OptionalRecordT[tlv.TlvType10, PaymentRelayInfo] 51 52 // Constraints provides the payment relay constraints for the hop. 53 Constraints tlv.OptionalRecordT[tlv.TlvType12, PaymentConstraints] 54 55 // Features is the set of features the payment requires. 56 Features tlv.OptionalRecordT[tlv.TlvType14, lnwire.FeatureVector] 57 } 58 59 // NewNonFinalBlindedRouteData creates the data that's provided for hops within 60 // a blinded route. 61 func NewNonFinalBlindedRouteData(chanID lnwire.ShortChannelID, 62 blindingOverride *btcec.PublicKey, relayInfo PaymentRelayInfo, 63 constraints *PaymentConstraints, 64 features *lnwire.FeatureVector) *BlindedRouteData { 65 66 info := &BlindedRouteData{ 67 ShortChannelID: tlv.SomeRecordT( 68 tlv.NewRecordT[tlv.TlvType2](chanID), 69 ), 70 RelayInfo: tlv.SomeRecordT( 71 tlv.NewRecordT[tlv.TlvType10](relayInfo), 72 ), 73 } 74 75 if blindingOverride != nil { 76 info.NextBlindingOverride = tlv.SomeRecordT( 77 tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride)) 78 } 79 80 if constraints != nil { 81 info.Constraints = tlv.SomeRecordT( 82 tlv.NewRecordT[tlv.TlvType12](*constraints)) 83 } 84 85 if features != nil { 86 info.Features = tlv.SomeRecordT( 87 tlv.NewRecordT[tlv.TlvType14](*features), 88 ) 89 } 90 91 return info 92 } 93 94 // NewNonFinalBlindedRouteData creates the data that's provided for hops within 95 // a blinded route. 96 func NewNonFinalBlindedRouteDataOnionMessage( 97 nextNode fn.Either[*btcec.PublicKey, lnwire.ShortChannelID], 98 blindingOverride *btcec.PublicKey, 99 features *lnwire.FeatureVector) *BlindedRouteData { 100 101 info := fn.ElimEither( 102 nextNode, 103 func(nextNodeID *btcec.PublicKey) *BlindedRouteData { 104 return &BlindedRouteData{ 105 NextNodeID: tlv.SomeRecordT( 106 tlv.NewPrimitiveRecord[tlv.TlvType4]( 107 nextNodeID, 108 ), 109 ), 110 } 111 }, 112 func(chanID lnwire.ShortChannelID) *BlindedRouteData { 113 return &BlindedRouteData{ 114 ShortChannelID: tlv.SomeRecordT( 115 tlv.NewRecordT[tlv.TlvType2](chanID), 116 ), 117 } 118 }, 119 ) 120 121 if blindingOverride != nil { 122 info.NextBlindingOverride = tlv.SomeRecordT( 123 tlv.NewPrimitiveRecord[tlv.TlvType8](blindingOverride)) 124 } 125 126 if features != nil { 127 info.Features = tlv.SomeRecordT( 128 tlv.NewRecordT[tlv.TlvType14](*features), 129 ) 130 } 131 132 return info 133 } 134 135 // NewFinalHopBlindedRouteData creates the data that's provided for the final 136 // hop in a blinded route. 137 func NewFinalHopBlindedRouteData(constraints *PaymentConstraints, 138 pathID []byte) *BlindedRouteData { 139 140 var data BlindedRouteData 141 if pathID != nil { 142 data.PathID = tlv.SomeRecordT( 143 tlv.NewPrimitiveRecord[tlv.TlvType6](pathID), 144 ) 145 } 146 147 if constraints != nil { 148 data.Constraints = tlv.SomeRecordT( 149 tlv.NewRecordT[tlv.TlvType12](*constraints)) 150 } 151 152 return &data 153 } 154 155 // NewDummyHopRouteData creates the data that's provided for any hop preceding 156 // a dummy hop. The presence of such a payload indicates to the reader that 157 // they are the intended recipient and should peel the remainder of the onion. 158 func NewDummyHopRouteData(ourPubKey *btcec.PublicKey, 159 relayInfo PaymentRelayInfo, 160 constraints PaymentConstraints) *BlindedRouteData { 161 162 return &BlindedRouteData{ 163 NextNodeID: tlv.SomeRecordT( 164 tlv.NewPrimitiveRecord[tlv.TlvType4](ourPubKey), 165 ), 166 RelayInfo: tlv.SomeRecordT( 167 tlv.NewRecordT[tlv.TlvType10](relayInfo), 168 ), 169 Constraints: tlv.SomeRecordT( 170 tlv.NewRecordT[tlv.TlvType12](constraints), 171 ), 172 } 173 } 174 175 // DecodeBlindedRouteData decodes the data provided within a blinded route. 176 func DecodeBlindedRouteData(r io.Reader) (*BlindedRouteData, error) { 177 var ( 178 d BlindedRouteData 179 180 padding = d.Padding.Zero() 181 scid = d.ShortChannelID.Zero() 182 nextNodeID = d.NextNodeID.Zero() 183 pathID = d.PathID.Zero() 184 blindingOverride = d.NextBlindingOverride.Zero() 185 relayInfo = d.RelayInfo.Zero() 186 constraints = d.Constraints.Zero() 187 features = d.Features.Zero() 188 ) 189 190 var tlvRecords lnwire.ExtraOpaqueData 191 if err := lnwire.ReadElements(r, &tlvRecords); err != nil { 192 return nil, err 193 } 194 195 typeMap, err := tlvRecords.ExtractRecords( 196 &padding, &scid, &nextNodeID, &pathID, &blindingOverride, 197 &relayInfo, &constraints, &features, 198 ) 199 if err != nil { 200 return nil, err 201 } 202 203 val, ok := typeMap[d.Padding.TlvType()] 204 if ok && val == nil { 205 d.Padding = tlv.SomeRecordT(padding) 206 } 207 208 if val, ok := typeMap[d.ShortChannelID.TlvType()]; ok && val == nil { 209 d.ShortChannelID = tlv.SomeRecordT(scid) 210 } 211 212 if val, ok := typeMap[d.NextNodeID.TlvType()]; ok && val == nil { 213 d.NextNodeID = tlv.SomeRecordT(nextNodeID) 214 } 215 216 if val, ok := typeMap[d.PathID.TlvType()]; ok && val == nil { 217 d.PathID = tlv.SomeRecordT(pathID) 218 } 219 220 val, ok = typeMap[d.NextBlindingOverride.TlvType()] 221 if ok && val == nil { 222 d.NextBlindingOverride = tlv.SomeRecordT(blindingOverride) 223 } 224 225 if val, ok := typeMap[d.RelayInfo.TlvType()]; ok && val == nil { 226 d.RelayInfo = tlv.SomeRecordT(relayInfo) 227 } 228 229 if val, ok := typeMap[d.Constraints.TlvType()]; ok && val == nil { 230 d.Constraints = tlv.SomeRecordT(constraints) 231 } 232 233 if val, ok := typeMap[d.Features.TlvType()]; ok && val == nil { 234 d.Features = tlv.SomeRecordT(features) 235 } 236 237 return &d, nil 238 } 239 240 // EncodeBlindedRouteData encodes the blinded route data provided. 241 func EncodeBlindedRouteData(data *BlindedRouteData) ([]byte, error) { 242 var ( 243 e lnwire.ExtraOpaqueData 244 recordProducers = make([]tlv.RecordProducer, 0, 5) 245 ) 246 247 data.Padding.WhenSome(func(p tlv.RecordT[tlv.TlvType1, []byte]) { 248 recordProducers = append(recordProducers, &p) 249 }) 250 251 data.ShortChannelID.WhenSome(func(scid tlv.RecordT[tlv.TlvType2, 252 lnwire.ShortChannelID]) { 253 254 recordProducers = append(recordProducers, &scid) 255 }) 256 257 data.NextNodeID.WhenSome(func(f tlv.RecordT[tlv.TlvType4, 258 *btcec.PublicKey]) { 259 260 recordProducers = append(recordProducers, &f) 261 }) 262 263 data.PathID.WhenSome(func(pathID tlv.RecordT[tlv.TlvType6, []byte]) { 264 recordProducers = append(recordProducers, &pathID) 265 }) 266 267 data.NextBlindingOverride.WhenSome(func(pk tlv.RecordT[tlv.TlvType8, 268 *btcec.PublicKey]) { 269 270 recordProducers = append(recordProducers, &pk) 271 }) 272 273 data.RelayInfo.WhenSome(func(r tlv.RecordT[tlv.TlvType10, 274 PaymentRelayInfo]) { 275 276 recordProducers = append(recordProducers, &r) 277 }) 278 279 data.Constraints.WhenSome(func(cs tlv.RecordT[tlv.TlvType12, 280 PaymentConstraints]) { 281 282 recordProducers = append(recordProducers, &cs) 283 }) 284 285 data.Features.WhenSome(func(f tlv.RecordT[tlv.TlvType14, 286 lnwire.FeatureVector]) { 287 288 recordProducers = append(recordProducers, &f) 289 }) 290 291 if err := e.PackRecords(recordProducers...); err != nil { 292 return nil, err 293 } 294 295 return e[:], nil 296 } 297 298 // PadBy adds "n" padding bytes to the BlindedRouteData using the Padding field. 299 // Callers should be aware that the total payload size will change by more than 300 // "n" since the "n" bytes will be prefixed by BigSize type and length fields. 301 // Callers may need to call PadBy iteratively until each encrypted data packet 302 // is the same size and so each call will overwrite the Padding record. 303 // Note that calling PadBy with an n value of 0 will still result in a zero 304 // length TLV entry being added. 305 func (b *BlindedRouteData) PadBy(n int) { 306 b.Padding = tlv.SomeRecordT( 307 tlv.NewPrimitiveRecord[tlv.TlvType1](make([]byte, n)), 308 ) 309 } 310 311 // PaymentRelayInfo describes the relay policy for a blinded path. 312 type PaymentRelayInfo struct { 313 // CltvExpiryDelta is the expiry delta for the payment. 314 CltvExpiryDelta uint16 315 316 // FeeRate is the fee rate that will be charged per millionth of a 317 // satoshi. 318 FeeRate uint32 319 320 // BaseFee is the per-htlc fee charged in milli-satoshis. 321 BaseFee lnwire.MilliSatoshi 322 } 323 324 // Record creates a tlv.Record that encodes the payment relay (type 10) type for 325 // an encrypted blob payload. 326 func (i *PaymentRelayInfo) Record() tlv.Record { 327 return tlv.MakeDynamicRecord( 328 10, &i, func() uint64 { 329 // uint16 + uint32 + tuint32 330 return 2 + 4 + tlv.SizeTUint32(uint32(i.BaseFee)) 331 }, encodePaymentRelay, decodePaymentRelay, 332 ) 333 } 334 335 func encodePaymentRelay(w io.Writer, val interface{}, buf *[8]byte) error { 336 if t, ok := val.(**PaymentRelayInfo); ok { 337 relayInfo := *t 338 339 // Just write our first 6 bytes directly. 340 binary.BigEndian.PutUint16(buf[:2], relayInfo.CltvExpiryDelta) 341 binary.BigEndian.PutUint32(buf[2:6], relayInfo.FeeRate) 342 if _, err := w.Write(buf[0:6]); err != nil { 343 return err 344 } 345 346 baseFee := uint32(relayInfo.BaseFee) 347 348 // We can safely reuse buf here because we overwrite its 349 // contents. 350 return tlv.ETUint32(w, &baseFee, buf) 351 } 352 353 return tlv.NewTypeForEncodingErr(val, "**hop.PaymentRelayInfo") 354 } 355 356 func decodePaymentRelay(r io.Reader, val interface{}, buf *[8]byte, 357 l uint64) error { 358 359 if t, ok := val.(**PaymentRelayInfo); ok && l <= 10 { 360 scratch := make([]byte, l) 361 362 n, err := io.ReadFull(r, scratch) 363 if err != nil { 364 return err 365 } 366 367 // We expect at least 6 bytes, because we have 2 bytes for 368 // cltv delta and 4 bytes for fee rate. 369 if n < 6 { 370 return tlv.NewTypeForDecodingErr(val, 371 "*hop.paymentRelayInfo", uint64(n), 6) 372 } 373 374 relayInfo := *t 375 376 relayInfo.CltvExpiryDelta = binary.BigEndian.Uint16( 377 scratch[0:2], 378 ) 379 relayInfo.FeeRate = binary.BigEndian.Uint32(scratch[2:6]) 380 381 // To be able to re-use the DTUint32 function we create a 382 // buffer with just the bytes holding the variable length u32. 383 // If the base fee is zero, this will be an empty buffer, which 384 // is okay. 385 b := bytes.NewBuffer(scratch[6:]) 386 387 var baseFee uint32 388 err = tlv.DTUint32(b, &baseFee, buf, l-6) 389 if err != nil { 390 return err 391 } 392 393 relayInfo.BaseFee = lnwire.MilliSatoshi(baseFee) 394 395 return nil 396 } 397 398 return tlv.NewTypeForDecodingErr(val, "*hop.paymentRelayInfo", l, 10) 399 } 400 401 // PaymentConstraints is a set of restrictions on a payment. 402 type PaymentConstraints struct { 403 // MaxCltvExpiry is the maximum expiry height for the payment. 404 MaxCltvExpiry uint32 405 406 // HtlcMinimumMsat is the minimum htlc size for the payment. 407 HtlcMinimumMsat lnwire.MilliSatoshi 408 } 409 410 func (p *PaymentConstraints) Record() tlv.Record { 411 return tlv.MakeDynamicRecord( 412 12, &p, func() uint64 { 413 // uint32 + tuint64. 414 return 4 + tlv.SizeTUint64(uint64( 415 p.HtlcMinimumMsat, 416 )) 417 }, 418 encodePaymentConstraints, decodePaymentConstraints, 419 ) 420 } 421 422 func encodePaymentConstraints(w io.Writer, val interface{}, 423 buf *[8]byte) error { 424 425 if c, ok := val.(**PaymentConstraints); ok { 426 constraints := *c 427 428 binary.BigEndian.PutUint32(buf[:4], constraints.MaxCltvExpiry) 429 if _, err := w.Write(buf[:4]); err != nil { 430 return err 431 } 432 433 // We can safely re-use buf here because we overwrite its 434 // contents. 435 htlcMsat := uint64(constraints.HtlcMinimumMsat) 436 437 return tlv.ETUint64(w, &htlcMsat, buf) 438 } 439 440 return tlv.NewTypeForEncodingErr(val, "**PaymentConstraints") 441 } 442 443 func decodePaymentConstraints(r io.Reader, val interface{}, buf *[8]byte, 444 l uint64) error { 445 446 if c, ok := val.(**PaymentConstraints); ok && l <= 12 { 447 scratch := make([]byte, l) 448 449 n, err := io.ReadFull(r, scratch) 450 if err != nil { 451 return err 452 } 453 454 // We expect at least 4 bytes for our uint32. 455 if n < 4 { 456 return tlv.NewTypeForDecodingErr(val, 457 "*paymentConstraints", uint64(n), 4) 458 } 459 460 payConstraints := *c 461 462 payConstraints.MaxCltvExpiry = binary.BigEndian.Uint32( 463 scratch[:4], 464 ) 465 466 // This could be empty if our minimum is zero, that's okay. 467 var ( 468 b = bytes.NewBuffer(scratch[4:]) 469 minHtlc uint64 470 ) 471 472 err = tlv.DTUint64(b, &minHtlc, buf, l-4) 473 if err != nil { 474 return err 475 } 476 payConstraints.HtlcMinimumMsat = lnwire.MilliSatoshi(minHtlc) 477 478 return nil 479 } 480 481 return tlv.NewTypeForDecodingErr(val, "**PaymentConstraints", l, l) 482 }