diff --git a/Changelog.md b/Changelog.md
new file mode 100644
--- /dev/null
+++ b/Changelog.md
@@ -0,0 +1,21 @@
+# 1.1.0
+
+- First stable release. Same API as 1.0.0, but now features documentation and
+  expected type class instances for data types.
+
+- QuickCheck properties added:
+
+  1. verify (hashEncoded options password salt) password == True
+  2. hash options password salt /= password
+
+- `hash` now uses the underlying "raw" hash routines, rather than the encoded
+  routines. This was a bug in 1.0.0. Thanks to @jorgen for this fix.
+
+- `verify` added, in order to correctly verify that a password matches an 
+  encoded password.
+
+- `defaultHashOptions` are now more expensive.
+
+# 1.0.0
+
+- Initial release
diff --git a/Tests.hs b/Tests.hs
new file mode 100644
--- /dev/null
+++ b/Tests.hs
@@ -0,0 +1,84 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import Crypto.Argon2
+import Test.QuickCheck
+import Test.Tasty
+import Data.Ix
+import Test.Tasty.QuickCheck
+import qualified Crypto.Argon2.FFI as FFI
+import qualified Data.ByteString as BS
+import qualified Data.Text.Encoding as T
+import Data.Bits (shiftL)
+
+arbitraryVariant :: Gen Argon2Variant
+arbitraryVariant = arbitraryBoundedEnum
+
+-- The hard-coded constants correspond to `decode_string`, but see
+-- https://github.com/P-H-C/phc-winner-argon2/issues/77
+arbitraryHashOptions :: Gen HashOptions
+arbitraryHashOptions =
+  do p <-
+       arbitraryWithin (max (max FFI.ARGON2_MIN_LANES FFI.ARGON2_MIN_THREADS) 1)
+                       (min (min FFI.ARGON2_MAX_LANES FFI.ARGON2_MAX_THREADS) 254)
+     HashOptions <$>
+       arbitraryWithin FFI.ARGON2_MIN_TIME
+                       (min FFI.ARGON2_MAX_TIME (2 ^ 32 - 1)) <*>
+       arbitraryWithin
+         (max (max FFI.ARGON2_MIN_MEMORY (8 * p))
+              (shiftL p 3))
+         FFI.ARGON2_MAX_MEMORY <*>
+       pure p <*>
+       arbitraryVariant
+
+arbitraryWithin lower upper = arbitrary `suchThat` (inRange (lower,upper))
+
+arbitraryBytes :: Gen BS.ByteString
+arbitraryBytes = fmap BS.pack arbitrary
+
+arbitraryPassword :: Gen BS.ByteString
+arbitraryPassword = arbitraryBytes `suchThat`
+                             (\pwd ->
+                                (FFI.ARGON2_MIN_PWD_LENGTH
+                                ,FFI.ARGON2_MAX_PWD_LENGTH) `inRange`
+                                BS.length pwd)
+
+arbitrarySalt =
+  arbitraryBytes `suchThat`
+  (\salt ->
+     (FFI.ARGON2_MIN_SALT_LENGTH,FFI.ARGON2_MAX_SALT_LENGTH) `inRange`
+     BS.length salt)
+
+main :: IO ()
+main =
+  defaultMain
+    (testGroup "Properties"
+               [testProperty
+                  "Round trip"
+                  (forAll arbitraryHashOptions $
+                   \hashOptions ->
+                     forAll arbitraryPassword $
+                     \password ->
+                       forAll arbitrarySalt $
+                       \salt ->
+                         verify (hashEncoded hashOptions password salt) password)
+               ,testProperty
+                  "Unencoded hashing"
+                  (forAll arbitraryHashOptions $
+                   \hashOptions ->
+                     forAll arbitraryPassword $
+                     \password ->
+                       forAll arbitrarySalt $
+                       \salt -> hash hashOptions password salt /= password)
+               ,testProperty
+                  "defaultHashOptions"
+                  (forAll arbitraryVariant $
+                   \variant ->
+                     (forAll arbitraryPassword $
+                      \password ->
+                        forAll arbitrarySalt $
+                        \salt ->
+                          verify (hashEncoded
+                                    (defaultHashOptions {hashVariant = variant})
+                                    password
+                                    salt)
+                                 password))])
diff --git a/argon2.cabal b/argon2.cabal
--- a/argon2.cabal
+++ b/argon2.cabal
@@ -1,8 +1,5 @@
--- Initial argon2.cabal generated by cabal init.  For further 
--- documentation, see http://haskell.org/cabal/users-guide/
-
 name:                argon2
