packages feed

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 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)+