-- Diffie-Hellman Algebra implementation -- This module implements a version of Diffie-Hellman in which -- exponents form a free Abelian group. It uses the basis elements as -- atoms principle. -- Copyright (c) 2009, 2014 The MITRE Corporation -- -- This program is free software: you can redistribute it and/or -- modify it under the terms of the BSD License as published by the -- University of California. -------------------------------------------------------------------- -- The module implements a many-sorted algebra, but is used as an -- order-sorted algebra. It exports a name, and the origin used to -- generate variables. -- The Diffie-Hellman Order-Sorted Signature is -- Sorts: mesg, text, data, name, skey, akey, -- string, base, expr, and expn -- -- Subsorts: text, data, name, skey, akey, -- base, expr < mesg and expn < expr -- -- Operations: -- cat : mesg X mesg -> mesg Pairing -- enc : mesg X mesg -> mesg Encryption -- hash : mesg -> mesg Hashing -- string : mesg Tag constants -- ltk : name X name -> skey Long term shared key -- pubk : name -> akey Public key of principal -- pubk : string X name -> akey Tagged public key of principal -- invk : akey -> akey Inverse of asymmetric key -- gen : base DH generator -- exp : base X expr -> base Exponentiation -- mul : expr X expr -> expr Group operation -- rec : expr -> expr Group inverse -- one : expr Group identity -- -- Atoms: messages of sort text, data, name, skey, akey, and expn, and -- messages of the form (exp (gen) x) where x is of sort expn. -- A free Abelian group has a set of basis elements, and the sort expn -- is the sort for basis elements. Limiting the atoms associated with -- an exponent to basis elements is the basis elements as atoms -- principle. This principle enables CPSA to correctly handle -- origination assumptions. -- Variables of sort string are forbidden. -- The implementation exploits the isomorphism between order-sorted -- algebras and many-sorted algebras by adding inclusion operations to -- produce an equivalent Diffie-Hellman Many-Sorted Signature. There -- is an inclusion operation for each subsort of mesg. Diffie-Hellman -- exponents are handled specially using a canonical representation as -- monomials. -- Sorts: mesg, text, data, name, skey, akey, -- string, base, expr, and expn -- -- Operations: -- cat : mesg X mesg -> mesg Pairing -- enc : mesg X mesg -> mesg Encryption -- hash : mesg -> mesg Hashing -- string : mesg Tag constants -- ltk : name X name -> skey Long term shared key -- pubk : name -> akey Public key of principal -- pubk : string X name -> akey Tagged public key of principal -- invk : akey -> akey Inverse of asymmetric key -- text : text -> mesg Sort text inclusion -- data : data -> mesg Sort date inclusion -- name : name -> mesg Sort name inclusion -- skey : skey -> mesg Sort skey inclusion -- akey : akey -> mesg Sort akey inclusion -- base : base -> mesg Sort base inclusion -- -- A message of sort expr, a monomial, is represented by a map from -- identifiers to descriptions. A description is a pair consisting -- of a flag saying if the variable is of sort expn or expr, and a -- non-zero integer. For t of sort expr, the monomial associated -- with t is -- -- x1 ^ c1 * x2 ^ c2 * ... * xn ^ cn -- -- for all xi in the domain of t and t(xi) = (_, ci). -- In both algebras, invk(invk(t)) = t for all t of sort akey, -- (exp h (one)) = h, (exp (exp h x) y) = (exp h (mul x y)), and -- the Abelian group axioms hold. {-# LANGUAGE MultiParamTypeClasses #-} module CPSA.DiffieHellman.Algebra (name, origin) where import Control.Monad (foldM) import qualified Data.List as L import qualified Data.Set as S import Data.Set (Set) import qualified Data.Map as M import Data.Map (Map) import Data.Char (isDigit) import qualified CPSA.Lib.CPSA as C import CPSA.Lib.CPSA (SExpr(..), Pos, annotation) {-- Debugging support import System.IO.Unsafe z :: Show a => a -> b -> b z x y = unsafePerformIO (print x >> return y) zz :: Show a => a -> a zz x = z x x zn :: Show a => a -> Maybe b -> Maybe b zn x Nothing = z x Nothing zn _ y = y zf :: Show a => a -> Bool -> Bool zf x False = z x False zf _ y = y zt :: Show a => a -> Bool -> Bool zt x True = z x True zt _ y = y --} {- Export iUnify and iMatch for GHCi for debugging For this to work, you must install the package bytestring-handle from Hackage and tell GHCi that it is not hidden on the command line or within GHCi with: :set -package bytestring-handle -} {-- import System.IO (Handle) import Data.ByteString.Lazy.Char8 (pack) import Data.ByteString.Handle import Control.Exception (try) import System.IO.Unsafe stringHandle :: String -> IO Handle stringHandle s = readHandle False (pack s) stringPosHandle :: String -> IO C.PosHandle stringPosHandle s = do h <- stringHandle s C.posHandle "" h stringLoad :: String -> IO [SExpr Pos] stringLoad s = do h <- stringPosHandle s loop h [] where loop h xs = do x <- C.load h case x of Nothing -> return $ reverse xs Just x -> loop h (x : xs) sLoad :: String -> [[SExpr Pos]] sLoad s = [unsafePerformIO $ stringLoad s] -- Test unification iUnify :: String -> String -> String -> [Subst] iUnify vars t t' = iRun unify emptySubst vars t t' -- Test matching iMatch :: String -> String -> String -> [Env] iMatch vars t t' = iRun match emptyEnv vars t t' iRun :: (Term -> Term -> (Gen, a) -> [(Gen, a)]) -> a -> String -> String -> String -> [a] iRun f mt vars t t' = do vars <- sLoad vars [t] <- sLoad t [t'] <- sLoad t' (gen, vars) <- loadVars origin vars t <- loadTerm vars t t' <- loadTerm vars t' (_, a) <- f t t' (gen, mt) return a gRun :: Gen -> Term -> a -> a gRun (Gen n) t a = foldVars f a t where f a t = case varId t of Id (m, _) | m >= n -> error ("Bad gen " ++ show n) _ -> a gMatch :: Term -> Term -> GenEnv -> [GenEnv] gMatch t t' r@(g, _) = gRun g t' (match t t' r) gUnify :: Term -> Term -> GenSubst -> [GenSubst] gUnify t t' r@(g, _) = gRun g (F Cat [t, t']) (unify t t' r) --} name :: String name = "diffie-hellman" -- An identifier newtype Id = Id (Integer, String) deriving Show -- The integer distinguishes an identifier, the string is for printing. instance Eq Id where (Id (x, _)) == (Id (x', _)) = x == x' instance Ord Id where compare (Id (x, _)) (Id (x', _)) = compare x x' idName :: Id -> String idName (Id (_, name)) = name -- Counter used for generating fresh identifiers. newtype Gen = Gen (Integer) deriving Show origin :: Gen origin = Gen (0) freshId :: Gen -> String -> (Gen, Id) freshId (Gen (i)) name = (Gen (i + 1), Id (i, name)) cloneId :: Gen -> Id -> (Gen, Id) cloneId gen x = freshId gen (idName x) -- A term in an Abelian group is a map from identifiers to pairs of -- bools and non-zero integers. The boolean is true if the variable -- is a basis element. type Coef = Int type Desc = (Bool, Coef) type Group = Map Id Desc isGroupVar :: Group -> Bool isGroupVar t = M.size t == 1 && snd (head (M.elems t)) == 1 isBasisVar :: Group -> Bool isBasisVar t = M.size t == 1 && head (M.elems t) == (True, 1) isExprVar :: Group -> Bool isExprVar t = M.size t == 1 && head (M.elems t) == (False, 1) -- Assumes isGroupVar t == True or isBasisVar t == True! getGroupVar :: Group -> Id getGroupVar x = head $ M.keys x -- Create group var as a basis element if be is true groupVar :: Bool -> Id -> Term groupVar be x = G $ M.singleton x (be, 1) dMapCoef :: (Coef -> Coef) -> Desc -> Desc dMapCoef f (be, c) = (be, f c) invert :: Group -> Group invert t = M.map (dMapCoef negate) t expg :: Group -> Int -> Group expg _ 0 = M.empty expg t 1 = t expg t n = M.map (dMapCoef (n *)) t mul :: Group -> Group -> Group mul t t' = M.foldrWithKey f t' t -- Fold over the mappings in t where f x c t = -- Alter the mapping of M.alter (g c) x t -- variable x in t g c Nothing = -- Variable x not currently mapped Just c -- so add a mapping g (b, c) (Just (b', c')) -- Variable x maps to c' | b /= b' = error "Algebra.mul: sort mismatch" | c + c' == 0 = Nothing -- Delete the mapping | otherwise = Just $ (b, c + c') -- Adjust the mapping -- Why not replace M.assocs with M.toList elsewhere? type Maplet = (Id, Desc) mMapCoef :: (Coef -> Coef) -> Maplet -> Maplet mMapCoef f (x, (be, c)) = (x, (be, f c)) mInverse :: [Maplet] -> [Maplet] mInverse maplets = map (mMapCoef negate) maplets isMapletNonzero :: Maplet -> Bool isMapletNonzero (_, (_, c)) = c /= 0 group :: [Maplet] -> Group group maplets = M.fromList $ filter isMapletNonzero maplets -- Function symbols--see foldVar to see the arity of each symbol. data Symbol = Text -- Text atom | Data -- Another text-like atom | Name -- Principal atom | Skey -- Symmetric key atom | Base -- Base of an exponentiated atom | Ltk -- Long term shared symmetric key | Akey -- Asymmetric key atom | Invk -- Inverse of asymmetric key | Pubk -- Public asymmetric key of a principal | Genr -- The generator constant for the group | Exp -- Exponentiation function symbol | Cat -- Term concatenation | Enc -- Encryption | Hash -- Hashing deriving (Show, Eq, Ord, Enum, Bounded) -- A Diffie-Hellman Algebra Term data Term = I !Id | C !String | F !Symbol ![Term] | G !Group -- An exponent, an Abelian group deriving Show equalTerm :: Term -> Term -> Bool equalTerm (I x) (I y) = x == y equalTerm (C c) (C c') = c == c' equalTerm (F Invk [F Invk [t]]) t' = equalTerm t t' equalTerm t (F Invk [F Invk [t']]) = equalTerm t t' equalTerm (F Exp [t0, G t1]) t' | M.null t1 = equalTerm t0 t' equalTerm t (F Exp [t0, G t1]) | M.null t1 = equalTerm t t0 equalTerm (F Exp [F Exp [t, G t0], G t1]) t' = equalTerm (F Exp [t, G (mul t0 t1)]) t' equalTerm t (F Exp [F Exp [t', G t0], G t1]) = equalTerm t (F Exp [t', G (mul t0 t1)]) equalTerm (F s u) (F s' u') = s == s' && equalTermLists u u' equalTerm (G t) (G t') = t == t' equalTerm _ _ = False equalTermLists :: [Term] -> [Term] -> Bool equalTermLists [] [] = True equalTermLists (t : u) (t' : u') = equalTerm t t' && equalTermLists u u' equalTermLists _ _ = False instance Eq Term where (==) = equalTerm -- Term comparison respecting the axiom compareTerm :: Term -> Term -> Ordering compareTerm (I x) (I y) = compare x y compareTerm (C c) (C c') = compare c c' compareTerm (F Invk [F Invk [t]]) t' = compareTerm t t' compareTerm t (F Invk [F Invk [t']]) = compareTerm t t' compareTerm (F Exp [t0, G t1]) t' | M.null t1 = compareTerm t0 t' compareTerm t (F Exp [t0, G t1]) | M.null t1 = compareTerm t t0 compareTerm (F Exp [F Exp [t, G t0], G t1]) t' = compareTerm (F Exp [t, G (mul t0 t1)]) t' compareTerm t (F Exp [F Exp [t', G t0], G t1]) = compareTerm t (F Exp [t', G (mul t0 t1)]) compareTerm (F s u) (F s' u') = case compare s s' of EQ -> compareTermLists u u' o -> o compareTerm (G t) (G t') = compare t t' compareTerm (I _) (C _) = LT compareTerm (C _) (I _) = GT compareTerm (I _) (F _ _) = LT compareTerm (F _ _) (I _) = GT compareTerm (I _) (G _) = LT compareTerm (G _) (I _) = GT compareTerm (C _) (F _ _) = LT compareTerm (F _ _) (C _) = GT compareTerm (C _) (G _) = LT compareTerm (G _) (C _) = GT compareTerm (F _ _) (G _) = LT compareTerm (G _) (F _ _) = GT compareTermLists :: [Term] -> [Term] -> Ordering compareTermLists [] [] = EQ compareTermLists (t : u) (t' : u') = case compareTerm t t' of EQ -> compareTermLists u u' o -> o compareTermLists [] _ = LT compareTermLists _ [] = GT instance Ord Term where compare = compareTerm -- Basic terms are introduced by defining a function used to decide if -- a term is well-formed. The context of an occurrence of an identifier -- determines its sort. A term that contains just an identifier and its -- sort information is called a variable. The sort of a variable is -- one of mesg, text, data, name, skey, akey, base, expr, or expn. -- Terms that represent variables. isVar :: Term -> Bool isVar (I _) = True -- Sort: mesg isVar (F s [I _]) = -- Sorts: text, data, name, skey, and akey s == Text || s == Data || s == Name || s == Skey || s == Akey || s == Base isVar (G x) = isGroupVar x isVar _ = False -- Extract the identifier from a variable varId :: Term -> Id varId (I x) = x varId (F Text [I x]) = x varId (F Data [I x]) = x varId (F Name [I x]) = x varId (F Skey [I x]) = x varId (F Akey [I x]) = x varId (F Base [I x]) = x varId (G x) | isGroupVar x = getGroupVar x varId _ = error "Algebra.varId: term not a variable with its sort" isAcquiredVar :: Term -> Bool isAcquiredVar (I _) = True isAcquiredVar (F Base [I _]) = True isAcquiredVar (G x) = isExprVar x isAcquiredVar _ = False -- A list of terms are well-formed if each one has the correct -- structure and every occurrence of an identifier in a term has the -- same sort. Variable environments are used to check the sort -- condition. It maps an identifier to a variable that contains the -- identifier. -- termsWellFormed u ensures all terms in u use each identifier at the -- same sort, and makes sure every term has the correct structure. termsWellFormed :: [Term] -> Bool termsWellFormed u = loop emptyVarEnv u where loop _ [] = True loop env (t : u) = case termWellFormed env t of Nothing -> False Just env' -> loop env' u newtype VarEnv = VarEnv (Map Id Term) deriving Show emptyVarEnv :: VarEnv emptyVarEnv = VarEnv M.empty -- Check the structure and sort condition. termWellFormed :: VarEnv -> Term -> Maybe VarEnv termWellFormed xts t@(I x) = extendVarEnv xts x t -- Mesg variable termWellFormed xts t@(F Text [I x]) = extendVarEnv xts x t -- Text variable termWellFormed xts t@(F Data [I x]) = extendVarEnv xts x t -- Data variable termWellFormed xts t@(F Name [I x]) = extendVarEnv xts x t -- Name variable termWellFormed xts t@(F Skey [I x]) = extendVarEnv xts x t -- Symmetric key variable termWellFormed xts (F Skey [F Ltk [I x, I y]]) = -- Long term shared symmetric key doubleTermWellFormed xts (F Name [I x]) (F Name [I y]) termWellFormed xts (F Akey [t]) = -- Asymmetric key terms case t of I x -> extendVarEnv xts x (F Akey [I x]) F Invk [I x] -> extendVarEnv xts x (F Akey [I x]) F Pubk [I x] -> extendVarEnv xts x (F Name [I x]) F Pubk [C _, I x] -> extendVarEnv xts x (F Name [I x]) F Invk [F Pubk [I x]] -> extendVarEnv xts x (F Name [I x]) F Invk [F Pubk [C _, I x]] -> extendVarEnv xts x (F Name [I x]) _ -> Nothing termWellFormed xts (F Base [t]) = baseVarEnv xts t where baseVarEnv xts t@(I x) = extendVarEnv xts x (F Base [t]) baseVarEnv xts (F Genr []) = Just xts baseVarEnv xts (F Exp [t0, G t1]) = do xts <- baseVarEnv xts t0 termWellFormed xts (G t1) baseVarEnv _ _ = Nothing termWellFormed xts (G t) = foldM exprVarEnv xts (M.assocs t) where exprVarEnv xts (x, (be, _)) = extendVarEnv xts x (groupVar be x) termWellFormed xts (C _) = Just xts -- Tags termWellFormed xts (F Cat [t0, t1]) = doubleTermWellFormed xts t0 t1 -- Concatenation termWellFormed xts (F Enc [t0, t1]) = doubleTermWellFormed xts t0 t1 -- Encryption termWellFormed xts (F Hash [t]) = termWellFormed xts t -- Hashing termWellFormed _ _ = Nothing -- Extend when sorts agree extendVarEnv :: VarEnv -> Id -> Term -> Maybe VarEnv extendVarEnv (VarEnv env) x t = case M.lookup x env of Nothing -> Just $ VarEnv $ M.insert x t env Just t' -> if t == t' then Just (VarEnv env) else Nothing doubleTermWellFormed :: VarEnv -> Term -> Term -> Maybe VarEnv doubleTermWellFormed xts t0 t1 = do xts <- termWellFormed xts t0 termWellFormed xts t1 -- Atoms are terms the adversary can create modulo origination -- assumptions. isAtom :: Term -> Bool isAtom (I _) = False isAtom (C _) = False isAtom (F Base [F Exp [F Genr [], G x]]) = isBasisVar x isAtom (F s _) = s == Text || s == Data || s == Name || s == Skey || s == Akey isAtom (G x) = isBasisVar x -- Does a term occur in another term? -- This function is always called with a variable that answers true to -- isAcquiredVar as its first argument, so the first case never gets used. occursIn :: Term -> Term -> Bool occursIn (G t) (G t') = t == t' occursIn t t' = t == t' || case t' of F _ u -> any (occursIn t) u _ -> False -- Fold f through a term applying it to each variable in the term. foldVars :: (a -> Term -> a) -> a -> Term -> a foldVars f acc t@(I _) = f acc t -- Mesg variable foldVars f acc t@(F Text [I _]) = f acc t -- Text variable foldVars f acc t@(F Data [I _]) = f acc t -- Data variable foldVars f acc t@(F Name [I _]) = f acc t -- Name variable foldVars f acc t@(F Skey [I _]) = f acc t -- Symmetric key variable foldVars f acc (F Skey [F Ltk [I x, I y]]) = -- Long term shared symmetric key f (f acc (F Name [I x])) (F Name [I y]) foldVars f acc t@(F Akey [I _]) = f acc t -- Asymmetric keys foldVars f acc (F Akey [F Invk [I x]]) = f acc (F Akey [I x]) foldVars f acc (F Akey [F Pubk [I x]]) = f acc (F Name [I x]) foldVars f acc (F Akey [F Pubk [C _, I x]]) = f acc (F Name [I x]) foldVars f acc (F Akey [F Invk [F Pubk [I x]]]) = f acc (F Name [I x]) foldVars f acc (F Akey [F Invk [F Pubk [C _, I x]]]) = f acc (F Name [I x]) foldVars f acc (F Base [t]) = baseAddVars acc t where baseAddVars acc t@(I _) = f acc (F Base [t]) baseAddVars acc (F Genr []) = acc baseAddVars acc (F Exp [t0, G t1]) = foldVars f (baseAddVars acc t0) (G t1) baseAddVars _ _ = error "Algebra.foldVars: Bad term" foldVars f acc (G t) = M.foldlWithKey exprAddVars acc t where exprAddVars acc x (be, _) = f acc (groupVar be x) foldVars _ acc (C _) = acc -- Tags foldVars f acc (F Cat [t0, t1]) = -- Concatenation foldVars f (foldVars f acc t0) t1 foldVars f acc (F Enc [t0, t1]) = -- Encryption foldVars f (foldVars f acc t0) t1 foldVars f acc (F Hash [t]) = -- Hashing foldVars f acc t foldVars _ _ t = error $ "Algebra.foldVars: Bad term " ++ show t -- Fold f through a term applying it to each term that is carried by the term. foldCarriedTerms :: (a -> Term -> a) -> a -> Term -> a foldCarriedTerms f acc t@(F Cat [t0, t1]) = -- Concatenation foldCarriedTerms f (foldCarriedTerms f (f acc t) t0) t1 foldCarriedTerms f acc t@(F Enc [t0, _]) = -- Encryption foldCarriedTerms f (f acc t) t0 foldCarriedTerms f acc t = f acc t -- atoms and tags -- Is a term carried by another term? carriedBy :: Term -> Term -> Bool carriedBy t t' = t == t' || case t' of F Cat [t0, t1] -> carriedBy t t0 || carriedBy t t1 F Enc [t0, _] -> carriedBy t t0 _ -> False -- The key used to decrypt an encrypted term, otherwise Nothing. decryptionKey :: Term -> Maybe Term decryptionKey (F Enc [_, t]) = Just (inv t) decryptionKey _ = Nothing buildable :: Set Term -> Set Term -> Term -> Bool buildable knowns unguessable term = ba term where ba (I _) = True -- A mesg sorted variable is always buildable ba (C _) = True -- So is a tag ba (F Cat [t0, t1]) = ba t0 && ba t1 ba t@(F Enc [t0, t1]) = S.member t knowns || ba t0 && ba t1 ba t@(F Hash [t1]) = S.member t knowns || ba t1 ba t@(F Base [t']) | S.member t knowns || bb t' = True ba t@(G t') | hasFluff unguessable t' = -- Expunge guessable part ba (defluff unguessable t') | S.member t knowns || M.null t' = True -- t known or is one ba t = isAtom t && S.notMember t unguessable -- Buildable base term bb (I _) = True -- A variable of sort base is always buildable bb (F Genr []) = True -- and so is the generator bb t@(F Exp [t0, G t1]) | hasFluff unguessable t1 = -- Expunge guessable part bb (F Exp [t0, defluff unguessable t1]) | otherwise = -- t known or base and exponent buildable S.member (F Base [t]) knowns || ba (G t1) && ba t0 bb (_) = False -- Compute the decomposition given some known terms and some unguessable -- atoms. The code is quite tricky. It iterates until the known -- terms don't change. The known terms ends up with all the -- encryptions that are known. decompose :: Set Term -> Set Term -> (Set Term, Set Term) decompose knowns unguessable = loop unguessable knowns S.empty [] where loop unguessable knowns old [] | old == knowns = (knowns, unguessable) -- Done | otherwise = loop unguessable knowns knowns (S.elems knowns) loop unguessable knowns old (t@(F Cat _) : todo) = loop unguessable (decat t (S.delete t knowns)) old todo loop unguessable knowns old ((F Enc [t0, t1]) : todo) | buildable knowns unguessable (inv t1) = -- Add plaintext loop unguessable (decat t0 knowns) old todo | otherwise = loop unguessable knowns old todo loop unguessable knowns old (G t : todo) | M.null t = loop unguessable (S.delete (G t) knowns) old todo | hasFluff unguessable t = loop unguessable (S.insert (defluff unguessable t) (S.delete (G t) knowns)) old todo loop unguessable knowns old (t@(F Base [F Exp [t0, G t1]]) : todo) | M.null t1 = loop unguessable (S.insert (F Base [t0]) (S.delete t knowns)) old todo | hasFluff unguessable t1 = -- Expunge guessable part loop unguessable (S.insert (F Base [F Exp [t0, t1']]) (S.delete t knowns)) old todo where t1' = defluff unguessable t1 loop unguessable knowns old (t : todo) | isAtom t = loop (S.delete t unguessable) (S.delete t knowns) old todo | otherwise = loop unguessable knowns old todo -- Decat decat (F Cat [t0, t1]) s = decat t1 (decat t0 s) decat t s = S.insert t s -- Inverts an asymmetric key inv :: Term -> Term inv (F Akey [F Invk [t]]) = F Akey [t] inv (F Akey [t]) = F Akey [F Invk [t]] inv (I _) = error "Algebra.inv: Cannot invert a variable of sort mesg" inv t = t -- Does a group term have a variable of sort expr or a variable not in -- the avoidance set a? hasFluff :: Set Term -> Group -> Bool hasFluff a t = any f (M.assocs t) where f (x, d) = fluff a x d fluff :: Set Term -> Id -> Desc -> Bool fluff a x (be, _) = not be || S.notMember (groupVar be x) a -- fluff a x (be, _) = be && S.notMember (groupVar be x) a -- Remove fluff from a group term defluff :: Set Term -> Group -> Term defluff a t = G $ M.filterWithKey (\x d -> not $ fluff a x d) t -- Extracts every encryption that is carried by a term along with its -- encryption key. Note that a hash is treated as a kind of -- encryption in which the term that is hashed is the encryption key. encryptions :: Term -> [(Term, [Term])] encryptions t = reverse $ loop t [] where loop (F Cat [t, t']) acc = loop t' (loop t acc) loop t@(F Enc [t', t'']) acc = loop t' (adjoin (t, [t'']) acc) loop t@(F Hash [t']) acc = adjoin (t, [t']) acc -- loop t@(F Base [F Exp [F Genr [], G t']]) acc = -- adjoin (t, basis t') acc loop t@(F Base [F Exp [_, G t']]) acc = adjoin (t, basis t') acc loop _ acc = acc adjoin x xs | x `elem` xs = xs | otherwise = x : xs -- The basis variables is a group term basis :: Group -> [Term] basis t = [groupVar True x | (x, (be, _)) <- M.assocs t, be] -- Returns the encryptions that carry the target. If the target is -- carried outside all encryptions, or is exposed because a decription -- key is derivable, Nothing is returned. protectors :: (Term -> Bool) -> Term -> Term -> Maybe [Term] protectors derivable target source = do ts <- bare source S.empty return $ S.elems ts where bare source _ | source == target = Nothing bare (F Cat [t, t']) acc = maybe Nothing (bare t') (bare t acc) bare t@(F Enc [t', key]) acc = if target `carriedBy` t' then if derivable (inv key) then bare t' acc else Just (S.insert t acc) else Just acc bare _ acc = Just acc -- FIX ME! Needs updating for Diffie-Hellman -- Support for data flow analysis of traces. A flow rule maps an -- initial set of atoms and a set of available terms to sets of pairs -- of the same sets. type FlowRule = (Set Term, Set Term) -> Set (Set Term, Set Term) -- Combine flow rules sequentially. comb :: FlowRule -> FlowRule -> FlowRule comb f g x = S.fold h S.empty (g x) where h a s = S.union (f a) s -- Analyze a term as a sent term. outFlow :: Term -> FlowRule outFlow t a@(_, available) | S.member t available = S.singleton a outFlow (I _) _ = S.empty outFlow (C _) a = S.singleton a outFlow (F Cat [t0, t1]) a = -- Construct non-atoms comb (outFlow t1) (outFlow t0) a outFlow (F Enc [t0, t1]) a = comb (outFlow t1) (outFlow t0) a outFlow (F Hash [t]) a = outFlow t a outFlow t (initial, available) = -- Don't look inside atoms S.singleton (S.insert t initial, S.insert t available) -- Analyze a term as a received term. inFlow :: Term -> FlowRule inFlow (C _) a = S.singleton a inFlow (F Cat [t0, t1]) a = -- Try to receive components S.union -- in both orders (comb (inFlow t1) (inFlow t0) a) (comb (inFlow t0) (inFlow t1) a) inFlow t@(F Enc [t0, t1]) (initial, available) = S.union -- Encryption can be built (outFlow t (initial, available)) -- or decrypted (comb (inFlow t0) (outFlow (inv t1)) a) where -- Derive decryption key first a = (initial, S.insert t available) inFlow (F Hash [t0]) (initial, available) = outFlow t0 (initial, available) inFlow t (initial, available) = S.singleton (initial, S.insert t available) instance C.Term Term where isVar = isVar isAcquiredVar = isAcquiredVar isAtom = isAtom termsWellFormed = termsWellFormed occursIn = occursIn foldVars = foldVars foldCarriedTerms = foldCarriedTerms carriedBy = carriedBy decryptionKey = decryptionKey decompose = decompose buildable = buildable encryptions = encryptions protectors = protectors outFlow = outFlow inFlow = inFlow loadTerm = loadTerm -- Places -- A place names a one subterm within a term. It is a list of -- integers giving a path through a term to that named subterm. Each -- integer in the list identifies the subterm in a function -- application on the path to the named subterm. The integer is the -- index of the subterm in the application's list of terms. -- The places and replace code fail to find the variable -- (F Akey [I x]) in (F Akey [Invk [I x]]). newtype Place = Place [Int] deriving Show -- Returns the places a variable occurs within a term. -- Returns no places for group variables. places :: Term -> Term -> [Place] places var source = f [] [] source where f paths path source | var == source = Place (reverse path) : paths f paths path (F _ u) = g paths path 0 u f paths _ _ = paths g paths _ _ [] = paths g paths path i (t : u) = g (f paths (i: path) t) path (i + 1) u -- Returns the places a term is carried by another term. carriedPlaces :: Term -> Term -> [Place] carriedPlaces target source = f [] [] source where f paths path source | target == source = Place (reverse path) : paths f paths path (F Cat [t, t']) = f (f paths (0 : path) t) (1 : path) t' f paths path (F Enc [t, _]) = f paths (0 : path) t f paths _ _ = paths -- Replace a variable within a term at a given place. replace :: Term -> Place -> Term -> Term replace var (Place ints) source = loop ints source where loop [] _ = var loop (i : path) (F s u) = F s (C.replaceNth (loop path (u !! i)) i u) loop _ (G _) = error "Algebra.replace: Path to expr" loop _ _ = error "Algebra.replace: Bad path to term" -- Return the ancestors of the term at the given place. ancestors :: Term -> Place -> [Term] ancestors source (Place ints) = loop [] ints source where loop ts [] _ = ts loop ts (i: path) t@(F _ u) = loop (t : ts) path (u !! i) loop _ _ _ = error "Algebra.ancestors: Bad path to term" instance C.Place Term Place where places = places carriedPlaces = carriedPlaces replace = replace ancestors = ancestors -- Rename the identifiers in a term. Gen keeps the state of the -- renamer. (Question: should alist be replaced by a Map?) clone :: Gen -> Term -> (Gen, Term) clone gen t = (gen', t') where (_, gen', t') = cloneTerm ([], gen) t cloneTerm (alist, gen) t = case t of -- The association list maps I x -> -- identifiers to identifier. case lookup x alist of Just y -> (alist, gen, I y) Nothing -> let (gen', y) = cloneId gen x in ((x, y) : alist, gen', I y) C c -> (alist, gen, C c) F sym u -> let (alist', gen', u') = foldl cloneTermList (alist, gen, []) u in (alist', gen', F sym $ reverse u') G t -> let (alist', gen', ts) = M.foldlWithKey cloneGroupList (alist, gen, []) t in (alist', gen', G $ group ts) cloneTermList (alist, gen, u) t = let (alist', gen', t') = cloneTerm (alist, gen) t in (alist', gen', t' : u) cloneGroupList (alist, gen, ts) x (be, c) = case lookup x alist of Just y -> (alist, gen, (y, (be, c)) : ts) Nothing -> let (gen', y) = cloneId gen x in ((x, y) : alist, gen', (y, (be, c)) : ts) instance C.Gen Term Gen where origin = origin clone = clone loadVars = loadVars -- Functions used in both unification and matching type IdMap = Map Id Term emptyIdMap :: IdMap emptyIdMap = M.empty -- Apply a substitution to a term idSubst :: IdMap -> Term -> Term idSubst subst (I x) = M.findWithDefault (I x) x subst idSubst _ t@(C _) = t idSubst subst (F Invk [t]) = case idSubst subst t of F Invk [t] -> t -- (invk (invk x)) = x t -> F Invk [t] idSubst subst (F Exp [t0, G t1]) = case idSubst subst t0 of -- (exp (exp g x) y) = (exp g (mul x y)) F Exp [t0', G t1'] -> case mul t1' $ groupSubst subst t1 of t2 | M.null t2 -> t0' | otherwise -> F Exp [t0', G t2] t -> expSubst subst t t1 idSubst subst (F s u) = F s (map (idSubst subst) u) idSubst subst (G t) = G $ groupSubst subst t expSubst :: IdMap -> Term -> Group -> Term expSubst subst t0 t1 = case groupSubst subst t1 of t1' | M.null t1' -> t0 -- (exp g (one)) = g | otherwise -> F Exp [t0, G t1'] groupSubst :: IdMap -> Group -> Group groupSubst subst t = M.foldrWithKey f M.empty t where f x (be, c) t = mul (expg (groupLookup subst be x) c) t groupLookup :: IdMap -> Bool -> Id -> Group groupLookup subst be x = case M.findWithDefault (groupVar be x) x subst of G t -> t w -> error ("Algebra.groupLookup: Bad substitution: " ++ show x ++ " -> " ++ show w) showMap :: (Show a, Show b) => Map a b -> ShowS showMap m = showAssocs (M.assocs m) where showAssocs [] = id showAssocs ((x,y):m) = showString "\n " . shows x . showString " -> " . shows y . showAssocs m -- Unification and substitution -- The rewrite rules used are: -- -- (vars (h base) (x y expr)) -- -- 1. ((exp h x) y) ==> (exp h (mul x y)) -- 2. (exp h (one)) ==> h -- 3. unify((exp h x), (exp h y), s) ==> -- unify(x, y, s) -- 4 unify((exp h x), (exp (gen) y), s) ==> -- unify(h, (exp gen (mul y (rec x))), s) -- 5. unify((exp (gen) x), (exp h y), s) ==> -- unify((exp h x), (exp (gen) y), s) newtype Subst = Subst IdMap deriving (Eq, Ord) instance Show Subst where showsPrec _ (Subst s) = showString "Subst (" . showMap s . showChar ')' emptySubst :: Subst emptySubst = Subst emptyIdMap -- Apply a substitution created by unification substitute :: Subst -> Term -> Term substitute (Subst s) t = idSubst s t -- Composition of substitutions -- substitute (compose s0 s1) t = substitute s0 (substitute s1 t) -- 1. apply s0 to range of s1 to obtain s2; -- 2. remove bindings is s0 where domains of s0 and s1 overlap to form s3; -- 3. remove trivial bindings from s2 to form s4; and -- 4. take the union of s4 and s3. compose :: Subst -> Subst -> Subst compose (Subst s0) (Subst s1) = let s2 = M.map (substitute (Subst s0)) s1 -- Step 1 s4 = M.filterWithKey nonTrivialBinding s2 in -- Step 3 Subst (M.union s4 s0) -- Steps 2 and 4, union is left-biased nonTrivialBinding :: Id -> Term -> Bool nonTrivialBinding x (I y) = x /= y nonTrivialBinding x (G y) | isGroupVar y = x /= getGroupVar y nonTrivialBinding _ _ = True -- During unification, variables determined to be equal are collected -- into an equivalence class. Multiple lookups of each variable in -- the internal representation of a substitution finds the canonical -- representive of the class. The chase function finds the current -- canonical representitive. -- Get the canonical representative of equivalent identifiers making use -- of this algebra's axiom. chase :: Subst -> Term -> Term chase (Subst s) (I x) = case M.lookup x s of Nothing -> I x Just t -> chase (Subst s) t chase s (F Invk [t]) = chaseInvk s t chase s (F Exp [t0, G t1]) = chaseExp s t0 t1 chase _ t = t chaseInvk :: Subst -> Term -> Term chaseInvk (Subst s) (I x) = case M.lookup x s of Nothing -> F Invk [I x] Just t -> chaseInvk (Subst s) t chaseInvk s (F Invk [t]) = chase s t chaseInvk _ t = F Invk [t] chaseExp :: Subst -> Term -> Group -> Term chaseExp s t0 t1 | M.null t1 = chase s t0 chaseExp s (I x) t1 = case chase s (I x) of F Exp [t0', G t1'] -> chaseExp s t0' (mul t1 t1') t0 -> F Exp [t0, chaseGroup s t1] chaseExp s (F Exp [t0', G t1']) t1 = chaseExp s t0' (mul t1 t1') chaseExp s t0 t1 = F Exp [t0, chaseGroup s t1] chaseGroup :: Subst -> Group -> Term chaseGroup (Subst s) x = G $ groupSubst s x -- Does x occur in t? occurs :: Id -> Term -> Bool occurs x (I y) = x == y occurs _ (C _) = False occurs x (F _ u) = any (occurs x) u occurs x (G t) = elem x (M.keys t) type GenSubst = (Gen, Subst) unifyChase :: Term -> Term -> GenSubst -> [GenSubst] unifyChase t t' (g, s) = unifyTerms (chase s t) (chase s t') (g, s) unifyTerms :: Term -> Term -> GenSubst -> [GenSubst] unifyTerms (I x) (I y) (g, Subst s) | x == y = [(g, Subst s)] | otherwise = [(g, Subst $ M.insert x (I y) s)] unifyTerms (I x) t (g, Subst s) | occurs x t = [] | otherwise = [(g, Subst $ M.insert x t s)] unifyTerms t (I x) s = unifyTerms (I x) t s unifyTerms (C c) (C c') s | c == c' = [s] | otherwise = [] unifyTerms (F Invk [I x]) (F Pubk [I y]) s = unifyTerms (I x) (F Invk [F Pubk [I y]]) s unifyTerms (F Invk [I x]) (F Pubk [C c, I y]) s = unifyTerms (I x) (F Invk [F Pubk [C c, I y]]) s unifyTerms (F Pubk [I x]) (F Invk [I y]) s = unifyTerms (I y) (F Invk [F Pubk [I x]]) s unifyTerms (F Pubk [C c, I x]) (F Invk [I y]) s = unifyTerms (I y) (F Invk [F Pubk [C c, I x]]) s unifyTerms (F Exp [t0, G t1]) (F Exp [t0', G t1']) s = unifyExp t0 t1 t0' t1' s unifyTerms (F sym u) (F sym' u') s | sym == sym' = unifyTermLists u u' s | otherwise = [] unifyTerms (G t) (G t') s = unifyGroup t t' s unifyTerms _ _ _ = [] unifyExp :: Term -> Group -> Term -> Group -> GenSubst -> [GenSubst] unifyExp t0 t1 t0' t1' s | t0 == t0' = unifyGroup t1 t1' s unifyExp (I x) t1 (F Genr []) t1' (g, Subst s) | t1 == t1' = [(g, Subst $ M.insert x (F Genr []) s)] | otherwise = [(g, Subst (M.insert x (F Exp [F Genr [], G $ mul t1' (invert t1)]) s))] unifyExp (F Genr []) t1 (I x) t1' s = unifyExp (I x) t1' (F Genr []) t1 s unifyExp _ _ _ _ _ = [] unifyTermLists :: [Term] -> [Term] -> GenSubst -> [GenSubst] unifyTermLists [] [] s = [s] unifyTermLists (t : u) (t' : u') s = do s' <- unifyChase t t' s unifyTermLists u u' s' unifyTermLists _ _ _ = [] unifyGroup :: Group -> Group -> GenSubst -> [GenSubst] unifyGroup t0 t1 (g, Subst s) = do let t = groupSubst s (mul t0 (invert t1)) (_, g', s') <- matchGroup t M.empty S.empty g s return (g', Subst s') -- The exported unifier converts the internal representation of a -- substitution into the external form using chaseMap. unify :: Term -> Term -> GenSubst -> [GenSubst] unify t t' s = do (g, s) <- unifyChase t t' s return (g, chaseMap s) -- Apply the chasing version of substitution to the range of s. chaseMap :: Subst -> Subst chaseMap (Subst s) = Subst $ M.map (substChase (Subst s)) s -- A chasing version of substitution. substChase :: Subst -> Term -> Term substChase subst t = case chase subst t of t@(I _) -> t t@(C _) -> t F Invk [t] -> case substChase subst t of F Invk [t] -> t -- Apply axiom t -> F Invk [t] F Exp [t0, G t1] -> case substChase subst t0 of F Exp [t0', G t1'] -> case mul t1' $ groupChase subst t1 of t2 | M.null t2 -> t0' | otherwise -> F Exp [t0', G t2] t -> expChase subst t t1 F s u -> F s (map (substChase subst) u) G t -> G $ groupChase subst t expChase :: Subst -> Term -> Group -> Term expChase subst t0 t1 = case groupChase subst t1 of t1' | M.null t1' -> t0 | otherwise -> F Exp [t0, G t1'] groupChase :: Subst -> Group -> Group groupChase (Subst subst) t = groupSubst subst t instance C.Subst Term Gen Subst where emptySubst = emptySubst substitute = substitute unify = unify compose = compose -- Matching and instantiation newtype Env = Env (Set Id, IdMap) deriving (Eq, Ord) instance Show Env where showsPrec _ (Env (v, r)) = showString "Env (\n " . shows v . showChar ',' . showMap r . showChar ')' -- An environment may contain an explicit identity mapping, whereas a -- substitution is erroneous if it has one. The set of variables -- associated with a map is the variables in the range that were -- generated by matching and should be treated as variables when using -- unification to perform matching. The other variables in the range -- are treated as constants. -- An environment contains an IdMap and the set of variables -- generated while matching. emptyEnv :: Env emptyEnv = Env (S.empty, emptyIdMap) -- Apply a substitution created my matching instantiate :: Env -> Term -> Term instantiate (Env (_, r)) t = idSubst r t -- Matching type GenEnv = (Gen, Env) -- The matcher has the property that when pattern P and term T match -- then instantiate (match P T emptyEnv) P = T. match :: Term -> Term -> GenEnv -> [GenEnv] match (I x) t (g, Env (v, r)) = case M.lookup x r of Nothing -> [(g, Env (v, M.insert x t r))] Just t' -> if t == t' then [(g, Env (v, r))] else [] match (C c) (C c') r = if c == c' then [r] else [] match (F Exp [t0, G t1]) (F Exp [t0', G t1']) r = matchExp t0 t1 t0' t1' r match (F s u) (F s' u') r | s == s' = matchLists u u' r match (F Invk [t]) t' r = match t (F Invk [t']) r match (G t) (G t') (g, Env (v, r)) = do (v', g', r') <- matchGroup t t' v g r return (g', Env(v', r')) match _ _ _ = [] matchExp :: Term -> Group -> Term -> Group -> GenEnv -> [GenEnv] matchExp (I x) t1 t0' t1' r@(_, Env (_, e)) = case M.lookup x e of Just (F Exp [t0'', G t1'']) | t0' == t0'' -> match (G t1) (G (mul t1' (invert t1''))) r _ -> matchLists [I x, G t1] [t0', G t1'] r matchExp (F Genr []) t1 t0' t1' r = matchLists [F Genr [], G t1] [t0', G t1'] r matchExp _ _ _ _ _ = error "Algebra.matchExp: Bad match term" matchLists :: [Term] -> [Term] -> GenEnv -> [GenEnv] matchLists [] [] r = [r] matchLists (t : u) (t' : u') r = do r' <- match t t' r matchLists u u' r' matchLists _ _ _ = [] -- Matching in a group -- t0 is the pattern -- t1 is the target term -- v is the set of previously freshly generated variables -- g is the generator -- Returns complete set of unifiers. Each unifier include the set of -- variables fresh generated and a generator. matchGroup :: Group -> Group -> Set Id -> Gen -> IdMap -> [(Set Id, Gen, IdMap)] matchGroup t0 t1 v g r = let (t0', t1') = merge t0 t1 r -- Apply subst to LHS (v', g', r') = genVars v g t0' r -- Gen vars for non-fresh vars d = mkInitMatchDecis t1' in -- Ensure expns on RHS stay distinct case partition (groupSubst r' t0') t1' v' of ([], []) -> return (v', g', r') ([], t) -> constSolve t v' g' r' d -- No variables of sort expr here (t0, t1) -> solve t0 t1 v' g' r' d -- Apply subst to LHS and add results to RHS merge :: Group -> Group -> IdMap -> (Group, Group) merge t t' r = (group t0, t0') where (t0, t0') = loop (M.assocs t) ([], t') loop [] acc = acc loop (p@(x, (_, c)) : t0) (t1, t1') = case M.lookup x r of Nothing -> loop t0 (p : t1, t1') Just (G t) -> loop t0 (t1, mul (expg t (negate c)) t1') Just t -> error $ "Algebra.merge: expecting an expr but got " ++ show t -- Generate vars for each non-fleshly generated vars genVars :: Set Id -> Gen -> Group -> IdMap -> (Set Id, Gen, IdMap) genVars v g t r = M.foldlWithKey genVar (v, g, r) t where genVar (v, g, r) x (be, _) = (S.insert x' v, g', M.insert x (groupVar be x') r) where (g', x') = cloneId g x -- A set of decisions records expn variables that have been identified -- and those that are distinct. data Decision t = Decision { same :: [(t, t)], dist :: [(t, t)] } deriving Show -- Create an initial set of decisions mkDecis :: Decision Id mkDecis = Decision { same = [], dist = [] } -- Ensure bases elements in t are never identified mkInitMatchDecis :: Group -> Decision Id mkInitMatchDecis t = mkDecis { dist = [(x, y) | x <- v, y <- v, x /= y] } where v = [x | (x, (be, _)) <- M.assocs t, be] -- Move fresh variables on the RHS of the equation to the LHS -- Move variables of sort expn on the LHS to the RHS partition :: Group -> Group -> Set Id -> ([Maplet], [Maplet]) partition t0 t1 v = (M.assocs lhs, M.assocs rhs) where (v1, c1) = M.partitionWithKey g t1 -- Fresh variables go in v1 g x _ = S.member x v (v0, c0) = M.partition f t0 -- Basis elements go in c0 f (be, _) = not be lhs = mul v0 (invert v1) rhs = mul c1 (invert c0) -- Solve equation when there are no variables of sort expr on LHS. -- Treat all variables as constants. constSolve :: [Maplet] -> Set Id -> Gen -> IdMap -> Decision Id -> [(Set Id, Gen, IdMap)] constSolve t v g r d | any (\(_, (be, _)) -> not be) t = [] -- Fail expr var is on RHS | otherwise = constSolve1 t v g r d -- All vars are expn constSolve1 :: [Maplet] -> Set Id -> Gen -> IdMap -> Decision Id -> [(Set Id, Gen, IdMap)] constSolve1 [] v g r _ = return (v, g, r) constSolve1 t v g r d = case orientDecis v $ nextDecis d t of [] -> [] -- All decisions already made ((x, y):_) -> -- Pick first undecided pair distinct ++ identified where distinct = constSolve1 t v g r neq neq = d {dist = (x, y):(y, x):dist d} -- Add new constraints -- eliminate x identified = constSolve1 t' v' g r' d' t' = identify x y t -- Equate x y in t v' = S.delete x v -- Eliminate x in v r' = eliminate x y' r -- And in r y' = groupVar True y d' = d {same = (x, y):same d} -- And note decision -- Find a pair of variables for which no decision has been made. nextDecis :: Decision Id -> [Maplet] -> [(Id, Id)] nextDecis d t = [(x, y) | x <- vars, y <- vars, x < y, not $ decided d x y] where vars = foldr f [] t f (x, (True, _)) v = x:v f (_, (False, _)) v = v decided d x y = -- Is x and y decided? u == v || any f (dist d) where u = chase x -- Find canonical representitive for x and y v = chase y f (w, z) = chase w == u && chase z == v chase = listChase (same d) -- Find canonical representive of the set of identified variables. listChase :: Eq t => [(t, t)] -> t -> t listChase d x = case lookup x d of Nothing -> x Just y -> listChase d y -- Ensure first var in pair is in v. orientDecis :: Set Id -> [(Id, Id)] -> [(Id, Id)] orientDecis v undecided = map f undecided where f (x, y) | S.notMember x v = (y, x) | otherwise = (x, y) -- Modify t by replacing x by y. identify :: Id -> Id -> [Maplet] -> [Maplet] identify x y t = case lookup x t of Nothing -> error ("Algebra.identify: bad lookup of " ++ show x ++ " in " ++ show t) Just (_, c) -> filter f (map g t) where f (z, (_, c)) = z /= x && c /= 0 g m@(z, (be, d)) | z == y = (z, (be, c + d)) | otherwise = m -- Solve when variables of sort expr are on LHS. This involves -- solving using the group axioms. The algorithm for matching in the -- group without added constant symbols is the same as the one for -- unification with constant symbols. -- -- For this description, additive notation is used for the group. To -- show sums, we write -- -- sum[i] c[i]*x[i] for c[0]*x[0] + c[1]*x[1] + ... + c[n-1]*x[n-1]. -- -- The unification problem is to solve -- -- sum[i] c[i]*x[i] = sum[j] d[j]*y[j] -- -- where x[i] is a variable and y[j] is a constant symbol. -- -- The algorithm used to find solutions is described in Vol. 2 of The -- Art of Computer Programming / Seminumerical Alorithms, 2nd Ed., -- 1981, by Donald E. Knuth, pg. 327. -- -- The algorithm's initial values are the linear equation (c,d) and an -- empty substitution s. -- -- 1. Let c[i] be the smallest non-zero coefficient in absolute value. -- -- 2. If c[i] < 0, multiply c and d by -1 and goto step 1. -- -- 3. If c[i] = 1, a general solution of the following form has been -- found: -- -- x[i] = sum[j] -c'[j]*x[j] + d[k] for all k -- -- where c' is c with c'[i] = 0. Use the equation to eliminate x[i] -- from the range of the current substitution s. If variable x[i] is -- in the original equation, add the mapping to substitution s. -- -- 4. If c[i] divides every coefficient in c, -- -- * if c[i] divides every constant in d, divide c and d by c[i] -- and goto step 3, -- -- * otherwise fail because there is no solution. In this case -- expn vars must be identified. -- -- 5. Otherwise, eliminate x[i] as above in favor of freshly created -- variable x[n], where n is the length of c. -- -- x[n] = sum[j] (c[j] div c[i] * x[j]) -- -- Goto step 1 and solve the equation: -- -- c[i]*x[n] + sum[j] (c[j] mod c[i])*x[j] = d[k] for all k solve :: [Maplet] -> [Maplet] -> Set Id -> Gen -> IdMap -> Decision Id -> [(Set Id, Gen, IdMap)] solve t0 t1 v g r d = let (x, ci, i) = smallest t0 in -- ci is the smallest coefficient, case compare ci 0 of -- x is its variable, i its position GT -> agSolve x ci i t0 t1 v g r d LT -> agSolve x (-ci) i (mInverse t0) (mInverse t1) v g r d -- Step 2 EQ -> error "Algebra.solve: zero coefficient found" -- Find the factor with smallest coefficient in absolute value. -- Returns the variable, the coefficient, and the position within the -- list. smallest :: [Maplet] -> (Id, Int, Int) smallest [] = error "Algebra.smallest given an empty list" smallest t = loop (Id (0, "x")) 0 0 0 0 t where loop v ci i _ _ [] = (v, ci, i) loop v ci i a j ((x, (_, c)):t) = if a < abs c then loop x c j (abs c) (j + 1) t else loop v ci i a (j + 1) t -- The group axioms are abbreviated by AG. agSolve :: Id -> Int -> Int -> [Maplet] -> [Maplet] -> Set Id -> Gen -> IdMap -> Decision Id -> [(Set Id, Gen, IdMap)] agSolve x 1 i t0 t1 v g r _ = -- Solve for x and return answer return (S.delete x v, g, eliminate x t r) -- Step 3 where t = G $ group (t1 ++ (mInverse (omit i t0))) agSolve x ci i t0 t1 v g r d | divisible ci t0 = -- Step 4 if divisible ci t1 then -- Solution found agSolve x 1 i (divide ci t0) (divide ci t1) v g r d else -- No possible solution without identifying variables identSolve x ci i t0 t1 v g r d | otherwise = -- Step 5, eliminate x in favor of x' solve t0' t1 (S.insert x' $ S.delete x v) g' r' d where (g', x') = cloneId g x t = G $ group ((x', (False, 1)) : mInverse (divide ci (omit i t0))) r' = eliminate x t r t0' = (x', (False, ci)) : modulo ci (omit i t0) eliminate :: Id -> Term -> IdMap -> IdMap eliminate x t r = M.map (idSubst (M.singleton x t)) r omit :: Int -> [a] -> [a] omit 0 (_:l) = l omit n _ | n < 0 = error "Algebra.omit: negative number given to omit" omit n (_:l) = omit (n - 1) l omit _ [] = error "Algebra.omit: number given to omit too large" divisible :: Int -> [Maplet] -> Bool divisible ci t = all (\(_, (_, c)) -> mod c ci == 0) t divide :: Int -> [Maplet] -> [Maplet] divide ci t = map (mMapCoef $ flip div ci) t modulo :: Int -> [Maplet] -> [Maplet] modulo ci t = [(x, (be, c')) | (x, (be, c)) <- t, let c' = mod c ci, c' /= 0] -- Explore two choices as to whether to identify a pair of variables. identSolve :: Id -> Int -> Int -> [Maplet] -> [Maplet] -> Set Id -> Gen -> IdMap -> Decision Id -> [(Set Id, Gen, IdMap)] identSolve z ci i t0 t1 v g r d = case orientDecis v $ nextDecis d t1 of [] -> [] ((x, y):_) -> distinct ++ identified where distinct = identSolve z ci i t0 t1 v g r neq neq = d {dist = (x, y):(y, x):dist d} -- eliminate x identified = agSolve z ci i t0 t1' v' g r' d' t1' = identify x y t1 -- Equate x y in t1 v' = S.delete x v -- Eliminate x in v r' = eliminate x y' r -- And in r y' = groupVar True y d' = d {same = (x, y):same d} -- Does every varible in ts not occur in the domain of e? -- Trivial bindings in e are ignored. identityEnvFor :: GenEnv -> [Term] -> [GenEnv] identityEnvFor ge ts = let env@(_, Env (_, r)) = nonTrivialEnv ge in if all (allId $ flip S.notMember $ M.keysSet r) ts then [env] else [] allId :: (Id -> Bool) -> Term -> Bool allId f (I x) = f x allId _ (C _) = True allId f (F _ u) = all (allId f) u allId f (G t) = all f (M.keys t) -- Eliminate all trivial bindings so that an environment can be used -- as a substitution. nonTrivialEnv :: GenEnv -> GenEnv nonTrivialEnv (g, Env (v, r)) = (g, Env (v, M.filterWithKey nonTrivialBinding r)) {-- nonTrivialEnv (g, Env (v, r)) = nonGroupEnv (M.assocs r) M.empty [] where nonGroupEnv [] env grp = groupEnv g v env grp grp nonGroupEnv ((x, I y):r) env grp | x == y = nonGroupEnv r env grp nonGroupEnv ((x, G y):r) env grp | isGroupVar y && getGroupVar y == x = nonGroupEnv r env grp | otherwise = nonGroupEnv r env ((x, y):grp) nonGroupEnv ((x, y):r) env grp = nonGroupEnv r (M.insert x y env) grp groupEnv :: Gen -> Set Id -> IdMap -> [(Id, Group)] -> [(Id, Group)] -> GenEnv groupEnv g v env grp [] = (g, Env (v, foldl (\env (x, y) -> M.insert x (G y) env) env grp)) groupEnv g v env grp ((x, t):map) | M.lookup x t /= Just 1 = groupEnv g v env grp map | otherwise = let (t0, t1) = partition M.empty (mul t (M.singleton x (-1))) v in case matchGroup (group t0) (group t1) S.empty g of Nothing -> groupEnv g v env grp map Just (v', g', subst) -> let grp' = L.delete (x, t) grp grp'' = L.map (\(x, t) -> (x, groupSubst subst t)) grp' in groupEnv g' (S.union v' v) env grp'' grp'' notGroupVarMap :: Id -> Group -> Bool notGroupVarMap x grp = case M.lookup x t of Nothing -> True Just (_, (_, c)) -> c /= 1 -} substitution :: Env -> Subst substitution (Env (_, r)) = Subst $ M.filterWithKey nonTrivialBinding r -- Add type information to an environment, and return it as a list of -- associations. reify :: [Term] -> Env -> [(Term, Term)] reify domain (Env (_, env)) = map (loop domain) $ M.assocs env where loop [] (x, _) = error $ "Algebra.reify: variable missing from domain " ++ idName x ++ " = " ++ show x ++ "\n" ++ show domain ++ "\n" ++ show env loop (I x : _) (y, t) | x == y = (I x, t) loop (F Text [I x] : _) (y, t) | x == y = (F Text [I x], F Text [t]) loop (F Data [I x] : _) (y, t) | x == y = (F Data [I x], F Data [t]) loop (F Name [I x] : _) (y, t) | x == y = (F Name [I x], F Name [t]) loop (F Skey [I x] : _) (y, t) | x == y = (F Skey [I x], F Skey [t]) loop (F Akey [I x] : _) (y, t) | x == y = (F Akey [I x], F Akey [t]) loop (F Base [I x] : _) (y, t) | x == y = (F Base [I x], F Base [t]) loop (G x : _) (y, G t) | isGroupVar x && getGroupVar x == y = (G x, G t) loop (_ : domain) pair = loop domain pair -- Ensure the range of an environment contains only variables and that -- the environment is injective. matchRenaming :: GenEnv -> Bool matchRenaming (_, Env (_, e)) = loop S.empty $ M.elems e where loop _ [] = True loop s (I x:e) = S.notMember x s && loop (S.insert x s) e loop s (G y:e) | isGroupVar y = let x = getGroupVar y in S.notMember x s && loop (S.insert x s) e loop _ _ = False {-- -- Ensure the range of an environment contains only variables and that -- the environment is injective. matchRenaming :: GenEnv -> Bool matchRenaming (gen, Env (v, e)) = nonGrp S.empty (M.elems e) && groupMatchRenaming v gen (M.foldrWithKey grp M.empty e) where nonGrp _ [] = True nonGrp s (I x:e) = S.notMember x s && nonGrp (S.insert x s) e nonGrp s (G _:e) = nonGrp s e -- Check group bindings elsewhere nonGrp _ _ = False grp x (G t) map = M.insert x t map grp _ _ map = map -- Get rid of non-group bindings groupMatchRenaming :: Set Id -> Gen -> Map Id Group -> Bool groupMatchRenaming v gen map = loop S.empty $ M.elems map where loop _ [] = True loop s (t:ge) | M.null t = False | isGroupVar t = let x = getGroupVar t in S.notMember x s && loop (S.insert x s) ge | otherwise = any (groupMatchElim v gen map t) (M.assocs t) groupMatchElim :: Set Id -> Gen -> Map Id Group -> Group -> Maplet -> Bool groupMatchElim v gen ge t (x, (be, 1)) = let (t0, t1) = partition M.empty (mul t (mlGrp (x, (be, -1)))) v in any f (matchGroup (group t0) (group t1) S.empty gen) where f (v', gen', subst) = groupMatchRenaming (S.union v' v) gen' $ M.map (groupSubst subst) ge groupMatchElim _ _ _ _ _ = False -} instance C.Env Term Gen Subst Env where emptyEnv = emptyEnv instantiate = instantiate match = match identityEnvFor = identityEnvFor substitution = substitution reify = reify matchRenaming = matchRenaming -- Term specific loading functions loadVars :: Monad m => Gen -> [SExpr Pos] -> m (Gen, [Term]) loadVars gen sexprs = do pairs <- mapM loadVarPair sexprs (g, vars) <- foldM loadVar (gen, []) (concat pairs) return (g, reverse vars) loadVarPair :: Monad m => SExpr Pos -> m [(SExpr Pos, SExpr Pos)] loadVarPair (L _ (x:xs)) = let (t:vs) = reverse (x:xs) in return [(v,t) | v <- reverse vs] loadVarPair x = fail (shows (annotation x) "Bad variable declaration") loadVar :: Monad m => (Gen, [Term]) -> (SExpr Pos, SExpr Pos) -> m (Gen, [Term]) loadVar (gen, vars) (S pos name, S pos' sort) = case loadLookup pos vars name of Right _ -> fail (shows pos "Duplicate variable declaration for " ++ name) Left _ -> do let (gen', x) = freshId gen name p <- mkVar x return (gen', p : vars) where mkVar x = let t = I x in case sort of "mesg" -> return t "text" -> return $ F Text [t] "data" -> return $ F Data [t] "name" -> return $ F Name [t] "skey" -> return $ F Skey [t] "akey" -> return $ F Akey [t] "base" -> return $ F Base [t] "expr" -> return $ groupVar False x "expn" -> return $ groupVar True x _ -> fail (shows pos' "Sort " ++ sort ++ " not recognized") loadVar _ (x,_) = fail (shows (annotation x) "Bad variable syntax") loadLookup :: Pos -> [Term] -> String -> Either String Term loadLookup pos [] name = Left (shows pos $ "Identifier " ++ name ++ " unknown") loadLookup pos (t : u) name = let name' = idName (varId t) in if name' == name then Right t else loadLookup pos u name loadLookupName :: Monad m => Pos -> [Term] -> String -> m Term loadLookupName pos vars name = either fail f (loadLookup pos vars name) where f t@(F Name [I _]) = return t f _ = fail (shows pos $ "Expecting " ++ name ++ " to be a name") loadLookupAkey :: Monad m => Pos -> [Term] -> String -> m Term loadLookupAkey pos vars name = either fail f (loadLookup pos vars name) where f t@(F Akey [I _]) = return t f _ = fail (shows pos $ "Expecting " ++ name ++ " to be an akey") -- Load term and check that it is well-formed. loadTerm :: Monad m => [Term] -> SExpr Pos -> m Term loadTerm vars (S pos s) = either fail return (loadLookup pos vars s) loadTerm _ (Q _ t) = return (C t) loadTerm vars (L pos (S _ s : l)) = case lookup s loadDispatch of Nothing -> fail (shows pos "Keyword " ++ s ++ " unknown") Just f -> f pos vars l loadTerm _ x = fail (shows (annotation x) "Malformed term") type LoadFunction m = Pos -> [Term] -> [SExpr Pos] -> m Term loadDispatch :: Monad m => [(String, LoadFunction m)] loadDispatch = [("pubk", loadPubk) ,("privk", loadPrivk) ,("invk", loadInvk) ,("ltk", loadLtk) ,("gen", loadGen) ,("exp", loadExp) ,("one", loadOne) ,("rec", loadRec) ,("mul", loadMul) ,("cat", loadCat) ,("enc", loadEnc) ,("hash", loadHash) ] -- Atom constructors: pubk privk invk ltk loadPubk :: Monad m => LoadFunction m loadPubk _ vars [S pos s] = do t <- loadLookupName pos vars s return $ F Akey [F Pubk [I $ varId t]] loadPubk _ vars [Q _ c, S pos s] = do t <- loadLookupName pos vars s return $ F Akey [F Pubk [C c, I $ varId t]] loadPubk pos _ _ = fail (shows pos "Malformed pubk") loadPrivk :: Monad m => LoadFunction m loadPrivk _ vars [S pos s] = do t <- loadLookupName pos vars s return $ F Akey [F Invk [F Pubk [I $ varId t]]] loadPrivk _ vars [Q _ c, S pos s] = do t <- loadLookupName pos vars s return $ F Akey [F Invk [F Pubk [C c, I $ varId t]]] loadPrivk pos _ _ = fail (shows pos "Malformed privk") loadInvk :: Monad m => LoadFunction m loadInvk _ vars [S pos s] = do t <- loadLookupAkey pos vars s return $ F Akey [F Invk [I $ varId t]] loadInvk pos _ _ = fail (shows pos "Malformed invk") loadLtk :: Monad m => LoadFunction m loadLtk _ vars [S pos s, S pos' s'] = do t <- loadLookupName pos vars s t' <- loadLookupName pos' vars s' return $ F Skey [F Ltk [I $ varId t, I $ varId t']] loadLtk pos _ _ = fail (shows pos "Malformed ltk") -- Base and exponents loadGen :: Monad m => LoadFunction m loadGen _ _ [] = return $ F Base [F Genr []] loadGen pos _ _ = fail (shows pos "Malformed gen") loadExp :: Monad m => LoadFunction m loadExp _ vars [x, x'] = do t <- loadBase vars x t' <- loadExpr vars x' return $ F Base [idSubst emptyIdMap $ F Exp [t, G t']] loadExp pos _ _ = fail (shows pos "Malformed exp") loadBase :: Monad m => [Term] -> SExpr Pos -> m Term loadBase vars x = do t <- loadTerm vars x case t of F Base [t] -> return t _ -> fail (shows (annotation x) "Malformed base") loadExpr :: Monad m => [Term] -> SExpr Pos -> m Group loadExpr vars x = do t <- loadTerm vars x case t of G t -> return t _ -> fail (shows (annotation x) "Malformed expr") loadOne :: Monad m => LoadFunction m loadOne _ _ [] = return $ G M.empty loadOne pos _ _ = fail (shows pos "Malformed one") loadRec :: Monad m => LoadFunction m loadRec _ vars [x] = do t <- loadExpr vars x return $ G $ invert t loadRec pos _ _ = fail (shows pos "Malformed rec") loadMul :: Monad m => LoadFunction m loadMul _ vars xs = do t <- foldM f M.empty xs return $ G t where f acc x = do t <- loadExpr vars x return $ mul t acc -- Term constructors: cat enc loadCat :: Monad m => LoadFunction m loadCat _ vars (l : ls) = do ts <- mapM (loadTerm vars) (l : ls) return $ foldr1 (\a b -> F Cat [a, b]) ts loadCat pos _ _ = fail (shows pos "Malformed cat") loadEnc :: Monad m => LoadFunction m loadEnc pos vars (l : l' : ls) = do let (butLast, last) = splitLast l (l' : ls) t <- loadCat pos vars butLast t' <- loadTerm vars last return $ F Enc [t, t'] loadEnc pos _ _ = fail (shows pos "Malformed enc") splitLast :: a -> [a] -> ([a], a) splitLast x xs = loop [] x xs where loop z x [] = (reverse z, x) loop z x (y : ys) = loop (x : z) y ys loadHash :: Monad m => LoadFunction m loadHash _ vars (l : ls) = do ts <- mapM (loadTerm vars) (l : ls) return $ F Hash [foldr1 (\a b -> F Cat [a, b]) ts] loadHash pos _ _ = fail (shows pos "Malformed hash") -- Term specific displaying functions newtype Context = Context [(Id, String)] deriving Show displayVars :: Context -> [Term] -> [SExpr ()] displayVars _ [] = [] displayVars ctx vars = let (v,t):pairs = map (displayVar ctx) vars in loop t [v] pairs where loop t vs [] = [L () (reverse (t:vs))] loop t vs ((v',t'):xs) | t == t' = loop t (v':vs) xs | otherwise = L () (reverse (t:vs)):loop t' [v'] xs displayVar :: Context -> Term -> (SExpr (), SExpr ()) displayVar ctx (I x) = displaySortId "mesg" ctx x displayVar ctx (F Text [I x]) = displaySortId "text" ctx x displayVar ctx (F Data [I x]) = displaySortId "data" ctx x displayVar ctx (F Name [I x]) = displaySortId "name" ctx x displayVar ctx (F Skey [I x]) = displaySortId "skey" ctx x displayVar ctx (F Akey [I x]) = displaySortId "akey" ctx x displayVar ctx (F Base [I x]) = displaySortId "base" ctx x displayVar ctx t@(G x) | isBasisVar x = displaySortId "expn" ctx (varId t) | isGroupVar x = displaySortId "expr" ctx (varId t) displayVar _ _ = error "Algebra.displayVar: term not a variable with its sort" displaySortId :: String -> Context -> Id -> (SExpr (), SExpr ()) displaySortId sort ctx x = (displayId ctx x, S () sort) displayId :: Context -> Id -> SExpr () displayId (Context ctx) x = case lookup x ctx of Nothing -> let msg = idName x ++ " in a display context" in error $ "Algebra.displayId: Cannot find variable " ++ msg Just name -> S () name displayTerm :: Context -> Term -> SExpr () displayTerm ctx (I x) = displayId ctx x displayTerm ctx (F Text [I x]) = displayId ctx x displayTerm ctx (F Data [I x]) = displayId ctx x displayTerm ctx (F Name [I x]) = displayId ctx x displayTerm ctx (F Skey [I x]) = displayId ctx x displayTerm ctx (F Skey [F Ltk [I x, I y]]) = L () [S () "ltk", displayId ctx x, displayId ctx y] displayTerm ctx (F Akey [t]) = case t of I x -> displayId ctx x F Invk [I x] -> L () [S () "invk", displayId ctx x] F Pubk [I x] -> L () [S () "pubk", displayId ctx x] F Pubk [C c, I x] -> L () [S () "pubk", Q () c, displayId ctx x] F Invk [F Pubk [I x]] -> L () [S () "privk", displayId ctx x] F Invk [F Pubk [C c, I x]] -> L () [S () "privk", Q () c, displayId ctx x] _ -> error ("Algebra.displayAkey: Bad term " ++ show t) displayTerm ctx (F Base [t]) = displayBase t where displayBase (I x) = displayId ctx x displayBase (F Genr []) = L () [S () "gen"] displayBase (F Exp [t0, G t1]) = L () [S () "exp", displayBase t0, displayTerm ctx (G t1)] displayBase t = error ("Algebra.displayBase: Bad term " ++ show t) displayTerm ctx (G t) = displayExpr t where displayExpr t | M.null t = L () [S () "one"] | otherwise = case factors t of [f] -> displayFactor f fs -> L () (S () "mul" : map displayFactor fs) displayFactor (x, n) | n >= 0 = displayId ctx x | otherwise = L () [S () "rec", displayId ctx x] displayTerm _ (C t) = Q () t displayTerm ctx (F Cat [t0, t1]) = L () (S () "cat" : displayTerm ctx t0 : displayList ctx t1) displayTerm ctx (F Enc [t0, t1]) = L () (S () "enc" : displayEnc ctx t0 t1) displayTerm ctx (F Hash [t]) = L () (S () "hash" : displayList ctx t) displayTerm _ t = error ("Algebra.displayTerm: Bad term " ++ show t) displayList :: Context -> Term -> [SExpr ()] displayList ctx (F Cat [t0, t1]) = displayTerm ctx t0 : displayList ctx t1 displayList ctx t = [displayTerm ctx t] displayEnc :: Context -> Term -> Term -> [SExpr ()] displayEnc ctx (F Cat [t0, t1]) t = displayTerm ctx t0 : displayEnc ctx t1 t displayEnc ctx t0 t1 = [displayTerm ctx t0, displayTerm ctx t1] displayEnv :: Context -> Context -> Env -> [SExpr ()] displayEnv ctx ctx' (Env (_, r)) = map (\(x, t) -> L () [displayTerm ctx x, displayTerm ctx' t]) r' where r' = map (\(x, t) -> (I x, inferSort t)) $ M.assocs r factors :: Group -> [(Id, Int)] factors t = do (x, (_, n)) <- M.assocs t case n >= 0 of True -> replicate n (x, 1) False -> replicate (negate n) (x, -1) -- displaySubst c s displays a substitution s in context c, where some -- variables that occur in s might not be in c. Enough sort -- inference is performed so as to allow the extension of the context. displaySubst :: Context -> Subst -> [SExpr ()] displaySubst ctx s@(Subst r) = map (\(x, t) -> L () [displayTerm ctx' x, displayTerm ctx' t]) r' where r' = map (\(x, t) -> (I x, inferSort (substitute s t))) $ M.assocs r ctx' = foldl (\ctx (x, t) -> addToContext ctx [x, t]) ctx r' inferSort :: Term -> Term inferSort t@(F Invk _) = F Akey [t] inferSort t@(F Pubk _) = F Akey [t] inferSort t@(F Ltk _) = F Skey [t] inferSort t@(F Genr _) = F Base [t] inferSort t@(F Exp _) = F Base [t] inferSort t = t emptyContext :: Context emptyContext = Context [] -- Generate names for output renaming as necessary. -- Assumes the input is a list of term that are well-formed addToContext :: Context -> [Term] -> Context addToContext ctx u = foldl (foldVars varContext) ctx u varContext :: Context -> Term -> Context varContext ctx t = let x = varId t name = rootName $ idName x in if hasId ctx x then ctx else if hasName ctx name then extendContext ctx x (genName ctx name) else extendContext ctx x name hasId :: Context -> Id -> Bool hasId (Context ctx) id = maybe False (const True) (lookup id ctx) hasName :: Context -> String -> Bool hasName (Context ctx) name = maybe False (const True) (L.find ((name ==) . snd) ctx) extendContext :: Context -> Id -> String -> Context extendContext (Context ctx) x name = Context $ (x, name) : ctx genName :: Context -> String -> String genName ctx name = loop 0 where root = '-' : reverse name loop :: Int -> String loop n = let name' = revapp root (show n) in if hasName ctx name' then loop (n + 1) else name' revapp [] s = s revapp (c : cs) s = revapp cs (c : s) rootName :: String -> String rootName name = noHyphen 0 name where noHyphen _ [] = name noHyphen i (c : s) | c == '-' = hyphen i (i + 1) s | otherwise = noHyphen (i + 1) s hyphen i _ [] = rootName $ take i name hyphen i j (c : s) | isDigit c = hyphen i (j + 1) s | otherwise = noHyphen j (c : s) instance C.Context Term Gen Subst Env Context where emptyContext = emptyContext addToContext = addToContext displayVars = displayVars displayTerm = displayTerm displayEnv = displayEnv displaySubst = displaySubst instance C.Algebra Term Place Gen Subst Env Context