packages feed

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