tahoe-chk-0.2.0.0: src/Tahoe/CHK/SHA256d.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Tahoe.CHK.SHA256d where
import Crypto.Hash (Context, Digest, HashAlgorithm, SHA256, digestFromByteString, hash)
import Crypto.Hash.IO (HashAlgorithm (..))
import qualified Data.ByteArray as BA
import Data.ByteString (packCStringLen, useAsCString)
import qualified Data.ByteString as B
import Data.ByteString.Base32 (decodeBase32Unpadded, encodeBase32Unpadded')
import qualified Data.ByteString.Char8 as C8
import Data.Char (toLower)
import Data.Coerce (coerce)
import Data.Data (Data)
import Data.Maybe (fromJust, fromMaybe)
import Data.Primitive (Ptr)
import Data.Primitive.Ptr (copyPtr)
import Data.String (IsString (..))
import Data.TreeDiff.Class (ToExpr (..))
import Foreign.C (CString)
{- | A newtype wrapper around Digest which comes with the string interpretation
Tahoe-LAFS is accustomed to (lowercase base32 rather than lowercase base16),
as well as a ToExpr instance for participation in nice diff computation.
-}
newtype Digest' a = Digest' (Digest a) deriving newtype (Eq, Ord)
instance HashAlgorithm hash => Show (Digest' hash) where
show (Digest' digest) = fmap toLower . C8.unpack . encodeBase32Unpadded' . toBytes $ digest
instance ToExpr (Digest' a) where
toExpr (Digest' d) = toExpr (toBytes d)
deriving instance BA.ByteArrayAccess (Digest' hash)
instance HashAlgorithm hash => IsString (Digest' hash) where
fromString =
Digest'
. fromMaybe (error "invalid base32-encoded digest")
. either (error "invalid base32-encoded digest") digestFromByteString
. decodeBase32Unpadded
. C8.pack
-- | The all-zero digest value at a specific hash algorithm.
zero :: forall hash. HashAlgorithm hash => Digest' hash
zero = Digest' . fromJust . digestFromByteString @hash . B.replicate (hashDigestSize (undefined :: hash)) $ 0
{- | A hash algorithm which computes its digest using the parameterized hash
algorithm and then computes a digest of _that_ digest with the same hash
algorithm.
-}
data DoubleHash hash = DoubleHash
-- | The double SHA256 hash algorithm.
type SHA256d = DoubleHash SHA256
deriving instance Show hash => Show (DoubleHash hash)
deriving instance Data hash => Data (DoubleHash hash)
instance HashAlgorithm hash => HashAlgorithm (DoubleHash hash) where
type HashBlockSize (DoubleHash hash) = HashBlockSize hash
type HashDigestSize (DoubleHash hash) = HashDigestSize hash
type HashInternalContextSize (DoubleHash hash) = HashInternalContextSize hash
-- cryptonite doesn't force the argument and neither will we, allowing the
-- pattern of passing `undefined` around as the value.
hashBlockSize _ = hashBlockSize @hash undefined
hashDigestSize _ = hashDigestSize @hash undefined
hashInternalContextSize _ = hashInternalContextSize @hash undefined
-- We'll re-use a Context for the wrapped hash type.
hashInternalInit ctxPtr = hashInternalInit (coerce ctxPtr :: Ptr (Context hash))
hashInternalUpdate ctxPtr = hashInternalUpdate (coerce ctxPtr :: Ptr (Context hash))
hashInternalFinalize ctxPtr digestPtr = do
-- Do the first pass
hashInternalFinalize
(coerce ctxPtr :: Ptr (Context hash))
(coerce digestPtr :: Ptr (Digest hash))
-- And then a second pass over the result
firstHash <- digestPtrToByteString digestPtr
let secondHash = hash firstHash :: Digest hash
-- And shove the second result into the output
useAsCString (toBytes secondHash) $ \new -> copyPtr (coerce digestPtr :: CString) new (hashDigestSize @hash undefined)
-- | Extract the bytes from a value like a `Digest' hash`.
toBytes :: BA.ByteArrayAccess a => a -> B.ByteString
toBytes = B.pack . BA.unpack
{- | Read the digest bytes out of a pointer to a Digest. This uses some
coerce trickery. I hope it's not too broken.
-}
digestPtrToByteString :: forall hash. HashAlgorithm hash => Ptr (Digest hash) -> IO B.ByteString
digestPtrToByteString = packCStringLen . (,hashDigestSize @hash undefined) . coerce @(Ptr (Digest hash)) @CString