-- SPDX-FileCopyrightText: 2021 Oxhead Alpha -- SPDX-License-Identifier: LicenseRef-MIT-OA -- | Tests for 'Morley.Tezos.Crypto'. module Test.Tezos.Crypto ( test_Roundtrip , test_Signing , test_parseRawSignature , test_parseRawPublicKey , test_parseRawKeyHash , unit_Key_Hashing , unit_Failing_Key_Hashing ) where import Fmt (pretty) import Test.HUnit (Assertion, assertBool, (@?=)) import Test.Hspec (Expectation, shouldSatisfy) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase) import Hedgehog.Gen.Tezos.Crypto (genKeyHash, genPublicKey, genSecretKey, genSignature) import Morley.Tezos.Crypto import Test.Cleveland.Util (fromHex) import Test.Util.Hedgehog (ShowThroughBuild(..), aesonRoundtrip, roundtripTreeSTB) test_Roundtrip :: [TestTree] test_Roundtrip = [ testGroup "parse . format ≡ pure" [ roundtripTreeSTB genPublicKey formatPublicKey parsePublicKey , roundtripTreeSTB genSignature formatSignature parseSignature , roundtripTreeSTB genKeyHash formatKeyHash parseKeyHash , roundtripTreeSTB genSecretKey formatSecretKey parseSecretKey ] , testGroup "JSON encoding/deconding" [ aesonRoundtrip genPublicKey , aesonRoundtrip genSignature , aesonRoundtrip genKeyHash ] ] ---------------------------------------------------------------------------- -- Signing ---------------------------------------------------------------------------- test_Signing :: [TestTree] test_Signing = [ testGroup "Formatting" [ testGroup "parsePublicKey" [ testCase "Successfully parses valid sample data" $ mapM_ (parsePublicKeySample . sdPublicKey) sampleSignatures , testCase "Fails to parse invalid data" $ do parsePublicKeyInvalid "" parsePublicKeyInvalid "aaa" parsePublicKeyInvalid "edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3v" parsePublicKeyInvalid "edsigtrs8bK7vNfiR4Kd9dWasVa1bAWaQSu2ipnmLGZuwQa8ktCEMYVKqbWsbJ7zTS8dgYT9tiSUKorWCPFHosL5zPsiDwBQ6vb" parsePublicKeyInvalid "sppk7Ze7NMs6EHF2uB8qq8GrEgJvE9PWYkUijN3LcesafzQuGyniHBd" parsePublicKeyInvalid "spsig1Ng2bs4PXCbjaFGuojk9K5Pt3CkfbUZyHLLrBxHSmTqrUUxQggi4yJBit3Ljqnqr61UpdTewTLiu4schSCfZvaRwu412oZ" parsePublicKeyInvalid "p2pk68C6tJr7pNLvgBH63K3hBVoztCPCA36zcWhXFUGywQJTjYbfpxk" parsePublicKeyInvalid "p2sigRmXDp38VNVaEQH28LYukfLPn8QB5hPEberhvQrrUpRscDZJrrApbRh2u46PTVTwKXjxTLKNN9dyLhPQU6U6jWPGxe4d9v" ] , testGroup "parseSecretKey" [ testCase "Successfully parses valid sample data" $ mapM_ parseSecretKeySample [ "spsk1wt9QxrpLg8UCR2S9YqXSP3YaTBCpSx3dnf62pQuw2wj7Sb8uL" , "p2sk32DYyag6h9GSMmysxsvM9LzWwC5rV1QqZAwivUuJ3zrjSKK1Pj" , "unencrypted:spsk1wt9QxrpLg8UCR2S9YqXSP3YaTBCpSx3dnf62pQuw2wj7Sb8uL" , "unencrypted:p2sk32DYyag6h9GSMmysxsvM9LzWwC5rV1QqZAwivUuJ3zrjSKK1Pj" ] ] , testGroup "parseSignature" [ testCase "Successfully parses valid sample data" $ mapM_ (parseSignatureSample . sdSignature) sampleSignatures , testCase "Fails to parse invalid data" $ do parseSignatureInvalid "" parseSignatureInvalid "bbb" parseSignatureInvalid "edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3V" parseSignatureInvalid "edsigtrs8bK7vNfiR4Kd9dWasVa1bAWaQSu2ipnmLGZuwQa8ktCEMYVKqbWsbJ7zTS8dgYT9tiSUKorWCPFHosL5zPsiDwBQ6vB" parseSignatureInvalid "sppk7cdA7Afj8MvuBFrP6KsTLfbM5DtH9GwYaRZwCf5tBVCz6UKGQFR" parseSignatureInvalid "spsig1Ng2bs4PXCbjaFGuojk9K5Pt3ckfbUZyHLLrBxHSmTqrUUxQggi4yJBit3Ljqnqr61UpdTewTLiu4schSCfZvaRwu412oZ" parseSignatureInvalid "p2pk68C6tJr7pNLvgBH63K3hBVoztCPCA36zcWhXFUGywQJTjYBfpxk" parseSignatureInvalid "p2sigRmXDp38VNVaEQH28LYukfLPn8QB5hPEberhvQrrUpRscDZJrrApbrh2u46PTVTwKXjxTLKNN9dyLhPQU6U6jWPGxe4d9v" ] ] , testCase "checkSignature" $ mapM_ checkSignatureSample sampleSignatures , testCase "checkRawSignature" $ mapM_ checkRawSignatureSample sampleRawSignatures ] data SignatureData = SignatureData { sdPublicKey :: Text , sdBytes :: ByteString , sdSignature :: Text , sdValid :: Bool } -- These signatures have been produced by `tezos-client`. sampleSignatures :: [SignatureData] sampleSignatures = [ SignatureData { sdPublicKey = "edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3V" , sdBytes = "\0" , sdSignature = "edsigtrs8bK7vNfiR4Kd9dWasVa1bAWaQSu2ipnmLGZuwQa8ktCEMYVKqbWsbJ7zTS8dgYT9tiSUKorWCPFHosL5zPsiDwBQ6vb" , sdValid = True } , SignatureData { sdPublicKey = "edpkupH22qrz1sNQt5HSvWfRJFfyJ9dhNbZLptE6GR4JbMoBcACZZH" , sdBytes = "\0\0" , sdSignature = "edsigtj8LhbJ2B3qhZvqzA49raG65dydFcWZW9b9L7ntF3bb29zxaBFFL8SM1jeBUY66hG122znyVA4wpzLdwxcNZwSK3Szu7iD" , sdValid = True } , SignatureData { sdPublicKey = "edpkupH22qrz1sNQt5HSvWfRJFfyJ9dhNbZLptE6GR4JbMoBcACZZH" , sdBytes = "kot" , sdSignature = "edsigtrs8bK7vNfiR4Kd9dWasVa1bAWaQSu2ipnmLGZuwQa8ktCEMYVKqbWsbJ7zTS8dgYT9tiSUKorWCPFHosL5zPsiDwBQ6vb" , sdValid = False } ] <> [ SignatureData { sdPublicKey = "sppk7cdA7Afj8MvuBFrP6KsTLfbM5DtH9GwYaRZwCf5tBVCz6UKGQFR" , sdBytes = "\0" , sdSignature = "spsig1Ng2bs4PXCbjaFGuojk9K5Pt3CkfbUZyHLLrBxHSmTqrUUxQggi4yJBit3Ljqnqr61UpdTewTLiu4schSCfZvaRwu412oZ" , sdValid = True } , SignatureData { sdPublicKey = "sppk7Ze7NMs6EHF2uB8qq8GrEgJvE9PWYkUijN3LcesafzQuGyniHBD" , sdBytes = "\0\0" , sdSignature = "spsig1aP7D9oheiraNuM1NgziMPSPKS1F9kSWyFqkE8WigaeU5Uzb3LwY34F7Y7RsF6sY5ZfUda1NWdrC5V4KEfm9jeU1eniHmy" , sdValid = True } , SignatureData { sdPublicKey = "sppk7Ze7NMs6EHF2uB8qq8GrEgJvE9PWYkUijN3LcesafzQuGyniHBD" , sdBytes = "kot" , sdSignature = "spsig1PJ9LG9ovbpVJ3CucFWL7iBaQZjqEWMvppgLjYiiSwzcxpuUqHr2BUVZDUwkmZKzMNDWJdgtyhYiicz197TbhS4LPpnxDY" , sdValid = False } ] <> [ SignatureData { sdPublicKey = "p2pk66qfVMXhFJWhtFDCT6F3JUM3M1iQpfWe4nPZKWcsqsKQtXXHFkQ" , sdBytes = "\0" , sdSignature = "p2sigv6HrN6xB5gQDnmKLC2P3ynwiPn4zfUj7CcZD1cepfFzX7xBDWFQu9uoKWbEzVgxCQxrE1J5X6FGYwF2dpoYcjpdPCBhuD" , sdValid = True } , SignatureData { sdPublicKey = "p2pk64bybDUtSjSQnsexpzhedhBo4vkoRX4tWfQQbBxKbA58wJqKkT2" , sdBytes = "\10" , sdSignature = "p2siggzjojhabur7zZvmNnhkhnU3nYA1ZUR9JSas57RVhNdAmQk6y3hns3F2zPBGsC964PFAE2HC3fbPkcqpVFbjoQQq9dFiZg" , sdValid = True } , SignatureData { sdPublicKey = "p2pk64bybDUtSjSQnsexpzhedhBo4vkoRX4tWfQQbBxKbA58wJqKkT2" , sdBytes = "kot" , sdSignature = "p2siggzjojhabur7zZvmNnhkhnU3nYA1ZUR9JSas57RVhNdAmQk6y3hns3F2zPBGsC964PFAE2HC3fbPkcqpVFbjoQQq9dFiZg" , sdValid = False } ] sampleRawSignatures :: [SignatureData] sampleRawSignatures = [ SignatureData { sdPublicKey = "00aad3f16293766169f7db278c5e0e9db4fb82ffe1cbcc35258059617dc0fec082" , sdBytes = "\0" , sdSignature = "91ac1e7fd668854fc7a40feec4034e42c06c068cce10622c607fda232db34c8cf5d8da83098dd891cd4cb4299b3fa0352ae323ad99b24541e54b91888fdc8201" , sdValid = True } , SignatureData { sdPublicKey = "0103b524d0184276467c848ac13557fb0ff8bec5907960f72683f22af430503edfc1" , sdBytes = "\0" , sdSignature = "80e4e72ffecf72953789625b1125e9f45f432c14e53a01ec68a1e1b77d60cfe96a97443733ba0f7f42db3a56d7a433df2b4fc0035c05ab92d062f33c5bab0244" , sdValid = True } , SignatureData { sdPublicKey = "0202041e5cb7fb3d7bc6fb7b9e94790919a9e76ccc372e6cc9cae925027c08ff95f3" , sdBytes = "\10" , sdSignature = "12d25210bb02998516bf6a776e1cd55a06c5fbe3c21afbeef29b99d96305e43263c75a4449906e0f2d79ecc973fff9ce7f8c43fee40b04d07c191f00ee176175" , sdValid = True } ] parsePublicKeySample :: Text -> Expectation parsePublicKeySample publicKeyText = parsePublicKey publicKeyText `shouldSatisfy` isRight parseSecretKeySample :: Text -> Expectation parseSecretKeySample secretKeyText = parseSecretKey secretKeyText `shouldSatisfy` isRight parsePublicKeyInvalid :: Text -> Expectation parsePublicKeyInvalid invalidPublicKeyText = parsePublicKey invalidPublicKeyText `shouldSatisfy` isLeft parseSignatureSample :: Text -> Expectation parseSignatureSample signatureText = parseSignature signatureText `shouldSatisfy` isRight parseSignatureInvalid :: Text -> Expectation parseSignatureInvalid invalidSignatureText = parseSignature invalidSignatureText `shouldSatisfy` isLeft checkSignatureSample :: SignatureData -> Assertion checkSignatureSample sd = checkSignature publicKey signature (sdBytes sd) @?= sdValid sd where publicKey = unsafe . parsePublicKey $ sdPublicKey sd signature = unsafe . parseSignature $ sdSignature sd checkRawSignatureSample :: SignatureData -> Assertion checkRawSignatureSample sd = checkSignature publicKey signature (sdBytes sd) @?= sdValid sd where publicKey = unsafe . parsePublicKeyRaw . unsafe . fromHex $ sdPublicKey sd signature = unsafe . parseSignatureRaw . unsafe . fromHex $ sdSignature sd ---------------------------------------------------------------------------- -- Key hashing ---------------------------------------------------------------------------- unit_Key_Hashing :: Assertion unit_Key_Hashing = mapM_ hashKeySample sampleKeyHashes unit_Failing_Key_Hashing :: Assertion unit_Failing_Key_Hashing = assertBool "Parsed key hash of invalid length" $ isLeft $ parseKeyHash "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSxZ" sampleKeyHashes :: [(Text, Text)] sampleKeyHashes = [ ( "edpkupH22qrz1sNQt5HSvWfRJFfyJ9dhNbZLptE6GR4JbMoBcACZZH" , "tz1NaZzLvdDBLfV2LWC6F4SJfNV2jHdZJXkJ" ) , ( "edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3V" , "tz1Yz3VPaCNB5FjhdEVnSoN8Xv3ZM8g2LYhw" ) , ( "sppk7cdA7Afj8MvuBFrP6KsTLfbM5DtH9GwYaRZwCf5tBVCz6UKGQFR" , "tz2EfqCbLmpfv7mNiLcMmhxAwdgHtPTcwR4W" ) , ( "sppk7Ze7NMs6EHF2uB8qq8GrEgJvE9PWYkUijN3LcesafzQuGyniHBD" , "tz2Darj3LyQzekU98ZK8diHvuyn1YYjcHpc6" ) , ( "p2pk67K1dwkDFPB63RZU5H3SoMCvmJdKZDZszc7U4FiGKN2YypKdDCB" , "tz3S7wbUwQV581kroR81fYbgDBskFicZ6czW" ) , ( "p2pk68C6tJr7pNLvgBH63K3hBVoztCPCA36zcWhXFUGywQJTjYBfpxk" , "tz3QEbmdCdsMcnUo2rNjXdbKwg5tyack3goN" ) ] hashKeySample :: (Text, Text) -> Assertion hashKeySample (pkText, keyHashText) = hashKey pk @?= keyHash where pk = unsafe $ parsePublicKey pkText keyHash = unsafe $ parseKeyHash keyHashText ---------------------------------------------------------------------------- -- Parsing raw data ---------------------------------------------------------------------------- test_parseRawSignature :: [TestTree] test_parseRawSignature = [ testCase "Successfully parses valid sample signatures in canonical form" $ forM_ samplePairSignatures (\(a, _) -> bimap STB STB (parseSignature a) `shouldSatisfy` isRight) , testCase "Successfully parses valid sample signatures in raw byte form" $ forM_ samplePairSignatures (\(_, b) -> fmap STB (parseSignatureRaw . unsafe . fromHex $ b) `shouldSatisfy` isRight) , testCase "Parsed signatures are equivalent in canonical and raw byte form" $ forM_ samplePairSignatures (\(a, b) -> (STB . unsafe . parseSignatureRaw . unsafe . fromHex $ b) @?= (STB . unsafe . parseSignature $ a)) , testGroup "Fails to parse invalid signatures:" [ testCase (toString a) (parseInvalidSignature a `shouldSatisfy` isLeft) | a <- invalidRawSignatures ] ] where parseInvalidSignature :: Text -> Either Text Signature parseInvalidSignature a = do bs <- fromHex a first pretty $ parseSignatureRaw bs samplePairSignatures :: [(Text, Text)] samplePairSignatures = [ ( "edsigtrs8bK7vNfiR4Kd9dWasVa1bAWaQSu2ipnmLGZuwQa8ktCEMYVKqbWsbJ7zTS8dgYT9tiSUKorWCPFHosL5zPsiDwBQ6vb" , "91ac1e7fd668854fc7a40feec4034e42c06c068cce10622c607fda232db34c8cf5d8da83098dd891cd4cb4299b3fa0352ae323ad99b24541e54b91888fdc8201") , ( "edsigtj8LhbJ2B3qhZvqzA49raG65dydFcWZW9b9L7ntF3bb29zxaBFFL8SM1jeBUY66hG122znyVA4wpzLdwxcNZwSK3Szu7iD" , "568722d9942f516ab369e7803b5646775459ef93080842400354cb638800861bb051509fcef5ad6940ba38af3697d09f1be5be829b8f6f50610ef28606755d0e") , ( "spsig1PJ9LG9ovbpVJ3CucFWL7iBaQZjqEWMvppgLjYiiSwzcxpuUqHr2BUVZDUwkmZKzMNDWJdgtyhYiicz197TbhS4LPpnxDY" , "85a75c026ae48f5e8d4891e30da2eef1174987f94401881e216c9e217d14247908ea0303579c1df08d70364e7309b410adb4f71787464df001bf7699a0749c8f") , ( "p2sigRmXDp38VNVaEQH28LYukfLPn8QB5hPEberhvQrrUpRscDZJrrApbRh2u46PTVTwKXjxTLKNN9dyLhPQU6U6jWPGxe4d9v" , "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222") ] invalidRawSignatures :: [Text] invalidRawSignatures = [ "" , "ac1e7fd668854fc7a40feec4034e42c06c068cce10622c607fda232db34c8cf5d8da83098dd891cd4cb4299b3fa0352ae323ad99b24541e54b91888fdc8201" , "a91ac1e7fd668854fc7a40feec4034e42c06c068cce10622c607fda232db34c8cf5d8da83098dd891cd4cb4299b3fa0352ae323ad99b24541e54b91888fdc8201" , "222222222222222222222222222222222222222222222222222222222222222222222222222" ] test_parseRawPublicKey :: [TestTree] test_parseRawPublicKey = [ testCase "Successfully parses valid sample public keys in canonical form" $ forM_ sampleRawPublicKeys (\(a, _) -> bimap STB STB (parsePublicKey a) `shouldSatisfy` isRight) , testCase "Successfully parses valid sample public keys in raw byte form" $ forM_ sampleRawPublicKeys (\(_, b) -> fmap STB (parsePublicKeyRaw . unsafe . fromHex $ b) `shouldSatisfy` isRight) , testCase "Parsed public keys are equivalent in canonical and raw byte form" $ forM_ sampleRawPublicKeys (\(a, b) -> (STB . unsafe . parsePublicKeyRaw . unsafe . fromHex $ b) @?= (STB . unsafe . parsePublicKey $ a)) , testGroup "Fails to parse invalid public keys:" [ testCase (toString a) (parseInvalidKey a `shouldSatisfy` isLeft) | a <- invalidRawPublicKeys ] ] where parseInvalidKey :: Text -> Either Text PublicKey parseInvalidKey a = do bs <- fromHex a first pretty $ parsePublicKeyRaw bs sampleRawPublicKeys :: [(Text, Text)] sampleRawPublicKeys = [ ( "edpkupH22qrz1sNQt5HSvWfRJFfyJ9dhNbZLptE6GR4JbMoBcACZZH" , "009a85e0f3f47852869ae667adc3b03a20fa9f324d046174dff6834e7d1fab0e8d") , ( "edpkupH22qrz1sNQt5HSvWfRJFfyJ9dhNbZLptE6GR4JbMoBcACZZH" , "009a85e0f3f47852869ae667adc3b03a20fa9f324d046174dff6834e7d1fab0e8d") , ( "sppk7cdA7Afj8MvuBFrP6KsTLfbM5DtH9GwYaRZwCf5tBVCz6UKGQFR" , "0103b524d0184276467c848ac13557fb0ff8bec5907960f72683f22af430503edfc1") , ( "p2pk67K1dwkDFPB63RZU5H3SoMCvmJdKZDZszc7U4FiGKN2YypKdDCB" , "020368afbb09255d849813712108a4144237dc1fdd5bb74e68335f4c68c12c1e5723") ] invalidRawPublicKeys :: [Text] invalidRawPublicKeys = [ "" , "009a85e0f3f47852869ae667adc3b03a20fa9f324d046174dff6834e7d1fab0e8" , "0009a85e0f3f47852869ae667adc3b03a20fa9f324d046174dff6834e7d1fab0e8d" ] test_parseRawKeyHash :: [TestTree] test_parseRawKeyHash = [ testCase "Successfully parses valid sample key hashes in canonical form" $ forM_ sampleRawKeyHashes (\(a, _) -> bimap STB STB (parseKeyHash a) `shouldSatisfy` isRight) , testCase "Successfully parses valid sample key hashes in raw byte form" $ forM_ sampleRawKeyHashes (\(_, b) -> fmap STB (parseKeyHashRaw . unsafe . fromHex $ b) `shouldSatisfy` isRight) , testCase "Parsed key hashes are equivalent in canonical and raw byte form" $ forM_ sampleRawKeyHashes (\(a, b) -> (STB . unsafe . parseKeyHashRaw . unsafe . fromHex $ b) @?= (STB . unsafe . parseKeyHash $ a)) , testGroup "Fails to parse invalid key hashes:" [ testCase (toString a) (parseInvalidKey a `shouldSatisfy` isLeft) | a <- invalidRawKeyHashes ] ] where parseInvalidKey :: Text -> Either Text KeyHash parseInvalidKey a = do bs <- fromHex a first pretty $ parseKeyHashRaw bs sampleRawKeyHashes :: [(Text, Text)] sampleRawKeyHashes = [ ("tz1NaZzLvdDBLfV2LWC6F4SJfNV2jHdZJXkJ" ,"002040d6c2ede06fbd1cbc16d51c3085dc2e43e396") , ("tz1Yz3VPaCNB5FjhdEVnSoN8Xv3ZM8g2LYhw" ,"0092629ed0afa9cd42835ce09ee2623c1efa0b590d") , ("tz2EfqCbLmpfv7mNiLcMmhxAwdgHtPTcwR4W" ,"0145b5e7d31bf6612e61ebfa7a6d929ce7800a55a4") , ("tz2Darj3LyQzekU98ZK8diHvuyn1YYjcHpc6" ,"0139ccf913874519b2d20917bf5f2de537420d2726") ] invalidRawKeyHashes :: [Text] invalidRawKeyHashes = [ "" , "15002040d6c2ede06fbd1cbc16d51c3085dc2e43e396" , "100000015002040d6c2ede06fbd1cbc16d51c3085dc2e43e396" , "1100000015002040d6c2ede06fbd1cbc16d51c3085dc2e43e396" ]