/ zpay32 / blinded_path.go
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  }