|
1 | 1 | package sphinx |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "errors" |
| 5 | + "fmt" |
| 6 | + |
4 | 7 | "github.com/btcsuite/btcd/btcec/v2" |
5 | 8 | ) |
6 | 9 |
|
7 | | -// NumMaxHops is the maximum path length. There is a maximum of 1300 bytes in |
8 | | -// the routing info block. Legacy hop payloads are always 65 bytes, while tlv |
9 | | -// payloads are at least 47 bytes (tlvlen 1, amt 2, timelock 2, nextchan 10, |
10 | | -// hmac 32) for the intermediate hops and 37 bytes (tlvlen 1, amt 2, timelock 2, |
11 | | -// hmac 32) for the exit hop. The maximum path length can therefore only be |
12 | | -// reached by using tlv payloads only. With that, the maximum number of |
13 | | -// intermediate hops is: Floor((1300 - 37) / 47) = 26. Including the exit hop, |
14 | | -// the maximum path length is 27 hops. |
15 | | -const NumMaxHops = 27 |
| 10 | +const ( |
| 11 | + // NumMaxHops is the maximum path length. There is a maximum of 1300 |
| 12 | + // bytes in the routing info block. Legacy hop payloads are always 65 |
| 13 | + // bytes, while tlv payloads are at least 47 bytes (tlvlen 1, amt 2, |
| 14 | + // timelock 2, nextchan 10, hmac 32) for the intermediate hops and 37 |
| 15 | + // bytes (tlvlen 1, amt 2, timelock 2, hmac 32) for the exit hop. The |
| 16 | + // maximum path length can therefore only be reached by using tlv |
| 17 | + // payloads only. With that, the maximum number of intermediate hops |
| 18 | + // is: Floor((1300 - 37) / 47) = 26. Including the exit hop, the |
| 19 | + // maximum path length is 27 hops. |
| 20 | + NumMaxHops = 27 |
| 21 | + |
| 22 | + routeBlindingHMACKey = "blinded_node_id" |
| 23 | +) |
16 | 24 |
|
17 | 25 | // PaymentPath represents a series of hops within the Lightning Network |
18 | 26 | // starting at a sender and terminating at a receiver. Each hop contains a set |
@@ -93,3 +101,158 @@ func (p *PaymentPath) TotalPayloadSize() int { |
93 | 101 |
|
94 | 102 | return totalSize |
95 | 103 | } |
| 104 | + |
| 105 | +// BlindedPath represents all the data that the creator of a blinded path must |
| 106 | +// transmit to the builder of route that will send to this path. |
| 107 | +type BlindedPath struct { |
| 108 | + // IntroductionPoint is the real node ID of the first hop in the blinded |
| 109 | + // path. The sender should be able to find this node in the network |
| 110 | + // graph and route to it. |
| 111 | + IntroductionPoint *btcec.PublicKey |
| 112 | + |
| 113 | + // BlindingPoint is the first ephemeral blinding point. This is the |
| 114 | + // point that the introduction node will use in order to create a shared |
| 115 | + // secret with the builder of the blinded route. This point will need |
| 116 | + // to be communicated to the introduction node by the sender in some |
| 117 | + // way. |
| 118 | + BlindingPoint *btcec.PublicKey |
| 119 | + |
| 120 | + // BlindedHops is a list of ordered BlindedHopInfo. Each entry |
| 121 | + // represents a hop in the blinded path along with the encrypted data to |
| 122 | + // be sent to that node. Note that the first entry in the list |
| 123 | + // represents the introduction point of the path and so the node ID of |
| 124 | + // this point does not strictly need to be transmitted to the sender |
| 125 | + // since they will be able to derive the point using the BlindingPoint. |
| 126 | + BlindedHops []*BlindedHopInfo |
| 127 | +} |
| 128 | + |
| 129 | +// BlindedHopInfo represents a blinded node pub key along with the encrypted |
| 130 | +// data for a node in a blinded route. |
| 131 | +type BlindedHopInfo struct { |
| 132 | + // BlindedNodePub is the blinded public key of the node in the blinded |
| 133 | + // route. |
| 134 | + BlindedNodePub *btcec.PublicKey |
| 135 | + |
| 136 | + // CipherText is the encrypted payload to be transported to the hop in |
| 137 | + // the blinded route. |
| 138 | + CipherText []byte |
| 139 | +} |
| 140 | + |
| 141 | +// HopInfo represents a real node pub key along with the plaintext data for a |
| 142 | +// node in a blinded route. |
| 143 | +type HopInfo struct { |
| 144 | + // NodePub is the real public key of the node in the blinded route. |
| 145 | + NodePub *btcec.PublicKey |
| 146 | + |
| 147 | + // PlainText is the un-encrypted payload to be transported to the hop |
| 148 | + // the blinded route. |
| 149 | + PlainText []byte |
| 150 | +} |
| 151 | + |
| 152 | +// Encrypt uses the given sharedSecret to blind the public key of the node and |
| 153 | +// encrypt the payload and returns the resulting BlindedHopInfo. |
| 154 | +func (i *HopInfo) Encrypt(sharedSecret Hash256) (*BlindedHopInfo, error) { |
| 155 | + blindedData, err := encryptBlindedHopData(sharedSecret, i.PlainText) |
| 156 | + if err != nil { |
| 157 | + return nil, err |
| 158 | + } |
| 159 | + |
| 160 | + return &BlindedHopInfo{ |
| 161 | + BlindedNodePub: blindNodeID(sharedSecret, i.NodePub), |
| 162 | + CipherText: blindedData, |
| 163 | + }, nil |
| 164 | +} |
| 165 | + |
| 166 | +// BuildBlindedPath creates a new BlindedPath from a session key along with a |
| 167 | +// list of HopInfo representing the nodes in the blinded path. The first hop in |
| 168 | +// paymentPath is expected to be the introduction node. |
| 169 | +func BuildBlindedPath(sessionKey *btcec.PrivateKey, |
| 170 | + paymentPath []*HopInfo) (*BlindedPath, error) { |
| 171 | + |
| 172 | + if len(paymentPath) < 1 { |
| 173 | + return nil, errors.New("at least 1 hop is required to create " + |
| 174 | + "a blinded path") |
| 175 | + } |
| 176 | + |
| 177 | + bp := &BlindedPath{ |
| 178 | + IntroductionPoint: paymentPath[0].NodePub, |
| 179 | + BlindingPoint: sessionKey.PubKey(), |
| 180 | + BlindedHops: make([]*BlindedHopInfo, len(paymentPath)), |
| 181 | + } |
| 182 | + |
| 183 | + keys := make([]*btcec.PublicKey, len(paymentPath)) |
| 184 | + for i, p := range paymentPath { |
| 185 | + keys[i] = p.NodePub |
| 186 | + } |
| 187 | + |
| 188 | + hopSharedSecrets, err := generateSharedSecrets(keys, sessionKey) |
| 189 | + if err != nil { |
| 190 | + return nil, fmt.Errorf("error generating shared secret: %v", |
| 191 | + err) |
| 192 | + } |
| 193 | + |
| 194 | + for i, hop := range paymentPath { |
| 195 | + blindedInfo, err := hop.Encrypt(hopSharedSecrets[i]) |
| 196 | + if err != nil { |
| 197 | + return nil, err |
| 198 | + } |
| 199 | + |
| 200 | + bp.BlindedHops[i] = blindedInfo |
| 201 | + } |
| 202 | + |
| 203 | + return bp, nil |
| 204 | +} |
| 205 | + |
| 206 | +// blindNodeID blinds the given public key using the provided shared secret. |
| 207 | +func blindNodeID(sharedSecret Hash256, |
| 208 | + pubKey *btcec.PublicKey) *btcec.PublicKey { |
| 209 | + |
| 210 | + blindingFactorBytes := generateKey(routeBlindingHMACKey, &sharedSecret) |
| 211 | + |
| 212 | + var blindingFactor btcec.ModNScalar |
| 213 | + blindingFactor.SetBytes(&blindingFactorBytes) |
| 214 | + |
| 215 | + return blindGroupElement(pubKey, blindingFactor) |
| 216 | +} |
| 217 | + |
| 218 | +// encryptBlindedHopData blinds/encrypts the given plain text data using the |
| 219 | +// provided shared secret. |
| 220 | +func encryptBlindedHopData(sharedSecret Hash256, plainTxt []byte) ([]byte, |
| 221 | + error) { |
| 222 | + |
| 223 | + rhoKey := generateKey("rho", &sharedSecret) |
| 224 | + |
| 225 | + return chacha20polyEncrypt(rhoKey[:], plainTxt) |
| 226 | +} |
| 227 | + |
| 228 | +// decryptBlindedHopData decrypts the data encrypted by the creator of the |
| 229 | +// blinded route. |
| 230 | +func decryptBlindedHopData(privKey SingleKeyECDH, ephemPub *btcec.PublicKey, |
| 231 | + encryptedData []byte) ([]byte, error) { |
| 232 | + |
| 233 | + ss, err := privKey.ECDH(ephemPub) |
| 234 | + if err != nil { |
| 235 | + return nil, err |
| 236 | + } |
| 237 | + |
| 238 | + ssHash := Hash256(ss) |
| 239 | + rho := generateKey("rho", &ssHash) |
| 240 | + |
| 241 | + return chacha20polyDecrypt(rho[:], encryptedData) |
| 242 | +} |
| 243 | + |
| 244 | +// NextEphemeral computes the next ephemeral key given the current ephemeral |
| 245 | +// key and this node's private key. |
| 246 | +func NextEphemeral(privKey SingleKeyECDH, |
| 247 | + ephemPub *btcec.PublicKey) (*btcec.PublicKey, error) { |
| 248 | + |
| 249 | + ss, err := privKey.ECDH(ephemPub) |
| 250 | + if err != nil { |
| 251 | + return nil, err |
| 252 | + } |
| 253 | + |
| 254 | + blindingFactor := computeBlindingFactor(ephemPub, ss[:]) |
| 255 | + nextEphem := blindGroupElement(ephemPub, blindingFactor) |
| 256 | + |
| 257 | + return nextEphem, nil |
| 258 | +} |
0 commit comments