diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Sam Rijs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
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/hwsl2-bytevector.cabal b/hwsl2-bytevector.cabal
new file mode 100644
--- /dev/null
+++ b/hwsl2-bytevector.cabal
@@ -0,0 +1,25 @@
+name:                hwsl2-bytevector
+version:             0.1.0.0
+synopsis:            A hashed byte-vector based on algebraic hashes and finger trees
+-- description:
+homepage:            https://github.com/srijs/hwsl2-haskell-bytevector
+license:             MIT
+license-file:        LICENSE
+author:              Sam Rijs
+maintainer:          srijs@airpost.net
+-- copyright:
+category:            Data
+build-type:          Simple
+-- extra-source-files:
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     Data.Hash.SL2.ByteVector
+  -- other-modules:
+  -- other-extensions:
+  build-depends:       base >=4.6 && <5,
+                       hwsl2 >=0.3.2,
+                       fingertree >=0.1,
+                       bytestring >=0.10
+  hs-source-dirs:      src
+  default-language:    Haskell2010
diff --git a/src/Data/Hash/SL2/ByteVector.hs b/src/Data/Hash/SL2/ByteVector.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Hash/SL2/ByteVector.hs
@@ -0,0 +1,180 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+module Data.Hash.SL2.ByteVector
+  ( ByteVector()
+    -- * Construction
+  , singleton, fromList
+    -- * Composition
+  , empty, append
+  , cons, snoc
+    -- * Introspection
+  , null, hash, length
+    -- * Deconstruction
+  , ViewL(..), viewl, viewl1
+  , ViewR(..), viewr, viewr1
+  , splitBefore, splitAt
+    -- * Transformation
+  , reverse, map
+    -- * Reduction
+  , foldl, foldr, foldl', foldr'
+  ) where
+
+import Prelude hiding (concat, null, reverse, map, length, foldl, foldr, splitAt)
+
+import Data.Coerce
+import qualified Data.Foldable as Foldable
+import Data.Monoid
+
+import Data.Hash.SL2 (Hash)
+import qualified Data.Hash.SL2 as Hash
+import Data.Hash.SL2.Chunk (Chunk(..), fromByteString)
+
+import Data.FingerTree (FingerTree, (<|), (|>))
+import qualified Data.FingerTree as FingerTree
+
+import qualified Data.ByteString as ByteString
+
+data Measure = Measure
+  { getHash :: !Hash
+  , getLength :: !Integer
+  }
+
+instance Monoid Measure where
+  mempty = Measure mempty 0
+  mappend a b = Measure (getHash a <> getHash b) (getLength a + getLength b)
+  mconcat as = Measure (mconcat $ fmap getHash as) (sum $ fmap getLength as)
+
+newtype MeasuredChunk = MeasuredChunk
+  { getChunk :: Chunk
+  } deriving (Eq, Ord)
+
+instance FingerTree.Measured Measure MeasuredChunk where
+  measure b = Measure (getChunkHash $ coerce b) (fromIntegral . ByteString.length . getChunkBytes $ coerce b)
+
+newtype ByteVector = ByteVector
+  { getTree :: FingerTree Measure MeasuredChunk
+  } deriving (Monoid)
+
+instance Eq ByteVector where
+  a == b = getHash (FingerTree.measure $ getTree a) == getHash (FingerTree.measure $ getTree b)
+
+instance Ord ByteVector where
+  compare a b = compare (getHash . FingerTree.measure $ getTree a) (getHash . FingerTree.measure $ getTree b)
+
+-- | /O(1)/ The empty vector. Alias for 'mempty'.
+empty :: ByteVector
+empty = ByteVector mempty
+
+-- | /O(1)/ Creates a vector from a single chunk.
+singleton :: Chunk -> ByteVector
+singleton = ByteVector . FingerTree.singleton . coerce
+
+-- | /O(n)/ Creates a vector from a list of chunks.
+fromList :: [Chunk] -> ByteVector
+fromList = ByteVector . FingerTree.fromList . fmap coerce
+
+-- | /O(n)/ Concatenates two vectors together. Alias for 'mappend'.
+append :: ByteVector -> ByteVector -> ByteVector
+append a b = ByteVector $ getTree a <> getTree b
+
+-- | /O(1)/ Adds a chunk to the left end of the vector.
+cons :: Chunk -> ByteVector -> ByteVector
+cons c v = ByteVector $ coerce c <| coerce v
+
+-- | /O(1)/ Adds a chunk to the right end of the vector.
+snoc :: ByteVector -> Chunk -> ByteVector
+snoc v c = ByteVector $ coerce v |> coerce c
+
+-- | /O(1)/ Is this the empty vector?
+null :: ByteVector -> Bool
+null v = getHash measure == Hash.unit && getLength measure == 0
+  where measure = FingerTree.measure (getTree v)
+
+-- | /O(1)/ Returns the hash of the vector.
+hash :: ByteVector -> Hash
+hash = getHash . FingerTree.measure . getTree
+
+-- | /O(1)/ Returns the number of bytes in the vector.
+length :: ByteVector -> Integer
+length = getLength . FingerTree.measure . getTree
+
+data ViewL = EmptyL | MostL Chunk ByteVector
+  deriving (Eq, Ord)
+
+-- | /O(1)/ Creates a view of the left end of the vector.
+viewl :: ByteVector -> ViewL
+viewl v = case FingerTree.viewl (getTree v) of
+  FingerTree.EmptyL -> EmptyL
+  most FingerTree.:< rest -> MostL (coerce most) (coerce rest)
+
+-- | /O(1)/ Creates a view of the left end of the vector.
+--          The single chunk is guaranteed to never be empty.
+viewl1 :: ByteVector -> ViewL
+viewl1 v = case viewl v of
+  EmptyL -> EmptyL
+  MostL most rest | mempty == most -> viewl1 rest
+                  | otherwise -> MostL most rest
+
+data ViewR = EmptyR | MostR ByteVector Chunk
+  deriving (Eq, Ord)
+
+-- | /O(1)/ Creates a view of the right end of the vector.
+viewr :: ByteVector -> ViewR
+viewr v = case FingerTree.viewr (getTree v) of
+  FingerTree.EmptyR -> EmptyR
+  rest FingerTree.:> most -> MostR (coerce rest) (coerce most)
+
+-- | /O(1)/ Creates a view of the right end of the vector.
+--          The single chunk is guaranteed to never be empty.
+viewr1 :: ByteVector -> ViewR
+viewr1 v = case viewr v of
+  EmptyR -> EmptyR
+  MostR rest most | mempty == most -> viewr1 rest
+                  | otherwise -> MostR rest most
+
+-- | /O(log(min(i,n-i)))/ Splits the vector at the chunk where the accumulated
+--   length equals the provided integer /i/.
+--   The resulting left side has a length smaller /i/.
+splitBefore :: Integer -> ByteVector -> (ByteVector, ByteVector)
+splitBefore i v = (coerce left, coerce right)
+  where (left, right) = FingerTree.split (\m -> getLength m >= i) (getTree v)
+
+-- | /O(log(min(i,n-i)))/ Splits the vector at the byte where the accumulated
+--   length equals the provided integer /i/.
+--   The resulting left side has a length of /min(i,n)/.
+--
+--   This is potentially less efficient than 'splitBefore' because it has to
+--   re-hash parts of the vector.
+splitAt :: Integer -> ByteVector -> (ByteVector, ByteVector)
+splitAt i v = splitView (viewl right)
+  where (left, right) = splitBefore i v
+        splitView EmptyL = (left, right)
+        splitView (MostL most rest) = (snoc left (fromByteString left'), cons (fromByteString right') rest)
+          where (left', right') = ByteString.splitAt (fromIntegral $ i - length left) . getChunkBytes $ coerce most
+
+-- | /O(n)/ Folds the vector from left to right.
+foldl :: (a -> Chunk -> a) -> a -> ByteVector -> a
+foldl f a v = Foldable.foldl (\a' -> f a' . getChunk) a (getTree v)
+
+-- | /O(n)/ Folds the vector from right to left.
+foldr :: (Chunk -> a -> a) -> a -> ByteVector -> a
+foldr f a v = Foldable.foldr (\c a' -> f (getChunk c) a') a (getTree v)
+
+-- | /O(n)/ Folds the vector from left to right, with strict application.
+foldl' :: (a -> Chunk -> a) -> a -> ByteVector -> a
+foldl' f a v = Foldable.foldl' (\a' -> f a' . getChunk) a (getTree v)
+
+-- | /O(n)/ Folds the vector from right to left, with strict application.
+foldr' :: (Chunk -> a -> a) -> a -> ByteVector -> a
+foldr' f a v = Foldable.foldr' (\c a' -> f (getChunk c) a') a (getTree v)
+
+-- | /O(n)/ Applies a function to every chunk in the vector.
+map :: (Chunk -> Chunk) -> ByteVector -> ByteVector
+map f v = ByteVector (FingerTree.fmap' (coerce . f . getChunk) $ coerce v)
+
+-- | /O(n)/ Reverses the vector.
+reverse :: ByteVector -> ByteVector
+reverse v = ByteVector $ FingerTree.reverse $ FingerTree.fmap' (coerce . reverseChunk . coerce) (getTree v)
+  where reverseChunk c = let r = ByteString.reverse (getChunkBytes c) in Chunk (Hash.hash r) r
