packages feed

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 view
@@ -1,5 +1,12 @@ # Crypto Multihash +[![Build Status](https://travis-ci.org/mseri/crypto-multihash.svg?branch=master)](https://travis-ci.org/mseri/crypto-multihash)+![Hackage](https://img.shields.io/hackage/v/crypto-multihash.svg)+![Hackage Dependencies](https://img.shields.io/hackage-deps/v/crypto-multihash.svg)+![Haskell Programming Language](https://img.shields.io/badge/language-Haskell-blue.svg)+![BSD3 License](http://img.shields.io/badge/license-BSD3-brightgreen.svg)++ 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)