{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -- | Types and type checking module Language.Bitcoin.Miniscript.Types ( BaseType (..), ModField (..), MiniscriptType (..), boolType, numberType, bytesType, keyDescriptorType, typeCheckMiniscript, MiniscriptTypeError (..), ) where import Control.Monad (unless) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Except (Except, runExcept, throwE) import Control.Monad.Trans.Reader (ReaderT, local, runReaderT) import Data.Bool (bool) import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Text (Text) import Language.Bitcoin.Miniscript.Syntax ( Miniscript (..), Value (..), ) import Language.Bitcoin.Utils (requiredContextValue) {-# ANN module ("HLint: ignore Reduce duplication" :: String) #-} data BaseType = -- | Base expression TypeB | -- | Verify expression TypeV | -- | Key expression TypeK | -- | Wrapped expression TypeW | -- | Number expression TypeNumber | -- | Bytes expression TypeBytes | -- | Key descriptor type TypeKeyDesc deriving (Eq, Show) notW :: BaseType -> Bool notW = (/= TypeW) -- | Type modifications that imply additional properties of the expression data ModField = ModField { -- | Consumes exactly 0 stack elements modZ :: Bool , -- | One-arg: this expression always consumes exactly 1 stack element. modO :: Bool , -- | Nonzero: this expression always consumes at least 1 stack element, no -- satisfaction for this expression requires the top input stack element to -- be zero. modN :: Bool , -- | Dissatisfiable: a dissatisfaction for this expression can -- unconditionally be constructed. modD :: Bool , -- | Unit: when satisfied put exactly 1 on the stack modU :: Bool } deriving (Eq, Show) data MiniscriptType = MiniscriptType { baseType :: BaseType , modifiers :: ModField } deriving (Eq, Show) emptyModField :: ModField emptyModField = ModField False False False False False boolType :: Bool -> MiniscriptType boolType = MiniscriptType TypeB . bool falseMods trueMods where trueMods = emptyModField{modZ = True, modU = True} falseMods = emptyModField{modZ = True, modU = True, modD = True} numberType :: MiniscriptType numberType = MiniscriptType TypeNumber emptyModField bytesType :: MiniscriptType bytesType = MiniscriptType TypeBytes emptyModField keyDescriptorType :: MiniscriptType keyDescriptorType = MiniscriptType TypeKeyDesc emptyModField data MiniscriptTypeError = MiniscriptTypeError Miniscript | UntypedVariable Text | -- | fields: @name expectedBaseType typeAnnotation@ WrongVariableType Text BaseType MiniscriptType deriving (Eq, Show) type TypeCheckM a = ReaderT (Map Text MiniscriptType) (Except MiniscriptTypeError) a requiredVarType :: Text -> TypeCheckM MiniscriptType requiredVarType name = requiredContextValue id (UntypedVariable name) name -- | Check that a miniscript expression is well-typed. typeCheckMiniscript :: -- | type hints for free variables in the miniscript expression Map Text MiniscriptType -> Miniscript -> Either MiniscriptTypeError MiniscriptType typeCheckMiniscript context = runExcept . (`runReaderT` context) . typeCheckInContext typeCheckInContext :: Miniscript -> TypeCheckM MiniscriptType typeCheckInContext = \case Var name -> requiredVarType name Let name expr body -> do ty <- typeCheckInContext expr local (Map.insert name ty) $ typeCheckInContext body Boolean b -> return $ boolType b Number{} -> return numberType Bytes{} -> return bytesType KeyDesc{} -> return keyDescriptorType Key x -> ondu TypeK <$ literal TypeKeyDesc x KeyH x -> ndu TypeK <$ literal TypeKeyDesc x Older x -> literal TypeNumber x >> exprType TypeB emptyModField{modZ = True} After x -> literal TypeNumber x >> exprType TypeB emptyModField{modZ = True} Sha256 x -> ondu TypeB <$ literal TypeBytes x Ripemd160 x -> ondu TypeB <$ literal TypeBytes x Hash256 x -> ondu TypeB <$ literal TypeBytes x Hash160 x -> ondu TypeB <$ literal TypeBytes x e@(AndOr x y z) -> do tx <- typeCheckInContext x ty <- typeCheckInContext y tz <- typeCheckInContext z let mx = modifiers tx my = modifiers ty mz = modifiers tz bty = baseType ty if (baseType tx == TypeB) && (baseType tz == bty) && notW bty && modD mx && modU mx then exprType bty emptyModField { modZ = modZ mx && modZ my && modZ mz , modO = (modZ mx && modO my && modO mz) || (modO mx && modZ my && modZ mz) , modU = modU my && modU mz , modD = modD mz } else typeError e e@(AndV x y) -> do tx <- typeCheckInContext x ty <- typeCheckInContext y let mx = modifiers tx my = modifiers ty bty = baseType ty if baseType tx == TypeV && notW bty then exprType bty emptyModField { modZ = modZ mx && modZ my , modO = (modZ mx && modO my) || (modO mx && modZ my) , modN = modN mx || (modZ mx && modN my) , modU = modU my } else typeError e e@(AndB x y) -> do tx <- typeCheckInContext x ty <- typeCheckInContext y let mx = modifiers tx my = modifiers ty if baseType tx == TypeB && baseType ty == TypeW then exprType TypeB emptyModField { modZ = modZ mx && modZ my , modO = (modZ mx && modO my) || (modO mx && modZ my) , modN = modN mx || (modZ mx && modN my) , modD = modD mx && modD my , modU = True } else typeError e e@(OrB x z) -> do tx <- typeCheckInContext x tz <- typeCheckInContext z let mx = modifiers tx mz = modifiers tz if baseType tx == TypeB && baseType tz == TypeW && modD mx && modD mz then exprType TypeB emptyModField { modZ = modZ mx && modZ mz , modO = (modZ mx && modO mz) || (modO mx && modZ mz) , modD = True , modU = True } else typeError e e@(OrC x z) -> do tx <- typeCheckInContext x tz <- typeCheckInContext z let mx = modifiers tx mz = modifiers tz if baseType tx == TypeB && baseType tz == TypeV && modD mx && modU mx then exprType TypeV emptyModField { modZ = modZ mx && modZ mz , modO = modO mx && modZ mz } else typeError e e@(OrD x z) -> do tx <- typeCheckInContext x tz <- typeCheckInContext z let mx = modifiers tx mz = modifiers tz if baseType tx == TypeB && baseType tz == TypeB && modD mx && modU mx then exprType TypeB emptyModField { modZ = modZ mx && modZ mz , modO = modO mx && modZ mz , modD = modD mz , modU = modU mz } else typeError e e@(OrI x z) -> do tx <- typeCheckInContext x tz <- typeCheckInContext z let mx = modifiers tx mz = modifiers tz btx = baseType tx if (baseType tz == btx) && notW btx then exprType btx emptyModField { modO = modZ mx && modZ mz , modD = modD mx || modD mz , modU = modU mx && modU mz } else typeError e e@(Thresh k x ys) -> do literal TypeNumber k tx <- typeCheckInContext x tys <- traverse typeCheckInContext ys let mx = modifiers tx mys = modifiers <$> tys allMods = mx : mys zCount = count modZ allMods oCount = count modO allMods :: Int count f = sum . fmap (bool 0 1 . f) isDU m = modD m && modU m if baseType tx == TypeB && all (== TypeW) (baseType <$> tys) && all isDU allMods then exprType TypeB emptyModField { modZ = all modZ allMods , modO = zCount == length ys && oCount == 1 , modD = True , modU = True } else typeError e Multi k ks -> do literal TypeNumber k mapM_ (literal TypeKeyDesc) ks return $ ndu TypeB e@(AnnA x) -> do tx <- typeCheckInContext x let mx = modifiers tx if baseType tx == TypeB then exprType TypeW emptyModField { modD = modD mx , modU = modU mx } else typeError e e@(AnnS x) -> do tx <- typeCheckInContext x let mx = modifiers tx if baseType tx == TypeB && modO mx then exprType TypeW emptyModField { modD = modD mx , modU = modU mx } else typeError e e@(AnnC x) -> do tx <- typeCheckInContext x let mx = modifiers tx if baseType tx == TypeK then exprType TypeB emptyModField { modO = modO mx , modN = modN mx , modD = modD mx , modU = True } else typeError e e@(AnnD x) -> do tx <- typeCheckInContext x let mx = modifiers tx if baseType tx == TypeV && modZ mx then exprType TypeB emptyModField { modO = modZ mx , modN = True , modU = True , modD = True } else typeError e e@(AnnV x) -> do tx <- typeCheckInContext x let mx = modifiers tx if baseType tx == TypeB then exprType TypeV emptyModField { modZ = modZ mx , modO = modO mx , modN = modN mx } else typeError e e@(AnnJ x) -> do tx <- typeCheckInContext x let mx = modifiers tx if baseType tx == TypeB && modN mx then exprType TypeB emptyModField { modO = modO mx , modN = True , modD = True , modU = modU mx } else typeError e e@(AnnN x) -> do tx <- typeCheckInContext x let mx = modifiers tx if baseType tx == TypeB then exprType TypeB emptyModField { modZ = modZ mx , modO = modO mx , modN = modN mx , modD = modD mx , modU = True } else typeError e where ondu = flip MiniscriptType emptyModField{modO = True, modN = True, modD = True, modU = True} ndu = flip MiniscriptType emptyModField{modN = True, modD = True, modU = True} exprType t = return . MiniscriptType t typeError = lift . throwE . MiniscriptTypeError literal bt (Variable n) = do t' <- requiredVarType n unless (baseType t' == bt) . lift . throwE $ WrongVariableType n bt t' literal _ _ = return ()