├── .ghci ├── .gitignore ├── .hgignore ├── LICENSE ├── README.md ├── Setup.hs ├── main └── Main.hs ├── multihash.cabal ├── src ├── Data │ └── Multihash │ │ ├── Base.hs │ │ └── Digest.hs └── System │ └── IO │ └── Streams │ └── Crypto.hs └── stack.yaml /.ghci: -------------------------------------------------------------------------------- 1 | :set -imain/ 2 | :set -package byteable 3 | :set -package optparse-applicative 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: regexp 2 | ~$ 3 | \#.*\#$ 4 | \.\# 5 | \.DS_Store$ 6 | \.cabal-sandbox/ 7 | \.hpc/ 8 | \.tix$ 9 | cabal\.sandbox\.config$ 10 | \.shake\.database$ 11 | dist/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Luke Hoersten 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Luke Hoersten nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This project is no longer maintained and has been archived.** 2 | 3 | # haskell-multihash 4 | 5 | [![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)](https://github.com/multiformats/multiformats) 6 | [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](https://webchat.freenode.net/?channels=%23ipfs) 7 | [![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 8 | 9 | > Multihash Haskell implementation 10 | 11 | This is the Haskell implementation of [multihash](https://github.com/multiformats/multihash), by [@LukeHoersten](https://github.com/LukeHoersten). 12 | 13 | ## Install 14 | 15 | This package can be installed with [stack](https://haskellstack.org/), a common Haskell package manager. 16 | 17 | `$ stack install` 18 | 19 | ## Usage 20 | 21 | TODO 22 | 23 | ## Maintainers 24 | 25 | [@LukeHoersten](https://github.com/LukeHoersten). 26 | 27 | ## Contribute 28 | 29 | This package follows the Haskell [Haskell Package Versioning Policy](https://pvp.haskell.org). 30 | 31 | Contributions welcome: please [open an issue](https://github.com/multiformats/haskell-multihash/issues). 32 | 33 | Check out our [contributing document](https://github.com/multiformats/multiformats/blob/master/contributing.md) for more information on how we work, and about contributing in general. Please be aware that all interactions related to multiformats are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md). 34 | 35 | Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification. 36 | 37 | ## License 38 | 39 | [Copyright](LICENSE) © 2015, Luke Hoersten 40 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | 3 | main = defaultMain 4 | -------------------------------------------------------------------------------- /main/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Main where 4 | 5 | import Crypto.Hash (Digest) 6 | import qualified Crypto.Hash as CH 7 | import Data.ByteArray (convert) 8 | import Data.ByteString (ByteString) 9 | import Data.ByteString.Lazy (toStrict) 10 | import Data.Monoid ((<>)) 11 | import Options.Applicative 12 | 13 | import System.IO.Streams (InputStream, stdin, stdout, withFileAsInput, write) 14 | import System.IO.Streams.Crypto (hashInputStream) 15 | 16 | import qualified Data.Multihash.Base as MB 17 | import qualified Data.Multihash.Digest as MH 18 | 19 | data Termination 20 | = Null 21 | | Newline 22 | deriving (Show, Eq) 23 | 24 | data Config = Config 25 | { cfFile :: Maybe FilePath 26 | , cfAlgo :: MH.HashAlgorithm 27 | , cfBase :: MB.BaseEncoding 28 | , cfHash :: Maybe MH.Digest 29 | , cfTerm :: Termination 30 | } deriving (Show) 31 | 32 | main :: IO () 33 | main 34 | -- TODO add file checking 35 | = do 36 | config <- execParser opts 37 | digest <- maybe (hashStdin config) (hashFile config) $ cfFile config 38 | write (multihash config digest) stdout 39 | where 40 | hashStdin config = hash (cfAlgo config) stdin 41 | hashFile config file = withFileAsInput file . hash $ cfAlgo config 42 | multihash (Config _file algo base _hash term) = 43 | Just . toStrict . line term . MB.encode base . MH.encode algo 44 | line Null = (<> "\0") 45 | line Newline = (<> "\n") 46 | 47 | hash :: MH.HashAlgorithm -> InputStream ByteString -> IO MH.Digest 48 | hash MH.SHA1 is = convert <$> (hashInputStream is :: IO (Digest CH.SHA1)) 49 | hash MH.SHA256 is = convert <$> (hashInputStream is :: IO (Digest CH.SHA256)) 50 | hash MH.SHA512 is = convert <$> (hashInputStream is :: IO (Digest CH.SHA512)) 51 | hash MH.SHA3 is = convert <$> (hashInputStream is :: IO (Digest CH.SHA3_256)) 52 | hash MH.BLAKE2B is = 53 | convert <$> (hashInputStream is :: IO (Digest CH.Blake2b_512)) 54 | hash MH.BLAKE2S is = 55 | convert <$> (hashInputStream is :: IO (Digest CH.Blake2s_256)) 56 | 57 | opts :: ParserInfo Config 58 | opts = 59 | info 60 | (helper <*> 61 | (Config <$> fileArg <*> algoOpt <*> baseOpt <*> checkOpt <*> nullTermFlag)) 62 | (fullDesc <> header "Generate a multihash for the given input." <> 63 | progDesc "Hash from FILE or stdin if not given.") 64 | 65 | algoOpt :: Parser MH.HashAlgorithm 66 | algoOpt = 67 | option auto $ 68 | long "algorithm" <> short 'a' <> metavar "ALGO" <> showDefault <> 69 | value MH.SHA256 <> 70 | help 71 | ("Hash algorithm to apply to input, ignored if checking hash " <> 72 | show ([minBound ..] :: [MH.HashAlgorithm])) 73 | 74 | baseOpt :: Parser MB.BaseEncoding 75 | baseOpt = 76 | option auto $ 77 | long "encoding" <> short 'e' <> metavar "ENCODING" <> showDefault <> 78 | value MB.Base58 <> 79 | help 80 | ("Base encoding of output digest, ignored if checking hash " <> 81 | show ([minBound ..] :: [MB.BaseEncoding])) 82 | 83 | checkOpt :: Parser (Maybe MH.Digest) 84 | checkOpt = 85 | optional . option auto $ 86 | long "check" <> short 'c' <> metavar "DIGEST" <> 87 | help "Check for matching digest" 88 | 89 | nullTermFlag :: Parser Termination 90 | nullTermFlag = 91 | flag Newline Null $ 92 | long "print0" <> short '0' <> 93 | help "End filenames with NUL, for use with xargs" 94 | 95 | fileArg :: Parser (Maybe FilePath) 96 | fileArg = optional . argument str $ metavar "FILE" 97 | -------------------------------------------------------------------------------- /multihash.cabal: -------------------------------------------------------------------------------- 1 | name: multihash 2 | version: 0.1.3 3 | synopsis: Multihash library and CLI executable 4 | description: Multihash is a protocol for encoding the hash algorithm 5 | and digest length at the start of the digest. 6 | More information available at the official 7 | . 8 | . 9 | Base32 encoding and file checking still to be added. 10 | . 11 | license: BSD3 12 | license-file: LICENSE 13 | author: Luke Hoersten 14 | maintainer: luke@hoersten.org 15 | copyright: 2015 Luke Hoersten 16 | category: Data 17 | build-type: Simple 18 | cabal-version: >=1.10 19 | homepage: https://github.com/LukeHoersten/multihash 20 | 21 | source-repository head 22 | type: git 23 | location: git://github.com/LukeHoersten/multihash.git 24 | 25 | 26 | library 27 | exposed-modules: Data.Multihash.Base 28 | , Data.Multihash.Digest 29 | , System.IO.Streams.Crypto 30 | 31 | hs-source-dirs: src 32 | default-language: Haskell2010 33 | 34 | build-depends: attoparsec >= 0.12 && < 0.14 35 | , base >= 4.7 && < 5.0 36 | -- , base32-bytestring >= 0.2 && < 0.3 37 | , base58-bytestring >= 0.1 && < 0.2 38 | , base64-bytestring >= 1.0 && < 1.1 39 | , bytestring >= 0.10 && < 0.11 40 | , cryptonite >= 0.24 && < 0.25 41 | , hex >= 0.1 && < 0.2 42 | , io-streams >= 1.2 && < 1.6 43 | 44 | 45 | executable multihash 46 | main-is: Main.hs 47 | hs-source-dirs: main 48 | default-language: Haskell2010 49 | ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2 -fdicts-cheap 50 | -fno-warn-orphans -fno-warn-unused-do-bind -rtsopts 51 | 52 | build-depends: base >= 4.7 && < 5.0 53 | -- , base32-bytestring >= 0.2 54 | , base64-bytestring >= 1.0 && < 1.1 55 | , base58-bytestring >= 0.1 && < 0.2 56 | , byteable >= 0.1 && < 0.2 57 | , bytestring >= 0.10 && < 0.11 58 | , cryptonite >= 0.24 && < 0.25 59 | , hex >= 0.1 && < 0.2 60 | , io-streams >= 1.2 && < 1.6 61 | , multihash >= 0.1 && < 0.2 62 | , optparse-applicative >= 0.11 && < 0.15 63 | , memory >= 0.14.11 && < 0.15.0 64 | -------------------------------------------------------------------------------- /src/Data/Multihash/Base.hs: -------------------------------------------------------------------------------- 1 | module Data.Multihash.Base where 2 | 3 | -- import qualified Data.ByteString.Base32 as B32 4 | import qualified Data.ByteString.Base58 as B58 5 | import qualified Data.ByteString.Base64.Lazy as B64 6 | import Data.ByteString.Lazy (ByteString, fromStrict, toStrict) 7 | import Data.Either (Either) 8 | import qualified Data.Hex as B16 9 | 10 | -- TODO add Base32 encoding 11 | data BaseEncoding 12 | = Base2 -- ^ Raw binary encoding 13 | | Base16 -- ^ Hexadecimal encoding 14 | | Base58 -- ^ Bitcoin encoding 15 | | Base64 16 | deriving (Show, Read, Eq, Enum, Bounded) 17 | 18 | encode :: BaseEncoding -> ByteString -> ByteString 19 | encode Base2 = id 20 | encode Base16 = B16.hex 21 | -- encode Base32 = B32.encode 22 | encode Base58 = fromStrict . B58.encodeBase58 B58.bitcoinAlphabet . toStrict 23 | encode Base64 = B64.encode 24 | 25 | decode :: BaseEncoding -> ByteString -> Either String ByteString 26 | decode Base2 = return . id 27 | decode Base16 = B16.unhex 28 | -- decode Base32 = B32.decode 29 | decode Base58 = 30 | maybe (Left "Failed to parse") (Right . fromStrict) . 31 | B58.decodeBase58 B58.bitcoinAlphabet . toStrict 32 | decode Base64 = B64.decode 33 | -------------------------------------------------------------------------------- /src/Data/Multihash/Digest.hs: -------------------------------------------------------------------------------- 1 | module Data.Multihash.Digest where 2 | 3 | import Control.Applicative ((<$>)) 4 | import Data.Attoparsec.ByteString (Parser, parseOnly) 5 | import qualified Data.Attoparsec.ByteString as A 6 | import qualified Data.ByteString as BS 7 | import Data.ByteString.Builder (Builder, byteString, toLazyByteString) 8 | import qualified Data.ByteString.Builder as BB 9 | import qualified Data.ByteString.Lazy as BL 10 | import Data.Monoid ((<>)) 11 | 12 | data MultihashDigest = MultihashDigest 13 | { algorithm :: !HashAlgorithm 14 | , length :: !Length 15 | , digest :: !Digest 16 | } deriving (Show, Eq) 17 | 18 | type Length = Int 19 | 20 | type Digest = BS.ByteString 21 | 22 | data HashAlgorithm 23 | = SHA1 24 | | SHA256 25 | | SHA512 26 | | SHA3 27 | | BLAKE2B 28 | | BLAKE2S 29 | deriving (Show, Read, Eq, Enum, Bounded) 30 | 31 | fromCode :: Int -> HashAlgorithm 32 | fromCode 0x11 = SHA1 33 | fromCode 0x12 = SHA256 34 | fromCode 0x13 = SHA512 35 | fromCode 0x14 = SHA3 36 | fromCode 0x40 = BLAKE2B 37 | fromCode 0x41 = BLAKE2S 38 | fromCode _ = error "Unknown hash function code" 39 | 40 | toCode :: HashAlgorithm -> Int 41 | toCode SHA1 = 0x11 42 | toCode SHA256 = 0x12 43 | toCode SHA512 = 0x13 44 | toCode SHA3 = 0x14 45 | toCode BLAKE2B = 0x40 46 | toCode BLAKE2S = 0x41 47 | 48 | encode :: HashAlgorithm -> Digest -> BL.ByteString 49 | encode h d = toLazyByteString $ encoder h d 50 | 51 | encoder :: HashAlgorithm -> Digest -> Builder 52 | encoder h d = 53 | (BB.word8 . fromIntegral $ toCode h) <> 54 | (BB.word8 . fromIntegral $ BS.length d) <> 55 | byteString d 56 | 57 | decode :: BS.ByteString -> Either String MultihashDigest 58 | decode = parseOnly decoder 59 | 60 | decoder :: Parser MultihashDigest 61 | decoder = do 62 | h <- (fromCode . fromIntegral <$> A.anyWord8) 63 | l <- (fromIntegral <$> A.anyWord8) 64 | d <- A.take l 65 | return $ MultihashDigest h l d 66 | -------------------------------------------------------------------------------- /src/System/IO/Streams/Crypto.hs: -------------------------------------------------------------------------------- 1 | module System.IO.Streams.Crypto where 2 | 3 | import Crypto.Hash 4 | ( Digest 5 | , HashAlgorithm(..) 6 | , hashFinalize 7 | , hashInit 8 | , hashUpdate 9 | ) 10 | import Data.ByteString (ByteString) 11 | import System.IO.Streams (InputStream, fold) 12 | 13 | hashInputStream :: (HashAlgorithm h) => InputStream ByteString -> IO (Digest h) 14 | hashInputStream = fmap hashFinalize . fold hashUpdate hashInit 15 | 16 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-10.3 2 | packages: 3 | - '.' 4 | extra-deps: 5 | - base58-bytestring-0.1.0 6 | --------------------------------------------------------------------------------