{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-| Module : Haskoin.Crypto.Hash Copyright : No rights reserved License : MIT Maintainer : jprupp@protonmail.ch Stability : experimental Portability : POSIX Hashing functions and corresponding data types. Uses functions from the cryptonite library. -} module Haskoin.Crypto.Hash ( -- * Hashes Hash512(getHash512) , Hash256(getHash256) , Hash160(getHash160) , CheckSum32(getCheckSum32) , sha512 , sha256 , ripemd160 , sha1 , doubleSHA256 , addressHash , checkSum32 , hmac512 , hmac256 , split512 , join512 ) where import Control.DeepSeq import Crypto.Hash (RIPEMD160 (..), SHA1 (..), SHA256 (..), SHA512 (..), hashWith) import Crypto.MAC.HMAC (HMAC, hmac) import Data.Binary (Binary (..)) import Data.ByteArray (ByteArrayAccess) import qualified Data.ByteArray as BA import Data.ByteString (ByteString) import qualified Data.ByteString as BS import Data.ByteString.Short (ShortByteString) import qualified Data.ByteString.Short as BSS import qualified Data.Bytes.Get as Get import qualified Data.Bytes.Put as Put import Data.Bytes.Serial (Serial (..)) import Data.Either (fromRight) import Data.Hashable (Hashable) import Data.Serialize (Serialize (..)) import Data.String (IsString, fromString) import Data.String.Conversions (cs) import Data.Word (Word32) import GHC.Generics (Generic) import Haskoin.Util import Text.Read as R -- | 'Word32' wrapped for type-safe 32-bit checksums. newtype CheckSum32 = CheckSum32 { getCheckSum32 :: Word32 } deriving (Eq, Ord, Serial, Show, Read, Hashable, Generic, NFData) instance Serialize CheckSum32 where put = serialize get = deserialize instance Binary CheckSum32 where put = serialize get = deserialize -- | Type for 512-bit hashes. newtype Hash512 = Hash512 { getHash512 :: ShortByteString } deriving (Eq, Ord, Hashable, Generic, NFData) -- | Type for 256-bit hashes. newtype Hash256 = Hash256 { getHash256 :: ShortByteString } deriving (Eq, Ord, Hashable, Generic, NFData) -- | Type for 160-bit hashes. newtype Hash160 = Hash160 { getHash160 :: ShortByteString } deriving (Eq, Ord, Hashable, Generic, NFData) instance Show Hash512 where showsPrec _ = shows . encodeHex . BSS.fromShort . getHash512 instance Read Hash512 where readPrec = do R.String str <- lexP maybe pfail return $ Hash512 . BSS.toShort <$> decodeHex (cs str) instance Show Hash256 where showsPrec _ = shows . encodeHex . BSS.fromShort . getHash256 instance Read Hash256 where readPrec = do R.String str <- lexP maybe pfail return $ Hash256 . BSS.toShort <$> decodeHex (cs str) instance Show Hash160 where showsPrec _ = shows . encodeHex . BSS.fromShort . getHash160 instance Read Hash160 where readPrec = do R.String str <- lexP maybe pfail return $ Hash160 . BSS.toShort <$> decodeHex (cs str) instance IsString Hash512 where fromString str = case decodeHex $ cs str of Nothing -> e Just bs -> case BS.length bs of 64 -> Hash512 (BSS.toShort bs) _ -> e where e = error "Could not decode hash from hex string" instance Serial Hash512 where deserialize = Hash512 . BSS.toShort <$> Get.getByteString 64 serialize = Put.putByteString . BSS.fromShort . getHash512 instance Serialize Hash512 where put = serialize get = deserialize instance Binary Hash512 where put = serialize get = deserialize instance IsString Hash256 where fromString str = case decodeHex $ cs str of Nothing -> e Just bs -> case BS.length bs of 32 -> Hash256 (BSS.toShort bs) _ -> e where e = error "Could not decode hash from hex string" instance Serial Hash256 where deserialize = Hash256 . BSS.toShort <$> Get.getByteString 32 serialize = Put.putByteString . BSS.fromShort . getHash256 instance Serialize Hash256 where put = serialize get = deserialize instance Binary Hash256 where put = serialize get = deserialize instance IsString Hash160 where fromString str = case decodeHex $ cs str of Nothing -> e Just bs -> case BS.length bs of 20 -> Hash160 (BSS.toShort bs) _ -> e where e = error "Could not decode hash from hex string" instance Serial Hash160 where deserialize = Hash160 . BSS.toShort <$> Get.getByteString 20 serialize = Put.putByteString . BSS.fromShort . getHash160 instance Serialize Hash160 where put = serialize get = deserialize instance Binary Hash160 where put = serialize get = deserialize -- | Calculate SHA512 hash. sha512 :: ByteArrayAccess b => b -> Hash512 sha512 = Hash512 . BSS.toShort . BA.convert . hashWith SHA512 -- | Calculate SHA256 hash. sha256 :: ByteArrayAccess b => b -> Hash256 sha256 = Hash256 . BSS.toShort . BA.convert . hashWith SHA256 -- | Calculate RIPEMD160 hash. ripemd160 :: ByteArrayAccess b => b -> Hash160 ripemd160 = Hash160 . BSS.toShort . BA.convert . hashWith RIPEMD160 -- | Claculate SHA1 hash. sha1 :: ByteArrayAccess b => b -> Hash160 sha1 = Hash160 . BSS.toShort . BA.convert . hashWith SHA1 -- | Compute two rounds of SHA-256. doubleSHA256 :: ByteArrayAccess b => b -> Hash256 doubleSHA256 = Hash256 . BSS.toShort . BA.convert . hashWith SHA256 . hashWith SHA256 -- | Compute SHA-256 followed by RIPMED-160. addressHash :: ByteArrayAccess b => b -> Hash160 addressHash = Hash160 . BSS.toShort . BA.convert . hashWith RIPEMD160 . hashWith SHA256 {- CheckSum -} -- | Computes a 32 bit checksum. checkSum32 :: ByteArrayAccess b => b -> CheckSum32 checkSum32 = fromRight (error "Could not decode bytes as CheckSum32") . Get.runGetS deserialize . BS.take 4 . BA.convert . hashWith SHA256 . hashWith SHA256 {- HMAC -} -- | Computes HMAC over SHA-512. hmac512 :: ByteString -> ByteString -> Hash512 hmac512 key msg = Hash512 $ BSS.toShort $ BA.convert (hmac key msg :: HMAC SHA512) -- | Computes HMAC over SHA-256. hmac256 :: (ByteArrayAccess k, ByteArrayAccess m) => k -> m -> Hash256 hmac256 key msg = Hash256 $ BSS.toShort $ BA.convert (hmac key msg :: HMAC SHA256) -- | Split a 'Hash512' into a pair of 'Hash256'. split512 :: Hash512 -> (Hash256, Hash256) split512 h = (Hash256 (BSS.toShort a), Hash256 (BSS.toShort b)) where (a, b) = BS.splitAt 32 . BSS.fromShort $ getHash512 h -- | Join a pair of 'Hash256' into a 'Hash512'. join512 :: (Hash256, Hash256) -> Hash512 join512 (a, b) = Hash512 . BSS.toShort $ BSS.fromShort (getHash256 a) `BS.append` BSS.fromShort (getHash256 b)