IMPL6.md (6567B)
1 # IMPL6: Route Blinding 2 3 **Module**: `Lightning.Protocol.BOLT4.Blinding` 4 5 **Dependencies**: IMPL1 (Prim), IMPL2 (Types, Codec) 6 7 **Can run in parallel with**: IMPL3, IMPL4, IMPL5 (after IMPL1 and IMPL2 complete) 8 9 **Priority**: Lower - can be deferred. Core functionality works without this. 10 11 ## Overview 12 13 Route blinding allows a recipient to provide a "blinded path" that hides 14 the identities of nodes in the path. The sender constructs a route to 15 the introduction point, then the blinded path takes over. 16 17 ## Types 18 19 ```haskell 20 -- | A blinded route provided by recipient. 21 data BlindedPath = BlindedPath 22 { bpIntroductionNode :: !Secp256k1.PubKey -- first node (unblinded) 23 , bpBlindingKey :: !Secp256k1.PubKey -- E_0, initial ephemeral 24 , bpBlindedHops :: ![BlindedHop] 25 } deriving (Eq, Show) 26 27 -- | A single hop in a blinded path. 28 data BlindedHop = BlindedHop 29 { bhBlindedNodeId :: !BS.ByteString -- 33 bytes, blinded pubkey 30 , bhEncryptedData :: !BS.ByteString -- encrypted routing data 31 } deriving (Eq, Show) 32 33 -- | Data encrypted for each blinded hop (before encryption). 34 data BlindedHopData = BlindedHopData 35 { bhdPadding :: !(Maybe BS.ByteString) -- TLV 1 36 , bhdShortChannelId :: !(Maybe ShortChannelId) -- TLV 2 37 , bhdNextNodeId :: !(Maybe BS.ByteString) -- TLV 4, 33-byte pubkey 38 , bhdPathId :: !(Maybe BS.ByteString) -- TLV 6 39 , bhdNextPathKeyOverride :: !(Maybe BS.ByteString) -- TLV 8 40 , bhdPaymentRelay :: !(Maybe PaymentRelay) -- TLV 10 41 , bhdPaymentConstraints :: !(Maybe PaymentConstraints) -- TLV 12 42 , bhdAllowedFeatures :: !(Maybe BS.ByteString) -- TLV 14 43 } deriving (Eq, Show) 44 45 -- | Payment relay parameters (TLV 10). 46 data PaymentRelay = PaymentRelay 47 { prCltvExpiryDelta :: {-# UNPACK #-} !Word16 48 , prFeeProportional :: {-# UNPACK #-} !Word32 -- millionths 49 , prFeeBaseMsat :: {-# UNPACK #-} !Word32 50 } deriving (Eq, Show) 51 52 -- | Payment constraints (TLV 12). 53 data PaymentConstraints = PaymentConstraints 54 { pcMaxCltvExpiry :: {-# UNPACK #-} !Word32 55 , pcHtlcMinimumMsat :: {-# UNPACK #-} !Word64 56 } deriving (Eq, Show) 57 ``` 58 59 ## Path Creation (Recipient) 60 61 ```haskell 62 -- | Create a blinded path from a list of nodes. 63 -- 64 -- The recipient generates this and shares it (e.g., in an invoice). 65 createBlindedPath 66 :: BS.ByteString -- ^ 32-byte random seed for ephemeral key 67 -> [(Secp256k1.PubKey, BlindedHopData)] -- ^ nodes with their data 68 -> Either Error BlindedPath 69 70 -- | Errors during blinded path creation. 71 data BlindingError 72 = InvalidSeed 73 | EmptyPath 74 | InvalidNodeKey Int 75 deriving (Eq, Show) 76 ``` 77 78 ## Path Processing (Blinded Node) 79 80 ```haskell 81 -- | Process a packet at a blinded node. 82 -- 83 -- Takes the node's private key, the path key (blinding point), and 84 -- the encrypted data. Returns decrypted routing data and next path key. 85 processBlindedHop 86 :: Secp256k1.SecKey -- ^ node's private key 87 , Secp256k1.PubKey -- ^ E_i, current path key (blinding point) 88 -> BS.ByteString -- ^ encrypted_data from onion payload 89 -> Either Error (BlindedHopData, Secp256k1.PubKey) 90 -- ^ (decrypted data, E_{i+1} next path key) 91 ``` 92 93 ## Internal Functions 94 95 ### Key Derivation for Blinding 96 97 ```haskell 98 -- | Derive blinded node ID. 99 -- B_i = HMAC256("blinded_node_id", ss_i) * N_i 100 deriveBlindedNodeId 101 :: SharedSecret -- ^ ss_i 102 -> Secp256k1.PubKey -- ^ N_i, node's real pubkey 103 -> Maybe BS.ByteString -- ^ blinded pubkey bytes 104 105 -- | Derive rho key for encrypting hop data. 106 -- Same as regular rho but used with ChaCha20-Poly1305. 107 deriveBlindingRho :: SharedSecret -> DerivedKey 108 ``` 109 110 ### Ephemeral Key Iteration 111 112 ```haskell 113 -- | Compute next ephemeral key pair for path creation. 114 -- e_{i+1} = SHA256(E_i || ss_i) * e_i 115 -- E_{i+1} = SHA256(E_i || ss_i) * E_i 116 nextEphemeral 117 :: Secp256k1.SecKey -- ^ e_i 118 -> Secp256k1.PubKey -- ^ E_i 119 -> SharedSecret -- ^ ss_i 120 -> Maybe (Secp256k1.SecKey, Secp256k1.PubKey) -- ^ (e_{i+1}, E_{i+1}) 121 ``` 122 123 ### Encryption 124 125 ```haskell 126 -- | Encrypt hop data with ChaCha20-Poly1305. 127 -- 128 -- Uses rho key and 12-byte zero nonce. 129 -- NOTE: This requires AEAD, unlike regular packet obfuscation. 130 encryptHopData 131 :: DerivedKey -- ^ rho key 132 -> BlindedHopData -- ^ plaintext data 133 -> BS.ByteString -- ^ ciphertext with auth tag 134 135 -- | Decrypt hop data with ChaCha20-Poly1305. 136 decryptHopData 137 :: DerivedKey -- ^ rho key 138 -> BS.ByteString -- ^ ciphertext with auth tag 139 -> Maybe BlindedHopData 140 ``` 141 142 ## Path Creation Algorithm 143 144 ``` 145 createBlindedPath(seed, nodes): 146 1. (e_0, E_0) = keypair from seed 147 2. introduction_node = nodes[0].pubkey 148 149 3. blinded_hops = [] 150 4. e_i, E_i = e_0, E_0 151 152 For each (N_i, data_i) in nodes: 153 5. ss_i = SHA256(ECDH(e_i, N_i)) 154 6. rho_i = deriveBlindingRho(ss_i) 155 7. B_i = deriveBlindedNodeId(ss_i, N_i) 156 8. encrypted_i = encryptHopData(rho_i, data_i) 157 9. blinded_hops.append(BlindedHop(B_i, encrypted_i)) 158 10. (e_i, E_i) = nextEphemeral(e_i, E_i, ss_i) 159 160 11. return BlindedPath(introduction_node, E_0, blinded_hops) 161 ``` 162 163 ## Hop Processing Algorithm 164 165 ``` 166 processBlindedHop(node_seckey, path_key, encrypted_data): 167 1. ss = SHA256(ECDH(node_seckey, path_key)) 168 2. rho = deriveBlindingRho(ss) 169 170 3. hop_data = decryptHopData(rho, encrypted_data) 171 If decryption fails: return error 172 173 4. Check for next_path_key_override in hop_data 174 If present: E_next = override value 175 Else: E_next = SHA256(path_key || ss) * path_key 176 177 5. return (hop_data, E_next) 178 ``` 179 180 ## Integration with Packet Construction 181 182 When constructing a packet with a blinded suffix: 183 184 1. Construct normal hops up to introduction point 185 2. At introduction point, include `current_path_key` (TLV 12) = E_0 186 3. For blinded hops, use blinded node IDs as "pubkeys" and include 187 `encrypted_recipient_data` (TLV 10) from BlindedHop 188 189 The blinded nodes don't know their position or the path structure. 190 191 ## Implementation Notes 192 193 1. Route blinding uses ChaCha20-Poly1305 (AEAD), not plain ChaCha20. 194 This may require ppad-aead as dependency. 195 196 2. The "blinded node ID" is a tweaked public key. Nodes must be able 197 to derive the corresponding private key tweak. 198 199 3. path_key_override allows path creators to inject specific keys, 200 useful for multi-path scenarios. 201 202 4. This is an optional feature. Core BOLT4 works without it. 203 204 ## Test Vectors 205 206 The spec includes blinded path test vectors. Key values to verify: 207 - Blinded node ID derivation 208 - Encrypted data for each hop 209 - Path key iteration 210 - Decryption at each blinded node