diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Luke Hoersten
+
+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 Luke Hoersten 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/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/main/Main.hs b/main/Main.hs
new file mode 100644
--- /dev/null
+++ b/main/Main.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Main where
+
+import           Crypto.Hash              (Digest)
+import qualified Crypto.Hash              as CH
+import           Data.Byteable            (toBytes)
+import           Data.ByteString          (ByteString)
+import           Data.ByteString.Lazy     (fromStrict, toStrict)
+import           Options.Applicative
+import           System.Exit              (exitFailure, exitSuccess)
+
+import           System.IO.Streams        (InputStream, stdin, stdout,
+                                           withFileAsInput, write)
+import           System.IO.Streams.Crypto (hashInputStream)
+
+import qualified Data.Multihash.Base      as MB
+import qualified Data.Multihash.Digest    as MH
+
+
+data Termination = Null | Newline deriving (Show, Eq)
+data Config =
+    Config
+    { cfFile :: Maybe FilePath
+    , cfAlgo :: MH.HashAlgorithm
+    , cfBase :: MB.BaseEncoding
+    , cfHash :: Maybe MH.Digest
+    , cfTerm :: Termination
+    } deriving Show
+
+
+main :: IO ()
+main = do
+    -- TODO add file checking
+    config <- execParser opts
+    digest <- maybe (hashStdin config) (hashFile config) $ cfFile config
+    write (multihash config digest) stdout
+  where
+    hashStdin config = hash (cfAlgo config) stdin
+    hashFile config file = withFileAsInput file . hash $ cfAlgo config
+    multihash (Config _file algo base _hash term) =
+        Just . toStrict . line term . MB.encode base . MH.encode algo
+
+    line Null    = (<> "\0")
+    line Newline = (<> "\n")
+
+
+-- TODO add BLAKE support
+hash :: MH.HashAlgorithm -> InputStream ByteString -> IO MH.Digest
+hash MH.SHA1 is   = toBytes <$> (hashInputStream is :: IO (Digest CH.SHA1))
+hash MH.SHA256 is = toBytes <$> (hashInputStream is :: IO (Digest CH.SHA256))
+hash MH.SHA512 is = toBytes <$> (hashInputStream is :: IO (Digest CH.SHA512))
+hash MH.SHA3 is   = toBytes <$> (hashInputStream is :: IO (Digest CH.SHA3_256))
+hash MH.BLAKE2B _ = undefined
+hash MH.BLAKE2S _ = undefined
+
+
+opts :: ParserInfo Config
+opts = info
+       (helper <*> (Config
+                    <$> fileArg
+                    <*> algoOpt
+                    <*> baseOpt
+                    <*> checkOpt
+                    <*> nullTermFlag
+                   ))
+       (fullDesc
+        <> header "Generate a multihash for the given input."
+        <> progDesc "Hash from FILE or stdin if not given.")
+
+
+algoOpt :: Parser MH.HashAlgorithm
+algoOpt =
+    option auto
+    $  long "algorithm"
+    <> short 'a'
+    <> metavar "ALGO"
+    <> showDefault <> value MH.SHA256
+    <> help ("Hash algorithm to apply to input, ignored if checking hash " <> show ([minBound..] :: [MH.HashAlgorithm]))
+
+
+baseOpt :: Parser MB.BaseEncoding
+baseOpt =
+    option auto
+    $  long "encoding"
+    <> short 'e'
+    <> metavar "ENCODING"
+    <> showDefault <> value MB.Base58
+    <> help ("Base encoding of output digest, ignored if checking hash " <> show ([minBound..] :: [MB.BaseEncoding]))
+
+
+checkOpt :: Parser (Maybe MH.Digest)
+checkOpt =
+    optional . option auto
+    $  long "check"
+    <> short 'c'
+    <> metavar "DIGEST"
+    <> help "Check for matching digest"
+
+
+nullTermFlag :: Parser Termination
+nullTermFlag =
+    flag Newline Null
+    $  long "print0"
+    <> short '0'
+    <> help "End filenames with NUL, for use with xargs"
+
+
+fileArg :: Parser (Maybe FilePath)
+fileArg = optional . argument str $ metavar "FILE"
diff --git a/multihash.cabal b/multihash.cabal
new file mode 100644
--- /dev/null
+++ b/multihash.cabal
@@ -0,0 +1,60 @@
+name:                multihash
+version:             0.1.0
+synopsis:            Multihash library and CLI executable
+description:         Multihash is a protocol for encoding the hash algorithm
+                     and digest length at the start of the digest.
+                     More information available at https:\/\/github.com\/jbenet\/multihash\/.
+                     .
+                     Base32 encoding, Blake hashing, and file checking still to be added.
+                     .
+license:             BSD3
+license-file:        LICENSE
+author:              Luke Hoersten
+maintainer:          luke@hoersten.org
+copyright:           2015 Luke Hoersten
+category:            Data
+build-type:          Simple
+cabal-version:       >=1.10
+
+source-repository head
+  type: git
+  location: git://github.com/LukeHoersten/multihash.git
+
+
+library
+  exposed-modules: Data.Multihash.Base
+                 , Data.Multihash.Digest
+                 , System.IO.Streams.Crypto
+
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+  build-depends: attoparsec        >= 0.12 && < 0.13
+               , base              >= 4.8  && < 4.9
+               -- , base32-bytestring >= 0.2  && < 0.3
+               , base58-bytestring >= 0.1  && < 0.2
+               , base64-bytestring >= 1.0  && < 1.1
+               , bytestring        >= 0.10 && < 0.11
+               , cryptohash        >= 0.11 && < 0.12
+               , hex               >= 0.1  && < 0.2
+               , io-streams        >= 1.2  && < 1.3
+
+
+executable multihash
+  main-is:           Main.hs
+  hs-source-dirs:    main
+  default-language:  Haskell2010
+  ghc-options:       -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2 -fdicts-cheap
+                     -fno-warn-orphans -fno-warn-unused-do-bind -rtsopts
+
+  build-depends: base                 >= 4.8  && < 4.9
+               -- , base32-bytestring >= 0.2  && < 0.3
+               , base64-bytestring    >= 1.0  && < 1.1
+               , base58-bytestring    >= 0.1  && < 0.2
+               , byteable             >= 0.1  && < 0.2
+               , bytestring           >= 0.10 && < 0.11
+               , cryptohash           >= 0.11 && < 0.12
+               , hex                  >= 0.1  && < 0.2
+               , io-streams           >= 1.2  && < 1.3
+               , multihash            >= 0.1  && < 0.2
+               , optparse-applicative >= 0.11 && < 0.12
diff --git a/src/Data/Multihash/Base.hs b/src/Data/Multihash/Base.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Multihash/Base.hs
@@ -0,0 +1,33 @@
+module Data.Multihash.Base where
+
+-- import qualified Data.ByteString.Base32      as B32
+import qualified Data.ByteString.Base58      as B58
+import qualified Data.ByteString.Base64.Lazy as B64
+import           Data.ByteString.Lazy        (ByteString, fromStrict, toStrict)
+import           Data.Either                 (Either)
+import qualified Data.Hex                    as B16
+
+
+-- TODO add Base32 encoding
+data BaseEncoding
+    = Base2  -- ^ Raw binary encoding
+    | Base16 -- ^ Hexadecimal encoding
+    | Base58 -- ^ Bitcoin encoding
+    | Base64
+    deriving (Show, Read, Eq, Enum, Bounded)
+
+
+encode :: BaseEncoding -> ByteString -> ByteString
+encode Base2  = id
+encode Base16 = B16.hex
+-- encode Base32 = B32.encode
+encode Base58 = fromStrict . B58.encodeBase58 B58.bitcoinAlphabet . toStrict
+encode Base64 = B64.encode
+
+
+decode :: BaseEncoding -> ByteString -> Either String ByteString
+decode Base2  = return . id
+decode Base16 = B16.unhex
+-- decode Base32 = B32.decode
+decode Base58 = maybe (Left "Failed to parse") (Right . fromStrict) . B58.decodeBase58 B58.bitcoinAlphabet . toStrict
+decode Base64 = B64.decode
diff --git a/src/Data/Multihash/Digest.hs b/src/Data/Multihash/Digest.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Multihash/Digest.hs
@@ -0,0 +1,76 @@
+module Data.Multihash.Digest where
+
+
+import           Data.Attoparsec.ByteString (Parser, parseOnly)
+import qualified Data.Attoparsec.ByteString as A
+import qualified Data.ByteString            as BS
+import           Data.ByteString.Builder    (Builder, byteString,
+                                             toLazyByteString)
+import qualified Data.ByteString.Builder    as BB
+import qualified Data.ByteString.Lazy       as BL
+import           Data.Monoid                ((<>))
+import           Data.Word                  (Word8)
+
+
+data MultihashDigest =
+    MultihashDigest
+    { algorithm :: !HashAlgorithm
+    , lenght    :: !Length
+    , digest    :: !Digest
+    } deriving (Show, Eq)
+
+
+type Length = Int
+type Digest = BS.ByteString
+
+
+data HashAlgorithm
+    = SHA1
+    | SHA256
+    | SHA512
+    | SHA3
+    | BLAKE2B
+    | BLAKE2S
+    deriving (Show, Read, Eq, Enum, Bounded)
+
+
+fromCode :: Int -> HashAlgorithm
+fromCode 0x11 = SHA1
+fromCode 0x12 = SHA256
+fromCode 0x13 = SHA512
+fromCode 0x14 = SHA3
+fromCode 0x40 = BLAKE2B
+fromCode 0x41 = BLAKE2S
+fromCode _ = error "Unknown hash funciton code"
+
+
+toCode :: HashAlgorithm -> Int
+toCode SHA1    = 0x11
+toCode SHA256  = 0x12
+toCode SHA512  = 0x13
+toCode SHA3    = 0x14
+toCode BLAKE2B = 0x40
+toCode BLAKE2S = 0x41
+
+
+encode :: HashAlgorithm -> Digest -> BL.ByteString
+encode h d = toLazyByteString $ encoder h d
+
+
+encoder :: HashAlgorithm -> Digest -> Builder
+encoder h d
+    =  (BB.word8 . fromIntegral $ toCode h)
+    <> (BB.word8 . fromIntegral $ BS.length d)
+    <> byteString d
+
+
+decode :: BS.ByteString -> Either String MultihashDigest
+decode = parseOnly decoder
+
+
+decoder :: Parser MultihashDigest
+decoder = do
+    h <- (fromCode . fromIntegral <$> A.anyWord8)
+    l <- (fromIntegral <$> A.anyWord8)
+    d <- A.take l
+    return $ MultihashDigest h l d
diff --git a/src/System/IO/Streams/Crypto.hs b/src/System/IO/Streams/Crypto.hs
new file mode 100644
--- /dev/null
+++ b/src/System/IO/Streams/Crypto.hs
@@ -0,0 +1,12 @@
+module System.IO.Streams.Crypto where
+
+import           Control.Exception     (bracket)
+import           Crypto.Hash           (Digest, HashAlgorithm (..))
+import           Data.ByteString       (ByteString)
+import           Data.Multihash.Digest (decoder)
+import           System.IO.Streams     (InputStream, fold, inputFoldM)
+
+
+hashInputStream :: (HashAlgorithm h) => InputStream ByteString -> IO (Digest h)
+hashInputStream = fmap hashFinalize . fold update hashInit
+  where update ctx bs = hashUpdates ctx [bs]
