diff --git a/exercises/crypto-square/src/CryptoSquare.hs b/exercises/crypto-square/src/CryptoSquare.hs
index 49c6b055d..5b4222694 100644
--- a/exercises/crypto-square/src/CryptoSquare.hs
+++ b/exercises/crypto-square/src/CryptoSquare.hs
@@ -1,18 +1,4 @@
-module CryptoSquare
-    ( ciphertext
-    , normalizeCiphertext
-    , normalizePlaintext
-    , plaintextSegments
-    ) where
+module CryptoSquare (encode) where
 
-ciphertext :: String -> String
-ciphertext = undefined
-
-normalizeCiphertext :: String -> String
-normalizeCiphertext = undefined
-
-normalizePlaintext :: String -> String
-normalizePlaintext = undefined
-
-plaintextSegments :: String -> [String]
-plaintextSegments = undefined
+encode :: String -> String
+encode = undefined
diff --git a/exercises/crypto-square/src/Example.hs b/exercises/crypto-square/src/Example.hs
index dcd33f09c..91c175f0c 100644
--- a/exercises/crypto-square/src/Example.hs
+++ b/exercises/crypto-square/src/Example.hs
@@ -1,23 +1,15 @@
-module CryptoSquare ( normalizePlaintext
-                    , plaintextSegments
-                    , ciphertext
-                    , normalizeCiphertext ) where
+module CryptoSquare (encode) where
 
-import Data.Char (isAlphaNum, toLower)
-import Data.List (transpose)
+import Data.Char       (isAlphaNum, toLower)
+import Data.List       (transpose)
 import Data.List.Split (chunksOf)
 
