|
| 1 | +package sphinx |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "crypto/hmac" |
| 6 | + "crypto/sha256" |
| 7 | + "errors" |
| 8 | + "fmt" |
| 9 | + |
| 10 | + "github.com/aead/chacha20" |
| 11 | + "github.com/btcsuite/btcd/btcec" |
| 12 | +) |
| 13 | + |
| 14 | +const ( |
| 15 | + // HMACSize is the length of the HMACs used to verify the integrity of |
| 16 | + // the onion. Any value lower than 32 will truncate the HMAC both |
| 17 | + // during onion creation as well as during the verification. |
| 18 | + HMACSize = 32 |
| 19 | +) |
| 20 | + |
| 21 | +// Hash256 is a statically sized, 32-byte array, typically containing |
| 22 | +// the output of a SHA256 hash. |
| 23 | +type Hash256 [sha256.Size]byte |
| 24 | + |
| 25 | +// zeroHMAC is the special HMAC value that allows the final node to determine |
| 26 | +// if it is the payment destination or not. |
| 27 | +var zeroHMAC [HMACSize]byte |
| 28 | + |
| 29 | +// calcMac calculates HMAC-SHA-256 over the message using the passed secret key |
| 30 | +// as input to the HMAC. |
| 31 | +func calcMac(key [keyLen]byte, msg []byte) [HMACSize]byte { |
| 32 | + hmac := hmac.New(sha256.New, key[:]) |
| 33 | + hmac.Write(msg) |
| 34 | + h := hmac.Sum(nil) |
| 35 | + |
| 36 | + var mac [HMACSize]byte |
| 37 | + copy(mac[:], h[:HMACSize]) |
| 38 | + |
| 39 | + return mac |
| 40 | +} |
| 41 | + |
| 42 | +// xor computes the byte wise XOR of a and b, storing the result in dst. Only |
| 43 | +// the frist `min(len(a), len(b))` bytes will be xor'd. |
| 44 | +func xor(dst, a, b []byte) int { |
| 45 | + n := len(a) |
| 46 | + if len(b) < n { |
| 47 | + n = len(b) |
| 48 | + } |
| 49 | + for i := 0; i < n; i++ { |
| 50 | + dst[i] = a[i] ^ b[i] |
| 51 | + } |
| 52 | + return n |
| 53 | +} |
| 54 | + |
| 55 | +// generateKey generates a new key for usage in Sphinx packet |
| 56 | +// construction/processing based off of the denoted keyType. Within Sphinx |
| 57 | +// various keys are used within the same onion packet for padding generation, |
| 58 | +// MAC generation, and encryption/decryption. |
| 59 | +func generateKey(keyType string, sharedKey *Hash256) [keyLen]byte { |
| 60 | + mac := hmac.New(sha256.New, []byte(keyType)) |
| 61 | + mac.Write(sharedKey[:]) |
| 62 | + h := mac.Sum(nil) |
| 63 | + |
| 64 | + var key [keyLen]byte |
| 65 | + copy(key[:], h[:keyLen]) |
| 66 | + |
| 67 | + return key |
| 68 | +} |
| 69 | + |
| 70 | +// generateCipherStream generates a stream of cryptographic psuedo-random bytes |
| 71 | +// intended to be used to encrypt a message using a one-time-pad like |
| 72 | +// construction. |
| 73 | +func generateCipherStream(key [keyLen]byte, numBytes uint) []byte { |
| 74 | + var ( |
| 75 | + nonce [8]byte |
| 76 | + ) |
| 77 | + cipher, err := chacha20.NewCipher(nonce[:], key[:]) |
| 78 | + if err != nil { |
| 79 | + panic(err) |
| 80 | + } |
| 81 | + output := make([]byte, numBytes) |
| 82 | + cipher.XORKeyStream(output, output) |
| 83 | + |
| 84 | + return output |
| 85 | +} |
| 86 | + |
| 87 | +// computeBlindingFactor for the next hop given the ephemeral pubKey and |
| 88 | +// sharedSecret for this hop. The blinding factor is computed as the |
| 89 | +// sha-256(pubkey || sharedSecret). |
| 90 | +func computeBlindingFactor(hopPubKey *btcec.PublicKey, |
| 91 | + hopSharedSecret []byte) Hash256 { |
| 92 | + |
| 93 | + sha := sha256.New() |
| 94 | + sha.Write(hopPubKey.SerializeCompressed()) |
| 95 | + sha.Write(hopSharedSecret) |
| 96 | + |
| 97 | + var hash Hash256 |
| 98 | + copy(hash[:], sha.Sum(nil)) |
| 99 | + return hash |
| 100 | +} |
| 101 | + |
| 102 | +// blindGroupElement blinds the group element P by performing scalar |
| 103 | +// multiplication of the group element by blindingFactor: blindingFactor * P. |
| 104 | +func blindGroupElement(hopPubKey *btcec.PublicKey, blindingFactor []byte) *btcec.PublicKey { |
| 105 | + newX, newY := btcec.S256().ScalarMult(hopPubKey.X, hopPubKey.Y, blindingFactor[:]) |
| 106 | + return &btcec.PublicKey{btcec.S256(), newX, newY} |
| 107 | +} |
| 108 | + |
| 109 | +// blindBaseElement blinds the groups's generator G by performing scalar base |
| 110 | +// multiplication using the blindingFactor: blindingFactor * G. |
| 111 | +func blindBaseElement(blindingFactor []byte) *btcec.PublicKey { |
| 112 | + newX, newY := btcec.S256().ScalarBaseMult(blindingFactor) |
| 113 | + return &btcec.PublicKey{btcec.S256(), newX, newY} |
| 114 | +} |
| 115 | + |
| 116 | +// sharedSecretGenerator is an interface that abstracts away exactly *how* the |
| 117 | +// shared secret for each hop is generated. |
| 118 | +// |
| 119 | +// TODO(roasbef): rename? |
| 120 | +type sharedSecretGenerator interface { |
| 121 | + // generateSharedSecret given a public key, generates a shared secret |
| 122 | + // using private data of the underlying sharedSecretGenerator. |
| 123 | + generateSharedSecret(dhKey *btcec.PublicKey) (Hash256, error) |
| 124 | +} |
| 125 | + |
| 126 | +// generateSharedSecret generates the shared secret by given ephemeral key. |
| 127 | +func (r *Router) generateSharedSecret(dhKey *btcec.PublicKey) (Hash256, error) { |
| 128 | + var sharedSecret Hash256 |
| 129 | + |
| 130 | + // Ensure that the public key is on our curve. |
| 131 | + if !btcec.S256().IsOnCurve(dhKey.X, dhKey.Y) { |
| 132 | + return sharedSecret, ErrInvalidOnionKey |
| 133 | + } |
| 134 | + |
| 135 | + // Compute our shared secret. |
| 136 | + sharedSecret = generateSharedSecret(dhKey, r.onionKey) |
| 137 | + return sharedSecret, nil |
| 138 | +} |
| 139 | + |
| 140 | +// generateSharedSecret generates the shared secret for a particular hop. The |
| 141 | +// shared secret is generated by taking the group element contained in the |
| 142 | +// mix-header, and performing an ECDH operation with the node's long term onion |
| 143 | +// key. We then take the _entire_ point generated by the ECDH operation, |
| 144 | +// serialize that using a compressed format, then feed the raw bytes through a |
| 145 | +// single SHA256 invocation. The resulting value is the shared secret. |
| 146 | +func generateSharedSecret(pub *btcec.PublicKey, priv *btcec.PrivateKey) Hash256 { |
| 147 | + s := &btcec.PublicKey{} |
| 148 | + s.X, s.Y = btcec.S256().ScalarMult(pub.X, pub.Y, priv.D.Bytes()) |
| 149 | + |
| 150 | + return sha256.Sum256(s.SerializeCompressed()) |
| 151 | +} |
| 152 | + |
| 153 | +// onionEncrypt obfuscates the data with compliance with BOLT#4. As we use a |
| 154 | +// stream cipher, calling onionEncrypt on an already encrypted piece of data |
| 155 | +// will decrypt it. |
| 156 | +func onionEncrypt(sharedSecret *Hash256, data []byte) []byte { |
| 157 | + p := make([]byte, len(data)) |
| 158 | + |
| 159 | + ammagKey := generateKey("ammag", sharedSecret) |
| 160 | + streamBytes := generateCipherStream(ammagKey, uint(len(data))) |
| 161 | + xor(p, data, streamBytes) |
| 162 | + |
| 163 | + return p |
| 164 | +} |
| 165 | + |
| 166 | +// onionErrorLength is the expected length of the onion error message. |
| 167 | +// Including padding, all messages on the wire should be 256 bytes. We then add |
| 168 | +// the size of the sha256 HMAC as well. |
| 169 | +const onionErrorLength = 2 + 2 + 256 + sha256.Size |
| 170 | + |
| 171 | +// DecryptError attempts to decrypt the passed encrypted error response. The |
| 172 | +// onion failure is encrypted in backward manner, starting from the node where |
| 173 | +// error have occurred. As a result, in order to decrypt the error we need get |
| 174 | +// all shared secret and apply decryption in the reverse order. |
| 175 | +func (o *OnionErrorDecrypter) DecryptError(encryptedData []byte) (*btcec.PublicKey, []byte, error) { |
| 176 | + // Ensure the error message length is as expected. |
| 177 | + if len(encryptedData) != onionErrorLength { |
| 178 | + return nil, nil, fmt.Errorf("invalid error length: "+ |
| 179 | + "expected %v got %v", onionErrorLength, |
| 180 | + len(encryptedData)) |
| 181 | + } |
| 182 | + |
| 183 | + sharedSecrets := generateSharedSecrets( |
| 184 | + o.circuit.PaymentPath, |
| 185 | + o.circuit.SessionKey, |
| 186 | + ) |
| 187 | + |
| 188 | + var ( |
| 189 | + sender *btcec.PublicKey |
| 190 | + msg []byte |
| 191 | + dummySecret Hash256 |
| 192 | + ) |
| 193 | + copy(dummySecret[:], bytes.Repeat([]byte{1}, 32)) |
| 194 | + |
| 195 | + // We'll iterate a constant amount of hops to ensure that we don't give |
| 196 | + // away an timing information pertaining to the position in the route |
| 197 | + // that the error emanated from. |
| 198 | + for i := 0; i < NumMaxHops; i++ { |
| 199 | + var sharedSecret Hash256 |
| 200 | + |
| 201 | + // If we've already found the sender, then we'll use our dummy |
| 202 | + // secret to continue decryption attempts to fill out the rest |
| 203 | + // of the loop. Otherwise, we'll use the next shared secret in |
| 204 | + // line. |
| 205 | + if sender != nil || i > len(sharedSecrets)-1 { |
| 206 | + sharedSecret = dummySecret |
| 207 | + } else { |
| 208 | + sharedSecret = sharedSecrets[i] |
| 209 | + } |
| 210 | + |
| 211 | + // With the shared secret, we'll now strip off a layer of |
| 212 | + // encryption from the encrypted error payload. |
| 213 | + encryptedData = onionEncrypt(&sharedSecret, encryptedData) |
| 214 | + |
| 215 | + // Next, we'll need to separate the data, from the MAC itself |
| 216 | + // so we can reconstruct and verify it. |
| 217 | + expectedMac := encryptedData[:sha256.Size] |
| 218 | + data := encryptedData[sha256.Size:] |
| 219 | + |
| 220 | + // With the data split, we'll now re-generate the MAC using its |
| 221 | + // specified key. |
| 222 | + umKey := generateKey("um", &sharedSecret) |
| 223 | + h := hmac.New(sha256.New, umKey[:]) |
| 224 | + h.Write(data) |
| 225 | + |
| 226 | + // If the MAC matches up, then we've found the sender of the |
| 227 | + // error and have also obtained the fully decrypted message. |
| 228 | + realMac := h.Sum(nil) |
| 229 | + if hmac.Equal(realMac, expectedMac) && sender == nil { |
| 230 | + sender = o.circuit.PaymentPath[i] |
| 231 | + msg = data |
| 232 | + } |
| 233 | + } |
| 234 | + |
| 235 | + // If the sender pointer is still nil, then we haven't found the |
| 236 | + // sender, meaning we've failed to decrypt. |
| 237 | + if sender == nil { |
| 238 | + return nil, nil, errors.New("unable to retrieve onion failure") |
| 239 | + } |
| 240 | + |
| 241 | + return sender, msg, nil |
| 242 | +} |
| 243 | + |
| 244 | +// EncryptError is used to make data obfuscation using the generated shared |
| 245 | +// secret. |
| 246 | +// |
| 247 | +// In context of Lightning Network is either used by the nodes in order to make |
| 248 | +// initial obfuscation with the creation of the hmac or by the forwarding nodes |
| 249 | +// for backward failure obfuscation of the onion failure blob. By obfuscating |
| 250 | +// the onion failure on every node in the path we are adding additional step of |
| 251 | +// the security and barrier for malware nodes to retrieve valuable information. |
| 252 | +// The reason for using onion obfuscation is to not give |
| 253 | +// away to the nodes in the payment path the information about the exact |
| 254 | +// failure and its origin. |
| 255 | +func (o *OnionErrorEncrypter) EncryptError(initial bool, data []byte) []byte { |
| 256 | + if initial { |
| 257 | + umKey := generateKey("um", &o.sharedSecret) |
| 258 | + hash := hmac.New(sha256.New, umKey[:]) |
| 259 | + hash.Write(data) |
| 260 | + h := hash.Sum(nil) |
| 261 | + data = append(h, data...) |
| 262 | + } |
| 263 | + |
| 264 | + return onionEncrypt(&o.sharedSecret, data) |
| 265 | +} |
0 commit comments