nova-cache-0.1.0.0: src/NovaCache/Hash.hs
-- | SHA-256 hashing with Nix-specific formatting.
--
-- Nix represents content hashes as @sha256:\<nix-base32\>@. This module
-- provides the bridge between raw cryptographic hashing and the Nix
-- string representation, composing 'Crypto.Hash' with 'NovaCache.Base32'.
module NovaCache.Hash
( NixHash (..),
hashBytes,
hashFile,
formatNixHash,
parseNixHash,
)
where
import Crypto.Hash (Digest, SHA256, hash)
import Data.ByteArray (convert)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Text (Text)
import qualified Data.Text as T
import qualified NovaCache.Base32 as Base32
-- | A SHA-256 hash as raw bytes (always exactly 32 bytes).
newtype NixHash = NixHash ByteString
deriving (Eq, Ord, Show)
-- | Size of a SHA-256 digest in bytes.
sha256DigestSize :: Int
sha256DigestSize = 32
-- | Hash algorithm prefix used in Nix hash strings.
sha256Prefix :: Text
sha256Prefix = "sha256:"
-- | Compute the SHA-256 hash of a strict 'ByteString'.
hashBytes :: ByteString -> NixHash
hashBytes = NixHash . convert . sha256
-- | Compute the SHA-256 hash of a file's contents.
hashFile :: FilePath -> IO NixHash
hashFile path = hashBytes <$> BS.readFile path
-- | Format a 'NixHash' as @sha256:\<nix-base32\>@.
formatNixHash :: NixHash -> Text
formatNixHash (NixHash raw) = sha256Prefix <> Base32.encode raw
-- | Parse a @sha256:\<nix-base32\>@ string back to a 'NixHash'.
--
-- Validates both the prefix and the decoded length.
parseNixHash :: Text -> Either String NixHash
parseNixHash txt = case T.stripPrefix sha256Prefix txt of
Nothing -> Left ("expected " ++ T.unpack sha256Prefix ++ " prefix, got: " ++ T.unpack txt)
Just encoded -> do
decoded <- Base32.decode encoded
if BS.length decoded == sha256DigestSize
then Right (NixHash decoded)
else Left (digestSizeError (BS.length decoded))
-- | Internal: compute SHA-256 digest.
sha256 :: ByteString -> Digest SHA256
sha256 = hash
-- | Error message for wrong digest size.
digestSizeError :: Int -> String
digestSizeError actual =
"expected " ++ show sha256DigestSize ++ "-byte SHA-256 digest, got " ++ show actual ++ " bytes"