blinded_path.go
1 package zpay32 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "io" 7 8 "github.com/btcsuite/btcd/btcec/v2" 9 sphinx "github.com/lightningnetwork/lightning-onion" 10 "github.com/lightningnetwork/lnd/lnwire" 11 "github.com/lightningnetwork/lnd/tlv" 12 ) 13 14 const ( 15 // maxNumHopsPerPath is the maximum number of blinded path hops that can 16 // be included in a single encoded blinded path. This is calculated 17 // based on the `data_length` limit of 638 bytes for any tagged field in 18 // a BOLT 11 invoice along with the estimated number of bytes required 19 // for encoding the most minimal blinded path hop. See the [bLIP 20 // proposal](https://github.com/lightning/blips/pull/39) for a detailed 21 // calculation. 22 maxNumHopsPerPath = 7 23 24 // maxCipherTextLength defines the largest cipher text size allowed. 25 // This is derived by using the `data_length` upper bound of 639 bytes 26 // and then assuming the case of a path with only a single hop (meaning 27 // the cipher text may be as large as possible). 28 maxCipherTextLength = 535 29 ) 30 31 var ( 32 // byteOrder defines the endian-ness we use for encoding to and from 33 // buffers. 34 byteOrder = binary.BigEndian 35 ) 36 37 // BlindedPaymentPath holds all the information a payer needs to know about a 38 // blinded path to a receiver of a payment. 39 type BlindedPaymentPath struct { 40 // FeeBaseMsat is the total base fee for the path in milli-satoshis. 41 FeeBaseMsat uint32 42 43 // FeeRate is the total fee rate for the path in parts per million. 44 FeeRate uint32 45 46 // CltvExpiryDelta is the total CLTV delta to apply to the path. 47 CltvExpiryDelta uint16 48 49 // HTLCMinMsat is the minimum number of milli-satoshis that any hop in 50 // the path will route. 51 HTLCMinMsat uint64 52 53 // HTLCMaxMsat is the maximum number of milli-satoshis that a hop in the 54 // path will route. 55 HTLCMaxMsat uint64 56 57 // Features is the feature bit vector for the path. 58 Features *lnwire.FeatureVector 59 60 // FirstEphemeralBlindingPoint is the blinding point to send to the 61 // introduction node. It will be used by the introduction node to derive 62 // a shared secret with the receiver which can then be used to decode 63 // the encrypted payload from the receiver. 64 FirstEphemeralBlindingPoint *btcec.PublicKey 65 66 // Hops is the blinded path. The first hop is the introduction node and 67 // so the BlindedNodeID of this hop will be the real node ID. 68 Hops []*sphinx.BlindedHopInfo 69 } 70 71 // DecodeBlindedPayment attempts to parse a BlindedPaymentPath from the passed 72 // reader. 73 func DecodeBlindedPayment(r io.Reader) (*BlindedPaymentPath, error) { 74 var payment BlindedPaymentPath 75 76 if err := binary.Read(r, byteOrder, &payment.FeeBaseMsat); err != nil { 77 return nil, err 78 } 79 80 if err := binary.Read(r, byteOrder, &payment.FeeRate); err != nil { 81 return nil, err 82 } 83 84 err := binary.Read(r, byteOrder, &payment.CltvExpiryDelta) 85 if err != nil { 86 return nil, err 87 } 88 89 err = binary.Read(r, byteOrder, &payment.HTLCMinMsat) 90 if err != nil { 91 return nil, err 92 } 93 94 err = binary.Read(r, byteOrder, &payment.HTLCMaxMsat) 95 if err != nil { 96 return nil, err 97 } 98 99 // Parse the feature bit vector. 100 f := lnwire.EmptyFeatureVector() 101 err = f.Decode(r) 102 if err != nil { 103 return nil, err 104 } 105 payment.Features = f 106 107 // Parse the first ephemeral blinding point. 108 var blindingPointBytes [btcec.PubKeyBytesLenCompressed]byte 109 _, err = r.Read(blindingPointBytes[:]) 110 if err != nil { 111 return nil, err 112 } 113 114 blinding, err := btcec.ParsePubKey(blindingPointBytes[:]) 115 if err != nil { 116 return nil, err 117 } 118 payment.FirstEphemeralBlindingPoint = blinding 119 120 // Read the one byte hop number. 121 var numHops [1]byte 122 _, err = r.Read(numHops[:]) 123 if err != nil { 124 return nil, err 125 } 126 127 payment.Hops = make([]*sphinx.BlindedHopInfo, int(numHops[0])) 128 129 // Parse each hop. 130 for i := 0; i < len(payment.Hops); i++ { 131 hop, err := DecodeBlindedHop(r) 132 if err != nil { 133 return nil, err 134 } 135 136 payment.Hops[i] = hop 137 } 138 139 return &payment, nil 140 } 141 142 // Encode serialises the BlindedPaymentPath and writes the bytes to the passed 143 // writer. 144 // 1) The first 26 bytes contain the relay info: 145 // - Base Fee in msat: uint32 (4 bytes). 146 // - Proportional Fee in PPM: uint32 (4 bytes). 147 // - CLTV expiry delta: uint16 (2 bytes). 148 // - HTLC min msat: uint64 (8 bytes). 149 // - HTLC max msat: uint64 (8 bytes). 150 // 151 // 2) Feature bit vector length (2 bytes). 152 // 3) Feature bit vector (can be zero length). 153 // 4) First blinding point: 33 bytes. 154 // 5) Number of hops: 1 byte. 155 // 6) Encoded BlindedHops. 156 func (p *BlindedPaymentPath) Encode(w io.Writer) error { 157 if err := binary.Write(w, byteOrder, p.FeeBaseMsat); err != nil { 158 return err 159 } 160 161 if err := binary.Write(w, byteOrder, p.FeeRate); err != nil { 162 return err 163 } 164 165 if err := binary.Write(w, byteOrder, p.CltvExpiryDelta); err != nil { 166 return err 167 } 168 169 if err := binary.Write(w, byteOrder, p.HTLCMinMsat); err != nil { 170 return err 171 } 172 173 if err := binary.Write(w, byteOrder, p.HTLCMaxMsat); err != nil { 174 return err 175 } 176 177 if err := p.Features.Encode(w); err != nil { 178 return err 179 } 180 181 _, err := w.Write(p.FirstEphemeralBlindingPoint.SerializeCompressed()) 182 if err != nil { 183 return err 184 } 185 186 numHops := len(p.Hops) 187 if numHops > maxNumHopsPerPath { 188 return fmt.Errorf("the number of hops, %d, exceeds the "+ 189 "maximum of %d", numHops, maxNumHopsPerPath) 190 } 191 192 if _, err := w.Write([]byte{byte(numHops)}); err != nil { 193 return err 194 } 195 196 for _, hop := range p.Hops { 197 if err := EncodeBlindedHop(w, hop); err != nil { 198 return err 199 } 200 } 201 202 return nil 203 } 204 205 // DecodeBlindedHop reads a sphinx.BlindedHopInfo from the passed reader. 206 func DecodeBlindedHop(r io.Reader) (*sphinx.BlindedHopInfo, error) { 207 var nodeIDBytes [btcec.PubKeyBytesLenCompressed]byte 208 _, err := r.Read(nodeIDBytes[:]) 209 if err != nil { 210 return nil, err 211 } 212 213 nodeID, err := btcec.ParsePubKey(nodeIDBytes[:]) 214 if err != nil { 215 return nil, err 216 } 217 218 dataLen, err := tlv.ReadVarInt(r, &[8]byte{}) 219 if err != nil { 220 return nil, err 221 } 222 223 if dataLen > maxCipherTextLength { 224 return nil, fmt.Errorf("a blinded hop cipher text blob may "+ 225 "not exceed the maximum of %d bytes", 226 maxCipherTextLength) 227 } 228 229 encryptedData := make([]byte, dataLen) 230 _, err = r.Read(encryptedData) 231 if err != nil { 232 return nil, err 233 } 234 235 return &sphinx.BlindedHopInfo{ 236 BlindedNodePub: nodeID, 237 CipherText: encryptedData, 238 }, nil 239 } 240 241 // EncodeBlindedHop writes the passed BlindedHopInfo to the given writer. 242 // 243 // 1) Blinded node pub key: 33 bytes 244 // 2) Cipher text length: BigSize 245 // 3) Cipher text. 246 func EncodeBlindedHop(w io.Writer, hop *sphinx.BlindedHopInfo) error { 247 _, err := w.Write(hop.BlindedNodePub.SerializeCompressed()) 248 if err != nil { 249 return err 250 } 251 252 if len(hop.CipherText) > maxCipherTextLength { 253 return fmt.Errorf("encrypted recipient data can not exceed a "+ 254 "length of %d bytes", maxCipherTextLength) 255 } 256 257 err = tlv.WriteVarInt(w, uint64(len(hop.CipherText)), &[8]byte{}) 258 if err != nil { 259 return err 260 } 261 262 _, err = w.Write(hop.CipherText) 263 264 return err 265 }