Blinding.hs (5242B)
1 {-# OPTIONS_HADDOCK hide #-} 2 {-# LANGUAGE DeriveGeneric #-} 3 4 -- | 5 -- Module: Lightning.Protocol.BOLT4.Blinding 6 -- Copyright: (c) 2025 Jared Tobin 7 -- License: MIT 8 -- Maintainer: Jared Tobin <jared@ppad.tech> 9 -- 10 -- Route blinding. 11 12 module Lightning.Protocol.BOLT4.Blinding ( 13 BlindingError(..) 14 , create_blinded_path 15 , decrypt_recipient_data 16 , unblind 17 ) where 18 19 import Control.DeepSeq (NFData) 20 import qualified Crypto.AEAD.ChaCha20Poly1305 as AEAD 21 import qualified Crypto.Curve.Secp256k1 as Secp256k1 22 import qualified Data.ByteString as BS 23 import GHC.Generics (Generic) 24 import qualified Lightning.Protocol.BOLT1 as BOLT1 25 import Lightning.Protocol.BOLT4.Codec 26 import Lightning.Protocol.BOLT4.Prim 27 import Lightning.Protocol.BOLT4.Types 28 29 -- | Why a blinded route could not be created. 30 data BlindingError 31 = EmptyPath 32 -- ^ the route has no hops 33 | InvalidNodeId {-# UNPACK #-} !Int 34 -- ^ the node id of the hop with this index is not a valid point 35 | InvalidHopData {-# UNPACK #-} !Int !EncodeError 36 -- ^ the data of the hop with this index can't be encoded 37 deriving (Eq, Show, Generic) 38 39 instance NFData BlindingError 40 41 -- | Create a blinded route to oneself, given a seed for the route's 42 -- ephemeral keys (which must be fresh and random for every route) and 43 -- each node's id and data, from the introduction node to oneself. 44 -- 45 -- >>> let Just seed = secret_key (BS.replicate 32 0x01) 46 -- >>> let Just me = secret_key (BS.replicate 32 0x45) 47 -- >>> let dat = empty_blinded_hop_data { bhd_path_id = Just "deadbeef" } 48 -- >>> let Right path = create_blinded_path seed [(public_key me, dat)] 49 -- >>> bp_first_node_id path == public_key me 50 -- True 51 -- >>> length (bp_hops path) 52 -- 1 53 create_blinded_path 54 :: SecretKey 55 -> [(BOLT1.Point, BlindedHopData)] 56 -> Either BlindingError BlindedPath 57 create_blinded_path seed nodes = case nodes of 58 [] -> Left EmptyPath 59 (intro, _) : _ -> do 60 hops <- go (sk_bytes seed) (sk_pub seed) (zip [0 ..] nodes) 61 pure (BlindedPath intro (public_key seed) hops) 62 where 63 go _ _ [] = Right [] 64 go e epub ((i, (nid, dat)) : rest) = do 65 let note = maybe (Left (InvalidNodeId i)) Right 66 node <- note (from_point nid) 67 ss <- note (ecdh e node) 68 bid <- note (blind_pub node (blinded_node_tweak ss) >>= to_point) 69 plain <- either (Left . InvalidHopData i) Right 70 (encode_blinded_hop_data dat) 71 enc <- maybe (Left (InvalidHopData i FieldTooLong)) Right 72 (encrypt (derive_rho ss) plain) 73 let hop = BlindedHop bid enc 74 case rest of 75 [] -> Right [hop] 76 _ -> do 77 let bf = blinding_factor epub ss 78 e' <- note (blind_scalar e bf) 79 epub' <- note (blind_pub epub bf) 80 (hop :) <$> go e' epub' rest 81 82 -- | Decrypt the @encrypted_recipient_data@ received by a node in a 83 -- blinded route, given the node's private key and the path key it 84 -- received. 85 -- 86 -- Returns the decrypted data and the path key for the next node (the 87 -- data's @next_path_key_override@, if present). 88 -- 'Lightning.Protocol.BOLT4.process' does this for payment onions. 89 -- 90 -- >>> let Just seed = secret_key (BS.replicate 32 0x01) 91 -- >>> let Just me = secret_key (BS.replicate 32 0x45) 92 -- >>> let dat = empty_blinded_hop_data { bhd_path_id = Just "deadbeef" } 93 -- >>> let Right path = create_blinded_path seed [(public_key me, dat)] 94 -- >>> let [hop] = bp_hops path 95 -- >>> let pk = bp_first_path_key path 96 -- >>> let Right info = decrypt_recipient_data me pk (bh_encrypted_data hop) 97 -- >>> bhd_path_id (bi_data info) 98 -- Just "deadbeef" 99 decrypt_recipient_data 100 :: SecretKey 101 -> BOLT1.Point 102 -> BS.ByteString 103 -> Either ProcessError BlindedInfo 104 decrypt_recipient_data sk pk enc = do 105 e <- maybe (Left InvalidPathKey) Right (from_point pk) 106 ss <- maybe (Left InvalidPathKey) Right (ecdh (sk_bytes sk) e) 107 unblind ss e enc 108 109 -- Decrypt encrypted_recipient_data, given the shared secret with the path 110 -- key, and the path key. 111 unblind 112 :: SharedSecret 113 -> Secp256k1.Projective 114 -> BS.ByteString 115 -> Either ProcessError BlindedInfo 116 unblind ss e enc = do 117 plain <- maybe (Left InvalidRecipientData) Right 118 (decrypt (derive_rho ss) enc) 119 dat <- either (const (Left InvalidRecipientData)) Right 120 (decode_blinded_hop_data plain) 121 next <- case bhd_next_path_key_override dat of 122 Just o -> Right o 123 Nothing -> maybe (Left InvalidPathKey) Right 124 (blind_pub e (blinding_factor e ss) >>= to_point) 125 pure (BlindedInfo dat next) 126 127 -- ChaCha20-Poly1305 with an all-zero nonce and no associated data; the 128 -- tag follows the ciphertext. Fails only on counter overflow. 129 encrypt :: DerivedKey -> BS.ByteString -> Maybe BS.ByteString 130 encrypt k plain = 131 case AEAD.encrypt BS.empty (un_derived_key k) (BS.replicate 12 0) plain of 132 Right (c, t) -> Just (c <> t) 133 Left _ -> Nothing 134 135 decrypt :: DerivedKey -> BS.ByteString -> Maybe BS.ByteString 136 decrypt k enc 137 | BS.length enc < 16 = Nothing 138 | otherwise = 139 let (c, t) = BS.splitAt (BS.length enc - 16) enc 140 in case AEAD.decrypt BS.empty (un_derived_key k) (BS.replicate 12 0) 141 (c, t) of 142 Right p -> Just p 143 Left _ -> Nothing