crypto-multihash 0.1.0.0 → 0.2.0.0
raw patch · 5 files changed
+191/−59 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Crypto.Multihash: hashBlockSize :: a -> Int
- Crypto.Multihash: hashDigestSize :: a -> Int
- Crypto.Multihash: hashInternalContextSize :: a -> Int
- Crypto.Multihash: hashInternalFinalize :: Ptr (Context a) -> Ptr (Digest a) -> IO ()
- Crypto.Multihash: hashInternalInit :: Ptr (Context a) -> IO ()
- Crypto.Multihash: hashInternalUpdate :: Ptr (Context a) -> Ptr Word8 -> Word32 -> IO ()
+ Crypto.Multihash: Base2 :: Base
+ Crypto.Multihash: checkMultihash :: ByteArrayAccess bs => ByteString -> bs -> Either String Bool
+ Crypto.Multihash: encode' :: (HashAlgorithm a, Codable a, Show a) => Base -> MultihashDigest a -> String
+ Crypto.Multihash: instance GHC.Classes.Eq a => GHC.Classes.Eq (Crypto.Multihash.MultihashDigest a)
+ Crypto.Multihash: instance GHC.Show.Show Crypto.Multihash.Base
- Crypto.Multihash: encode :: (HashAlgorithm a, Codable a, Show a) => Base -> MultihashDigest a -> String
+ Crypto.Multihash: encode :: (HashAlgorithm a, Codable a, Show a) => Base -> MultihashDigest a -> Either String String
Files
- README.md +47/−6
- app/Main.hs +8/−6
- crypto-multihash.cabal +2/−1
- src/Crypto/Multihash.hs +131/−43
- test/Spec.hs +3/−3
README.md view
@@ -1,5 +1,12 @@ # Crypto Multihash +[](https://travis-ci.org/mseri/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/). @@ -17,21 +24,55 @@ import Crypto.Multihash import Data.ByteString (ByteString)+import Data.ByteString.Char8 (pack) main = do- let m = multihash SHA256 ("test"::ByteString)+ let v = "test"::ByteString+ let m = multihash SHA256 v putStrLn $ "Base16: " ++ (encode Base16 m) putStrLn $ "Base58: " ++ (encode Base58 m) putStrLn $ "Base64: " ++ (encode Base64 m)+ + let h = encode' Base58 m+ let h8 = (Data.ByteString.Char8.pack h)+ checkMultihash h8 v+ -- Right True ``` # 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 -- -`)+Some preliminary tests can be performed with `stack test`. +A simple example encoder is in `app/Main.hs`. +You can run it on files++```{.bash}+echo -n test | stack exec mh -- somefile someotherfile+```++or read data from the standard input ++```{.bash}+echo -n test | stack exec mh -- -`+```++# Contribution++1. Fork repository+2. Do some changes+3. Create pull request+4. Wait for CI build and review++You can use stack to build the project: `stack build`++To run tests: `stack test`+ # 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++- ~~Improve documentation~~+- ~~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~~+- Add testing for the newly introduced checker and for raised exceptions+- Add multihash checker into the cli example - Implement `shake-128` and `shake-256` multihashes-- Implement `Base32` encoding+- Implement `Base32` encoding waiting for https://github.com/jbenet/multihash/issues/31 to be resolved)
app/Main.hs view
@@ -2,7 +2,6 @@ module Main where -import Control.Monad import Crypto.Multihash import Data.ByteString (ByteString) import qualified Data.ByteString as B@@ -15,7 +14,7 @@ main :: IO () main = do- (args, files) <- getArgs >>= parse+ (_args, files) <- getArgs >>= parse mapM_ printers files putStrLn "Done! Note: shake-128/256 and Base32 are not yet part of the library" @@ -23,12 +22,13 @@ printer alg bs = do let m = multihash alg bs putStrLn $ printf "Hash algorithm: %s" (show alg)- putStrLn $ printf "Base16: %s" (encode Base16 m)+ putStrLn $ printf "Base16: %s" (encode' Base16 m) -- Base32 missing- putStrLn $ printf "Base58: %s" (encode Base58 m)- putStrLn $ printf "Base64: %s" (encode Base64 m)+ putStrLn $ printf "Base58: %s" (encode' Base58 m)+ putStrLn $ printf "Base64: %s" (encode' Base64 m) putStrLn "" +printers :: FilePath -> IO () printers f = do d <- withFile f putStrLn $ printf "Hashing %s\n" (if f == "-" then show d else show f)@@ -47,8 +47,10 @@ data Flag = Help -- --help deriving (Eq,Ord,Enum,Show,Bounded) +flags :: [OptDescr Flag] flags = [Option [] ["help"] (NoArg Help) "Print this help message"]- ++parse :: [String] -> IO ([Flag], [String]) parse argv = case getOpt Permute flags argv of (args,fs,[]) -> do let files = if null fs then ["-"] else fs
crypto-multihash.cabal view
@@ -1,5 +1,5 @@ name: crypto-multihash-version: 0.1.0.0+version: 0.2.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@@ -20,6 +20,7 @@ library hs-source-dirs: src exposed-modules: Crypto.Multihash+ ghc-options: -Wall -fwarn-tabs -fno-warn-name-shadowing -fwarn-pointless-pragmas build-depends: base >= 4.7 && < 5 , base58-bytestring , bytestring
src/Crypto/Multihash.hs view
@@ -1,8 +1,37 @@+-- |+-- Module : Crypto.Multihash+-- License : BSD3+-- Maintainer : Marcello Seri <marcello.seri@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+-- Multihash library built on top of haskell 'cryptonite' crypto package+-- 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 poroposal github repo>.+--+-- The library re-exports the needed types and typclasses from 'Crypto.Hash.Algorithms'+-- namely 'HashAlgorithm', 'SHA1', 'SHA256', 'SHA512', 'SHA3_512', 'SHA3_384',+-- 'SHA3_256', 'SHA3_224', 'Blake2b_512', 'Blake2s_256'. +--+-- For additional informations refer to the README.md or the+-- <https://github.com/mseri/crypto-multihash gihub repository>.+--+{-# LANGUAGE OverloadedStrings #-}+ module Crypto.Multihash- ( MultihashDigest+ ( -- * Multihash Types+ MultihashDigest , Base (..) , Codable (..)- , HashAlgorithm (..)+ -- * Multihash helpers+ , encode+ , encode'+ , multihash+ , multihashlazy+ , checkMultihash+ -- * Re-exported types+ , HashAlgorithm , SHA1(..) , SHA256(..) , SHA512(..)@@ -12,37 +41,40 @@ , SHA3_224(..) , Blake2b_512(..) , Blake2s_256(..)- , encode- , multihash- , multihashlazy ) where -import Crypto.Hash (HashAlgorithm(..), Digest(..), hash, hashlazy)+import Crypto.Hash (HashAlgorithm(..), Digest, hash, hashlazy) import Crypto.Hash.Algorithms-import Crypto.Hash.IO+--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.Char8 as C import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Base58 as B58+import Data.List (findIndex) 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+-- | 'Base' usable to encode the digest +data Base = Base2 -- ^ Binary form+ | Base16 -- ^ Hex encoding+ | Base32 -- ^ Not yet implemented. Waiting for <https://github.com/jbenet/multihash/issues/31 this issue to resolve>+ | Base58 -- ^ Bitcoin base58 encoding | Base64 -- ^ Base64 encoding- deriving (Eq)+ deriving (Show, Eq) -- | Multihash Digest container data MultihashDigest a = MultihashDigest- { getAlgorithm :: a- , getLength :: Int- , getDigest :: Digest a- }+ { getAlgorithm :: a -- ^ hash algorithm+ , getLength :: Int -- ^ hash lenght+ , getDigest :: Digest a -- ^ binary digest data+ } deriving (Eq) +-- | 'Codable' hash algorithms are the algorithms supported for multihashing class Codable a where+ -- | Returns the first byte for the head of the multihash digest toCode :: a -> Int instance Codable SHA1 where@@ -71,34 +103,40 @@ instance Show (MultihashDigest a) where show (MultihashDigest _ _ d) = show d +-- | Helper to multihash a lazy 'ByteString' using a supported hash algorithm. multihashlazy :: (HashAlgorithm a, Codable a) => a -> BL.ByteString -> MultihashDigest a multihashlazy alg bs = let digest = (hashlazy bs) in MultihashDigest alg (BA.length digest) digest +-- | Helper to multihash a 'ByteArrayAccess' (e.g. a 'ByteString') using a supported hash algorithm. 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++-- | Safe encoder for 'MultihashDigest'.+encode :: (HashAlgorithm a, Codable a, Show a) => Base -> MultihashDigest a -> Either String 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'+ then do+ d <- fullDigestUnpacked+ return $ map (toEnum . fromIntegral) d+ else Left $ 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+ fullDigestUnpacked :: Either String [Word8]+ fullDigestUnpacked = do+ d <- encoder fullDigest+ return $ BA.unpack d where - encoder :: ByteArrayAccess a => a -> Bytes+ encoder :: ByteArrayAccess a => a -> Either String 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+ Base2 -> return $ BA.convert $ bs+ Base16 -> return $ BE.convertToBase BE.Base16 bs+ Base32 -> Left "Base32 encoder not implemented"+ Base58 -> return $ BA.convert $ B58.encodeBase58 B58.bitcoinAlphabet $ (BA.convert bs :: BS.ByteString)+ Base64 -> return $ BE.convertToBase BE.Base64 bs fullDigest :: Bytes fullDigest = BA.pack [dHead, dSize] `BA.append` dTail@@ -110,20 +148,70 @@ dTail :: Bytes dTail = BA.convert md ---checkHash :: Base -> String -> +-- | Encoder for 'MultihashDigest'.+-- Throws an error if the 'MultihashDigest' length field does not match the 'Digest' length.+encode' :: (HashAlgorithm a, Codable a, Show a) => Base -> MultihashDigest a -> String+encode' base md = + case encode base md of+ Right enc -> enc+ Left err -> error err --- 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+-- | Check the correctness of an encoded 'MultihashDigest' against the data it+-- is supposed to have hashed, passed as a 'ByteArrayAccess' (e.g. a 'BinaryString').+checkMultihash :: ByteArrayAccess bs => BS.ByteString -> bs -> Either String Bool+checkMultihash hash unahshedData = do+ base <- getBase hash+ mhd <- convertFromBase base hash+ m <- getBinaryEncodedMultihash mhd unahshedData+ -- Hacky... think to a different approach+ return (C.pack m == mhd) --- 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"+-- Helpers++maybeToEither :: l -> Maybe r -> Either l r+maybeToEither _ (Just res) = Right res+maybeToEither err _ = Left err++-- | Convert a 'ByteString' from a 'Base' into a 'BinaryString' in 'Base2'.+convertFromBase :: Base -> BS.ByteString -> Either String BS.ByteString+convertFromBase b bs = case b of+ Base2 -> Left "This is not supposed to happen"+ Base16 -> BE.convertFromBase BE.Base16 bs+ Base32 -> Left "Base32 decoder not implemented"+ Base58 -> do+ dec <- maybeToEither "Base58 decoding error" (B58.decodeBase58 B58.bitcoinAlphabet bs)+ return (BA.convert dec)+ Base64 -> BE.convertFromBase BE.Base64 bs++-- | Infer the 'Base' encoding function from an encoded 'BinaryString' representing +-- a 'MultihashDigest'.+getBase :: BS.ByteString -> Either String Base+getBase h+ | startWiths h ["1114", "1220", "1340", "1440", "1530", "1620", "171c", "4040", "4120"] = Right Base16+ | startWiths h ["5d", "Qm", "8V", "8t", "G9", "W1", "5d", "S2", "2U"] = Right Base58+ | startWiths h ["ER", "Ei", "E0", "FE", "FT", "Fi", "Fx", "QE", "QS"] = Right Base64+ | otherwise = Left "Unable to infer an encoding"+ where startWiths h ls = any (flip BS.isPrefixOf h) ls++-- | Infer the hash function from an unencoded 'BinaryString' representing +-- a 'MultihashDigest' and uses it to binary encode the data in a 'MultihashDigest'.+getBinaryEncodedMultihash :: ByteArrayAccess bs => BS.ByteString -> bs -> Either String String+getBinaryEncodedMultihash mhd uh = let bitOne = head $ BS.unpack mhd in+ case findIndex ((==) bitOne) hashCodes of+ Just 0 -> rs SHA1 uh+ Just 1 -> rs SHA256 uh+ Just 2 -> rs SHA512 uh+ Just 3 -> rs SHA3_512 uh+ Just 4 -> rs SHA3_384 uh+ Just 5 -> rs SHA3_256 uh+ Just 6 -> rs SHA3_224 uh+ Just 7 -> rs Blake2b_512 uh+ Just 8 -> rs Blake2s_256 uh+ Just _ -> Left "This should be impossible"+ Nothing -> Left "Impossible to infer the appropriate hash from the header"+ where + rs alg = encode Base2 . multihash alg+ hashCodes :: [Word8]+ hashCodes = map (\i -> fromIntegral i) + ([0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x40, 0x41]::[Int])+ mhdPrefix = flip BS.isPrefixOf mhd
test/Spec.hs view
@@ -40,11 +40,11 @@ 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+ encode' Base16 m `shouldBe` sm16 it "returns the correct Base58 hash" $ - encode Base58 m `shouldBe` sm58+ encode' Base58 m `shouldBe` sm58 it "returns the correct Base64 hash" $ - encode Base64 m `shouldBe` sm64+ encode' Base64 m `shouldBe` sm64 where m = multihash alg ("test"::ByteString)