-version:             1.0.0
+version:             1.1.0
 synopsis:            Haskell bindings to libargon2 - the reference implementation of the Argon2 password-hashing function
 -- description:         
 homepage:            https://github.com/ocharles/argon2.git
@@ -13,7 +10,7 @@
 -- copyright:           
 -- category:            
 build-type:          Simple
--- extra-source-files:  
+extra-source-files:  Changelog.md
 cabal-version:       >=1.10
 
 library
@@ -29,3 +26,8 @@
              phc-winner-argon2/src/ref.c
              phc-winner-argon2/src/encoding.c
   include-dirs: phc-winner-argon2/src
+
+test-suite tests
+  type: exitcode-stdio-1.0
+  main-is: Tests.hs
+  build-depends: argon2, base >= 4.8 && <4.9, QuickCheck >= 2.0, tasty-quickcheck, tasty, bytestring, text
diff --git a/phc-winner-argon2/src/argon2.c b/phc-winner-argon2/src/argon2.c
--- a/phc-winner-argon2/src/argon2.c
+++ b/phc-winner-argon2/src/argon2.c
@@ -11,11 +11,9 @@
  * <http://creativecommons.org/publicdomain/zero/1.0/>.
  */
 
-#include <stdint.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
-#include <limits.h>
 
 #include "argon2.h"
 #include "encoding.h"
@@ -102,7 +100,9 @@
     /*},
 {ARGON2_ENCODING_FAIL, */ "Encoding failed",
     /*},
-{ARGON2_DECODING_FAIL, */ "Decoding failed", /*},*/
+{ARGON2_DECODING_FAIL, */ "Decoding failed",
+    /*},
+{ARGON2_THREAD_FAIL */ "Threading failure", /*},*/
 };
 
 
@@ -151,8 +151,11 @@
     }
 
     /* 4. Filling memory */
-    fill_memory_blocks(&instance);
+    result = fill_memory_blocks(&instance);
 
+    if (ARGON2_OK != result) {
+        return result;
+    }
     /* 5. Finalization */
     finalize(context, &instance);
 
@@ -191,9 +194,9 @@
 
     context.out = (uint8_t *)out;
     context.outlen = (uint32_t)hashlen;
-    context.pwd = (uint8_t *)pwd;
+    context.pwd = CONST_CAST(uint8_t *)pwd;
     context.pwdlen = (uint32_t)pwdlen;
-    context.salt = (uint8_t *)salt;
+    context.salt = CONST_CAST(uint8_t *)salt;
     context.saltlen = (uint32_t)saltlen;
     context.secret = NULL;
     context.secretlen = 0;
@@ -273,7 +276,7 @@
                        hash, hashlen, NULL, 0, Argon2_d);
 }
 
-int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) {
+static int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) {
     size_t i;
     uint8_t d = 0U;
 
@@ -391,7 +394,7 @@
             !!((sizeof(Argon2_ErrorMessage) / sizeof(Argon2_ErrorMessage[0])) ==
                ARGON2_ERROR_CODES_LENGTH)
     };
-    if (error_code < ARGON2_ERROR_CODES_LENGTH) {
+    if (error_code >= 0 && error_code < ARGON2_ERROR_CODES_LENGTH) {
         return Argon2_ErrorMessage[(argon2_error_codes)error_code];
     }
     return "Unknown error code.";
diff --git a/phc-winner-argon2/src/core.c b/phc-winner-argon2/src/core.c
--- a/phc-winner-argon2/src/core.c
+++ b/phc-winner-argon2/src/core.c
@@ -249,25 +249,25 @@
     return 0;
 }
 
