{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-} -- | A representation for proofs constructed using our security protocol -- verification theory. module Scyther.Proof ( -- * Datatypes Proof(..) , TrivReason(..) , Rule(..) -- ** Proof construction , dfsProof , shortestProof , minimizeProof , mapProofSequents -- ** Queries , prfSequent , prfProto , isAxiomProof , isTrivialProof , complete , sound , depends , extractTypeInvariant -- *** Proof sizes , ProofSize , getProofSize , missingProofSize , proofSize -- * Special "Proofs" , existsPossibleAttack , displayChainRule ) where import Data.Function import Data.Maybe import Data.List import Data.Monoid import qualified Data.Set as S import Data.Data import Control.Applicative import Control.Monad import Control.Monad.State import Control.Monad.BoundedDFS import Extension.Prelude import Scyther.Facts import qualified Scyther.Equalities as E import Scyther.Sequent ------------------------------------------------------------------------------ -- Data Types ------------------------------------------------------------------------------ -- | A proof of a sequent. data Proof = Axiom Sequent -- ^ Assumed as an axiom. | PossibleAttack Sequent Sequent -- ^ A possible attack on the given sequent. | Missing Sequent String Bool -- ^ A missing proof and the reason why it is -- missing plus a flag for displaying the sequent. | Trivial Sequent TrivReason -- ^ A trivial proof for the sequent and the -- reason why it is trivial. | RuleApp Sequent Rule [Proof] -- ^ An application of a rule/theorem and the -- proofs for all the resulting sequents. deriving( Eq, Show, Data, Typeable {-! NFData !-} ) -- | Triviality reasons. data TrivReason = TrivContradictoryPremises | TrivPremisesImplyConclusion | TrivLongTermKeySecrecy Message deriving( Eq, Show, Data, Typeable {-! NFData !-} ) -- | Supported rules. data Rule = Saturate | ReduceInjectivity | ForwardResolution (Named Sequent) E.Mapping | ChainRule Message [(String, [Either TID ArbMsgId])] | SplitEq E.MsgEq [Bool] -- True, if sub-proof for this case is present. | TypingCases [String] deriving( Eq, Show, Data, Typeable {-! NFData !-} ) ------------------------------------------------------------------------------ -- Simple Queries ------------------------------------------------------------------------------ -- | True iff the proof is trivial. isTrivialProof :: Proof -> Bool isTrivialProof (Trivial _ _) = True isTrivialProof _ = False -- | True iff the proof is an axiom definition. -- -- NOTE: This checks only if the proof is directly by axiom. isAxiomProof :: Proof -> Bool isAxiomProof (Axiom _) = True isAxiomProof _ = False -- | Extract the proven sequent from a proof. prfSequent :: Proof -> Sequent prfSequent (Axiom se) = se prfSequent (PossibleAttack se _) = se prfSequent (Missing se _ _) = se prfSequent (Trivial se _) = se prfSequent (RuleApp se _ _) = se -- | Extract the protocol concerned by a proof. prfProto :: Proof -> Protocol prfProto = seProto . prfSequent ------------------------------------------------------------------------------ -- Proof Construction ------------------------------------------------------------------------------ -- | Try proving a sequent by assumption; i.e. by showing that the premises -- directly imply the conclusion. byAssumption :: Monad m => Sequent -> m Proof byAssumption se@(Sequent prem concl Standard) | proveFalse prem = return $ Trivial se TrivContradictoryPremises | proveFormula prem concl = return $ Trivial se TrivPremisesImplyConclusion | otherwise = case exploitLongTermKeySecrecy prem of Just key -> return $ Trivial se (TrivLongTermKeySecrecy key) Nothing -> fail $ "byAssumption: cannot prove conclusion from premises" byAssumption _ = fail $ "byAssumption: only work for standard sequents" -- | An order preferring typing conclusions and then false conclusions. reuseOrder :: Formula -> Formula -> Ordering reuseOrder (FAtom (ATyping typ1)) (FAtom (ATyping typ2)) = compare typ1 typ2 reuseOrder (FAtom (ATyping _)) _ = LT reuseOrder _ (FAtom (ATyping _)) = GT reuseOrder (FAtom (ABool False)) (FAtom (ABool False)) = EQ reuseOrder (FAtom (ABool False)) _ = LT reuseOrder _ (FAtom (ABool False)) = GT reuseOrder f1 f2 = compare f1 f2 -- | A generic proof strategy for proving sequents. Eagerly reuses lemmas in -- the order they were given using forward resolution. genericProof :: MonadPlus m => m () -- ^ A marker for usages of the chain rule. -> (Facts -> [Message]) -- ^ A heuristic for the order of -- messages to be tried using the chain rule. -> [Named Sequent] -- ^ Rules that should be reused in this proof. -> Sequent -- ^ The sequent to be proven -> m Proof genericProof chainRuleMarker goals rawRules se0 = case reduceInjectivity se0 of Just (Left err) -> return $ Missing se0 ("failed to reduce injectivity: " ++ err) True Just (Right se1) -> (RuleApp se0 ReduceInjectivity . return) `liftM` (genericProof chainRuleMarker goals rawRules se1) Nothing -> case saturate se0 of Just se1 -> (RuleApp se0 Saturate . return) `liftM` prove se1 Nothing -> prove se0 where -- the theorems for reuse: prefer false conclusions over arbitrary conclusions reusableRules :: [Named Sequent] reusableRules = sortBy (reuseOrder `on` (seConcl . snd)) rawRules -- build a proof step corresponding to the reuse of a rule. reuseRule se rule = do (mapping, optSequent) <- frule (snd rule) se let mkProof = RuleApp se (ForwardResolution rule mapping) case optSequent of Nothing -> return $ return $ mkProof [] Just sequent -> return $ (mkProof . return) `liftM` prove sequent -- use the chain rule useChainRule se m = do chainRuleMarker case chainRule se m of Just cases -> do let (info, sequents) = unzip cases (RuleApp se (ChainRule m info)) `liftM` mapM prove sequents Nothing -> return $ Missing se "prover stuck => no type invariant available" True -- prove a sequent prove se = case byAssumption se of Just prf -> return prf -- proven by assumption Nothing -> case wellTypedCases se of Just cases -> do -- try to prove well-typedness cases let (names, sequents) = unzip cases RuleApp se (TypingCases names) `liftM` mapM prove sequents Nothing -> case msum (map (reuseRule se) reusableRules) of Just mkPrf -> mkPrf -- could reuse a proven sequent Nothing -> -- try to use the chain rule case splittableEqs (sePrem se) of -- split the first available equality eq:_ -> do let sequents = splitEq eq se RuleApp se (SplitEq eq (map isJust sequents)) `liftM` mapM prove (catMaybes sequents) -- there is none => chain rule is our last resort [] -> case goals (sePrem se) of [] -> return $ PossibleAttack se se ms -> msum $ map (useChainRule se) ms -- | Use a (possibly bounded) depth-first search for finding the proof. -- TODO: Make error handling explicit. dfsProof :: Maybe Int -> (Facts -> [Message]) -> [Named Sequent] -> Sequent -> Maybe Proof dfsProof Nothing heuristic rules se = runUnboundedDFS $ genericProof (return ()) heuristic rules se dfsProof (Just bound) heuristic rules se = evalBoundedDFS (genericProof (updateCost succ) heuristic rules se) (<= bound) 0 -- | Use branch-and-bound search to find the shortest proof. -- TODO: Make error handling explicit. shortestProof :: Maybe Int -> (Facts -> [Message]) -> [Named Sequent] -> Sequent -> Maybe Proof shortestProof optBound heuristic rules se = evalBranchAndBound (genericProof (updateCost (fmap succ)) heuristic rules se) optBound -- | Check if there exists a case where the prover gets stuck using unbounded DFS -- and the given heuristic and theorems to reuse. existsPossibleAttack :: (Facts -> [Message]) -> [Named Sequent] -> Sequent -> Maybe Proof existsPossibleAttack heuristic rules se = findAttack =<< (runUnboundedDFS $ genericProof (return ()) heuristic rules se) ------------------------------------------------------------------------------ -- Proof Checking and Minimization ------------------------------------------------------------------------------ -- | A proof is locally sound iff each rule application is sound. checkProof :: Sequent -> Proof -> Maybe Proof checkProof se0 prf0 = evalStateT (go prf0) se0 where mkSubproof se prf = put se >> go prf go :: Proof -> StateT Sequent Maybe Proof go (Missing _ reason showSequent) = Missing <$> get <*> pure reason <*> pure showSequent go prf@(Axiom seAxiom) = do se <- get guard (se == seAxiom) return prf go prf@(PossibleAttack seAttacked _) = do se <- get guard (se == seAttacked) return prf go (Trivial _ reason@TrivContradictoryPremises) = do prem <- gets sePrem guard (proveFalse prem) Trivial <$> get <*> pure reason go (Trivial _ reason@TrivPremisesImplyConclusion) = do se <- get guard (proveFormula (sePrem se) (seConcl se)) Trivial <$> get <*> pure reason go (Trivial _ reason@(TrivLongTermKeySecrecy key)) = do se <- get case exploitLongTermKeySecrecy (sePrem se) of Just key' -> do guard (key == key') Trivial <$> get <*> pure reason Nothing -> mzero go (RuleApp _ ReduceInjectivity [prf]) = do se <- get case reduceInjectivity se of Just (Right se') -> (RuleApp se ReduceInjectivity . return) <$> (put se' >> go prf) Just _ -> mzero Nothing -> go prf go (RuleApp _ Saturate [prf]) = do se <- get case saturate se of Just se' -> (RuleApp se Saturate . return) <$> (put se' >> go prf) Nothing -> go prf go (RuleApp _ rule@(ForwardResolution thm mapping) prfs) = do se <- get let statePrem = sePrem se guard (proveFacts statePrem (sePrem $ snd thm) mapping) optSequent <- fruleInst (snd thm) mapping se case optSequent of Nothing -> do guard (null prfs) return $ RuleApp se rule prfs Just sequent -> case prfs of [prf] -> (RuleApp se rule . return) `liftM` mkSubproof sequent prf _ -> mzero go (RuleApp _ rule@(ChainRule m info) prfs) = do se <- get -- FIXME: Here we could get a problem if the atom can no longer be -- certified. This will probably not occur in our current setting, as -- proof minimization does not change the quantifiers because only -- non-existentially quantified conclusions are reused. guard (proveAtom (sePrem se) (AEv (Learn m))) (info', sequents') <- unzip `liftM` chainRule se m guard (info == info') RuleApp <$> get <*> pure rule <*> zipWithM mkSubproof sequents' prfs go (RuleApp _ rule@(SplitEq eq info) prfs) = do se <- get guard (eq `elem` splittableEqs (sePrem se)) let optSequents' = splitEq eq se info' = map isJust optSequents' sequents' = catMaybes optSequents' guard (info == info') RuleApp <$> get <*> pure rule <*> zipWithM mkSubproof sequents' prfs go (RuleApp _ rule@(TypingCases info) prfs) = do se <- get (info', sequents') <- unzip `liftM` wellTypedCases se guard (info == info') RuleApp <$> get <*> pure rule <*> zipWithM mkSubproof sequents' prfs go _ = mzero -- | Find the first 'PossibleAttack' proof state and replace all other proof -- steps on the way to this state by missing steps. findAttack :: Proof -> Maybe Proof findAttack = fmap head . go . return where unvisited prf = Missing (prfSequent prf) "not yet investigated" False go :: [Proof] -> Maybe [Proof] go [] = Nothing go (prf : prfs) = case prf of RuleApp se rule subprfs -> -- look for an attack in the subproofs of the given rule (do subprfs' <- go subprfs return $ RuleApp se rule subprfs' : map unvisited prfs ) `mplus` -- look for an attack in the remaining proofs ((unvisited prf :) `liftM` go prfs) -- attack found: return it together with the unvisited siblings PossibleAttack _ _ -> return $ prf : map unvisited prfs -- look for an attack in the siblings and prepend ourselves unvisited _ -> (unvisited prf :) `liftM` go prfs -- | A proof is complete iff no 'Missing' proof or 'PossibleAttack' is part of it. complete :: Proof -> Bool complete (Missing _ _ _) = False complete (PossibleAttack _ _) = False complete (RuleApp _ _ prfs) = all complete prfs complete _ = True -- | A proof is sound iff it is complete and each inference step is sound. sound :: Proof -> Bool sound prf = complete prf && isJust (checkProof (prfSequent prf) prf) -- | Minimize a proof by removing all unnecesary forward resolutions. minimizeProof :: Proof -> Proof minimizeProof prf0 = fromMaybe prf0 $ do prf <- go prf0 checkProof (prfSequent prf) prf where go (RuleApp se rule@(ForwardResolution _ _) [prf]) = do prf' <- go prf (checkProof se prf' <|> pure (RuleApp se rule [prf'])) go (RuleApp se rule prfs) = RuleApp se rule <$> mapM go prfs go prf = return prf -- | Output the set of theorems a proof depends on. depends :: Proof -> S.Set (String, Protocol) depends (RuleApp _ (ForwardResolution (name, se) _) prfs) = S.insert (name, seProto se) $ S.unions $ map depends prfs depends (RuleApp _ _ prfs) = S.unions $ map depends prfs depends _ = S.empty -- | Extracts the first type invariant occurring in a forward resolution. This -- is required because in Isabelle type invariants are handled using locales, -- while we are handling them using forward resolution. -- -- Note that our system here is more general, but for the current setup, we do -- not see this generality. extractTypeInvariant :: Proof -> Maybe (Named Typing) extractTypeInvariant (RuleApp _ (ForwardResolution (name, se) _) prfs) = case destTypingFormula (seConcl se) of Just typ -> return (name, typ) Nothing -> msum $ map extractTypeInvariant prfs extractTypeInvariant (RuleApp _ _ prfs) = msum $ map extractTypeInvariant prfs extractTypeInvariant _ = mzero ------------------------------------------------------------------------------ -- Proof Sizes ------------------------------------------------------------------------------ -- | Proof size datatype counting number of chain rule applications and the -- number of forward resolutions. The 'Monoid' instance corresponds to adding -- proof sizes. newtype ProofSize = ProofSize (Sum Int, Sum Int, Sum Int) deriving( Eq, Ord, Monoid ) instance Show ProofSize where show s = concat ["C:", show nChain, " F:", show nForward, " M:", show nMissing] where (nChain, nForward, nMissing) = getProofSize s -- | Extract the raw proof size information. getProofSize :: ProofSize -> (Int, Int, Int) getProofSize (ProofSize (nChain, nForward, nMissing)) = (getSum nChain, getSum nForward, getSum nMissing) -- | The size of a missing proof. missingProofSize :: ProofSize missingProofSize = ProofSize (mempty, mempty, Sum 1) -- | Compute the size of a proof. proofSize :: Proof -> ProofSize proofSize (Axiom _) = mempty proofSize (PossibleAttack _ _) = mempty proofSize (Trivial _ _) = mempty proofSize (Missing _ _ _) = missingProofSize proofSize (RuleApp _ rule prfs) = mconcat $ (case rule of ChainRule _ _ -> pure $ ProofSize (Sum 1, mempty, mempty) ForwardResolution _ _ -> pure $ ProofSize (mempty, Sum 1, mempty) _ -> mempty ) ++ map proofSize prfs -- | Map the sequents of the proof. mapProofSequents :: (Sequent -> Sequent) -> Proof -> Proof mapProofSequents f = go where go (Axiom se) = Axiom (f se) go (PossibleAttack se se') = PossibleAttack (f se) (f se') go (Trivial se reason) = Trivial (f se) reason go (Missing se reason hide) = Missing (f se) reason hide go (RuleApp se rule prfs) = RuleApp (f se) rule (map go prfs) -- Chain Rule Display ------------------------------------------------------------------------------ displayChainRule :: Protocol -> Maybe Typing -> Proof displayChainRule _ _ = error "displayChainRule: not yet upgraded" {- displayChainRule proto optTyp = RuleApp se (ChainRule optTyp m (map fst cases)) (map mkPrf cases) where tid = 0 m = (MMVar (LocalId (Id "m", tid))) prem = insertEv (Learn m) $ insertThread tid $ emptyFacts se = Sequent proto (FFacts prem) FFalse cases = chainRule proto optTyp m prem mkPrf c = Missing (Sequent proto (FFacts (snd c)) FFalse) "chain rule case:" -}