crypto-multihash (empty) → 0.1.0.0
raw patch · 7 files changed
+366/−0 lines, 7 filesdep +basedep +base58-bytestringdep +bytestringsetup-changed
Dependencies added: base, base58-bytestring, bytestring, containers, crypto-multihash, cryptonite, hspec, memory
Files
- LICENSE +30/−0
- README.md +37/−0
- Setup.hs +2/−0
- app/Main.hs +65/−0
- crypto-multihash.cabal +53/−0
- src/Crypto/Multihash.hs +129/−0
- test/Spec.hs +50/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Marcello Seri (c) 2016++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 Marcello Seri 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.
+ README.md view
@@ -0,0 +1,37 @@+# Crypto Multihash++Multihash library implemented on top of [cryptonite](https://hackage.haskell.org/package/cryptonite) cryptographic library. +Multihash is a protocol for encoding the hash algorithm and digest length at the start of the digest, see the official [multihash github page](https://github.com/jbenet/multihash/).++This library is still experimental and the api is not guaranteed stable. +I will increment the version number appropriately in case of breaking changes.++For the moment the library implements all the expected hashing algorithms with the exception of shake-128 and shake-256. A Multihash can be encoded in hex (`Base16`), bitcoin base58 (`Base58`) and base64 (`Base64`). ++The `Base32` encoding is not yet supported due to discrepancy between the encoding from `Data.ByteArray.Encoding` and the one appearing in the official multihash page.++# Usage++```{.haskell}+{-# LANGUAGE OverloadedStrings #-}++import Crypto.Multihash+import Data.ByteString (ByteString)++main = do+ let m = multihash SHA256 ("test"::ByteString)+ putStrLn $ "Base16: " ++ (encode Base16 m)+ putStrLn $ "Base58: " ++ (encode Base58 m)+ putStrLn $ "Base64: " ++ (encode Base64 m)+```++# Test++Some preliminary tests can be performed with `stack test`.+A simple example encoder is in `app/Main.hs`. You can run it on files (e.g. `echo -n test | stack exec mh -- somefile someotherfile`) or read data from the standard input (e.g. `echo -n test | stack exec mh -- -`)++# TODO+- Implement hash checker that takes some data and an encoded multihash and check that the multihash corresponds to the data (inferring automatically the appropriate hash function)+- Evaluate if throwing an error in the encode function is the wanted behaviour and anyway implement a safe version returning an Either type+- Implement `shake-128` and `shake-256` multihashes+- Implement `Base32` encoding
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Control.Monad+import Crypto.Multihash+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Data.List+import System.Console.GetOpt+import System.IO hiding (withFile)+import System.Environment+import System.Exit+import Text.Printf (printf)++main :: IO ()+main = do+ (args, files) <- getArgs >>= parse+ mapM_ printers files+ putStrLn "Done! Note: shake-128/256 and Base32 are not yet part of the library"+ +printer :: (HashAlgorithm a, Codable a, Show a) => a -> ByteString -> IO ()+printer alg bs = do+ let m = multihash alg bs+ putStrLn $ printf "Hash algorithm: %s" (show alg)+ putStrLn $ printf "Base16: %s" (encode Base16 m)+ -- Base32 missing+ putStrLn $ printf "Base58: %s" (encode Base58 m)+ putStrLn $ printf "Base64: %s" (encode Base64 m)+ putStrLn ""++printers f = do+ d <- withFile f+ putStrLn $ printf "Hashing %s\n" (if f == "-" then show d else show f)+ printer SHA1 d+ printer SHA256 d+ printer SHA512 d+ printer SHA3_512 d+ printer SHA3_384 d+ printer SHA3_256 d+ printer SHA3_224 d+ printer Blake2b_512 d+ printer Blake2s_256 d+ putStrLn ""+ where withFile f = if f == "-" then B.getContents else B.readFile f++data Flag = Help -- --help+ deriving (Eq,Ord,Enum,Show,Bounded)+ +flags = [Option [] ["help"] (NoArg Help) "Print this help message"]+ +parse argv = case getOpt Permute flags argv of+ (args,fs,[]) -> do+ let files = if null fs then ["-"] else fs+ if Help `elem` args+ then do hPutStrLn stderr (usageInfo header flags)+ exitWith ExitSuccess+ else return (nub (concatMap set args), files)+ + (_,_,errs) -> do+ hPutStrLn stderr (concat errs ++ usageInfo header flags)+ exitWith (ExitFailure 1)+ + where header = "Usage: mh [file ...]"+ set f = [f]
+ crypto-multihash.cabal view
@@ -0,0 +1,53 @@+name: crypto-multihash+version: 0.1.0.0+synopsis: Multihash library on top of cryptonite crypto library+description: Multihash is a protocol for encoding the hash algorithm+ and digest length at the start of the digest, see the official+ <https://github.com/jbenet/multihash/ multihash github>.+ Usage and additional informations are on README.md+homepage: https://github.com/mseri/crypto-multihash#readme+license: BSD3+license-file: LICENSE+author: Marcello Seri+maintainer: marcello.seri@gmail.com+copyright: 2016 Marcello Seri+category: Cryptography+stability: Experimental+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Crypto.Multihash+ build-depends: base >= 4.7 && < 5+ , base58-bytestring+ , bytestring+ , containers+ , cryptonite+ , memory+ default-language: Haskell2010++executable mh+ hs-source-dirs: app+ main-is: Main.hs+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends: base+ , bytestring+ , crypto-multihash+ default-language: Haskell2010++test-suite crypto-multihash-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ build-depends: base+ , bytestring+ , crypto-multihash+ , hspec+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/mseri/crypto-multihash
+ src/Crypto/Multihash.hs view
@@ -0,0 +1,129 @@+module Crypto.Multihash+ ( MultihashDigest+ , Base (..)+ , Codable (..)+ , HashAlgorithm (..)+ , SHA1(..)+ , SHA256(..)+ , SHA512(..)+ , SHA3_512(..)+ , SHA3_384(..)+ , SHA3_256(..)+ , SHA3_224(..)+ , Blake2b_512(..)+ , Blake2s_256(..)+ , encode+ , multihash+ , multihashlazy+ ) where++import Crypto.Hash (HashAlgorithm(..), Digest(..), hash, hashlazy)+import Crypto.Hash.Algorithms+import Crypto.Hash.IO+import Data.ByteArray (ByteArrayAccess, Bytes)+import qualified Data.ByteArray as BA+import qualified Data.ByteArray.Encoding as BE+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Base58 as B58+import Data.Word (Word8)+import Text.Printf (printf)++data Base = Base16 -- ^ Hex encoding+ | Base32 -- ^ Not implemented. For reasons that I did not investigate, the instance in Data.ByteArray produces output not conformant with the multihash spec.+ | Base58 -- ^ Bitcoin Base58 encoding, the one used also by IPFS+ | Base64 -- ^ Base64 encoding+ deriving (Eq)++-- | Multihash Digest container+data MultihashDigest a = MultihashDigest+ { getAlgorithm :: a+ , getLength :: Int+ , getDigest :: Digest a+ }++class Codable a where+ toCode :: a -> Int++instance Codable SHA1 where+ toCode SHA1 = 0x11+instance Codable SHA256 where+ toCode SHA256 = 0x12+instance Codable SHA512 where+ toCode SHA512 = 0x13+instance Codable SHA3_512 where+ toCode SHA3_512 = 0x14+instance Codable SHA3_384 where+ toCode SHA3_384 = 0x15+instance Codable SHA3_256 where+ toCode SHA3_256 = 0x16+instance Codable SHA3_224 where+ toCode SHA3_224 = 0x17+instance Codable Blake2b_512 where+ toCode Blake2b_512 = 0x40+instance Codable Blake2s_256 where+ toCode Blake2s_256 = 0x41++-- TODO: add shake-128/256 to Codable. Probably+-- fromCode 0x18 = Keccak_256+-- fromCode 0x19 = Keccak_512++instance Show (MultihashDigest a) where+ show (MultihashDigest _ _ d) = show d++multihashlazy :: (HashAlgorithm a, Codable a) => a -> BL.ByteString -> MultihashDigest a+multihashlazy alg bs = let digest = (hashlazy bs) + in MultihashDigest alg (BA.length digest) digest++multihash :: (HashAlgorithm a, Codable a, ByteArrayAccess bs) => a -> bs -> MultihashDigest a+multihash alg bs = let digest = (hash bs) + in MultihashDigest alg (BA.length digest) digest++-- | Encoder for 'Multihash'es.+-- Throws an error if the Multihash length field does not match the Digest length+encode :: (HashAlgorithm a, Codable a, Show a) => Base -> MultihashDigest a -> String+encode base (MultihashDigest alg len md) = if len == len'+ then map (toEnum . fromIntegral) fullDigestUnpacked+ else error $ printf "Corrupted %s MultihashDigest. Lenght is %d but should be %d." (show alg) len len'+ where+ len' :: Int+ len' = BA.length md++ fullDigestUnpacked :: [Word8]+ fullDigestUnpacked = BA.unpack $ encoder fullDigest+ where + encoder :: ByteArrayAccess a => a -> Bytes+ encoder bs = case base of+ --Base2 -> BA.convert $ bs+ Base16 -> BE.convertToBase BE.Base16 bs+ Base32 -> error "Base32 encoder not implemented"+ Base58 -> BA.convert $ B58.encodeBase58 B58.bitcoinAlphabet $ (BA.convert bs :: BS.ByteString)+ Base64 -> BE.convertToBase BE.Base64 bs++ fullDigest :: Bytes+ fullDigest = BA.pack [dHead, dSize] `BA.append` dTail+ where+ dHead :: Word8+ dHead = fromIntegral $ toCode alg+ dSize :: Word8+ dSize = fromIntegral $ len'+ dTail :: Bytes+ dTail = BA.convert md++--checkHash :: Base -> String -> ++-- data MultihashAlgorithm = S1 SHA1 | S256 SHA256 | S512 SHA512 +-- | S3512 SHA3_512 | S3384 SHA3_384 | S3256 SHA3_256 | S3224 SHA3_224+-- | B2b Blake2b_512 | B2s Blake2s_256++-- toCode :: MultihashAlgorithm -> Int+-- toCode (S1 SHA1) = 0x11+-- toCode (S256 SHA256) = 0x12+-- toCode (S512 SHA512) = 0x13+-- toCode (S3512 SHA3_512) = 0x14+-- toCode (S3384 SHA3_384) = 0x15+-- toCode (S3256 SHA3_256) = 0x16+-- toCode (S3224 SHA3_224) = 0x17+-- toCode (B2b Blake2b_512) = 0x40+-- toCode (B2s Blake2s_256) = 0x41+-- toCode _ = error "Multihash: unknown or unimplemented hash function code"
+ test/Spec.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}++import Crypto.Multihash+import Data.ByteString (ByteString)+import Test.Hspec+import Text.Printf (printf)++main :: IO ()+main = hspec $ do+ testMHEncoding SHA1 ( "1114a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"+ , "5dt9CqvXK9qs7vazf7k7ZRqe28VPTg"+ , "ERSpSo/lzLGbphxMCHPTkemHmC+70w==")+ testMHEncoding SHA256 ( "12209f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"+ , "QmZ5NmGeStdit7tV6gdak1F8FyZhPsfA843YS9f2ywKH6w"+ , "EiCfhtCBiEx9ZZov6qDFWtAVo79PGysLgizRXWwVsPAKCA==")+ testMHEncoding SHA512 ( "1340ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"+ , "8VxYhLL7s5BvLogtUGgNJ7DebZ5Ba9mG3izfc7v6o4RZ28x469vJaifa3TQ13Z9DycmAuJWnp7ErkZofm4rsMo78fQ"+ , "E0DuJrDdSvfnSaoajuPBCumSP2GJgHcuRz+IGaXUlA4NsnrBhfig4dX4T4i8iH/WexQ3MsMEzF+prY5vV/UAKKj/")+ testMHEncoding SHA3_512 ( "14409ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"+ , "8tXEcJyq2MCx27UHYbZxmte37ezBawV35QhfKPtq5QeSnX66q4DDf1cwMYUh2pUVbxdQgrDaSjbrPrfNxzvSSLQAtT"+ , "FECezghum6xJH6xcHRBGyhHXN7kqKy69k/AF17cQEQwKZ4KIFm5/vnlog6Ty6bPKn0hPUh0M5GQ0XMGuyWd5FJwU")+ testMHEncoding SHA3_384 ( "1530e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7ff0f1b41eecb9db3ff219007c4e097260d58621bd"+ , "G9LerVN7c3uUAAymoAGkCGZPn53PZi1SHwPJ2nznLp82jcM2M1KLwpfrZh3F1QRVG3f2"+ , "FTDlFtq7I7bjACaGNUMoJ4Cjrg3M8FVRzwKVF41/8PG0Huy52z/yGQB8TglyYNWGIb0=")+ testMHEncoding SHA3_256 ( "162036f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80"+ , "W1d9SeHn1mCnY3jZMs5YeqfFbwEnq5gQy1VDymGoPK28RD"+ , "FiA28ChYC7AsyCcqmgIPQgDjRuJ2rmZORe6AdFV04vWrgA==")+ testMHEncoding SHA3_224 ( "171c3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b"+ , "5daZNVMeTfSuCvu7rBKsFkzEMebnuGjNpos1ThF1c"+ , "Fxw3l78K+7/KSnu7p2AqK1UnRodlF6f5t84tsK57")+ testMHEncoding Blake2b_512 ( "4040a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a896c0411f38312e1d66e0bf16386c86a89bea572"+ , "S2XUqUDxz3MHMZtJpCZKt5oRjXHQ34gsyDBT759qNwoSP9rDBHVHxjQUQtXfExotxTqf4rMEXQkNmXE3N9mhoZX6wK"+ , "QECnEHnUKFPeom5FMAQzhnClOBS3gTf/vtB2A6QddqSDqpvDO1gvd9MKZebymolsBBHzgxLh1m4L8WOGyGqJvqVy")+ testMHEncoding Blake2s_256 ( "4120f308fc02ce9172ad02a7d75800ecfc027109bc67987ea32aba9b8dcc7b10150e"+ , "2UPuEK7FVakwP3yUak5jKQhZb6pgpbcqYoRZ2tDzgeCfVr5"+ , "QSDzCPwCzpFyrQKn11gA7PwCcQm8Z5h+oyq6m43MexAVDg==")+ where+ testMHEncoding :: (HashAlgorithm a, Codable a, Show a) => a + -> (String, String, String) -> SpecWith ()+ testMHEncoding alg (sm16, sm58, sm64) = do+ describe (printf "Encoding %s multihash" (show alg)) $ do+ it "returns the correct Base16 hash" $ + encode Base16 m `shouldBe` sm16+ it "returns the correct Base58 hash" $ + encode Base58 m `shouldBe` sm58+ it "returns the correct Base64 hash" $ + encode Base64 m `shouldBe` sm64+ where + m = multihash alg ("test"::ByteString)+