diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2013--inf, Bogdan Opanchuk
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Bogdan Opanchuk nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.rst b/README.rst
new file mode 100644
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,57 @@
+Random123
+=========
+
+This is a Haskell port of counter-based random number generators from `Random123 library <http://www.thesalmons.org/john/random123/>`_ v1.07 (with a minor bugfix).
+The description of algorithms can be also found in `Salmon et al., P. Int. C. High. Perform. 16 (2011) <http://dx.doi.org/doi:10.1145/2063384.2063405>`_.
+
+
+Contributing
+------------
+
+When making changes to the library, run (or update, if necessary) functionality tests.
+This can be done as
+
+::
+
+    $ cabal configure --enable-tests
+    $ cabal build
+    $ cabal test
+
+or just by executing ``cd test; ./test.sh``.
+You can also check the performance by running benchmarks as
+
+::
+
+    $ cabal configure --enable-benchmarks
+    $ cabal build
+    $ cabal bench
+
+or by executing ``cd test; ./test_perf.sh``.
+Benchmarks will create a report file ``test_perf.html``
+in the folder where they were executed from.
+
+
+TODO
+----
+
+* Performance issues:
+
+    * According to Salmon et al., Threefry-4x64 should be the fastest algorithm on CPUs.
+      This is what not I'm seeing; need to investigate it further.
+      If it is made faster, it should be used as the default bijection for ``CBRNG32/64``
+      instead of ``philox4``.
+
+    * ``mulhilo`` function in 64-bit Philox is not optimal in terms of performance.
+      It can be made faster given the access to CPU intrinsics.
+
+    * 32-bit Threefry shows suprisingly low performance (see ``Bijection`` benchmark group).
+
+    * In general, there seems to be a lot of optimizations that can be done,
+      in particular in terms of strategically placed strictness enforcement.
+
+* Current ``split`` implementation is a quick solution that kind of works
+  (much like ``StdGen``'s one).
+  A mathematically robust implementation is required
+  (and CBRNGs by nature should be well-suited for this).
+  Moreover, it would be great to have some tests that could distinguish
+  "bad" ``split`` from a "good" one.
diff --git a/Random123.cabal b/Random123.cabal
new file mode 100644
--- /dev/null
+++ b/Random123.cabal
@@ -0,0 +1,72 @@
+Name:                Random123
+Version:             0.1.0
+Synopsis:            Haskell port of Random123 library
+Description:
+  This is a Haskell port of counter-based random number generators from the Random123 library
+  (<http://www.thesalmons.org/john/random123/>) v1.07 (with a minor bugfix).
+  The description of algorithms can be also found in
+  Salmon et al., P. Int. C. High. Perform. 16 (2011)
+  (<http://dx.doi.org/doi:10.1145/2063384.2063405>).
+
+Homepage:            http://github.com/Manticore/haskell-random123
+License:             MIT
+License-file:        LICENSE
+Author:              Bogdan Opanchuk <bogdan@opanchuk.net>
+Maintainer:          Bogdan Opanchuk <bogdan@opanchuk.net>
+Category:            Random
+Build-type:          Simple
+Cabal-version:       >=1.8
+
+Extra-source-files:
+  README.rst
+  test/*.sh
+
+Source-repository head
+  type:     git
+  location: http://github.com/Manticore/haskell-random123.git
+  branch:   develop
+
+Source-repository this
+  type:     git
+  location: http://github.com/Manticore/haskell-random123.git
+  tag:      0.1.0
+
+Library
+  Exposed-modules:
+    System.Random.Random123
+    System.Random.Random123.Misc
+    System.Random.Random123.Threefry
+    System.Random.Random123.Philox
+    System.Random.Random123.Types
+    System.Random.Random123.RandomGen
+
+  Build-Depends:
+    base >= 4.4.0.0 && < 5.0,
+    array >= 0.4,
+    random >= 1.0.0.0
+
+Test-Suite test
+  Type: exitcode-stdio-1.0
+  Hs-source-dirs: test
+  Main-is: test.hs
+  Other-modules: TestPhilox TestThreefry TestRandomGen TestTypeclasses
+  Build-depends:
+    base >= 4.4 && < 5.0,
+    random >= 1.0,
+    test-framework >= 0.8,
+    test-framework-hunit >= 0.3,
+    test-framework-quickcheck2 >= 0.3,
+    HUnit >= 1.2,
+    QuickCheck >= 2.5,
+    Random123
+
+Benchmark test_perf
+  Type: exitcode-stdio-1.0
+  Hs-source-dirs: test
+  Main-is: test_perf.hs
+  Build-depends:
+    base >= 4.4 && < 5.0,
+    random >= 1.0,
+    criterion >= 0.6,
+    Random123
+  Ghc-options: -O2
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/System/Random/Random123.hs b/System/Random/Random123.hs
new file mode 100644
--- /dev/null
+++ b/System/Random/Random123.hs
@@ -0,0 +1,70 @@
+{-# LANGUAGE FlexibleInstances #-}
+
+-- | This module is a Haskell port of the Random123 library
+-- (<http://www.thesalmons.org/john/random123/>).
+-- It is based on counter-based pseudo-random number generators (CBRNGs), which are, essentially,
+-- keyed bijections which transform successive counters into randomly distributed integers.
+-- For details about the theory behind the algorithms along with statistical and performance tests
+-- see the paper Salmon et al., P. Int. C. High. Perform. 16 (2011)
+-- (<http://dx.doi.org/doi:10.1145/2063384.2063405>).
+--
+-- The module exposes both bijection functions themselves (for customized approach) and
+-- instances of 'RandomGen'.
+--
+-- Since CBRNGs are based on bijection functions, their periods are equal to the size of their
+-- corresponding counters.
+-- For example, 32-bit 'philox4' has 'Array4' 'Word32' counter,
+-- therefore the total counter size is @4 * 32 = 128@ bit, and the period is @2^128@.
+--
+-- 'RandomGen' instances use each generated random array for several random integers,
+-- so their periods are several times bigger.
+-- Consider now that the 'philox4' bijection was used to create a 'CustomCBRNG64' generator.
+-- For each 64-bit 'Int' its 'next' function returns,
+-- it will use two of the elements of the 'Array4' 'Word32',
+-- so the total period is @2 * 2^128 = 2^129@.
+--
+-- /Note:/ There is no point in creating 64-bit RNGs when your platform has only 32-bit 'Int's.
+-- The remaining bits will be truncated by 'next'.
+module System.Random.Random123 (
+    -- * Default RNGs
+    -- | Use these if you want the (usually) optimal bijection algorithm,
+    -- and serialization capabilities.
+    CBRNG32,
+    mkCBRNG32,
+    restoreCBRNG32,
+    CBRNG64,
+    mkCBRNG64,
+    restoreCBRNG64,
+
+    -- * Custom RNGs
+    -- | Use these if you want a custom bijection algorithm.
+    CustomCBRNG32,
+    mkCustomCBRNG32,
+    restoreCustomCBRNG32,
+    CustomCBRNG64,
+    mkCustomCBRNG64,
+    restoreCustomCBRNG64,
+
+    -- * Keyed bijection functions
+    -- | Use these if you want the ultimate control over keys and counters.
+    -- Sometimes it is advantageous to bind part of the counter or the key
+    -- the node or thread identifier, or to indices of a time or space grid.
+    -- You can use 'liFromInteger' (or just a tuple constructor) to create keys and counters,
+    -- and 'skip' and 'increment' to change counter values.
+    --
+    -- If you want further control over the number of rounds in these bijections,
+    -- see "System.Random.Random123.Philox" and "System.Random.Random123.Threefry" modules.
+    philox2,
+    philox4,
+    threefry2,
+    threefry4
+    ) where
+
+import Data.Word
+import Data.Bits
+import System.Random
+
+import System.Random.Random123.Types
+import System.Random.Random123.Philox
+import System.Random.Random123.Threefry
+import System.Random.Random123.RandomGen
diff --git a/System/Random/Random123/Misc.hs b/System/Random/Random123/Misc.hs
new file mode 100644
--- /dev/null
+++ b/System/Random/Random123/Misc.hs
@@ -0,0 +1,17 @@
+{-# OPTIONS_HADDOCK hide #-}
+
+-- | Miscellaneous helper functions.
+module System.Random.Random123.Misc where
+
+
+-- | Apply a function to its result sequentially,
+--   additionally passing it the current iteration number.
+apply :: (Int -> a -> a) -> Int -> a -> a
+apply f n = applyLoop 0 where
+    applyLoop i v
+        | i == n = v
+        | otherwise = applyLoop (i + 1) $! f i v
+
+-- | Apply a function to its result sequentially.
+apply_ :: (a -> a) -> Int -> a -> a
+apply_ f = apply (\_ v -> f v)
diff --git a/System/Random/Random123/Philox.hs b/System/Random/Random123/Philox.hs
new file mode 100644
--- /dev/null
+++ b/System/Random/Random123/Philox.hs
@@ -0,0 +1,116 @@
+{-# LANGUAGE BangPatterns #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-- | Philox, a counter-based random number generator (keyed bijection function).
+-- Characterized by a low number of rounds involving relatively expensive computations.
+module System.Random.Random123.Philox (
+    philox2, philox4, philox2R, philox4R, PhiloxWord) where
+
+import Data.Word
+import Data.Bits
+
+import System.Random.Random123.Misc
+import System.Random.Random123.Types
+
+
+-- | Class of integer types suitable for use in Philox algorithm.
+class (Bits a, Num a) => PhiloxWord a where
+    mulhilo :: a -> a -> Array2 a
+    philoxW_0 :: a
+    philoxW_1 :: a
+    philoxM2 :: a
+    philoxM4_0 :: a
+    philoxM4_1 :: a
+
+
+instance PhiloxWord Word32 where
+    mulhilo a b = (hi, lo) where
+        r :: Word64
+        r = fromIntegral a * fromIntegral b
+        hi = fromIntegral (r `shiftR` 32)
+        lo = fromIntegral r
+
+    philoxW_0 = 0x9E3779B9 -- golden ratio
+    philoxW_1 = 0xBB67AE85 -- sqrt(3)-1
+
+    philoxM2 = 0xD256D193
+
+    -- The order is swapped as compared to the reference implementation,
+    -- to make the call order in philoxRound4 more logical.
+    philoxM4_0 = 0xCD9E8D57
+    philoxM4_1 = 0xD2511F53
+
+
+instance PhiloxWord Word64 where
+    mulhilo a b = (hi, lo) where
+        r :: Integer
+        r = fromIntegral a * fromIntegral b
+        hi = fromIntegral (r `shiftR` 64)
+        lo = fromIntegral r
+
+    philoxW_0 = 0x9E3779B97F4A7C15 -- golden ratio
+    philoxW_1 = 0xBB67AE8584CAA73B -- sqrt(3)-1
+
+    philoxM2 = 0xD2B74407B1CE6E93
+
+    -- The order is swapped as compared to the reference implementation,
+    -- to make the call order in philoxRound4 more logical.
+    philoxM4_0 = 0xCA5A826395121157
+    philoxM4_1 = 0xD2E7470EE14C6C93
+
+
+philoxSubround :: PhiloxWord a => Int -> a -> a -> a -> Array2 a -> Array2 a
+philoxSubround r w m k (x0, x1) = (x0', x1') where
+    k' = k + w * fromIntegral r
+    (hi, lo) = mulhilo m x0
+    x0' = hi `xor` k' `xor` x1
+    x1' = lo
+
+-- FIXME: For some reason, if I do not force strictness in x0 and x1 here, it eats up the stack.
+philoxRound2 :: PhiloxWord a => a -> Int -> Array2 a -> Array2 a
+philoxRound2 k r (!x0, !x1) = (x0', x1') where
+    (x0', x1') = philoxSubround r philoxW_0 philoxM2 k (x0, x1)
+
+-- FIXME: For some reason, if I do not force strictness in x0-x3 here, it eats up the stack.
+philoxRound4 :: PhiloxWord a => Array2 a -> Int -> Array4 a -> Array4 a
+philoxRound4 (k0, k1) r (!x0, !x1, !x2, !x3) = (x0', x1', x2', x3') where
+    (x0', x1') = philoxSubround r philoxW_0 philoxM4_0 k0 (x2, x1)
+    (x2', x3') = philoxSubround r philoxW_1 philoxM4_1 k1 (x0, x3)
+
+
+-- | Generates a Philox-2 random number with a custom number of rounds.
+philox2R :: PhiloxWord a
+    => Int -- ^ number of rounds (1-16),
+    -> a -- ^ key,
+    -> Array2 a -- ^ counter,
+    -> Array2 a -- ^ random number.
+philox2R rounds key ctr
+    | (rounds >= 1) && (rounds <= 16) = apply (philoxRound2 key) rounds ctr
+    | otherwise = error "The number of rounds in Philox-2 must be between 1 and 16"
+
+
+-- | Generates a Philox-4 random number with a custom number of rounds.
+philox4R :: PhiloxWord a
+    => Int -- ^ number of rounds (1-16),
+    -> Array2 a -- ^ key,
+    -> Array4 a -- ^ counter,
+    -> Array4 a -- ^ random number.
+philox4R rounds key ctr
+    | (rounds >= 1) && (rounds <= 16) = apply (philoxRound4 key) rounds ctr
+    | otherwise = error "The number of rounds in Philox-4 must be between 1 and 16"
+
+
+-- | Generates a Philox-2 random number with the optimal number of rounds.
+philox2 :: PhiloxWord a
+    => a -- ^ key,
+    -> Array2 a -- ^ counter,
+    -> Array2 a -- ^ random number.
+philox2 = philox2R 10
+
+
+-- | Generates a Philox-4 random number with the optimal number of rounds.
+philox4 :: PhiloxWord a
+    => Array2 a -- ^ key,
+    -> Array4 a -- ^ counter,
+    -> Array4 a -- ^ random number.
+philox4 = philox4R 10
diff --git a/System/Random/Random123/RandomGen.hs b/System/Random/Random123/RandomGen.hs
new file mode 100644
--- /dev/null
+++ b/System/Random/Random123/RandomGen.hs
@@ -0,0 +1,180 @@
+{-# OPTIONS_HADDOCK not-home #-}
+
+-- | Integration with the standard library 'RandomGen' class.
+module System.Random.Random123.RandomGen (
+    mkCustomCBRNG32,
+    mkCustomCBRNG64,
+    restoreCustomCBRNG32,
+    restoreCustomCBRNG64,
+    mkCBRNG32,
+    mkCBRNG64,
+    restoreCBRNG32,
+    restoreCBRNG64,
+    CBRNG32,
+    CBRNG64,
+    CustomCBRNG32,
+    CustomCBRNG64,
+    CBRNGState,
+    SerializableCBRNG(..)
+    ) where
+
+import System.Random
+import Data.Word
+
+import System.Random.Random123.Types
+import System.Random.Random123.Philox
+import System.Random.Random123.Threefry
+
+
+-- | 32-bit RNG with a custom bijection function.
+-- Can be serialized with 'getState' and restored with 'restoreCustomCBRNG32'
+-- (but it is the user's responsibility to provide the original bijection).
+data CustomCBRNG32 k c = CustomCBRNG32 (k -> c -> c) k c Int
+
+-- | 64-bit RNG with a custom bijection function.
+-- Can be serialized with 'getState' and restored with 'restoreCustomCBRNG32'
+-- (but it is the user's responsibility to provide the original bijection).
+data CustomCBRNG64 k c = CustomCBRNG64 (k -> c -> c) k c Int
+
+-- | Default 32-bit RNG.
+-- Supports serialization through 'Show' / 'Read' interface.
+-- Alternatively, can be serialized with 'getState' and restored with 'restoreCBRNG32'.
+data CBRNG32 = CBRNG32 (Array2 Word32) (Array4 Word32) Int deriving (Eq, Show, Read)
+
+-- | Default 64-bit RNG.
+-- Supports serialization through 'Show' / 'Read' interface.
+-- Alternatively, can be serialized with 'getState' and restored with 'restoreCBRNG64'.
+data CBRNG64 = CBRNG64 (Array2 Word64) (Array4 Word64) Int deriving (Eq, Show, Read)
+
+
+next32 :: (Counter c, Word32Array c) => (c -> c) -> c -> Int -> (Int, c, Int)
+next32 bijection ctr wctr = (fromIntegral w32, ctr', wctr') where
+    arr = bijection ctr
+    w32 = getWord32 wctr arr
+    (ctr', wctr') = if wctr + 1 < numWords32 arr
+        then (ctr, wctr + 1)
+        else (increment ctr, 0)
+
+genRange32 = (0, min maxBound (2^32 - 1)) :: (Int, Int)
+
+next64 :: (Counter c, Word64Array c) => (c -> c) -> c -> Int -> (Int, c, Int)
+next64 bijection ctr wctr = (fromIntegral w64, ctr', wctr') where
+    arr = bijection ctr
+    w64 = getWord64 wctr arr
+    (ctr', wctr') = if wctr + 1 < numWords64 arr
+        then (ctr, wctr + 1)
+        else (increment ctr, 0)
+
+genRange64 = (0, min maxBound (2^64 - 1)) :: (Int, Int)
+
+
+instance (Counter c, Word32Array c) => RandomGen (CustomCBRNG32 k c) where
+    next (CustomCBRNG32 bijection key ctr wctr) = (res, new_gen) where
+        (res, ctr', wctr') = next32 (bijection key) ctr wctr
+        new_gen = CustomCBRNG32 bijection key ctr' wctr'
+    genRange _ = genRange32
+    split (CustomCBRNG32 bijection key ctr wctr) = (gen', gen'') where
+        ctr' = increment ctr
+        ctr'' = bijection key ctr'
+        gen' = CustomCBRNG32 bijection key ctr' 0
+        gen'' = CustomCBRNG32 bijection key ctr'' 0
+
+instance (Counter c, Word64Array c) => RandomGen (CustomCBRNG64 k c) where
+    next (CustomCBRNG64 bijection key ctr wctr) = (res, new_gen) where
+        (res, ctr', wctr') = next64 (bijection key) ctr wctr
+        new_gen = CustomCBRNG64 bijection key ctr' wctr'
+    genRange _ = genRange64
+    split (CustomCBRNG64 bijection key ctr wctr) = (gen', gen'') where
+        ctr' = increment ctr
+        ctr'' = bijection key ctr'
+        gen' = CustomCBRNG64 bijection key ctr' 0
+        gen'' = CustomCBRNG64 bijection key ctr'' 0
+
+instance RandomGen CBRNG32 where
+    next (CBRNG32 key ctr wctr) = (res, new_gen) where
+        (res, ctr', wctr') = next32 (philox4 key) ctr wctr
+        new_gen = CBRNG32 key ctr' wctr'
+    genRange _ = genRange32
+    split (CBRNG32 key ctr wctr) = (gen', gen'') where
+        ctr' = increment ctr
+        ctr'' = philox4 key ctr'
+        gen' = CBRNG32 key ctr' 0
+        gen'' = CBRNG32 key ctr'' 0
+
+instance RandomGen CBRNG64 where
+    next (CBRNG64 key ctr wctr) = (res, new_gen) where
+        (res, ctr', wctr') = next64 (philox4 key) ctr wctr
+        new_gen = CBRNG64 key ctr' wctr'
+    genRange _ = genRange64
+    split (CBRNG64 key ctr wctr) = (gen', gen'') where
+        ctr' = increment ctr
+        ctr'' = philox4 key ctr'
+        gen' = CBRNG64 key ctr' 0
+        gen'' = CBRNG64 key ctr'' 0
+
+-- | Generalized CBRNG state, consisting of key, counter and subcounter,
+-- where the first two are cast to integers (using 'liToInteger').
+data CBRNGState = CBRNGState Integer Integer Int deriving (Eq, Show, Read)
+
+
+-- | Class of RNGs allowing the state extraction.
+class SerializableCBRNG a where
+    getState :: a -> CBRNGState
+
+
+instance SerializableCBRNG CBRNG32 where
+    getState (CBRNG32 key ctr wctr) = CBRNGState (liToInteger key) (liToInteger ctr) wctr
+
+instance SerializableCBRNG CBRNG64 where
+    getState (CBRNG64 key ctr wctr) = CBRNGState (liToInteger key) (liToInteger ctr) wctr
+
+instance (LimitedInteger k, LimitedInteger c) => SerializableCBRNG (CustomCBRNG32 k c) where
+    getState (CustomCBRNG32 bijection key ctr wctr) =
+        CBRNGState (liToInteger key) (liToInteger ctr) wctr
+
+instance (LimitedInteger k, LimitedInteger c) => SerializableCBRNG (CustomCBRNG64 k c) where
+    getState (CustomCBRNG64 bijection key ctr wctr) =
+        CBRNGState (liToInteger key) (liToInteger ctr) wctr
+
+
+-- | Creates a custom 32-bit RNG from a keyed bijection
+-- ('Word32'- or 'Word64'-parametrized version of 'philox2', 'philox2R', 'philox4', 'philox4R',
+-- 'threefry2', 'threefry2R', 'threefry4', 'threefry4R')
+-- and a corresponding key.
+mkCustomCBRNG32 :: LimitedInteger c => (k -> c -> c) -> k -> CustomCBRNG32 k c
+mkCustomCBRNG32 bijection key = CustomCBRNG32 bijection key (liFromInteger 0) 0
+
+-- | Restores a custom 32-bit RNG from a saved state.
+restoreCustomCBRNG32 :: (LimitedInteger k, LimitedInteger c)
+    => (k -> c -> c) -> CBRNGState -> CustomCBRNG32 k c
+restoreCustomCBRNG32 bijection (CBRNGState key ctr wctr) =
+    CustomCBRNG32 bijection (liFromInteger key) (liFromInteger ctr) wctr
+
+-- | Creates a custom 64-bit RNG from a keyed bijection
+-- ('Word32'- or 'Word64'-parametrized version of 'philox2', 'philox2R', 'philox4', 'philox4R',
+-- 'threefry2', 'threefry2R', 'threefry4', 'threefry4R')
+-- and a corresponding key.
+mkCustomCBRNG64 :: LimitedInteger c => (k -> c -> c) -> k -> CustomCBRNG64 k c
+mkCustomCBRNG64 bijection key = CustomCBRNG64 bijection key (liFromInteger 0) 0
+
+-- | Restores a custom 64-bit RNG from a saved state.
+restoreCustomCBRNG64 :: (LimitedInteger k, LimitedInteger c)
+    => (k -> c -> c) -> CBRNGState -> CustomCBRNG64 k c
+restoreCustomCBRNG64 bijection (CBRNGState key ctr wctr) =
+    CustomCBRNG64 bijection (liFromInteger key) (liFromInteger ctr) wctr
+
+-- | Creates a default 32-bit RNG (based on 32-bit 'philox4') with an 'Integer' key.
+mkCBRNG32 :: Integer -> CBRNG32
+mkCBRNG32 key = CBRNG32 (liFromInteger key) (liFromInteger 0) 0
+
+-- | Restores a default 32-bit RNG from a saved state.
+restoreCBRNG32 :: CBRNGState -> CBRNG32
+restoreCBRNG32 (CBRNGState key ctr wctr) = CBRNG32 (liFromInteger key) (liFromInteger ctr) wctr
+
+-- | Creates a default 64-bit RNG (based on 64-bit 'philox4') with an 'Integer' key.
+mkCBRNG64 :: Integer -> CBRNG64
+mkCBRNG64 key = CBRNG64 (liFromInteger key) (liFromInteger 0) 0
+
+-- | Restores a default 64-bit RNG from a saved state.
+restoreCBRNG64 :: CBRNGState -> CBRNG64
+restoreCBRNG64 (CBRNGState key ctr wctr) = CBRNG64 (liFromInteger key) (liFromInteger ctr) wctr
diff --git a/System/Random/Random123/Threefry.hs b/System/Random/Random123/Threefry.hs
new file mode 100644
--- /dev/null
+++ b/System/Random/Random123/Threefry.hs
@@ -0,0 +1,206 @@
+{-# LANGUAGE BangPatterns #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-- | Threefry, a counter-based random number generator (keyed bijection function).
+-- Characterized by a high number of rounds involving relatively cheap computations.
+module System.Random.Random123.Threefry (
+    threefry2, threefry4, threefry2R, threefry4R, ThreefryWord) where
+
+import Data.Word
+import Data.Bits
+import Data.Array.Base
+import Data.Array.Unboxed
+
+
+import System.Random.Random123.Types
+import System.Random.Random123.Misc
+
+
+-- Helper type to store small constant arrays of rotation constants,
+-- and accompanying function for their extraction.
+-- Not sure if UArray and unsafeAt are the best choice.
+
+type RotationConstants = UArray Int Int
+
+getRotationConstant :: RotationConstants -> Int -> Int
+getRotationConstant = unsafeAt
+
+
+-- | Class of integer types suitable for use in Threefry algorithm.
+class ThreefryWord a where
+    parityConstant :: a
+    rotationConstant2 :: a -> RotationConstants
+    rotationConstant4_0 :: a -> RotationConstants
+    rotationConstant4_1 :: a -> RotationConstants
+
+
+instance ThreefryWord Word32 where
+    parityConstant = 0x1BD11BDA
+
+    -- Output from skein_rot_search (srs32x2-X5000.out)
+    -- Random seed = 1. BlockSize = 64 bits. sampleCnt =  1024. rounds =  8, minHW_or=28
+    -- Start: Tue Jul 12 11:11:33 2011
+    -- rMin = 0.334. #0206[*07] [CRC=1D9765C0. hw_OR=32. cnt=16384. blkSize=  64].format
+    -- 4 rounds: minHW =  4  [  4  4  4  4 ]
+    -- 5 rounds: minHW =  6  [  6  8  6  8 ]
+    -- 6 rounds: minHW =  9  [  9 12  9 12 ]
+    -- 7 rounds: minHW = 16  [ 16 24 16 24 ]
+    -- 8 rounds: minHW = 32  [ 32 32 32 32 ]
+    -- 9 rounds: minHW = 32  [ 32 32 32 32 ]
+    -- 10 rounds: minHW = 32  [ 32 32 32 32 ]
+    -- 11 rounds: minHW = 32  [ 32 32 32 32 ]
+    rotationConstant2 _ = listArray (0, 7) [13, 15, 26, 6, 17, 29, 16, 24]
+
+    -- Output from skein_rot_search: (srs-B128-X5000.out)
+    -- Random seed = 1. BlockSize = 64 bits. sampleCnt =  1024. rounds =  8, minHW_or=28
+    -- Start: Mon Aug 24 22:41:36 2009
+    -- ...
+    -- rMin = 0.472. #0A4B[*33] [CRC=DD1ECE0F. hw_OR=31. cnt=16384. blkSize= 128].format
+    -- 4 rounds: minHW =  3  [  3  3  3  3 ]
+    -- 5 rounds: minHW =  7  [  7  7  7  7 ]
+    -- 6 rounds: minHW = 12  [ 13 12 13 12 ]
+    -- 7 rounds: minHW = 22  [ 22 23 22 23 ]
+    -- 8 rounds: minHW = 31  [ 31 31 31 31 ]
+    -- 9 rounds: minHW = 32  [ 32 32 32 32 ]
+    -- 10 rounds: minHW = 32  [ 32 32 32 32 ]
+    -- 11 rounds: minHW = 32  [ 32 32 32 32 ]
+    rotationConstant4_0 _ = listArray (0, 7) [10, 11, 13, 23, 6, 17, 25, 18]
+    rotationConstant4_1 _ = listArray (0, 7) [26, 21, 27, 5, 20, 11, 10, 20]
+
+
+instance ThreefryWord Word64 where
+    parityConstant = 0x1BD11BDAA9FC1A22
+
+    -- Output from skein_rot_search: (srs64_B64-X1000)
+    -- Random seed = 1. BlockSize = 128 bits. sampleCnt =  1024. rounds =  8, minHW_or=57
+    -- Start: Tue Mar  1 10:07:48 2011
+    -- rMin = 0.136. #0325[*15] [CRC=455A682F. hw_OR=64. cnt=16384. blkSize= 128].format
+    -- 4 rounds: minHW =  4  [  4  4  4  4 ]
+    -- 5 rounds: minHW =  8  [  8  8  8  8 ]
+    -- 6 rounds: minHW = 16  [ 16 16 16 16 ]
+    -- 7 rounds: minHW = 32  [ 32 32 32 32 ]
+    -- 8 rounds: minHW = 64  [ 64 64 64 64 ]
+    -- 9 rounds: minHW = 64  [ 64 64 64 64 ]
+    -- 10 rounds: minHW = 64  [ 64 64 64 64 ]
+    -- 11 rounds: minHW = 64  [ 64 64 64 64 ]
+    rotationConstant2 _ = listArray (0, 7) [16, 42, 12, 31, 16, 32, 24, 21]
+
+    -- These are the R_256 constants from the Threefish reference sources
+    -- with names changed to R_64x4...
+    rotationConstant4_0 _ = listArray (0, 7) [14, 52, 23, 5, 25, 46, 58, 32]
+    rotationConstant4_1 _ = listArray (0, 7) [16, 57, 40, 37, 33, 12, 22, 32]
+
+
+-- S-box
+
+-- FIXME: For some reason, if I do not force strictness in x0 and x1 here, it eats up the stack.
+sbox' :: Bits a => Int -> (a -> RotationConstants) -> Array2 a -> Array2 a
+sbox' r r_constant (!x0, !x1) = (x0', x1') where
+    rot = getRotationConstant (r_constant (undefined :: a)) (r `mod` 8)
+    x0' = x0 + x1
+    x1' = x0' `xor` (x1 `rotate` rot)
+
+sbox2 :: (ThreefryWord a, Bits a) => Int -> Array2 a -> Array2 a
+sbox2 r = sbox' r rotationConstant2
+
+-- FIXME: For some reason, if I do not force strictness in x0-x3 here, it eats up the stack.
+sbox4 :: (ThreefryWord a, Bits a) => Int -> Array4 a -> Array4 a
+sbox4 r (!x0, !x1, !x2, !x3) = (x0', x1', x2', x3') where
+    (xa, xb) = if r `mod` 2 == 0 then (x1, x3) else (x3, x1)
+    (x0', xa') = sbox' r rotationConstant4_0 (x0, xa)
+    (x2', xb') = sbox' r rotationConstant4_1 (x2, xb)
+    (x1', x3') = if r `mod` 2 == 0 then (xa', xb') else (xb', xa')
+
+
+-- P-box
+
+shiftTuple2 :: Int -> (a, a, a) -> Array2 a
+shiftTuple2 i (k0, k1, k2)
+    | remainder == 0 = (k0, k1)
+    | remainder == 1 = (k1, k2)
+    | otherwise = (k2, k0)
+    where
+        remainder = i `mod` 3
+
+shiftTuple4 :: Int -> (a, a, a, a, a) -> Array4 a
+shiftTuple4 i (k0, k1, k2, k3, k4)
+    | remainder == 0 = (k0, k1, k2, k3)
+    | remainder == 1 = (k1, k2, k3, k4)
+    | remainder == 2 = (k2, k3, k4, k0)
+    | remainder == 3 = (k3, k4, k0, k1)
+    | otherwise = (k4, k0, k1, k2)
+    where
+        remainder = i `mod` 5
+
+addTuple2 :: Num a => Array2 a -> Array2 a -> Array2 a
+addTuple2 (k0, k1) (x0, x1) = (k0 + x0, k1 + x1)
+
+addTuple4 :: Num a => Array4 a -> Array4 a -> Array4 a
+addTuple4 (k0, k1, k2, k3) (x0, x1, x2, x3) = (k0 + x0, k1 + x1, k2 + x2, k3 + x3)
+
+pbox2 :: Bits a => (a, a, a) -> Int -> Array2 a -> Array2 a
+pbox2 extended_key r x = (x0', x1' + fromIntegral shift) where
+    shift = r `div` 4 + 1
+    (x0', x1') = addTuple2 x (shiftTuple2 shift extended_key)
+
+pbox4 :: Bits a => (a, a, a, a, a) -> Int -> Array4 a -> Array4 a
+pbox4 extended_key r x = (x0', x1', x2', x3' + fromIntegral shift) where
+    shift = r `div` 4 + 1
+    (x0', x1', x2', x3') = addTuple4 x (shiftTuple4 shift extended_key)
+
+
+-- Additional helper functions.
+
+threefryRound pbox sbox r x = if r `mod` 4 == 3
+    then pbox r (sbox r x)
+    else sbox r x
+
+extendKey2 :: (ThreefryWord a, Bits a) => Array2 a -> (a, a, a)
+extendKey2 (k0, k1) = (k0, k1, k0 `xor` k1 `xor` parityConstant)
+
+extendKey4 :: (ThreefryWord a, Bits a) => Array4 a -> (a, a, a, a, a)
+extendKey4 (k0, k1, k2, k3) = (k0, k1, k2, k3, k0 `xor` k1 `xor` k2 `xor` k3 `xor` parityConstant)
+
+
+-- | Generates a Threefry-2 random number with a custom number of rounds.
+threefry2R :: (ThreefryWord a, Bits a)
+    => Int -- ^ number of rounds (1-32),
+    -> Array2 a -- ^ key,
+    -> Array2 a -- ^ counter,
+    -> Array2 a -- ^ random number.
+threefry2R rounds key ctr
+    | (rounds >= 1) && (rounds <= 32) = apply (threefryRound pbox sbox2) rounds starting_x
+    | otherwise = error "The number of rounds in Threefry-2 must be between 1 and 32"
+    where
+        starting_x = addTuple2 key ctr
+        pbox = pbox2 (extendKey2 key)
+
+
+-- | Generates a Threefry-4 random number with a custom number of rounds.
+threefry4R :: (ThreefryWord a, Bits a)
+    => Int -- ^ number of rounds (1-72),
+    -> Array4 a -- ^ key,
+    -> Array4 a -- ^ counter,
+    -> Array4 a -- ^ random number.
+threefry4R rounds key ctr
+    | (rounds >= 1) && (rounds <= 72) = apply (threefryRound pbox sbox4) rounds starting_x
+    | otherwise = error "The number of rounds in Threefry-4 must be between 1 and 72"
+    where
+        starting_x = addTuple4 key ctr
+        pbox = pbox4 (extendKey4 key)
+
+
+-- | Generates a Threefry-2 random number with the optimal number of rounds.
+threefry2 :: (ThreefryWord a, Bits a)
+    => Array2 a -- ^ key,
+    -> Array2 a -- ^ counter,
+    -> Array2 a -- ^ random number.
+threefry2 = threefry2R 20
+
+
+-- | Generates a Threefry-4 random number with the optimal number of rounds.
+threefry4 :: (ThreefryWord a, Bits a)
+    => Array4 a -- ^ key,
+    -> Array4 a -- ^ counter,
+    -> Array4 a -- ^ random number.
+threefry4 = threefry4R 20
diff --git a/System/Random/Random123/Types.hs b/System/Random/Random123/Types.hs
new file mode 100644
--- /dev/null
+++ b/System/Random/Random123/Types.hs
@@ -0,0 +1,193 @@
+{-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances #-}
+
+-- | Type synonyms and type classes for use in function and instance declarations.
+module System.Random.Random123.Types (
+    Array2,
+    Array4,
+    LimitedInteger(..),
+    Counter(..),
+    Word32Array(..),
+    Word64Array(..)
+    ) where
+
+import Data.Bits
+import Data.Word
+
+-- | Type synonym for a 2-element array.
+type Array2 a = (a, a)
+
+-- | Type synonym for a 4-element array.
+type Array4 a = (a, a, a, a)
+
+
+-- | Class of integers with more bits than in simple types yet having fixed limited size
+-- (unlike the built-in 'Integer').
+class LimitedInteger a where
+    -- | Creates an instance from an 'Integer' (which is truncated by modulus @2^'liBitSize'@).
+    liFromInteger :: Integer -> a
+    -- | Creates an 'Integer' in range @[0, 2^'liBitSize')@ from an instance.
+    liToInteger :: a -> Integer
+    -- | Returns the size of the information in the array.
+    liBitSize :: a -> Int
+
+
+array2FromInteger :: Bits a => Integer -> Array2 a
+array2FromInteger i = (x0, x1) where
+    x1 = fromInteger i
+    bits = bitSize x1 -- need this because cannot use 'a' type variable
+    x0 = fromInteger (i `shiftR` bits)
+
+array4FromInteger :: Bits a => Integer -> Array4 a
+array4FromInteger i = (x0, x1, x2, x3) where
+    x3 = fromInteger i
+    bits = bitSize x3 -- need this because cannot use 'a' type variable
+    x0 = fromInteger (i `shiftR` (bits * 3))
+    x1 = fromInteger (i `shiftR` (bits * 2))
+    x2 = fromInteger (i `shiftR` bits)
+
+array2ToInteger :: (Integral a, Bits a) => Array2 a -> Integer
+array2ToInteger (x0, x1) = x0' + x1' where
+    bits = bitSize x0
+    x0' = toInteger x0 `shiftL` bits
+    x1' = toInteger x1
+
+array4ToInteger :: (Integral a, Bits a) => Array4 a -> Integer
+array4ToInteger (x0, x1, x2, x3) = x0' + x1' + x2' + x3' where
+    bits = bitSize x0
+    x0' = toInteger x0 `shiftL` (bits * 3)
+    x1' = toInteger x1 `shiftL` (bits * 2)
+    x2' = toInteger x2 `shiftL` bits
+    x3' = toInteger x3
+
+-- Technically, Word32 and Word64 instances are identical,
+-- but I couldn't persuade GHC to compile them in generalized form
+-- (like "instance (Num a, Bits a, Integral a) => LimitedInteger (Array2 a)").
+
+instance LimitedInteger Word32 where
+    liFromInteger = fromInteger
+    liToInteger = toInteger
+    liBitSize = bitSize
+
+instance LimitedInteger (Array2 Word32) where
+    liFromInteger = array2FromInteger
+    liToInteger = array2ToInteger
+    liBitSize _ = bitSize (undefined :: Word32) * 2
+
+instance LimitedInteger (Array4 Word32) where
+    liFromInteger = array4FromInteger
+    liToInteger = array4ToInteger
+    liBitSize _ = bitSize (undefined :: Word32) * 4
+
+
+instance LimitedInteger Word64 where
+    liFromInteger = fromInteger
+    liToInteger = toInteger
+    liBitSize = bitSize
+
+instance LimitedInteger (Array2 Word64) where
+    liFromInteger = array2FromInteger
+    liToInteger = array2ToInteger
+    liBitSize _ = bitSize (undefined :: Word64) * 2
+
+instance LimitedInteger (Array4 Word64) where
+    liFromInteger = array4FromInteger
+    liToInteger = array4ToInteger
+    liBitSize _ = bitSize (undefined :: Word64) * 4
+
+
+-- | Class of CBRNG counters.
+class LimitedInteger a => Counter a where
+    -- | Skip ahead the given amount of steps.
+    skip :: Integer -> a -> a
+    skip i x = liFromInteger (liToInteger x + i)
+    -- | Increment the counter.
+    -- Usually this function is faster than @'skip' 1@.
+    increment :: a -> a
+    increment = skip 1
+
+
+instance (LimitedInteger (Array2 a), Ord a, Num a, Bounded a) => Counter (Array2 a) where
+    increment (c0, c1)
+        | c1 < maxBound = (c0, c1 + 1)
+        | otherwise = (c0 + 1, 0)
+
+instance (LimitedInteger (Array4 a), Ord a, Num a, Bounded a) => Counter (Array4 a) where
+    increment (c0, c1, c2, c3)
+        | c3 < maxBound = (c0, c1, c2, c3 + 1)
+        | c2 < maxBound = (c0, c1, c2 + 1, 0)
+        | c1 < maxBound = (c0, c1 + 1, 0, 0)
+        | otherwise = (c0 + 1, 0, 0, 0)
+
+
+-- | Class of objects allowing the extraction of 32-bit words from the given position.
+class Word32Array a where
+    -- | Returns a 'Word32' from a position in range @[0, 'numWords32' - 1)@.
+    getWord32 :: Int -> a -> Word32
+    -- | Number of 32-bit words in this array.
+    numWords32 :: a -> Int
+
+instance Word32Array (Array2 Word32) where
+    getWord32 0 (x0, x1) = x0
+    getWord32 1 (x0, x1) = x1
+    numWords32 _ = 2
+
+instance Word32Array (Array4 Word32) where
+    getWord32 0 (x0, x1, x2, x3) = x0
+    getWord32 1 (x0, x1, x2, x3) = x1
+    getWord32 2 (x0, x1, x2, x3) = x2
+    getWord32 3 (x0, x1, x2, x3) = x3
+    numWords32 _ = 4
+
+instance Word32Array (Array2 Word64) where
+    getWord32 0 (x0, x1) = fromIntegral (x0 `shiftR` 32)
+    getWord32 1 (x0, x1) = fromIntegral x0
+    getWord32 2 (x0, x1) = fromIntegral (x1 `shiftR` 32)
+    getWord32 3 (x0, x1) = fromIntegral x1
+    numWords32 _ = 4
+
+instance Word32Array (Array4 Word64) where
+    getWord32 0 (x0, x1, x2, x3) = fromIntegral (x0 `shiftR` 32)
+    getWord32 1 (x0, x1, x2, x3) = fromIntegral x0
+    getWord32 2 (x0, x1, x2, x3) = fromIntegral (x1 `shiftR` 32)
+    getWord32 3 (x0, x1, x2, x3) = fromIntegral x1
+    getWord32 4 (x0, x1, x2, x3) = fromIntegral (x2 `shiftR` 32)
+    getWord32 5 (x0, x1, x2, x3) = fromIntegral x2
+    getWord32 6 (x0, x1, x2, x3) = fromIntegral (x3 `shiftR` 32)
+    getWord32 7 (x0, x1, x2, x3) = fromIntegral x3
+    numWords32 _ = 8
+
+
+-- | Class of objects allowing the extraction of 64-bit words from a given position.
+class Word64Array a where
+    -- | Returns a 'Word64' from a position in range @[0, 'numWords64' - 1)@.
+    getWord64 :: Int -> a -> Word64
+    -- | Number of 64-bit words in this array.
+    numWords64 :: a -> Int
+
+
+instance Word64Array (Array2 Word32) where
+    getWord64 0 (x0, x1) = hi `shiftL` 32 + lo where
+        lo = fromIntegral x1 :: Word64
+        hi = fromIntegral x0 :: Word64
+    numWords64 _ = 1
+
+instance Word64Array (Array4 Word32) where
+    getWord64 0 (x0, x1, x2, x3) = hi `shiftL` 32 + lo where
+        lo = fromIntegral x1 :: Word64
+        hi = fromIntegral x0 :: Word64
+    getWord64 1 (x0, x1, x2, x3) = hi `shiftL` 32 + lo where
+        lo = fromIntegral x2 :: Word64
+        hi = fromIntegral x3 :: Word64
+    numWords64 _ = 2
+
+instance Word64Array (Array2 Word64) where
+    getWord64 0 (x0, x1) = x0
+    getWord64 1 (x0, x1) = x1
+    numWords64 _ = 2
+
+instance Word64Array (Array4 Word64) where
+    getWord64 0 (x0, x1, x2, x3) = x0
+    getWord64 1 (x0, x1, x2, x3) = x1
+    getWord64 2 (x0, x1, x2, x3) = x2
+    getWord64 3 (x0, x1, x2, x3) = x3
+    numWords64 _ = 4
diff --git a/test/TestPhilox.hs b/test/TestPhilox.hs
new file mode 100644
--- /dev/null
+++ b/test/TestPhilox.hs
@@ -0,0 +1,101 @@
+module TestPhilox (test_philox) where
+
+import Data.Word
+
+import Test.Framework (testGroup)
+import Test.Framework.Providers.HUnit
+import Test.HUnit
+
+import System.Random.Random123.Types
+import System.Random.Random123.Philox
+
+
+-- Reference results were obtained using Random123 v1.07
+
+
+-- Checking reference results for Philox-2x32-10
+
+test_philox2x32 = results @?= reference where
+    key = 123 :: Word32
+    rounds = 10
+    counters = [
+        (0x6f55b92a, 0xab1c5ea9),
+        (0x2e24118f, 0xf96cd57b),
+        (0x35c2faac, 0x229e22ea),
+        (0x71d82da5, 0xe7758e07),
+        (0xefdadeee, 0x58849b8f)]
+    reference = [
+        (0xc8a74eda, 0x43748166),
+        (0x0f2f49b3, 0x5440de8c),
+        (0x9b0b15f0, 0x62ad33f0),
+        (0x8e0d363a, 0xec949c27),
+        (0xef1c72c0, 0xabcdf6f6)]
+    results = map (philox2R rounds key) counters
+
+
+-- Checking reference results for Philox-4x32-10
+
+test_philox2x64 = results @?= reference where
+    key = (123, 456) :: Array2 Word32
+    rounds = 10
+    counters = [
+        (0x5bf34ff4, 0x98f9badb, 0x4f816cab, 0xe62bd835),
+        (0xdfe40202, 0xdf14b7de, 0x9f66a741, 0xcf4157f7),
+        (0x839ac18e, 0xa043ba06, 0x94fd2987, 0x7d176d4c),
+        (0xdbba4414, 0x4a0462e2, 0x74cf99d4, 0x68d78dad),
+        (0x694f3a06, 0xc113b26e, 0x6b7be5fe, 0x47e59594)]
+    reference = [
+        (0x01c5a89a, 0xc8783325, 0x6fcbdf46, 0x0e5e2c08),
+        (0xbdfdcee8, 0x2302ddcb, 0x0c6b097d, 0xfe5b1b6d),
+        (0x41852fa7, 0xca2e06e0, 0x421b06e6, 0x825db2b2),
+        (0x858a0b3f, 0x9c4ee5ae, 0x409bca71, 0x6174268f),
+        (0x1fb47ee3, 0x465c46e9, 0xac8a8a63, 0x770b5eb8)]
+    results = map (philox4R rounds key) counters
+
+
+-- Checking reference results for Philox-2x64-10
+
+test_philox4x32 = results @?= reference where
+    key = 123 :: Word64
+    rounds = 10
+    counters = [
+        (0xf5bd87c1f90bf000, 0xfdcf5569188d8000),
+        (0x1fcbd563f0fc8e00, 0x9eb6d45bd5096000),
+        (0x5baaae0aecd1e000, 0x53d0329123492c00),
+        (0x62ef71add3320800, 0x625c1fc53fd21000),
+        (0x83d3483f4f369000, 0x395f94432bdf0200)]
+    reference = [
+        (0x70aa5fafe6b960f0, 0xf0296d49836ec74d),
+        (0x5e6a58a58da65da7, 0xd58ae0056c4f6eb0),
+        (0x79feffb4c50d7a7e, 0x586c57e69f636783),
+        (0x9f0ef9aa1c02cbb3, 0x64d05e4f231859ea),
+        (0x529c1fe09c72783b, 0x6a3b27e993d5fd7b)]
+    results = map (philox2R rounds key) counters
+
+
+-- Checking reference results for Philox-4x64-10
+
+test_philox4x64 = results @?= reference where
+    key = (123, 456) :: Array2 Word64
+    rounds = 10
+    counters = [
+        (0x1d1fc5123d698500, 0x6a0fd3ccbc9d3800, 0xe93a590b53026000, 0x3b339cb99a06fa00),
+        (0x22f53249be020e00, 0xcfbd27dc0b0dc000, 0xbe8468a6b6ac3000, 0x0428398af2cfcac0),
+        (0xd07b0391ea202000, 0xc6bb23b0fb74c800, 0x62720dec16217800, 0xdd5d41a2c6c29800),
+        (0x4f9ffd1a930a3800, 0x41ee8eae5f157c00, 0x1e7ac9fcae5af500, 0x9e618d09ef001000),
+        (0x9712f6164d422800, 0xc04f3f2e6d87a800, 0x7e2369ed83cdac00, 0xdb986d520bfcc000)]
+    reference = [
+        (0xcf1ee3ae885c3928, 0x90a39b52bb92f018, 0x1e2c1e36cdafc6d5, 0x2068f5a63acafdbb),
+        (0x28b86429c0cc8d2f, 0xedb2c9aeaa6ef776, 0x7615c3e5815ea96c, 0xdcf5e8849a76aec0),
+        (0x775904470661a226, 0xeceefd4563c20c51, 0x5d842c642c640a6b, 0xd17f2f87ad2fa74e),
+        (0x00c263e4bc04441f, 0xaecb35c835e03874, 0x46bb918e4a85adb7, 0x6c5fd577457f6315),
+        (0x61ca2527c8acdb96, 0x87c6dd6227eb5256, 0x6901e9c532adea59, 0x9bd765a53cca4d5f)]
+    results = map (philox4R rounds key) counters
+
+
+test_philox = testGroup "Philox, comparison with the reference" [
+    testCase "2x32" test_philox2x32,
+    testCase "4x32" test_philox4x32,
+    testCase "2x64" test_philox2x64,
+    testCase "4x64" test_philox4x64
+    ]
diff --git a/test/TestRandomGen.hs b/test/TestRandomGen.hs
new file mode 100644
--- /dev/null
+++ b/test/TestRandomGen.hs
@@ -0,0 +1,58 @@
+-- | Test standard RandomGen interface of Random123-backed generators.
+module TestRandomGen (test_randomgen) where
+
+import Data.Word
+import System.Random
+
+import Test.Framework (testGroup)
+import Test.Framework.Providers.HUnit
+import Test.HUnit
+
+import System.Random.Random123.Types
+import System.Random.Random123.RandomGen
+
+
+-- Expected mean and standard deviation for a list of uniformly distributed random floats
+num_floats = 10000
+e_mean = 0.5
+e_var = 1 / 12
+e_std = sqrt e_var
+e_mean_std = e_std / sqrt (fromIntegral num_floats)
+e_std_std = sqrt ((e_var ** 2) / (fromIntegral num_floats - 1) * 2)
+
+-- Functions to calculate mean and stddev of a list (not very effective)
+mean xs = sum xs / fromIntegral (length xs)
+std xs = sqrt (sum xsquares / fromIntegral (length xs)) where
+    xmean = mean xs
+    xsquares = map (\x -> (x - xmean) ** 2) xs
+
+-- Check that mean and stddev of a generated list are inside [-5,5] sigma interval
+-- around their expected values (chance of failure ~1e-6).
+
+test_gen32_mean = (xmean < e_mean + 5 * e_std && xmean > e_mean - 5 * e_std) @?= True where
+    gen = mkCBRNG32 123456
+    floats = take num_floats (randoms gen :: [Float])
+    xmean = mean floats
+
+test_gen32_std = (xstd < e_std + 5 * e_std_std && xstd > e_std - 5 * e_std_std) @?= True where
+    gen = mkCBRNG32 123456
+    floats = take num_floats (randoms gen :: [Float])
+    xstd = std floats
+
+test_gen64_mean = (xmean < e_mean + 5 * e_std && xmean > e_mean - 5 * e_std) @?= True where
+    gen = mkCBRNG64 123456
+    floats = take num_floats (randoms gen :: [Float])
+    xmean = mean floats
+
+test_gen64_std = (xstd < e_std + 5 * e_std_std && xstd > e_std - 5 * e_std_std) @?= True where
+    gen = mkCBRNG64 123456
+    floats = take num_floats (randoms gen :: [Float])
+    xstd = std floats
+
+
+test_randomgen = testGroup "RandomGen" [
+    testCase "Default 32-bit (mean of array of floats)" test_gen32_mean,
+    testCase "Default 32-bit (std of array of floats)" test_gen32_std,
+    testCase "Default 64-bit (mean of array of floats)" test_gen64_mean,
+    testCase "Default 64-bit (std of array of floats)" test_gen64_std
+    ]
diff --git a/test/TestThreefry.hs b/test/TestThreefry.hs
new file mode 100644
--- /dev/null
+++ b/test/TestThreefry.hs
@@ -0,0 +1,102 @@
+module TestThreefry (test_threefry) where
+
+import Data.Word
+
+import Test.Framework (testGroup)
+import Test.Framework.Providers.HUnit
+import Test.HUnit
+
+import System.Random.Random123.Types
+import System.Random.Random123.Threefry
+
+
+-- Reference results were obtained using Random123 v1.07
+-- (with a minor bug fixed --- rotation constant order is broken in 20-th round)
+
+
+-- Checking reference results for Threefry-2x32-32
+
+test_threefry2x32 = results @?= reference where
+    key = (123, 456) :: Array2 Word32
+    rounds = 32
+    counters = [
+        (0x058741f8, 0x346b43e7),
+        (0xcedc5891, 0x2c5c1d28),
+        (0x5a086b94, 0x1acbdc33),
+        (0xa76cc7bf, 0x857c8f0f),
+        (0xad8d9e57, 0x01b1d9fa)]
+    reference = [
+        (0x2d4c5ff6, 0x76375d6e),
+        (0x78b73f35, 0xea905a45),
+        (0xfde30fed, 0x9c104974),
+        (0x15861c3d, 0x293ba61f),
+        (0x5033c8d1, 0x7b92151a)]
+    results = map (threefry2R rounds key) counters
+
+
+-- Checking reference results for Threefry-4x32-72
+
+test_threefry2x64 = results @?= reference where
+    key = (123, 456, 789, 101112) :: Array4 Word32
+    rounds = 72
+    counters = [
+        (0x589c8db6, 0x19d662ad, 0x3e63b1e5, 0x25dad2c5),
+        (0x403512be, 0x370c9a86, 0x66d4c418, 0x92ed3769),
+        (0x1920eb77, 0x2d6340fe, 0xa2f618e3, 0xb8e4aacf),
+        (0x66005aa2, 0xf70ecfdf, 0xe38e29c5, 0xcbbd54d2),
+        (0x295e45f0, 0xa3d266cd, 0xb102152d, 0x2f57b507)]
+    reference = [
+        (0xcc07f462, 0x18cbe434, 0x97b800fe, 0xc515a3c1),
+        (0x67bf63ec, 0x08274d21, 0x18557796, 0x36706c15),
+        (0x428a06be, 0xbf1caad7, 0x25e51e44, 0x89e4bd74),
+        (0xbfff9163, 0x4106e304, 0x03157e56, 0xebfbd11c),
+        (0x5a1f4a9c, 0x1725d096, 0x2b561b17, 0xeb9b4807)]
+    results = map (threefry4R rounds key) counters
+
+
+-- Checking reference results for Threefry-2x64-32
+
+test_threefry4x32 = results @?= reference where
+    key = (123, 456) :: Array2 Word64
+    rounds = 32
+    counters = [
+        (0x097a4f5083c77380, 0x51794d6039c75400),
+        (0x65918ba6ccfe6400, 0x3d91c69276916c00),
+        (0xdfe3269c92f1f800, 0xe6a0ab67f2207800),
+        (0x6240b418d521f000, 0x40299ed82854f400),
+        (0x9acc98590f691000, 0x89df228bd6bae800)]
+    reference = [
+        (0xa6102e76d20f3382, 0x44dbf36431f9e9ee),
+        (0x0da48a45f9295d0e, 0x95d3b41c67e0f4c2),
+        (0x050a8dfccae89041, 0x76c474fad535292d),
+        (0xea84579be191a8fc, 0x3236c9527cba73e9),
+        (0x31f9c11860715320, 0x7ba946b5076efd08)]
+    results = map (threefry2R rounds key) counters
+
+
+-- Checking reference results for Threefry-4x64-72
+
+test_threefry4x64 = results @?= reference where
+    key = (123, 456, 789, 101112) :: Array4 Word64
+    rounds = 72
+    counters = [
+        (0x916c0b160f927800, 0x52eb051cf7d14400, 0xbb685306834af800, 0x472bc9ebd7c67000),
+        (0x61b7386b8fea1800, 0x8e3c63aaaf6f0000, 0x32a99c88983d4400, 0x0e6631cb0b255a00),
+        (0x5ac82c4303a38400, 0xe3b2d6d630f00000, 0xb0fd6567c0918800, 0x103c4caced3ad200),
+        (0x8df94ae6b44d9000, 0x6a20b71e020c1800, 0x7a7554df0c355000, 0x0ab535e581250880),
+        (0x2f8ec42369683200, 0xda1197831643b000, 0xe1e589e32f32c800, 0x0b7a9b0053df1480)]
+    reference = [
+        (0x8d72c51e3e02c62c, 0xf343d5bdd4428602, 0xc3769f920f6bb846, 0x68f81b2a1276a4e1),
+        (0x333f98bad6286630, 0xbd1330b5f841bc3a, 0x95f3aab12571ae68, 0xbc2f487dd4b9d63c),
+        (0xebec496e8c1198ab, 0xdd7ea52e93dbf166, 0x6894711ca28c7c12, 0x5817ba9c9f36f1bc),
+        (0x892f196d93952843, 0x4341d023d4fe64f3, 0x72bd9fba2c6ed9c3, 0x5f7affd1dd0b449f),
+        (0xc91f23d22b34be06, 0x8ddbf89963b13e67, 0x230bb28bfb3f4b3c, 0xefaa712baaa3dafb)]
+    results = map (threefry4R rounds key) counters
+
+
+test_threefry = testGroup "Threefry, comparison with the reference" [
+    testCase "2x32" test_threefry2x32,
+    testCase "4x32" test_threefry4x32,
+    testCase "2x64" test_threefry2x64,
+    testCase "4x64" test_threefry4x64
+    ]
diff --git a/test/TestTypeclasses.hs b/test/TestTypeclasses.hs
new file mode 100644
--- /dev/null
+++ b/test/TestTypeclasses.hs
@@ -0,0 +1,116 @@
+-- | Test standard RandomGen interface of Random123-backed generators.
+module TestTypeclasses (test_typeclasses) where
+
+import Data.Word
+
+import Test.Framework (testGroup)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+
+import System.Random.Random123.Types
+
+
+test_li1x32_bijection initial = initial == mapped initial where
+    types = initial :: Word32
+    mapped = liFromInteger . liToInteger
+
+test_li2x32_bijection initial = initial == mapped initial where
+    types = initial :: Array2 Word32
+    mapped = liFromInteger . liToInteger
+
+test_li4x32_bijection initial = initial == mapped initial where
+    types = initial :: Array4 Word32
+    mapped = liFromInteger . liToInteger
+
+test_li1x64_bijection initial = initial == mapped initial where
+    types = initial :: Word64
+    mapped = liFromInteger . liToInteger
+
+test_li2x64_bijection initial = initial == mapped initial where
+    types = initial :: Array2 Word64
+    mapped = liFromInteger . liToInteger
+
+test_li4x64_bijection initial = initial == mapped initial where
+    types = initial :: Array4 Word64
+    mapped = liFromInteger . liToInteger
+
+
+test_li1x32_bitsize li = liToInteger li < 2 ^ liBitSize li where
+    types = li :: Word32
+
+test_li2x32_bitsize li = liToInteger li < 2 ^ liBitSize li where
+    types = li :: Array2 Word32
+
+test_li4x32_bitsize li = liToInteger li < 2 ^ liBitSize li where
+    types = li :: Array4 Word32
+
+test_li1x64_bitsize li = liToInteger li < 2 ^ liBitSize li where
+    types = li :: Word64
+
+test_li2x64_bitsize li = liToInteger li < 2 ^ liBitSize li where
+    types = li :: Array2 Word64
+
+test_li4x64_bitsize li = liToInteger li < 2 ^ liBitSize li where
+    types = li :: Array4 Word64
+
+
+test_ctr2x32_skip ctr i = liFromInteger (liToInteger ctr + i) == skip i ctr where
+    types = (ctr :: Array2 Word32, i :: Integer)
+
+test_ctr4x32_skip ctr i = liFromInteger (liToInteger ctr + i) == skip i ctr where
+    types = (ctr :: Array4 Word32, i :: Integer)
+
+test_ctr2x64_skip ctr i = liFromInteger (liToInteger ctr + i) == skip i ctr where
+    types = (ctr :: Array2 Word64, i :: Integer)
+
+test_ctr4x64_skip ctr i = liFromInteger (liToInteger ctr + i) == skip i ctr where
+    types = (ctr :: Array4 Word64, i :: Integer)
+
+
+test_ctr2x32_increment ctr = increment ctr == skip 1 ctr where
+    types = ctr :: Array2 Word32
+
+test_ctr4x32_increment ctr = increment ctr == skip 1 ctr where
+    types = ctr :: Array4 Word32
+
+test_ctr2x64_increment ctr = increment ctr == skip 1 ctr where
+    types = ctr :: Array2 Word64
+
+test_ctr4x64_increment ctr = increment ctr == skip 1 ctr where
+    types = ctr :: Array4 Word64
+
+
+
+test_typeclasses = testGroup "Typeclasses" [
+    testGroup "LimitedInteger" [
+        testGroup "liFromInteger . liToInteger == id" [
+            testProperty "1x32" test_li1x32_bijection,
+            testProperty "2x32" test_li2x32_bijection,
+            testProperty "4x32" test_li4x32_bijection,
+            testProperty "1x64" test_li1x64_bijection,
+            testProperty "2x64" test_li2x64_bijection,
+            testProperty "4x64" test_li4x64_bijection
+            ],
+        testGroup "liToInteger < 2 ^ liBitSize" [
+            testProperty "1x32" test_li1x32_bitsize,
+            testProperty "2x32" test_li2x32_bitsize,
+            testProperty "4x32" test_li4x32_bitsize,
+            testProperty "1x64" test_li1x64_bitsize,
+            testProperty "2x64" test_li2x64_bitsize,
+            testProperty "4x64" test_li4x64_bitsize
+            ]
+        ],
+    testGroup "Counter" [
+        testGroup "skip behaves like the default implementation" [
+            testProperty "2x32" test_ctr2x32_skip,
+            testProperty "4x32" test_ctr4x32_skip,
+            testProperty "2x64" test_ctr2x64_skip,
+            testProperty "4x64" test_ctr4x64_skip
+            ],
+        testGroup "increment == skip 1" [
+            testProperty "2x32" test_ctr2x32_increment,
+            testProperty "4x32" test_ctr4x32_increment,
+            testProperty "2x64" test_ctr2x64_increment,
+            testProperty "4x64" test_ctr4x64_increment
+            ]
+        ]
+    ]
diff --git a/test/hlint.sh b/test/hlint.sh
new file mode 100644
--- /dev/null
+++ b/test/hlint.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+hlint ../System/Random -i "Use camelCase"
+hlint ./ -i "Use camelCase"
diff --git a/test/profile.sh b/test/profile.sh
new file mode 100644
--- /dev/null
+++ b/test/profile.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+rm profile
+rm profile.hi
+rm profile.o
+
+ghc -i../ -prof -fprof-auto -rtsopts -O2 profile.hs
+./profile +RTS -p
+
+cat profile.prof
diff --git a/test/test.hs b/test/test.hs
new file mode 100644
--- /dev/null
+++ b/test/test.hs
@@ -0,0 +1,15 @@
+import Test.Framework (defaultMain)
+
+import TestThreefry (test_threefry)
+import TestPhilox (test_philox)
+import TestTypeclasses (test_typeclasses)
+import TestRandomGen (test_randomgen)
+
+tests = [
+    test_threefry,
+    test_philox,
+    test_typeclasses,
+    test_randomgen
+    ]
+
+main = defaultMain tests
diff --git a/test/test.sh b/test/test.sh
new file mode 100644
--- /dev/null
+++ b/test/test.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+runhaskell -i../ test.hs $@
diff --git a/test/test_perf.hs b/test/test_perf.hs
new file mode 100644
--- /dev/null
+++ b/test/test_perf.hs
@@ -0,0 +1,90 @@
+import Data.Word
+
+import Criterion.Main
+import Criterion.Config
+
+import System.Random.Random123.Threefry
+import System.Random.Random123.Misc
+import System.Random.Random123.Types
+import System.Random.Random123
+import System.Random
+
+rng_iterations = 10000
+bijection_iterations = 10000
+
+testfunc rng = sum (take rng_iterations (randoms rng :: [Float]))
+
+test_rng_default 32 key = testfunc (mkCBRNG32 key)
+test_rng_default 64 key = testfunc (mkCBRNG64 key)
+
+data Bijection =
+    Philox4x32 | Philox4x64 | Threefry4x32 | Threefry4x64 |
+    Philox2x32 | Philox2x64 | Threefry2x32 | Threefry2x64
+
+key1x32 key = liFromInteger key :: Word32
+key2x32 key = liFromInteger key :: Array2 Word32
+key2x64 key = liFromInteger key :: Array2 Word64
+key1x64 key = liFromInteger key :: Word64
+key4x32 key = liFromInteger key :: Array4 Word32
+key4x64 key = liFromInteger key :: Array4 Word64
+
+test_rng_custom 32 Philox4x32 key = testfunc (mkCustomCBRNG32 philox4 $ key2x32 key)
+test_rng_custom 32 Philox4x64 key = testfunc (mkCustomCBRNG32 philox4 $ key2x64 key)
+test_rng_custom 64 Philox4x32 key = testfunc (mkCustomCBRNG64 philox4 $ key2x32 key)
+test_rng_custom 64 Philox4x64 key = testfunc (mkCustomCBRNG64 philox4 $ key2x64 key)
+test_rng_custom 32 Threefry4x32 key = testfunc (mkCustomCBRNG32 threefry4 $ key4x32 key)
+test_rng_custom 32 Threefry4x64 key = testfunc (mkCustomCBRNG32 threefry4 $ key4x64 key)
+test_rng_custom 64 Threefry4x32 key = testfunc (mkCustomCBRNG64 threefry4 $ key4x32 key)
+test_rng_custom 64 Threefry4x64 key = testfunc (mkCustomCBRNG64 threefry4 $ key4x64 key)
+
+
+-- Normalizing iteration number to make sure every bijection
+-- processes the same amount of data.
+test_bijection2x32 Philox2x32 key =
+    apply_ (philox2 $ key1x32 key) bijection_iterations (liFromInteger 0)
+test_bijection2x32 Threefry2x32 key =
+    apply_ (threefry2 $ key2x32 key) bijection_iterations (liFromInteger 0)
+test_bijection4x32 Philox4x32 key =
+    apply_ (philox4 $ key2x32 key) (bijection_iterations `div` 2) (liFromInteger 0)
+test_bijection4x32 Threefry4x32 key =
+    apply_ (threefry4 $ key4x32 key) (bijection_iterations `div` 2) (liFromInteger 0)
+test_bijection2x64 Philox2x64 key =
+    apply_ (philox2 $ key1x64 key) (bijection_iterations `div` 2) (liFromInteger 0)
+test_bijection2x64 Threefry2x64 key =
+    apply_ (threefry2 $ key2x64 key) (bijection_iterations `div` 2) (liFromInteger 0)
+test_bijection4x64 Philox4x64 key =
+    apply_ (philox4 $ key2x64 key) (bijection_iterations `div` 4) (liFromInteger 0)
+test_bijection4x64 Threefry4x64 key =
+    apply_ (threefry4 $ key4x64 key) (bijection_iterations `div` 4) (liFromInteger 0)
+
+
+myConfig = defaultConfig {
+    cfgPerformGC = ljust True,
+    cfgReport = ljust "test_perf.html" }
+
+main = defaultMainWith myConfig (return ()) [
+    bgroup "Bijections" [
+        bench "Philox-2x32" $ nf (test_bijection2x32 Philox2x32) 101,
+        bench "Philox-4x32" $ nf (test_bijection4x32 Philox4x32) 102,
+        bench "Philox-2x64" $ nf (test_bijection2x64 Philox2x64) 103,
+        bench "Philox-4x64" $ nf (test_bijection4x64 Philox4x64) 104,
+        bench "Threefry-2x32" $ nf (test_bijection2x32 Threefry2x32) 105,
+        bench "Threefry-4x32" $ nf (test_bijection4x32 Threefry4x32) 106,
+        bench "Threefry-2x64" $ nf (test_bijection2x64 Threefry2x64) 107,
+        bench "Threefry-4x64" $ nf (test_bijection4x64 Threefry4x64) 108
+        ],
+    bgroup "32-bit RNGs" [
+        bench "Default 32-bit" $ nf (test_rng_default 32) 1,
+        bench "Philox-4x32" $ nf (test_rng_custom 32 Philox4x32) 2,
+        bench "Philox-4x64" $ nf (test_rng_custom 32 Philox4x64) 3,
+        bench "Threefry-4x32" $ nf (test_rng_custom 32 Threefry4x32) 4,
+        bench "Threefry-4x64" $ nf (test_rng_custom 32 Threefry4x64) 5
+        ],
+    bgroup "64-bit RNGs" [
+        bench "Default 64-bit" $ nf (test_rng_default 64) 6,
+        bench "Philox-4x32" $ nf (test_rng_custom 64 Philox4x32) 7,
+        bench "Philox-4x64" $ nf (test_rng_custom 64 Philox4x64) 8,
+        bench "Threefry-4x32" $ nf (test_rng_custom 64 Threefry4x32) 9,
+        bench "Threefry-4x64" $ nf (test_rng_custom 64 Threefry4x64) 10
+        ]
+    ]
diff --git a/test/test_perf.sh b/test/test_perf.sh
new file mode 100644
--- /dev/null
+++ b/test/test_perf.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+rm test_perf
+rm test_perf.hi
+rm test_perf.o
+
+ghc -i../ -O2 test_perf.hs
+
+./test_perf
