{-# language DataKinds #-}
{-# language ScopedTypeVariables #-}
{-# language CPP #-}
module Hash where
import qualified Data.ByteString.Base16 as B16
import qualified System.Nix.Base32 as B32
import qualified Data.ByteString.Base64.Lazy as B64
import Test.Hspec
import Test.Tasty.QuickCheck
import System.Nix.Hash
import System.Nix.StorePath
import Arbitrary
import System.Nix.Internal.Base
import Crypto.Hash ( MD5
, SHA1
, SHA256
, hash
, Digest
)
spec_hash :: Spec
spec_hash = do
describe "hashing parity with nix-store" $ do
cmp "produces (base32 . sha256) of \"nix-output:foo\" the same as Nix does at the moment for placeholder \"foo\""
NixBase32 (hash @ByteString @SHA256) "nix-output:foo" "1x0ymrsy7yr7i9wdsqy9khmzc1yy7nvxw6rdp72yzn50285s67j5"
cmp "produces (base16 . md5) of \"Hello World\" the same as the thesis"
Base16 (hash @ByteString @MD5) "Hello World" "b10a8db164e0754105b7a99be72e3fe5"
cmp "produces (base32 . sha1) of \"Hello World\" the same as the thesis"
NixBase32 (hash @ByteString @SHA1) "Hello World" "s23c9fs0v32pf6bhmcph5rbqsyl5ak8a"
-- The example in question:
-- https://nixos.org/nixos/nix-pills/nix-store-paths.html
it "produces same base32 as nix pill flat file example" $ do
shouldBe (encodeWith NixBase32 $ coerce $ mkStorePathHashPart "source:sha256:2bfef67de873c54551d884fdab3055d84d573e654efa79db3c0d7b98883f9ee3:/nix/store:myfile")
"xv2iccirbrvklck36f1g7vldn5v58vck"
where
cmp :: String -> BaseEncoding -> (ByteString -> Digest a) -> ByteString -> Text -> SpecWith ()
cmp t b f s h =
it t $
shouldBe (encodeDigestWith b $ f s) h
-- | Test that Nix-like base32 encoding roundtrips
prop_nixBase32Roundtrip :: Property
prop_nixBase32Roundtrip = forAllShrink nonEmptyString genericShrink $
\x -> pure (encodeUtf8 x) === (B32.decode . B32.encode . encodeUtf8 $ x)
-- | API variants
prop_nixBase16Roundtrip :: StorePathHashPart -> Property
prop_nixBase16Roundtrip x = pure (coerce x) === decodeWith Base16 (encodeWith Base16 $ coerce x)
-- | Hash encoding conversion ground-truth.
-- Similiar to nix/tests/hash.sh
spec_nixhash :: Spec
spec_nixhash = do
describe "hashing parity with nix-nash" $ do
cmp
"b16 encoded . b32 decoded should equal original b16"
B16.encode B32.decode b32s b16s
cmp
"b64 encoded . b32 decoded should equal original b64"
(B64.encode . fromStrict) B32.decode b32s b64s
cmp
"b32 encoded . b64 decoded should equal original b32"
(B32.encode . toStrict) B64.decode b64s b32s
cmp
"b16 encoded . b64 decoded should equal original b16"
(B16.encode . toStrict) B64.decode b64s b16s
#if MIN_VERSION_base16_bytestring(1,0,0)
cmp
"b32 encoded . b16 decoded should equal original b32"
B32.encode B16.decode b16s b32s
cmp
"b64 encoded . b16 decoded should equal original b64"
(B64.encode . fromStrict) B16.decode b16s b64s
#else
it "b32 encoded . b16 decoded should equal original b32" $
traverse_ (\ b -> shouldBe (B32.encode $ fst $ B16.decode $ fst b) (snd b)) $ zip b16s b32s
it "b64 encoded . b16 decoded should equal original b64" $
traverse_ (\ b -> shouldBe (B64.encode . fromStrict $ fst $ B16.decode $ fst b) (snd b)) $ zip b16s b64s
#endif
where
cmp
:: ( Eq b
, Show b
)
=> String
-> (a -> b)
-> (c -> Either String a)
-> [c]
-> [b]
-> SpecWith ()
cmp s f1 f2 b1 b2 = it s $ traverse_ (uncurry shouldBe . bimap (fmap f1 . f2) pure) $ zip b1 b2
b16s = takeAxis (\(a,_,_) -> a)
b32s = takeAxis (\(_,b,_) -> b)
b64s = takeAxis (\(_,_,c) -> c)
takeAxis f = fmap f samples
samples =
[ ( "800d59cfcd3c05e900cb4e214be48f6b886a08df"
, "vw46m23bizj4n8afrc0fj19wrp7mj3c0"
, "gA1Zz808BekAy04hS+SPa4hqCN8="
)
, ( "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
, "1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s"
, "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="
)
, ( "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445"
, "12k9jiq29iyqm03swfsgiw5mlqs173qazm3n7daz43infy12pyrcdf30fkk3qwv4yl2ick8yipc2mqnlh48xsvvxl60lbx8vp38yji0"
, "IEqPxt2oLwoM7XvrjgikFlfBbvRosiioJ5vjMacDwzWW/RXBOxsH+aodO+pXeJygMa2Fx6cd1wNU7GMSOMo0RQ=="
)
]