-squareSize :: String -> Int
-squareSize = ceiling . (sqrt :: Double -> Double) . fromIntegral . length
-
-normalizePlaintext :: String -> String
-normalizePlaintext = map toLower . filter isAlphaNum
-
-plaintextSegments :: String -> [String]
-plaintextSegments = (squareSize >>= chunksOf) . normalizePlaintext
-
-ciphertext :: String -> String
-ciphertext = concat . transpose . plaintextSegments
-
-normalizeCiphertext :: String -> String
-normalizeCiphertext = unwords . transpose . plaintextSegments
+encode :: String -> String
+encode = unwords
+       . transpose
+       . (squareSize >>= chunksOf)
+       . map toLower
+       . filter isAlphaNum
+  where
+    squareSize :: String -> Int
+    squareSize = ceiling . (sqrt :: Double -> Double) . fromIntegral . length
diff --git a/exercises/crypto-square/test/Tests.hs b/exercises/crypto-square/test/Tests.hs
index 25f9faaf6..3d3ac7b85 100644
--- a/exercises/crypto-square/test/Tests.hs
+++ b/exercises/crypto-square/test/Tests.hs
@@ -1,93 +1,53 @@
-import Test.Hspec        (Spec, describe, it, shouldBe)
+{-# LANGUAGE RecordWildCards #-}
+
+import Data.Char         (isSpace)
+import Data.Foldable     (for_)
+import Data.Function     (on)
+import Test.Hspec        (Spec, describe, it, shouldBe, shouldMatchList)
 import Test.Hspec.Runner (configFastFail, defaultConfig, hspecWith)
 
-import CryptoSquare
-  ( ciphertext
-  , normalizeCiphertext 
-  , normalizePlaintext
-  , plaintextSegments
-  )
+import CryptoSquare (encode)
 
 main :: IO ()
 main = hspecWith defaultConfig {configFastFail = True} specs
 
 specs :: Spec
-specs = describe "crypto-square" $ do
-
-    -- Test cases adapted from `exercism/x-common/crypto-square.json`
-    -- on 2016-08-02. Some deviations exist and are noted in comments.
-
-    describe "normalizePlaintext" $ do
-
-      it "Lowercase" $
-          normalizePlaintext "Hello"
-          `shouldBe`         "hello"
-
-      it "Remove spaces" $
-          normalizePlaintext "Hi there"
-          `shouldBe`         "hithere"
-
-      it "Remove punctuation" $
-          normalizePlaintext "@1, 2%, 3 Go!"
-          `shouldBe`         "123go"
-
-    describe "plaintextSegments" $ do
-
-      it "empty plaintext results in an empty rectangle" $
-          plaintextSegments ""
-          `shouldBe`        []
-
-      it "4 character plaintext results in an 2x2 rectangle" $
-          plaintextSegments "Ab Cd"
-          `shouldBe`        [ "ab"
-                            , "cd" ]
-
-      it "9 character plaintext results in an 3x3 rectangle" $
-          plaintextSegments "This is fun!"
-          `shouldBe`        [ "thi"
-                            , "sis"
-                            , "fun" ]
-
-      it "54 character plaintext results in an 8x7 rectangle" $
-          plaintextSegments "If man was meant to stay on the ground, god would have given us roots."
-          `shouldBe`        [ "ifmanwas"
-                            , "meanttos"
-                            , "tayonthe"
-                            , "groundgo"
-                            , "dwouldha"
-                            , "vegivenu"
-                            , "sroots"  ]
-
-    describe "ciphertext" $ do
-
-    -- The function described by the reference file in `x-common`
-    -- as `encoded` is called `ciphertext` in this track.
-
-      it "empty plaintext results in an empty encode" $
-          ciphertext ""
-          `shouldBe` ""
-
-      it "Non-empty plaintext results in the combined plaintext segments" $
-          ciphertext "If man was meant to stay on the ground, god would have given us roots."
-          `shouldBe` "imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau"
-
-    describe "normalizeCiphertext" $ do
-
-    -- The function described by the reference file in `x-common`
-    -- as `ciphertext` is called `normalizeCiphertext` in this track.
-
-      it "empty plaintext results in an empty ciphertext" $
-          normalizeCiphertext ""
-          `shouldBe`          ""
-
-      it "9 character plaintext results in 3 chunks of 3 characters" $
-          normalizeCiphertext "This is fun!"
-          `shouldBe`          "tsf hiu isn"
-
-    {- In this track the encoded text chunks are not padded with spaces.
-
-      it "54 character plaintext results in 7 chunks, the last two padded with spaces" $
-          normalizeCiphertext "If man was meant to stay on the ground, god would have given us roots."
-          `shouldBe`          "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn  sseoau "
-
-    -}
+specs = describe "crypto-square" $
+          describe "encode" $ for_ cases test
+  where
+
+    test Case{..} = describe description $ do
+
+      let shouldMatchWords  = shouldBe        `on` words
+          shouldMatchString = shouldBe        `on` filter (not . isSpace)
+          shouldMatchChars  = shouldMatchList `on` filter (not . isSpace)
+
+      it "normalizes the input"    $ encode input `shouldMatchChars`  expected
+      it "reorders the characters" $ encode input `shouldMatchString` expected
+      it "groups the output"       $ encode input `shouldMatchWords`  expected
+
+-- Test cases created from scratch on 2016-10-05, diverging from `x-common`.
+
+data Case = Case { description :: String
+                 , input       :: String
+                 , expected    :: String
+                 }
+
+cases :: [Case]
+cases = [ Case { description = "perfect square, all lowercase with space"
+               , input       = "a dog"
+               , expected    = "ao dg"
+               }
+        , Case { description = "perfect rectangle, mixed case"
+               , input       = "A camel"
+               , expected    = "am ce al"
+               }
+        , Case { description = "incomplete square with punctuation"
+               , input       = "Wait, fox!"
+               , expected    = "wtx af io"
+               }
+        , Case { description = "incomplete rectangle with symbols"
+               , input       = "cat | cut -d@ -f1 | sort | uniq"
+               , expected    = "ctoi adrq tft c1u usn"
+               }
+        ]