11{-|
2- Description : Cryptographic hashes for hnix-store.
3- Maintainer : Greg Hale <[email protected] > 2+ Description : Cryptographic hashing interface for hnix-store, on top
3+ of the cryptohash family of libraries.
44-}
55{-# LANGUAGE AllowAmbiguousTypes #-}
6- {-# LANGUAGE DataKinds #-}
76{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8- {-# LANGUAGE PolyKinds #-}
9- {-# LANGUAGE RankNTypes #-}
107{-# LANGUAGE TypeFamilies #-}
11- {-# LANGUAGE KindSignatures #-}
128{-# LANGUAGE ScopedTypeVariables #-}
139{-# LANGUAGE TypeApplications #-}
14- {-# LANGUAGE TypeInType #-}
10+ {-# LANGUAGE DataKinds #-}
1511{-# LANGUAGE OverloadedStrings #-}
1612
1713module System.Nix.Internal.Hash where
@@ -21,162 +17,129 @@ import qualified Crypto.Hash.SHA1 as SHA1
2117import qualified Crypto.Hash.SHA256 as SHA256
2218import qualified Data.ByteString as BS
2319import qualified Data.ByteString.Base16 as Base16
24- import qualified Data.ByteString.Char8 as BSC
2520import Data.Bits (xor )
26- import qualified Data.ByteString as BS
2721import qualified Data.ByteString.Lazy as BSL
2822import qualified Data.Hashable as DataHashable
29- import Data.Kind (Type )
3023import Data.List (foldl' )
31- import Data.Monoid
3224import Data.Proxy (Proxy (Proxy ))
3325import Data.Text (Text )
3426import qualified Data.Text as T
3527import qualified Data.Text.Encoding as T
36- import qualified Data.Vector as V
3728import Data.Word (Word8 )
38- import GHC.TypeLits
29+ import GHC.TypeLits (Nat , KnownNat , natVal )
30+ import qualified System.Nix.Base32 as Base32
3931
40- -- | A tag for different hashing algorithms
41- -- Also used as a type-level tag for hash digests
42- -- (e.g. @Digest SHA256@ is the type for a sha256 hash)
32+ -- | The universe of supported hash algorithms.
4333--
44- -- When used at the type level, `n` is `Nat`
45- data HashAlgorithm' n
34+ -- Currently only intended for use at the type level.
35+ data HashAlgorithm
4636 = MD5
4737 | SHA1
4838 | SHA256
49- | Truncated n (HashAlgorithm' n )
50- deriving (Eq , Show )
51-
52- class HashAlgoText a where
53- algoString :: Proxy a -> Text
39+ | Truncated Nat HashAlgorithm
40+ -- ^ The hash algorithm obtained by truncating the result of the
41+ -- input 'HashAlgorithm' to the given number of bytes. See
42+ -- 'truncateDigest' for a description of the truncation algorithm.
43+
44+ -- | The result of running a 'HashAlgorithm'.
45+ newtype Digest (a :: HashAlgorithm ) =
46+ Digest BS. ByteString deriving (Show , Eq , Ord , DataHashable.Hashable )
47+
48+ -- | The primitive interface for incremental hashing for a given
49+ -- 'HashAlgorithm'. Every 'HashAlgorithm' should have an instance.
50+ class ValidAlgo (a :: HashAlgorithm ) where
51+ -- | The incremental state for constructing a hash.
52+ type AlgoCtx a
53+
54+ -- | Start building a new hash.
55+ initialize :: AlgoCtx a
56+ -- | Append a 'BS.ByteString' to the overall contents to be hashed.
57+ update :: AlgoCtx a -> BS. ByteString -> AlgoCtx a
58+ -- | Finish hashing and generate the output.
59+ finalize :: AlgoCtx a -> Digest a
5460
55- instance HashAlgoText 'MD5 where
56- algoString (Proxy :: Proxy 'MD5) = " md5"
61+ -- | A 'HashAlgorithm' with a canonical name, for serialization
62+ -- purposes (e.g. SRI hashes)
63+ class NamedAlgo (a :: HashAlgorithm ) where
64+ algoName :: Text
5765
58- instance HashAlgoText 'SHA1 where
59- algoString ( Proxy :: Proxy 'SHA1) = " sha1 "
66+ instance NamedAlgo 'MD5 where
67+ algoName = " md5 "
6068
61- instance HashAlgoText 'SHA256 where
62- algoString ( Proxy :: Proxy 'SHA256) = " sha256 "
69+ instance NamedAlgo 'SHA1 where
70+ algoName = " sha1 "
6371
64- type HashAlgorithm = HashAlgorithm' Nat
72+ instance NamedAlgo 'SHA256 where
73+ algoName = " sha256"
6574
66- -- | Types with kind @HashAlgorithm@ may be a @HasDigest@ instance
67- -- if they are able to hash bytestrings via the init/update/finalize
68- -- API of cryptonite
75+ -- | Hash an entire (strict) 'BS.ByteString' as a single call.
6976--
70- -- Each instance defined here simply defers to one of the underlying
71- -- monomorphic hashing libraries, such as `cryptohash-sha256`.
72- class HasDigest (a :: HashAlgorithm ) where
73-
74- type AlgoCtx a :: Type
75-
76- initialize :: AlgoCtx a
77- update :: AlgoCtx a -> BS. ByteString -> AlgoCtx a
78- finalize :: AlgoCtx a -> Digest a
79-
80-
81- -- | The cryptographic hash of of a strict bytestring, where hash
82- -- algorithm is chosen by the type of the digest
8377-- For example:
8478-- > let d = hash "Hello, sha-256!" :: Digest SHA256
8579-- or
8680-- > :set -XTypeApplications
8781-- > let d = hash @SHA256 "Hello, sha-256!"
88- hash :: forall a . HasDigest a => BS. ByteString -> Digest a
82+ hash :: forall a . ValidAlgo a => BS. ByteString -> Digest a
8983hash bs =
9084 finalize $ update @ a (initialize @ a ) bs
9185
92- -- | The cryptographic hash of a lazy bytestring. Use is the same
93- -- as for @hash@. This runs in constant space, but forces the
94- -- entire bytestring
95- hashLazy :: forall a . HasDigest a => BSL. ByteString -> Digest a
86+ -- | Hash an entire (lazy) 'BSL.ByteString' as a single call.
87+ --
88+ -- Use is the same as for 'hash'. This runs in constant space, but
89+ -- forces the entire bytestring.
90+ hashLazy :: forall a . ValidAlgo a => BSL. ByteString -> Digest a
9691hashLazy bsl =
9792 finalize $ foldl' (update @ a ) (initialize @ a ) (BSL. toChunks bsl)
9893
99- digestText32 :: forall a . HashAlgoText a => Digest a -> T. Text
100- digestText32 d = algoString (Proxy :: Proxy a ) <> " :" <> printAsBase32 d
101-
102- digestText16 :: forall a . HashAlgoText a => Digest a -> T. Text
103- digestText16 (Digest bs) = algoString (Proxy :: Proxy a ) <> " :" <> T. decodeUtf8 (Base16. encode bs)
94+ -- | Encode a 'Digest' in the special Nix base-32 encoding.
95+ encodeBase32 :: Digest a -> T. Text
96+ encodeBase32 (Digest bs) = Base32. encode bs
10497
105- -- | Convert any Digest to a base32-encoded string.
106- -- This is not used in producing store path hashes
107- printAsBase32 :: Digest a -> T. Text
108- printAsBase32 (Digest bs) = printHashBytes32 bs
98+ -- | Encode a 'Digest' in hex.
99+ encodeBase16 :: Digest a -> T. Text
100+ encodeBase16 (Digest bs) = T. decodeUtf8 (Base16. encode bs)
109101
110-
111- instance HasDigest MD5 where
102+ -- | Uses "Crypto.Hash.MD5" from cryptohash-md5.
103+ instance ValidAlgo ' MD5 where
112104 type AlgoCtx 'MD5 = MD5. Ctx
113105 initialize = MD5. init
114106 update = MD5. update
115107 finalize = Digest . MD5. finalize
116108
117- instance HasDigest 'SHA1 where
118- type AlgoCtx SHA1 = SHA1. Ctx
109+ -- | Uses "Crypto.Hash.SHA1" from cryptohash-sha1.
110+ instance ValidAlgo 'SHA1 where
111+ type AlgoCtx 'SHA1 = SHA1. Ctx
119112 initialize = SHA1. init
120113 update = SHA1. update
121114 finalize = Digest . SHA1. finalize
122115
123- instance HasDigest 'SHA256 where
124- type AlgoCtx SHA256 = SHA256. Ctx
116+ -- | Uses "Crypto.Hash.SHA256" from cryptohash-sha256.
117+ instance ValidAlgo 'SHA256 where
118+ type AlgoCtx 'SHA256 = SHA256. Ctx
125119 initialize = SHA256. init
126120 update = SHA256. update
127121 finalize = Digest . SHA256. finalize
128122
129- instance (HasDigest a , KnownNat n ) => HasDigest (Truncated n a ) where
130- type AlgoCtx (Truncated n a ) = AlgoCtx a
123+ -- | Reuses the underlying 'ValidAlgo' instance, but does a
124+ -- 'truncateDigest' at the end.
125+ instance (ValidAlgo a , KnownNat n ) => ValidAlgo ('Truncated n a ) where
126+ type AlgoCtx ('Truncated n a ) = AlgoCtx a
131127 initialize = initialize @ a
132128 update = update @ a
133129 finalize = truncateDigest @ n . finalize @ a
134130
135- -- | A raw hash digest, with a type-level tag
136- newtype Digest (a :: HashAlgorithm ) = Digest
137- { digestBytes :: BS. ByteString
138- -- ^ The bytestring in a Digest is an opaque string of bytes,
139- -- not some particular text encoding.
140- } deriving (Show , Eq , Ord , DataHashable.Hashable )
141-
142-
143- -- instance DataHashable.Hashable (Digest a) where
144- -- hashWithSalt a (Digest bs) = DataHashable.hashWithSalt a bs
145- -- hashWithSalt = coerce . DataHash
146-
147-
148- -- | Internal function for encoding bytestrings into base32 according to
149- -- nix's convention
150- printHashBytes32 :: BS. ByteString -> T. Text
151- printHashBytes32 c = T. pack $ concatMap char32 [nChar - 1 , nChar - 2 .. 0 ]
152- where
153- -- The base32 encoding is 8/5's as long as the base256 digest. This `+ 1`
154- -- `- 1` business is a bit odd, but has always been used in C++ since the
155- -- base32 truncation was added in was first added in
156- -- d58a11e019813902b6c4547ca61a127938b2cc20.
157- nChar = fromIntegral $ ((BS. length c * 8 - 1 ) `div` 5 ) + 1
158-
159- char32 :: Integer -> [Char ]
160- char32 i = [digits32 V. ! digitInd]
161- where
162- byte j = BS. index c (fromIntegral j)
163- digitInd = fromIntegral $
164- sum [fromIntegral (byte j) * (256 ^ j)
165- | j <- [0 .. BS. length c - 1 ]]
166- `div` (32 ^ i)
167- `mod` 32
168-
169-
170- -- | Internal function for producing the bitwise truncation of bytestrings.
171- -- When truncation length is greater than the length of the bytestring,
172- -- but less than twice the bytestring length, truncation splits the
173- -- bytestring into a head part (truncation length) and tail part (leftover
174- -- part) right-pads the leftovers with 0 to the truncation length, and
175- -- combines the two strings bytewise with `xor`
176- truncateDigest :: forall n a . (HasDigest a , KnownNat n ) => Digest a -> Digest (Truncated n a )
177- truncateDigest (Digest c) = Digest $ BS. pack $ map truncOutputByte [0 .. n- 1 ]
131+ -- | Bytewise truncation of a 'Digest'.
132+ --
133+ -- When truncation length is greater than the length of the bytestring
134+ -- but less than twice the bytestring length, truncation splits the
135+ -- bytestring into a head part (truncation length) and tail part
136+ -- (leftover part), right-pads the leftovers with 0 to the truncation
137+ -- length, and combines the two strings bytewise with 'xor'.
138+ truncateDigest
139+ :: forall n a . (KnownNat n ) => Digest a -> Digest ('Truncated n a )
140+ truncateDigest (Digest c) =
141+ Digest $ BS. pack $ map truncOutputByte [0 .. n- 1 ]
178142 where
179-
180143 n = fromIntegral $ natVal (Proxy @ n )
181144
182145 truncOutputByte :: Int -> Word8
@@ -189,23 +152,3 @@ truncateDigest (Digest c) = Digest $ BS.pack $ map truncOutputByte [0.. n-1]
189152 aux i x j = if j `mod` fromIntegral n == fromIntegral i
190153 then xor x (inputByte $ fromIntegral j)
191154 else x
192-
193- digits32 :: V. Vector Char
194- digits32 = V. fromList " 0123456789abcdfghijklmnpqrsvwxyz"
195-
196-
197- -- | Convert type-level @HashAlgorithm@ into the value level
198- class AlgoVal (a :: HashAlgorithm ) where
199- algoVal :: HashAlgorithm' Integer
200-
201- instance AlgoVal MD5 where
202- algoVal = MD5
203-
204- instance AlgoVal SHA1 where
205- algoVal = SHA1
206-
207- instance AlgoVal SHA256 where
208- algoVal = SHA256
209-
210- instance forall a n . (AlgoVal a , KnownNat n ) => AlgoVal (Truncated n a ) where
211- algoVal = Truncated (natVal (Proxy @ n )) (algoVal @ a )
0 commit comments