scyther-proof-0.5.0.0: src/Scyther/Sequent.hs
{-# LANGUAGE DeriveDataTypeable #-}
module Scyther.Sequent (
-- * Datatype
Sequent(..)
, SequentQualifier(..)
, seProto
-- ** Logically safe construction
, wellTypedCases
, reduceInjectivity
, saturate
, frule
, fruleInst
, chainRule
, splitEq
, exploitTyping
, uniqueTIDQuantifiers
) where
import Data.Maybe
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Data
import Control.Arrow
import Control.Monad
import Control.Applicative
import Extension.Prelude (sortednub)
import qualified Scyther.Equalities as E
import Scyther.Facts
import Scyther.Formula
------------------------------------------------------------------------------
-- Sequents
------------------------------------------------------------------------------
-- | A qualifier changing the interpretation of a sequent.
data SequentQualifier =
Standard
-- ^ The standard interpretation of a sequent as given in our CSF'10
-- paper on strong invariants for the efficient construction of
-- machine-checked security proofs.
| Injective
-- ^ An injective interpretation of a sequent. Only valid for sequents
-- that have exactly one free TID-variable in the premise and exactly
-- one further TID-variable in the conclusion.
deriving( Eq, Ord, Show, Data, Typeable )
-- | A sequent with a conjunction of a set of facts as the premise and a single
-- formula as the conclusion denoting a statement about a reachable state of
-- a protocol.
data Sequent = Sequent {
sePrem :: Facts
, seConcl :: Formula
, seQualifier :: SequentQualifier
}
deriving( Eq, Show, Ord, Data, Typeable )
-- | The protocol of a sequent.
seProto :: Sequent -> Protocol
seProto = protocol . sePrem
-- | 'True' iff the sequent is viewed with the 'Standard' interpretation.
isStandard :: Sequent -> Bool
isStandard = (Standard ==) . seQualifier
-- Construction
---------------
-- | Make all thread identifiers occurring in the sequent unique by
-- consistently relabeling the thread identifiers in the conclusion.
uniqueTIDQuantifiers :: Sequent -> Sequent
uniqueTIDQuantifiers (Sequent prem concl quali) =
Sequent prem (relabelTIDs [nextTID prem..] concl) quali
-- | Apply a function to the premise, but return only the updated sequent if
-- the premise was changed.
changePrem :: MonadPlus m => (Facts -> m Facts) -> Sequent -> m Sequent
changePrem f se = do
let prem0 = sePrem se
prem1 <- f prem0
guard (prem0 /= prem1)
return $ se { sePrem = prem1 }
-- | The named list of sequents which need to be proven in order to prove that
-- the given protocol is well typed
--
-- PRE: The conclusion of the sequent must be typing atom.
--
-- Uses 'fail' for error reporting.
wellTypedCases :: MonadPlus m => Sequent -> m [(String, Sequent)]
wellTypedCases se@(Sequent _ (FAtom (ATyping typ)) Standard) =
return $ protoRoles (seProto se) >>= roleProofs
where
roleProofs role =
proveRecvs S.empty (roleSteps role)
where
proveRecvs _ [] = []
proveRecvs recv ( Send _ _ : steps) = proveRecvs recv steps
proveRecvs recv ((Recv _ (PMVar lid)) : steps) =
-- don't prove single reiceves as they are handled directly by the tactic
-- on the Isabelle side.
proveRecvs (S.insert lid recv) steps
proveRecvs recv (step@(Recv _ pt) : steps) =
let mvars = patFMV pt
in (S.toList mvars >>= proveVar) `mplus`
(proveRecvs (recv `S.union` mvars) steps)
where
proveVar v
| v `S.member` recv = fail "proveVar: not first receive"
| otherwise = return (name, Sequent prem concl Standard)
where
name = roleName role ++ "_" ++ stepLabel step ++ "_" ++ getId v
(tid, prem0) = freshTID (sePrem se)
mv = MVar (LocalId (v, tid))
premErr = error $ "wellTypedCases: could not add thread " ++ show tid ++ ". This should not happen."
prem1 = maybe premErr saturateFacts . join $
conjoinAtoms [AEv (Step tid step), AEq (E.TIDRoleEq (tid, role))] prem0
prem = fromMaybe (error "failed to set typing") $ setTyping typ prem1
concl = FAtom $ case M.lookup (v, role) typ of
Just ty -> AHasType (MMVar mv, ty, tid)
Nothing -> error $
"wellTypedCases: no type given for '"++show v++"' in role '"++roleName role++"'"
wellTypedCases _ = mzero
-- | Emulate a variant Isabelle's 'frule' tactic. It works only if the given
-- maping of free variables of the rule makes the premise of the rule provable
-- under the given proof state. Then, the conclusion of the rule with free
-- variables mapped accordingly is added to premises of the proof state. The
-- last step works currently only for conclusions being false of pure
-- conclusions.
--
-- NOTE that 'frule' works only for rules that are standard sequents and that
-- contain no existential quantifiers in the conclusion.
fruleInst :: MonadPlus m
=> Sequent -- ^ rule
-> E.Mapping -- ^ mapping of free variables of rule to proof state
-> Sequent -- ^ proof state
-> m (Maybe Sequent) -- ^ some result if resolution worked. Nothing
-- denotes that False was derived. Just means that
-- premises of proof state were extended.
--
-- mzero if rule could not be applied
fruleInst rule mapping state
| isStandard rule && isStandard state = do
atoms <- conjunctionToAtoms $ seConcl rule
let statePrem = sePrem state
guard (proveFacts statePrem (sePrem rule) mapping)
optStatePrem' <- conjoinAtoms (map (substAtom (E.getMappingEqs mapping)) atoms) statePrem
case optStatePrem' of
Nothing -> do return Nothing
Just statePrem' -> do guard (statePrem /= statePrem')
return . Just $ Sequent statePrem' (seConcl state) Standard
| otherwise = mzero
-- | Like 'fruleInst' but tries all mappings.
frule :: MonadPlus m
=> Sequent -- ^ rule
-> Sequent -- ^ proof state
-> m (E.Mapping, Maybe Sequent)
-- ^ some result if resolution worked. Nothing denotes that False was
-- derived. Just means that premises of proof state were extended.
--
-- mzero if rule could not be applied
frule rule state = case resolutions of
[] -> mzero
res : _ -> return res
where
resolutions = do
mapping <- freeVariableMappings (sePrem rule) (sePrem state)
((,) mapping) `liftM` fruleInst rule mapping state
{-
-- | Emulate Isabelle's 'frule' tactic; i.e. the first sequent is the rule that
-- is used for resolution.
--
-- NOTE that 'frule' works only for rules that contain no existential
-- quantifiers in the conclusion.
frule :: Sequent -- ^ Rule to use for resolution.
-> Sequent -- ^ Proof state that this rule is resolved against.
-> [(Mapping, Maybe Sequent)]
-- ^ The mapping and no resulting proof state
-- if the resolution solved this subgoal;
-- otherwise the new subgoal provided it is
-- differnt from the old one.
frule rule state = do
atoms <- conjunctionToAtoms $ seConcl rule
let prem0 = sePrem state
mapping <- resolve (sePrem rule) prem0
optPrem1 <- conjoinAtoms (map (substAtom (getMappingEqs mapping)) atoms) prem0
case optPrem1 of
Nothing -> do return (mapping, Nothing)
Just prem1 -> do guard (prem1 /= prem0)
return (mapping, Just $ Sequent prem1 (seConcl state))
-}
-- | Try to prove an 'Injective' sequent by reducing it to its non-injective
-- counterpart.
reduceInjectivity :: Sequent -> Maybe (Either String Sequent)
reduceInjectivity se
| seQualifier se /= Injective = Nothing
-- We have an injective sequent => we must reduce it or report a failure to
-- reduce it.
| otherwise = Just $
case sortednub $ formulaTIDs $ toFormula $ sePrem se of
[premTID] -> case decomposeConcl $ seConcl se of
Just (concTID, atoms) ->
if check premTID concTID atoms
then Right (se { seQualifier = Standard })
else Left "conclusion does not immediatly entail injectivity"
Nothing -> Left $
"only works for conclusions of the form '? tid. ato1 & ... & atoN'"
premTIDs -> Left $
"too few/many thread identifiers in premises " ++ show premTIDs
where
decomposeConcl (FExists (Left tid) fm) = (,) tid <$> conjunctionToAtoms fm
decomposeConcl _ = Nothing
-- check that conclusion entails equality of premise TIDs
check premTID0 concTID atoms =
case E.solve (rawEqs0 ++ rawEqs1) E.empty of
Just eqs -> E.substTID eqs premTID0 == E.substTID eqs premTID1
_ -> False
where
premTID1 = 1 + max premTID0 concTID
rawEqs0 = [ eq | AEq eq <- atoms ]
rawEqs1 = map rename rawEqs0
rename = E.substAnyEq $ E.getMappingEqs $
E.addTIDMapping premTID0 premTID1 E.emptyMapping
-- | Try to saturate a sequent, if possible and leading to new facts.
saturate :: MonadPlus m => Sequent -> m Sequent
saturate se = do
guard (isStandard se)
changePrem (return . saturateFacts) se
-- | Try to use the chain rule.
--
-- MonadPlus is used to report a failure to apply the rule.
--
chainRule :: MonadPlus m
=> Sequent -> Message
-> m [((String, [Either TID ArbMsgId]), Sequent)]
chainRule se m = do
guard (isStandard se)
map (second mkSequent) `liftM` chainRuleFacts m (sePrem se)
where
mkSequent prem = Sequent prem (seConcl se) Standard
-- | Try to exploit the typing. Fails if no new facts could be derived.
exploitTyping :: MonadPlus m => Sequent -> m Sequent
exploitTyping = changePrem exploitTypingFacts
-- | Split a splittable equality.
-- splitting can be done.
splitEq :: E.MsgEq -> Sequent -> [Maybe Sequent]
splitEq eq se
| eq `elem` splittableEqs prems = map (fmap updPrem) $ splitEqFacts eq prems
| otherwise = error $ "splitEq: equality not present"
where
prems = sePrem se
updPrem prem' = se {sePrem = prem'}