diff --git a/osek-verification.cabal b/osek-verification.cabal
index 0330434eda7a8951124bf2dae4a98e9382264ce3..243f54745d1e1cd79ad8fd912c0cc983b5d42794 100644
--- a/osek-verification.cabal
+++ b/osek-verification.cabal
@@ -24,6 +24,7 @@ library
                      , Search.States2Dot
                      , Search.Search2
                      , Search.Types
+                     , Search.EpsilonElimination
                      , Protocol
   hs-source-dirs:      src
   build-depends:       base >=4.9 && <4.10
@@ -60,6 +61,7 @@ test-suite spec
                      , CFG.CSpec
                      , CFG.SanitizeSpec
                      , ProtocolSpec
+                     , Search.EpsilonEliminationSpec
   build-depends:       base >= 4.8 && <4.10
                      , osek-verification
                      , hspec >= 2.2.3 && <2.3
diff --git a/src/Search/EpsilonElimination.hs b/src/Search/EpsilonElimination.hs
new file mode 100644
index 0000000000000000000000000000000000000000..84ef7c6ebcdfb1d0925fd4a0daff967483f0cf95
--- /dev/null
+++ b/src/Search/EpsilonElimination.hs
@@ -0,0 +1,97 @@
+module Search.EpsilonElimination
+  ( eliminateEpsilons
+  -- * Internal functions exported for easier testing
+  , epsilonClosure
+  , addTransitiveEdges
+  , removeEpsilonEdges
+  , removeUnreachableNodes
+  ) where
+
+import           Data.Map (Map)
+import qualified Data.Map as M
+import           Data.Maybe
+import           Data.Set (Set)
+import qualified Data.Set as S
+
+import           Search.Types
+
+eliminateEpsilons :: Vertex -> Graph -> Graph
+eliminateEpsilons start graph =
+  let
+    closureMap = epsilonClosure graph
+    transitiveClosure = addTransitiveEdges graph closureMap
+    withoutEpsilons = removeEpsilonEdges transitiveClosure
+    finalGraph = removeUnreachableNodes start withoutEpsilons
+  in
+    finalGraph
+
+type Closure = Set Vertex
+
+-- | Compute the epsilon closure for all vertices.
+--
+-- The epsilon closure for a vertex v is defined as the set of vertices that are
+-- reachable from v by following only ɛ transitions.
+epsilonClosure :: Graph -> Map Vertex Closure
+epsilonClosure graph = fixEq (epsilonClosureStep graph) identityMap
+  where
+    identityMap = foldr (\x m -> M.insert x (S.singleton x) m) M.empty (M.keys graph)
+
+-- | One step to compute the epsilon closure.
+--
+-- This has to be iterated until the result doesn't change (in respect to '==')
+epsilonClosureStep :: Graph -> Map Vertex Closure -> Map Vertex Closure
+epsilonClosureStep graph closureMap =
+  foldr reduceVertex closureMap (M.keys graph)
+
+  where
+    -- Add the closures of all epsilon successors to our own closure
+    reduceVertex vert cloMap = S.foldr (addClosure vert) cloMap (epsilonSuccessors graph vert)
+    -- Add the closure from the second vertex to the closure of the first
+    addClosure from to cloMap = M.insertWith S.union from (cloMap M.! to) cloMap
+
+-- | Return all direct successors of the vertex reachable over an ɛ edge.
+epsilonSuccessors :: Graph -> Vertex -> Set Vertex
+epsilonSuccessors graph vert =
+  let
+    edges = graph M.! vert
+    epsilonEdges = S.filter isEpsilon edges
+  in
+    S.map edgeTo epsilonEdges
+
+
+-- | For each vertex, add all outgoing vertices from it's ɛ-closure to the
+-- vertex itself.
+addTransitiveEdges :: Graph -> Map Vertex Closure -> Graph
+addTransitiveEdges graph closureMap = M.mapWithKey addEdges graph
+  where
+    addEdges vertex edges = S.unions (edges : closureEdges vertex)
+    closureEdges vertex = map (\w -> graph M.! w) $ S.toList (closureMap M.! vertex)
+
+-- | Delete all epsilon transitions from the graph
+removeEpsilonEdges :: Graph -> Graph
+removeEpsilonEdges = M.map (S.filter (not . isEpsilon))
+
+removeUnreachableNodes :: Vertex -> Graph -> Graph
+removeUnreachableNodes start graph =
+  let
+    reachable = getReachable S.empty start
+    unreachable = M.keysSet graph S.\\ reachable
+  in
+    S.foldr M.delete graph unreachable
+
+  where
+    getReachable visited node =
+      S.foldr (\succ visited' -> getReachable visited' succ) (S.insert node visited) (successors node)
+    successors node = S.map edgeTo $ graph M.! node
+
+-- | Return true iff the edge is an ɛ-transition
+isEpsilon :: Edge -> Bool
+isEpsilon = isNothing . edgeSyscall
+
+fixEq :: Eq a => (a -> a) -> a -> a
+fixEq f a =
+  let result = f a
+  in
+    if result == a
+      then result
+      else fixEq f result
diff --git a/test/Search/EpsilonEliminationSpec.hs b/test/Search/EpsilonEliminationSpec.hs
new file mode 100644
index 0000000000000000000000000000000000000000..25f7b324dec5b8956988ba84bffbc3f71c4d0d09
--- /dev/null
+++ b/test/Search/EpsilonEliminationSpec.hs
@@ -0,0 +1,162 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Search.EpsilonEliminationSpec (spec) where
+
+import           Test.Hspec
+
+import           Control.Arrow
+
+import           Data.Map (Map)
+import qualified Data.Map as M
+import           Data.Set (Set)
+import qualified Data.Set as S
+import           Data.Text (Text)
+
+import           Search.EpsilonElimination
+import           Search.Types
+
+spec :: Spec
+spec = do
+  describe "epsilonClosure" epsilonClosureSpec
+  describe "eliminateEpsilons" eliminateEpsilonsSpec
+  describe "addTransitiveEdges" addTransitiveEdgesSpec
+  describe "removeEpsilonEdges" removeEpsilonEdgesSpec
+  describe "removeUnreachableNodes" removeUnreachableNodesSpec
+
+epsilonClosureSpec :: Spec
+epsilonClosureSpec = do
+  it "Returns the identity map for a graph without epsilons" $
+    let
+      graph = mkGraph [ ("A", edges [(Just "a", "B"), (Just "c", "C")])
+                      , ("B", edges [(Just "b", "C")])
+                      , ("C", [])
+                      ]
+      closureMap = mkClosure [ ("A", ["A"])
+                             , ("B", ["B"])
+                             , ("C", ["C"])
+                             ]
+    in epsilonClosure graph `shouldBe` closureMap
+
+  it "returns the correct closure in the presence of epsilons transitions" $
+    let
+      graph = mkGraph [ ("A", edges [(Nothing, "B"), (Just "c", "C")])
+                      , ("B", edges [(Nothing, "C")])
+                      , ("C", [])
+                      ]
+      closureMap = mkClosure [ ("A", ["A", "B", "C"])
+                             , ("B", ["B", "C"])
+                             , ("C", ["C"])
+                             ]
+    in epsilonClosure graph `shouldBe` closureMap
+
+
+eliminateEpsilonsSpec :: Spec
+eliminateEpsilonsSpec = do
+  it "Does nothing without epsilon transitions" $
+    let graph = mkGraph [ ("A", edges [(Just "a", "B"), (Just "c", "C")])
+                        , ("B", edges [(Just "b", "C")])
+                        , ("C", [])
+                        ]
+    in eliminateEpsilons (mkVertex "A") graph `shouldBe` graph
+
+  it "Correctly transforms a graph in the presence of epsilon transitions" $
+    let
+      graph1 = mkGraph [ ("A", edges [(Nothing, "B"), (Just "c", "C")])
+                       , ("B", edges [(Just "b", "C")])
+                       , ("C", [])
+                       ]
+      graph2 = mkGraph [ ("A", edges [(Just "b", "C"), (Just "c", "C")])
+                       , ("C", [])
+                       ]
+    in eliminateEpsilons (mkVertex "A") graph1 `shouldBe` graph2
+
+addTransitiveEdgesSpec :: Spec
+addTransitiveEdgesSpec = do
+  it "Does nothing without epsilon transitions" $
+    let
+      graph = mkGraph [ ("A", edges [(Just "a", "B"), (Just "c", "C")])
+                      , ("B", edges [(Just "b", "C")])
+                      , ("C", [])
+                      ]
+      closureMap = mkClosure [ ("A", ["A"])
+                             , ("B", ["B"])
+                             , ("C", ["C"])
+                             ]
+    in addTransitiveEdges graph closureMap `shouldBe` graph
+
+  it "Works in the presence of epsilons" $
+    let
+      graph1 = mkGraph [ ("A", edges [(Nothing, "B"), (Just "c", "C")])
+                       , ("B", edges [(Just "b", "C")])
+                       , ("C", [])
+                       ]
+      graph2 = mkGraph [ ("A", edges [(Nothing, "B"), (Just "b", "C"), (Just "c", "C")])
+                       , ("B", edges [(Just "b", "C")])
+                       , ("C", [])
+                       ]
+      closureMap = mkClosure [ ("A", ["A", "B"])
+                             , ("B", ["B"])
+                             , ("C", ["C"])
+                             ]
+    in
+      addTransitiveEdges graph1 closureMap `shouldBe` graph2
+
+removeEpsilonEdgesSpec :: Spec
+removeEpsilonEdgesSpec = do
+  it "does nothing without epsilon transitions" $
+    let
+      graph = mkGraph [ ("A", edges [(Just "a", "B"), (Just "c", "C")])
+                      , ("B", edges [(Just "b", "C")])
+                      , ("C", [])
+                      ]
+    in
+      removeEpsilonEdges graph `shouldBe` graph
+
+  it "removes present epsilon edges" $
+    let
+      graph1 = mkGraph [ ("A", edges [(Nothing, "B"), (Just "c", "C")])
+                       , ("B", edges [(Nothing, "C")])
+                       , ("C", [])
+                       ]
+      graph2 = mkGraph [ ("A", edges [(Just "c", "C")])
+                       , ("B", edges [])
+                       , ("C", [])
+                       ]
+    in
+      removeEpsilonEdges graph1 `shouldBe` graph2
+
+
+removeUnreachableNodesSpec :: Spec
+removeUnreachableNodesSpec = do
+  it "does nothing without unreachable nodes" $
+    let
+      graph = mkGraph [ ("A", edges [(Just "a", "B"), (Just "c", "C")])
+                      , ("B", edges [(Just "b", "C")])
+                      , ("C", [])
+                      ]
+    in
+      removeUnreachableNodes (mkVertex "A") graph `shouldBe` graph
+
+  it "removes unreachable nodes" $
+    let
+      graph1 = mkGraph [ ("A", edges [(Just "c", "C")])
+                       , ("B", edges [(Just "b", "C")])
+                       , ("C", [])
+                       ]
+      graph2 = mkGraph [ ("A", edges [(Just "c", "C")])
+                       , ("C", [])
+                       ]
+    in
+      removeUnreachableNodes (mkVertex "A") graph1 `shouldBe` graph2
+
+mkGraph :: [(Text, [Edge])] -> Graph
+mkGraph = M.fromList . map (\(v, es) -> (mkVertex v, S.fromList es))
+
+edges :: [(Maybe Text, Text)] -> [Edge]
+edges = map (\(e, v) -> Edge (mkVertex v) e)
+
+mkVertex :: Text -> Vertex
+mkVertex v = Vertex v "" ""
+
+mkClosure :: [(Text, [Text])] -> Map Vertex (Set Vertex)
+mkClosure = M.fromList . map (mkVertex *** (S.fromList . map mkVertex))