-void fill_memory_blocks(argon2_instance_t *instance) {
+int fill_memory_blocks(argon2_instance_t *instance) {
     uint32_t r, s;
     argon2_thread_handle_t *thread = NULL;
     argon2_thread_data *thr_data = NULL;
 
     if (instance == NULL || instance->lanes == 0) {
-        return;
+        return ARGON2_THREAD_FAIL;
     }
 
     /* 1. Allocating space for threads */
     thread = calloc(instance->lanes, sizeof(argon2_thread_handle_t));
     if (thread == NULL) {
-        return;
+        return ARGON2_MEMORY_ALLOCATION_ERROR;
     }
 
     thr_data = calloc(instance->lanes, sizeof(argon2_thread_data));
     if (thr_data == NULL) {
         free(thread);
-        return;
+        return ARGON2_MEMORY_ALLOCATION_ERROR;
     }
 
     for (r = 0; r < instance->passes; ++r) {
@@ -283,10 +283,7 @@
                 if (l >= instance->threads) {
                     rc = argon2_thread_join(thread[l - instance->threads]);
                     if (rc) {
-                        printf(
-                            "ERROR; return code from pthread_join() #1 is %d\n",
-                            rc);
-                        exit(-1);
+                        return ARGON2_THREAD_FAIL;
                     }
                 }
 
@@ -302,10 +299,7 @@
                 rc = argon2_thread_create(&thread[l], &fill_segment_thr,
                                           (void *)&thr_data[l]);
                 if (rc) {
-                    printf("ERROR; return code from argon2_thread_create() is "
-                           "%d\n",
-                           rc);
-                    exit(-1);
+                    return ARGON2_THREAD_FAIL;
                 }
 
                 /* fill_segment(instance, position); */
@@ -317,9 +311,7 @@
                  ++l) {
                 rc = argon2_thread_join(thread[l]);
                 if (rc) {
-                    printf("ERROR; return code from pthread_join() is %d\n",
-                           rc);
-                    exit(-1);
+                    return ARGON2_THREAD_FAIL;
                 }
             }
         }
@@ -335,6 +327,7 @@
     if (thr_data != NULL) {
         free(thr_data);
     }
+    return ARGON2_OK;
 }
 
 int validate_inputs(const argon2_context *context) {
diff --git a/phc-winner-argon2/src/ref.c b/phc-winner-argon2/src/ref.c
--- a/phc-winner-argon2/src/ref.c
+++ b/phc-winner-argon2/src/ref.c
@@ -16,7 +16,6 @@
 #include <stdlib.h>
 
 #include "argon2.h"
-#include "core.h"
 #include "ref.h"
 
 #include "blake2/blamka-round-ref.h"
diff --git a/src/Crypto/Argon2.hs b/src/Crypto/Argon2.hs
--- a/src/Crypto/Argon2.hs
+++ b/src/Crypto/Argon2.hs
@@ -1,78 +1,185 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE ForeignFunctionInterface #-}
 {-# LANGUAGE RecordWildCards #-}
 
-module Crypto.Argon2 (hashEncoded, hash, HashOptions(..), Argon2Variant(..), defaultHashOptions) where
+{-|
 
+"Crypto.Argon2" provides bindings to the
+<https://github.com/P-H-C/phc-winner-argon2 reference implementation> of Argon2,
+the password-hashing function that won the
+<https://password-hashing.net/ Password Hashing Competition (PHC)>. 
+
+The main entry points to this module are 'hashEncoded', which produces a
+crypt-like ASCII output; and 'hash' which produces a 'BS.ByteString' (a stream
+of bytes). Argon2 is a configurable hash function, and can be configured by
+supplying a particular set of 'HashOptions' - 'defaultHashOptions' should provide
+a good starting point. See 'HashOptions' for more documentation on the particular
+parameters that can be adjusted.
+
+For access directly to the C interface, see "Crypto.Argon2.FFI".
+
+-}
+module Crypto.Argon2
+       ( -- * Computing hashes
+         hashEncoded, hash,
+         -- * Verification
+         verify,
+         -- * Configuring hashing
+         HashOptions(..), Argon2Variant(..), defaultHashOptions,
+         -- * Exceptions
+         Argon2Exception(..))
+       where
+
+import GHC.Generics (Generic)
 import Control.Exception
 import Data.Typeable
 import Foreign
 import Foreign.C
 import Numeric.Natural
 import System.IO.Unsafe (unsafePerformIO)
+import qualified Crypto.Argon2.FFI as FFI
 import qualified Data.ByteString as BS
-import qualified Data.Text.Encoding as T
 import qualified Data.Text as T
-import qualified Crypto.Argon2.FFI as FFI
+import qualified Data.Text.Encoding as T
 
-data Argon2Variant = Argon2i | Argon2d
+-- | Which variant of Argon2 to use. You should choose the variant that is most
+-- applicable to your intention to hash inputs.
+data Argon2Variant
+  = Argon2i  -- ^ Argon2i uses data-independent memory access, which is preferred
+             -- for password hashing and password-based key derivation. Argon2i
+             -- is slower as it makes more passes over the memory to protect from
+             -- tradeoff attacks.
+  | Argon2d -- ^ Argon2d is faster and uses data-depending memory access, which
+            -- makes it suitable for cryptocurrencies and applications with no
+            -- threats from side-channel timing attacks.
+  deriving (Eq,Ord,Read,Show,Bounded,Generic,Typeable,Enum)
 
+-- | Parameters that can be adjusted to change the runtime performance of the
+-- hashing.
 data HashOptions =
-  HashOptions {hashIterations :: !Word32
-              ,hashMemory :: !Word32
-              ,hashParallelism :: !Word32
-              ,hashVariant :: !Argon2Variant}
+  HashOptions {hashIterations :: !Word32 -- ^ The time cost, which defines the amount of computation realized and therefore the execution time, given in number of iterations.
+                                         --
+                                         -- 'FFI.ARGON2_MIN_TIME' <= 'hashIterations' <= 'FFI.ARGON2_MAX_TIME'
+              ,hashMemory :: !Word32 -- ^ The memory cost, which defines the memory usage, given in kibibytes.
+                                     --
+                                     -- max 'FFI.ARGON2_MIN_MEMORY' (8 * 'hashParallelism') <= 'hashMemory' <= 'FFI.ARGON2_MAX_MEMORY'
+              ,hashParallelism :: !Word32 -- ^ A parallelism degree, which defines the number of parallel threads.
+                                          --
+                                          -- 'FFI.ARGON2_MIN_LANES' <= 'hashParallelism' <= 'FFI.ARGON2_MAX_LANES' && 'FFI.ARGON_MIN_THREADS' <= 'hashParallelism' <= 'FFI.ARGON2_MAX_THREADS'
+              ,hashVariant :: !Argon2Variant -- ^ Which version of Argon2 to use.
+              }
+  deriving (Eq,Ord,Read,Show,Bounded,Generic,Typeable)
 
+-- | A set of default 'HashOptions', taken from the @argon2@ executable.
+--
+-- @
+-- 'defaultHashOptions' :: 'HashOptions'
+-- 'defaultHashOptions' =
+--   'HashOptions' {'hashIterations' = 1
+--               ,'hashMemory' = 2 ^ 17
+--               ,'hashParallelism' = 4
+--               ,'hashVariant' = 'Argon2i'}
+-- @
 defaultHashOptions :: HashOptions
 defaultHashOptions =
-  HashOptions {hashIterations = 3
-              ,hashMemory = 2 ^ 12
-              ,hashParallelism = 1
+  HashOptions {hashIterations = 1
+              ,hashMemory = 2 ^ 17
+              ,hashParallelism = 4
               ,hashVariant = Argon2i}
 
-hashEncoded :: HashOptions -- ^ Options pertaining to how expensive the hash is to calculate
-            -> BS.ByteString -- ^ The password to hash
-            -> BS.ByteString -- ^ The salt to use when hashing
-            -> T.Text -- ^ The encoded password hash
+-- | Encode a password with a given salt and 'HashOptions' and produce a textual
+-- encoding of the result.
+hashEncoded :: HashOptions -- ^ Options pertaining to how expensive the hash is to calculate.
+            -> BS.ByteString -- ^ The password to hash. Must be less than 4294967295 bytes.
+            -> BS.ByteString -- ^ The salt to use when hashing. Must be less than 4294967295 bytes.
+            -> T.Text -- ^ The encoded password hash.
 hashEncoded options password salt =
-  unsafePerformIO (hash' options password salt FFI.argon2i_hash_encoded FFI.argon2d_hash_encoded asText)
-  where asText = fmap T.decodeUtf8 . BS.packCString
+  unsafePerformIO
+    (hashEncoded' options password salt FFI.argon2i_hash_encoded FFI.argon2d_hash_encoded)
 
-hash :: HashOptions -- ^ Options pertaining to how expensive the hash is to calculate
-     -> BS.ByteString -- ^ The password to hash
-     -> BS.ByteString -- ^ The salt to use when hashing
-     -> BS.ByteString -- ^ The un-encoded password hash
+-- | Encode a password with a given salt and 'HashOptions' and produce a stream
+-- of bytes.
+hash :: HashOptions -- ^ Options pertaining to how expensive the hash is to calculate.
+     -> BS.ByteString -- ^ The password to hash. Must be less than 4294967295 bytes.
+     -> BS.ByteString -- ^ The salt to use when hashing. Must be less than 4294967295 bytes.
+     -> BS.ByteString -- ^ The un-encoded password hash.
 hash options password salt =
-  unsafePerformIO (hash' options password salt FFI.argon2i_hash_encoded FFI.argon2d_hash_encoded BS.packCString)
+  unsafePerformIO (hash' options password salt FFI.argon2i_hash_raw FFI.argon2d_hash_raw)
 
 variant :: a -> a -> Argon2Variant -> a
 variant a _ Argon2i = a
 variant _ b Argon2d = b
 {-# INLINE variant #-}
 
-type Argon2 a = Word32 -> Word32 -> Word32 -> CString -> Word64 -> CString -> Word64 -> Word64 -> a -> Word64 -> IO Int32
-
+-- | Not all 'HashOptions' can necessarily be used to compute hashes. If you
+-- supply invalid 'HashOptions' (or hashing otherwise fails) a 'Argon2Exception'
+-- will be throw.
 data Argon2Exception
-  = Argon2PasswordLengthOutOfRange Word64 Word64 Word64
-  | Argon2SaltLengthOutOfRange Word64 Word64 Word64
-  | Argon2MemoryUseOutOfRange Word32 Word32 Word32
-  | Argon2IterationCountOutOfRange Word32 Word32 Word32
-  | Argon2ParallelismOutOfRange Word32 Word32 Word32
-  | Argon2Exception Int32
+  = -- | The length of the supplied password is outside the range supported by @libargon2@.
+    Argon2PasswordLengthOutOfRange !Word64 -- ^ The erroneous length.
+  | -- | The length of the supplied salt is outside the range supported by @libargon2@.
+    Argon2SaltLengthOutOfRange !Word64 -- ^ The erroneous length.
+  | -- | Either too much or too little memory was requested via 'hashMemory'.
+    Argon2MemoryUseOutOfRange !Word32 -- ^ The erroneous 'hashMemory' value.
+  | -- | Either too few or too many iterations were requested via 'hashIterations'.
+    Argon2IterationCountOutOfRange !Word32 -- ^ The erroneous 'hashIterations' value.
+  | -- | Either too much or too little parallelism was requested via 'hasParallelism'.
+    Argon2ParallelismOutOfRange !Word32 -- ^ The erroneous 'hashParallelism' value.
+  | -- | An unexpected exception was throw. Please <https://github.com/ocharles/argon2/issues report this as a bug>!
+    Argon2Exception !Int32 -- ^ The =libargon2= error code.
   deriving (Typeable, Show)
 
 instance Exception Argon2Exception
 
+type Argon2Encoded = Word32 -> Word32 -> Word32 -> CString -> Word64 -> CString -> Word64 -> Word64 -> CString -> Word64 -> IO Int32
+
+hashEncoded' :: HashOptions
+             -> BS.ByteString
+             -> BS.ByteString
+             -> Argon2Encoded
+             -> Argon2Encoded
+             -> IO T.Text
+hashEncoded' options@HashOptions{..} password salt argon2i argon2d =
+  do let saltLen = fromIntegral (BS.length salt)
+         passwordLen = fromIntegral (BS.length password)
+         outLen =
+           (BS.length salt * 4 + 32 * 4 +
+            length ("$argon2x$m=,t=,p=$$" :: String) +
+            3 * 3)
+     out <- mallocBytes outLen
+     res <-
+       BS.useAsCString password $
+       \password' ->
+         BS.useAsCString salt $
+         \salt' ->
+           argon2 hashIterations
+                  hashMemory
+                  hashParallelism
+                  password'
+                  passwordLen
+                  salt'
+                  saltLen
+                  64
+                  out
+                  (fromIntegral outLen)
+     handleSuccessCode res options password salt
+     fmap T.decodeUtf8 (BS.packCString out)
+  where argon2 = variant argon2i argon2d hashVariant
+
+type Argon2Unencoded = Word32 -> Word32 -> Word32 -> CString -> Word64 -> CString -> Word64 -> CString -> Word64 -> IO Int32
+
 hash' :: HashOptions
       -> BS.ByteString
       -> BS.ByteString
-      -> Argon2 (Ptr a)
-      -> Argon2 (Ptr a)
-      -> (Ptr a -> IO b)
-      -> IO b
-hash' HashOptions{..} password salt argon2i argon2d postProcess =
-  do out <- mallocBytes 512
-     let saltLen = fromIntegral (BS.length salt)
+      -> Argon2Unencoded
+      -> Argon2Unencoded
+      -> IO BS.ByteString
+hash' options@HashOptions{..} password salt argon2i argon2d =
+  do let saltLen = fromIntegral (BS.length salt)
          passwordLen = fromIntegral (BS.length password)
+         outLen = 512
+     out <- mallocBytes outLen
      res <-
        BS.useAsCString password $
        \password' ->
@@ -85,32 +192,54 @@
                   passwordLen
                   salt'
                   saltLen
-                  64
                   out
-                  512
-     case res of
+                  (fromIntegral outLen)
+     handleSuccessCode res options password salt
+     BS.packCString out
+  where argon2 = variant argon2i argon2d hashVariant
+
+handleSuccessCode :: Int32
+                  -> HashOptions
+                  -> BS.ByteString
+                  -> BS.ByteString
+                  -> IO ()
+handleSuccessCode res HashOptions{..} password salt =
+  let saltLen = fromIntegral (BS.length salt)
+      passwordLen = fromIntegral (BS.length password)
+  in case res of
        a
-         | a `elem` [FFI.ARGON2_OK] -> postProcess out
+         | a `elem` [FFI.ARGON2_OK] -> return ()
          | a `elem` [FFI.ARGON2_SALT_TOO_SHORT,FFI.ARGON2_SALT_TOO_LONG] ->
-           throwIO (Argon2SaltLengthOutOfRange saltLen
-                                               FFI.ARGON2_MIN_SALT_LENGTH
-                                               FFI.ARGON2_MAX_SALT_LENGTH)
+           throwIO (Argon2SaltLengthOutOfRange saltLen)
          | a `elem` [FFI.ARGON2_PWD_TOO_SHORT,FFI.ARGON2_PWD_TOO_LONG] ->
-           throwIO (Argon2PasswordLengthOutOfRange passwordLen
-                                                   FFI.ARGON2_MIN_PWD_LENGTH
-                                                   FFI.ARGON2_MAX_PWD_LENGTH)
+           throwIO (Argon2PasswordLengthOutOfRange passwordLen)
          | a `elem` [FFI.ARGON2_TIME_TOO_SMALL,FFI.ARGON2_TIME_TOO_LARGE] ->
-           throwIO (Argon2IterationCountOutOfRange hashIterations
-                                                   FFI.ARGON2_MIN_TIME
-                                                   FFI.ARGON2_MAX_TIME)
+           throwIO (Argon2IterationCountOutOfRange hashIterations)
          | a `elem` [FFI.ARGON2_MEMORY_TOO_LITTLE,FFI.ARGON2_MEMORY_TOO_MUCH] ->
-           throwIO (Argon2MemoryUseOutOfRange
-                      hashMemory
-                      (max FFI.ARGON2_MIN_MEMORY (8 * hashParallelism))
-                      FFI.ARGON2_MAX_MEMORY)
-         | a `elem` [FFI.ARGON2_LANES_TOO_FEW,FFI.ARGON2_LANES_TOO_MANY] ->
-           throwIO (Argon2ParallelismOutOfRange hashParallelism
-                                                FFI.ARGON2_MIN_LANES
-                                                FFI.ARGON2_MAX_LANES)
+           throwIO (Argon2MemoryUseOutOfRange hashMemory)
+         | a `elem`
+             [FFI.ARGON2_LANES_TOO_FEW
+             ,FFI.ARGON2_LANES_TOO_MANY
+             ,FFI.ARGON2_THREADS_TOO_FEW
+             ,FFI.ARGON2_THREADS_TOO_MANY] ->
+           throwIO (Argon2ParallelismOutOfRange hashParallelism)
          | otherwise -> throwIO (Argon2Exception a)
-  where argon2 = variant argon2i argon2d hashVariant
+
+-- | Verify that a given password could result in a given hash output.
+-- Automatically determines the correct 'HashOptions' based on the
+-- encoded hash (as produced by 'hashEncoded').
+verify
+  :: T.Text -> BS.ByteString -> Bool
+verify encoded password =
+  unsafePerformIO
+    (BS.useAsCString password $
+     \pwd ->
+       BS.useAsCString (T.encodeUtf8 encoded) $
+       \enc ->
+         do res <-
+              (variant FFI.argon2i_verify FFI.argon2d_verify v) enc
+                                                                pwd
+                                                                (fromIntegral (BS.length password))
+            return (res == FFI.ARGON2_OK))
+    where v | T.pack "$argon2i" `T.isPrefixOf` encoded = Argon2i
+            | otherwise = Argon2d
diff --git a/src/Crypto/Argon2/FFI.hsc b/src/Crypto/Argon2/FFI.hsc
--- a/src/Crypto/Argon2/FFI.hsc
+++ b/src/Crypto/Argon2/FFI.hsc
@@ -17,6 +17,10 @@
 
 foreign import ccall unsafe "argon2.h argon2d_hash_raw" argon2d_hash_raw :: (#type const uint32_t) -> (#type const uint32_t) -> (#type const uint32_t) -> Ptr a -> (#type const size_t) -> Ptr b -> (#type size_t) -> Ptr c -> (#type const size_t) -> IO (#type int)
 
+foreign import ccall unsafe "argon2.h argon2i_verify" argon2i_verify :: CString -> Ptr a -> (#type const size_t) -> IO (#type int)
+
+foreign import ccall unsafe "argon2.h argon2d_verify" argon2d_verify :: CString -> Ptr a -> (#type const size_t) -> IO (#type int)
+
 pattern ARGON2_OK = (#const ARGON2_OK)
 pattern ARGON2_OUTPUT_PTR_NULL = (#const ARGON2_OUTPUT_PTR_NULL)
 pattern ARGON2_OUTPUT_TOO_SHORT = (#const ARGON2_OUTPUT_TOO_SHORT)
