Validate.hs (8075B)
1 {-# OPTIONS_HADDOCK prune #-} 2 {-# LANGUAGE BangPatterns #-} 3 {-# LANGUAGE DeriveGeneric #-} 4 5 -- | 6 -- Module: Lightning.Protocol.BOLT9.Validate 7 -- Copyright: (c) 2025 Jared Tobin 8 -- License: MIT 9 -- Maintainer: Jared Tobin <jared@ppad.tech> 10 -- 11 -- Validation for BOLT #9 feature vectors. 12 13 module Lightning.Protocol.BOLT9.Validate ( 14 -- * Error types 15 ValidationError(..) 16 17 -- * Local validation 18 , validateLocal 19 20 -- * Remote validation 21 , validateRemote 22 23 -- * Helpers 24 , highestSetBit 25 , setBits 26 ) where 27 28 import Control.DeepSeq (NFData) 29 import Data.ByteString (ByteString) 30 import qualified Data.ByteString as BS 31 import qualified Data.Bits as B 32 import Data.Word (Word16) 33 import GHC.Generics (Generic) 34 import Lightning.Protocol.BOLT9.Codec (isFeatureSet, testBit) 35 import Lightning.Protocol.BOLT9.Features 36 import Lightning.Protocol.BOLT9.Types 37 38 -- | Validation errors for feature vectors. 39 data ValidationError 40 = BothBitsSet {-# UNPACK #-} !Word16 !String 41 -- ^ Both optional and required bits are set for a feature. 42 -- Arguments: base bit index, feature name. 43 | MissingDependency !String !String 44 -- ^ A feature's dependency is not set. 45 -- Arguments: feature name, missing dependency name. 46 | ContextNotAllowed !String !Context 47 -- ^ A feature is not allowed in the given context. 48 -- Arguments: feature name, context. 49 | UnknownRequiredBit {-# UNPACK #-} !Word16 50 -- ^ An unknown required (even) bit is set (remote validation only). 51 -- Argument: bit index. 52 | InvalidParity {-# UNPACK #-} !Word16 !Context 53 -- ^ A bit has invalid parity for a channel context. 54 -- Arguments: bit index, context (ChanAnnOdd or ChanAnnEven). 55 deriving (Eq, Show, Generic) 56 57 instance NFData ValidationError 58 59 -- Local validation ----------------------------------------------------------- 60 61 -- | Validate a feature vector for local use (vectors we create/send). 62 -- 63 -- Checks: 64 -- 65 -- * No feature has both optional and required bits set 66 -- * All set features are valid for the given context 67 -- * All dependencies of set features are also set 68 -- * C- context forces odd bits only, C+ forces even bits only 69 -- 70 -- >>> import Data.Maybe (fromJust) 71 -- >>> import Lightning.Protocol.BOLT9.Codec (setFeature) 72 -- >>> let mpp = fromJust (featureByName "basic_mpp") 73 -- >>> let ps = fromJust (featureByName "payment_secret") 74 -- >>> validateLocal Init (setFeature mpp False empty) 75 -- Left [MissingDependency "basic_mpp" "payment_secret"] 76 -- >>> validateLocal Init (setFeature mpp False (setFeature ps False empty)) 77 -- Right () 78 validateLocal :: Context -> FeatureVector -> Either [ValidationError] () 79 validateLocal !ctx !fv = 80 let errs = bothBitsErrors fv 81 ++ contextErrors ctx fv 82 ++ dependencyErrors fv 83 ++ parityErrors ctx fv 84 in if null errs 85 then Right () 86 else Left errs 87 88 -- | Check for features with both bits set. 89 bothBitsErrors :: FeatureVector -> [ValidationError] 90 bothBitsErrors !fv = foldr check [] knownFeatures 91 where 92 check !f !acc = 93 let !baseBit = featureBaseBit f 94 in if testBit baseBit fv && testBit (baseBit + 1) fv 95 then BothBitsSet baseBit (featureName f) : acc 96 else acc 97 98 -- | Check for features not allowed in the given context. 99 contextErrors :: Context -> FeatureVector -> [ValidationError] 100 contextErrors !ctx !fv = foldr check [] knownFeatures 101 where 102 check !f !acc = 103 let !contexts = featureContexts f 104 in if isFeatureSet f fv 105 && not (null contexts) 106 && not (contextAllowed ctx contexts) 107 then ContextNotAllowed (featureName f) ctx : acc 108 else acc 109 110 -- | Check if a context is allowed given a list of allowed contexts. 111 contextAllowed :: Context -> [Context] -> Bool 112 contextAllowed !ctx !allowed = ctx `elem` allowed || channelMatch 113 where 114 channelMatch = isChannelContext ctx && any isChannelContext allowed 115 116 -- | Check for missing dependencies. 117 dependencyErrors :: FeatureVector -> [ValidationError] 118 dependencyErrors !fv = foldr check [] knownFeatures 119 where 120 check !f !acc = 121 if isFeatureSet f fv 122 then checkDeps f (featureDependencies f) ++ acc 123 else acc 124 125 checkDeps !f = foldr (checkOneDep f) [] 126 127 checkOneDep !f !depName !acc = 128 case featureByName depName of 129 Nothing -> acc -- unknown dep, skip 130 Just !dep -> 131 if isFeatureSet dep fv 132 then acc 133 else MissingDependency (featureName f) depName : acc 134 135 -- | Check for parity errors in C- and C+ contexts. 136 parityErrors :: Context -> FeatureVector -> [ValidationError] 137 parityErrors !ctx !fv = case channelParity ctx of 138 Nothing -> [] 139 Just wantEven -> foldr (checkParity wantEven) [] (setBits fv) 140 where 141 checkParity !wantEven !bit !acc = 142 let isEven = bit `mod` 2 == 0 143 in if isEven /= wantEven 144 then InvalidParity bit ctx : acc 145 else acc 146 147 -- Remote validation ---------------------------------------------------------- 148 149 -- | Validate a feature vector received from a remote peer. 150 -- 151 -- Checks: 152 -- 153 -- * Unknown odd (optional) bits are acceptable (ignored) 154 -- * Unknown even (required) bits are errors 155 -- * If both bits of a pair are set, treat as required (not an error) 156 -- * Context restrictions still apply for known features 157 -- 158 -- >>> import Lightning.Protocol.BOLT9.Codec (setBit) 159 -- >>> validateRemote Init (setBit 999 empty) -- unknown odd bit: ok 160 -- Right () 161 -- >>> validateRemote Init (setBit 998 empty) -- unknown even bit: error 162 -- Left [UnknownRequiredBit 998] 163 validateRemote :: Context -> FeatureVector -> Either [ValidationError] () 164 validateRemote !ctx !fv = 165 let errs = unknownRequiredErrors fv 166 ++ contextErrors ctx fv 167 ++ parityErrors ctx fv 168 in if null errs 169 then Right () 170 else Left errs 171 172 -- | Check for unknown required bits. 173 unknownRequiredErrors :: FeatureVector -> [ValidationError] 174 unknownRequiredErrors !fv = foldr check [] (setBits fv) 175 where 176 check !bit !acc 177 | bit `mod` 2 == 1 = acc -- odd bit, optional, ignore 178 | otherwise = case featureByBit bit of 179 Just _ -> acc -- known feature 180 Nothing -> UnknownRequiredBit bit : acc 181 182 -- Helpers -------------------------------------------------------------------- 183 184 -- | Find the highest set bit in a feature vector. 185 -- 186 -- Returns 'Nothing' if the vector is empty or has no bits set. 187 highestSetBit :: FeatureVector -> Maybe Word16 188 highestSetBit !fv = 189 let !bs = unFeatureVector fv 190 in if BS.null bs 191 then Nothing 192 else findHighestBit bs 193 194 -- | Find the highest set bit in a non-empty ByteString. 195 findHighestBit :: ByteString -> Maybe Word16 196 findHighestBit !bs = go 0 197 where 198 !len = BS.length bs 199 200 go !i 201 | i >= len = Nothing 202 | otherwise = 203 let !byte = BS.index bs i 204 in if byte == 0 205 then go (i + 1) 206 else 207 let !bytePos = len - 1 - i 208 !highBit = 7 - B.countLeadingZeros byte 209 !bitIdx = fromIntegral bytePos * 8 + fromIntegral highBit 210 in Just bitIdx 211 212 -- | Collect all set bits in a feature vector. 213 -- 214 -- Returns a list of bit indices in ascending order. 215 setBits :: FeatureVector -> [Word16] 216 setBits !fv = 217 let !bs = unFeatureVector fv 218 !len = BS.length bs 219 in collectBits bs len 0 [] 220 221 -- | Collect bits from a ByteString into a list. 222 collectBits :: ByteString -> Int -> Int -> [Word16] -> [Word16] 223 collectBits !bs !len !i !acc 224 | i >= len = acc 225 | otherwise = 226 let !byte = BS.index bs (len - 1 - i) 227 !baseIdx = fromIntegral i * 8 228 !acc' = collectByteBits byte baseIdx acc 229 in collectBits bs len (i + 1) acc' 230 231 -- | Collect set bits from a single byte. 232 collectByteBits :: B.Bits a => a -> Word16 -> [Word16] -> [Word16] 233 collectByteBits !byte !baseIdx = go 7 234 where 235 go !bit !acc 236 | bit < 0 = acc 237 | B.testBit byte bit = go (bit - 1) ((baseIdx + fromIntegral bit) : acc) 238 | otherwise = go (bit - 1) acc