crypto-multihash 0.2.0.1 → 0.3.0.0
raw patch · 5 files changed
+156/−62 lines, 5 files
Files
- README.md +21/−8
- app/Main.hs +3/−3
- crypto-multihash.cabal +1/−1
- src/Crypto/Multihash.hs +45/−18
- test/Spec.hs +86/−32
README.md view
@@ -20,23 +20,36 @@ # Usage ```{.haskell}+-- in ghci `:set -XOverloadedStrings` {-# LANGUAGE OverloadedStrings #-} import Crypto.Multihash import Data.ByteString (ByteString)-import Data.ByteString.Char8 (pack) main = do 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+ putStrLn $ "Base16: " ++ (encode' Base16 m)+ -- You might need to specify the encoded string type+ putStrLn $ "Base58: " ++ (encode' Base58 m :: String)++ -- `encode` is the safe interface returning an `Either` type+ putStrLn $ "Base64: " ++ show (encode Base64 m :: Either String String)+ + let h = encode' Base58 m :: ByteString+ checkMultihash h v -- Right True++ -- There is also an unsafe version, as for encode+ checkMultihash' "whatever" v+ -- *** Exception: Unable to infer an encoding+ checkMultihash' "Eiwhatever" v+ -- *** Exception: base64: input: invalid length+ checkMultihash' "EiCfhtCBiEx9ZZov6qDFWtAVo79PGysLgizRXWwVsPA1CA==" v+ -- False+ checkMultihash' h v+ -- True ``` # Test@@ -72,7 +85,7 @@ - ~~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 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 waiting for https://github.com/jbenet/multihash/issues/31 to be resolved)
app/Main.hs view
@@ -22,10 +22,10 @@ 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 :: String) -- Base32 missing- putStrLn $ printf "Base58: %s" (encode' Base58 m)- putStrLn $ printf "Base64: %s" (encode' Base64 m)+ putStrLn $ printf "Base58: %s" (encode' Base58 m :: String)+ putStrLn $ printf "Base64: %s" (encode' Base64 m :: String) putStrLn "" printers :: FilePath -> IO ()
crypto-multihash.cabal view
@@ -1,5 +1,5 @@ name: crypto-multihash-version: 0.2.0.1+version: 0.3.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
src/Crypto/Multihash.hs view
@@ -30,6 +30,7 @@ , multihash , multihashlazy , checkMultihash+ , checkMultihash' -- * Re-exported types , HashAlgorithm , SHA1(..)@@ -54,6 +55,7 @@ import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Base58 as B58 import Data.List (elemIndex)+import Data.String (IsString(..)) import Data.Word (Word8) import Text.Printf (printf) @@ -104,27 +106,30 @@ show (MultihashDigest _ _ d) = show d -- | Helper to multihash a lazy 'BL.ByteString' using a supported hash algorithm.+-- Uses 'Crypto.Hash.hashlazy' for hashing. 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 'BS.ByteString') using a supported hash algorithm.+-- | Helper to multihash a 'ByteArrayAccess' (e.g. a 'BS.ByteString') using a +-- supported hash algorithm. Uses 'Crypto.Hash.hash' for hashing. 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 -- | 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'+encode :: (HashAlgorithm a, Codable a, Show a, IsString s) => + Base -> MultihashDigest a -> Either String s+encode base (MultihashDigest alg len md) = + if len == BA.length md 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+ return $ fromString $ map (toEnum . fromIntegral) d+ else + Left $ printf "Corrupted %s MultihashDigest: invalid length" (show alg) + where fullDigestUnpacked :: Either String [Word8] fullDigestUnpacked = do d <- encoder fullDigest@@ -145,30 +150,43 @@ dHead :: Word8 dHead = fromIntegral $ toCode alg dSize :: Word8- dSize = fromIntegral len'+ dSize = fromIntegral len dTail :: Bytes dTail = BA.convert md --- | Encoder for 'MultihashDigest'.+-- | Unsafe encoder for 'MultihashDigest'. -- Throws an error if there are encoding issues or the 'MultihashDigest' -- length field does not match the 'Digest' length.-encode' :: (HashAlgorithm a, Codable a, Show a) => Base -> MultihashDigest a -> String+encode' :: (HashAlgorithm a, Codable a, Show a, IsString s) + => Base -> MultihashDigest a -> s encode' base md = case encode base md of Right enc -> enc Left err -> error err --- | Check the correctness of an encoded 'MultihashDigest' against the data it--- is supposed to have hashed. Tha data is passed as a --- 'ByteArrayAccess' (e.g. a 'BS.BinaryString').+-- | Safely check the correctness of an encoded 'MultihashDigest' against the +-- corresponding data. Tha data is passed as a 'ByteArrayAccess' +-- (e.g. a 'BS.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)+ if badLength mhd + then + Left "Corrupted MultihasDigest: invalid length"+ else do+ m <- getBinaryEncodedMultihash mhd unahshedData+ return (C.pack m == mhd) +-- | Unsafe version of 'checkMultihash'. Throws on encoding/decoding errors +-- instead of returning an 'Either' type. +checkMultihash' :: ByteArrayAccess bs => BS.ByteString -> bs -> Bool+checkMultihash' hash unahshedData = + case checkMultihash hash unahshedData of+ Right ans -> ans+ Left err -> error err+ -- Helpers - These are not exported currently, and probably will never be. maybeToEither :: l -> Maybe r -> Either l r@@ -196,9 +214,18 @@ | otherwise = Left "Unable to infer an encoding" where startWiths h = any (`BS.isPrefixOf` h) +-- | Compares the lenght of the encoded 'MultihashDigest' with the encoded hash length.+-- Returns 'True' if the lengths are matching.+badLength :: ByteArrayAccess bs => bs -> Bool+badLength mh = + case BA.length mh of+ n | n <= 2 -> True+ n | BA.index mh 1 /= (fromIntegral n-2) -> True+ _ -> False+ -- | Infer the hash function from an unencoded 'BS.BinaryString' representing --- a 'MultihashDigest' and uses it to binary encode the data in a 'MultihashDigest'.-getBinaryEncodedMultihash :: ByteArrayAccess bs => BS.ByteString -> bs -> Either String String+-- a 'MultihashDigest' and uses it to binary encode the data in a 'MultihashDigest'.+getBinaryEncodedMultihash :: (ByteArrayAccess bs, IsString s) => BS.ByteString -> bs -> Either String s getBinaryEncodedMultihash mhd uh = let bitOne = head $ BS.unpack mhd in case elemIndex bitOne hashCodes of Just 0 -> rs SHA1 uh
test/Spec.hs view
@@ -1,43 +1,50 @@ {-# LANGUAGE OverloadedStrings #-} +import Control.Monad (zipWithM) import Crypto.Multihash-import Data.ByteString (ByteString)+import Data.ByteString (ByteString, pack) import Test.Hspec import Text.Printf (printf) +testString :: ByteString+testString = "test"++failTestString :: ByteString+failTestString = "test1"+ 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==")+ testMHEncoding SHA1 h1+ hashChecker SHA1 h1+ testMHEncoding SHA256 h2+ hashChecker SHA256 h2+ testMHEncoding SHA512 h3+ hashChecker SHA512 h3+ testMHEncoding SHA3_512 h4+ hashChecker SHA3_512 h4+ testMHEncoding SHA3_384 h5+ hashChecker SHA3_384 h5+ testMHEncoding SHA3_256 h6+ hashChecker SHA3_256 h6+ testMHEncoding SHA3_224 h7+ hashChecker SHA3_224 h7+ testMHEncoding Blake2b_512 h8+ hashChecker Blake2b_512 h8+ testMHEncoding Blake2s_256 h9+ hashChecker Blake2s_256 h9++ describe ("Fails correctly when") $ do+ it "checking a truncated multihash" $+ checkMultihash "1340ee26b0dd4af7e749aa1a8e" testString + `shouldBe` Left "Corrupted MultihasDigest: invalid length"+ it "checking an invalid multihash" $+ checkMultihash "dd4af7e749aa1a8e1340ee26b0" testString + `shouldBe` Left "Unable to infer an encoding" where testMHEncoding :: (HashAlgorithm a, Codable a, Show a) => a - -> (String, String, String) -> SpecWith ()- testMHEncoding alg (sm16, sm58, sm64) = do+ -> (ByteString, ByteString, ByteString) -> SpecWith ()+ testMHEncoding alg (sm16, sm58, sm64) = + let m = multihash alg testString in do describe (printf "Encoding %s multihash" (show alg)) $ do it "returns the correct Base16 hash" $ encode' Base16 m `shouldBe` sm16@@ -45,6 +52,53 @@ encode' Base58 m `shouldBe` sm58 it "returns the correct Base64 hash" $ encode' Base64 m `shouldBe` sm64- where - m = multihash alg ("test"::ByteString)+ + hashChecker :: (HashAlgorithm a, Codable a, Show a) => a + -> (ByteString, ByteString, ByteString) -> SpecWith ()+ hashChecker alg (e16, e58, e64) = do+ describe (printf "Using checkMultihash on %s hashes" (show alg)) $ do+ it "checks correctly Base16 hashes" $+ checkMultihash e16 testString `shouldBe` Right True+ it "fails correctly on Base16 hashes" $+ checkMultihash e16 failTestString `shouldBe` Right False+ it "checks correctly Base58 hashes" $+ checkMultihash e58 testString `shouldBe` Right True+ it "fails correctly on Base58 hashes" $+ checkMultihash e58 failTestString `shouldBe` Right False+ it "checks correctly Base64 hashes" $+ checkMultihash e64 testString `shouldBe` Right True+ it "fails correctly on Base64 hashes" $+ checkMultihash e64 failTestString `shouldBe` Right False++ -- array of triples of hashes of the string "test"+ (h1:h2:h3:h4:h5:h6:h7:h8:h9:[]) = + [+ ( "1114a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"+ , "5dt9CqvXK9qs7vazf7k7ZRqe28VPTg"+ , "ERSpSo/lzLGbphxMCHPTkemHmC+70w==")+ , ( "12209f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"+ , "QmZ5NmGeStdit7tV6gdak1F8FyZhPsfA843YS9f2ywKH6w"+ , "EiCfhtCBiEx9ZZov6qDFWtAVo79PGysLgizRXWwVsPAKCA==")+ , ( "1340ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"+ , "8VxYhLL7s5BvLogtUGgNJ7DebZ5Ba9mG3izfc7v6o4RZ28x469vJaifa3TQ13Z9DycmAuJWnp7ErkZofm4rsMo78fQ"+ , "E0DuJrDdSvfnSaoajuPBCumSP2GJgHcuRz+IGaXUlA4NsnrBhfig4dX4T4i8iH/WexQ3MsMEzF+prY5vV/UAKKj/")+ , ( "14409ece086e9bac491fac5c1d1046ca11d737b92a2b2ebd93f005d7b710110c0a678288166e7fbe796883a4f2e9b3ca9f484f521d0ce464345cc1aec96779149c14"+ , "8tXEcJyq2MCx27UHYbZxmte37ezBawV35QhfKPtq5QeSnX66q4DDf1cwMYUh2pUVbxdQgrDaSjbrPrfNxzvSSLQAtT"+ , "FECezghum6xJH6xcHRBGyhHXN7kqKy69k/AF17cQEQwKZ4KIFm5/vnlog6Ty6bPKn0hPUh0M5GQ0XMGuyWd5FJwU")+ , ( "1530e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7ff0f1b41eecb9db3ff219007c4e097260d58621bd"+ , "G9LerVN7c3uUAAymoAGkCGZPn53PZi1SHwPJ2nznLp82jcM2M1KLwpfrZh3F1QRVG3f2"+ , "FTDlFtq7I7bjACaGNUMoJ4Cjrg3M8FVRzwKVF41/8PG0Huy52z/yGQB8TglyYNWGIb0=")+ , ( "162036f028580bb02cc8272a9a020f4200e346e276ae664e45ee80745574e2f5ab80"+ , "W1d9SeHn1mCnY3jZMs5YeqfFbwEnq5gQy1VDymGoPK28RD"+ , "FiA28ChYC7AsyCcqmgIPQgDjRuJ2rmZORe6AdFV04vWrgA==")+ , ( "171c3797bf0afbbfca4a7bbba7602a2b552746876517a7f9b7ce2db0ae7b"+ , "5daZNVMeTfSuCvu7rBKsFkzEMebnuGjNpos1ThF1c"+ , "Fxw3l78K+7/KSnu7p2AqK1UnRodlF6f5t84tsK57")+ , ( "4040a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a896c0411f38312e1d66e0bf16386c86a89bea572"+ , "S2XUqUDxz3MHMZtJpCZKt5oRjXHQ34gsyDBT759qNwoSP9rDBHVHxjQUQtXfExotxTqf4rMEXQkNmXE3N9mhoZX6wK"+ , "QECnEHnUKFPeom5FMAQzhnClOBS3gTf/vtB2A6QddqSDqpvDO1gvd9MKZebymolsBBHzgxLh1m4L8WOGyGqJvqVy")+ , ( "4120f308fc02ce9172ad02a7d75800ecfc027109bc67987ea32aba9b8dcc7b10150e"+ , "2UPuEK7FVakwP3yUak5jKQhZb6pgpbcqYoRZ2tDzgeCfVr5"+ , "QSDzCPwCzpFyrQKn11gA7PwCcQm8Z5h+oyq6m43MexAVDg==")+ ]