-- | Tests for 'Tezos.Crypto'. module Test.Tezos.Crypto ( test_Roundtrip , test_Signing , test_Bytes_Hashing , unit_Key_Hashing ) where import Fmt (fmt, hexF, pretty) import Test.Hspec (Expectation, shouldSatisfy) import Test.HUnit (Assertion, (@?=)) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (testCase) import Tezos.Crypto import Test.Util.QuickCheck (aesonRoundtrip, roundtripTestSTB) test_Roundtrip :: [TestTree] test_Roundtrip = [ testGroup "parse . format ≡ pure" [ roundtripTestSTB formatPublicKey parsePublicKey , roundtripTestSTB formatSecretKey parseSecretKey , roundtripTestSTB formatSignature parseSignature , roundtripTestSTB formatKeyHash parseKeyHash ] , testGroup "JSON encoding/deconding" [ aesonRoundtrip @PublicKey , aesonRoundtrip @Signature , aesonRoundtrip @KeyHash ] ] ---------------------------------------------------------------------------- -- 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 "aaa" parsePublicKeyInvalid "edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3v" parsePublicKeyInvalid "edsigtrs8bK7vNfiR4Kd9dWasVa1bAWaQSu2ipnmLGZuwQa8ktCEMYVKqbWsbJ7zTS8dgYT9tiSUKorWCPFHosL5zPsiDwBQ6vb" ] , testGroup "parseSignature" [ testCase "Successfully parses valid sample data" $ mapM_ (parseSignatureSample . sdSignature) sampleSignatures , testCase "Fails to parse invalid data" $ do parseSignatureInvalid "bbb" parseSignatureInvalid "edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3V" parseSignatureInvalid "edsigtrs8bK7vNfiR4Kd9dWasVa1bAWaQSu2ipnmLGZuwQa8ktCEMYVKqbWsbJ7zTS8dgYT9tiSUKorWCPFHosL5zPsiDwBQ6vB" ] ] , testCase "checkSignature" $ mapM_ checkSignatureSample sampleSignatures ] 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 } ] parsePublicKeySample :: Text -> Expectation parsePublicKeySample publicKeyText = parsePublicKey publicKeyText `shouldSatisfy` isRight parsePublicKeyInvalid :: Text -> Expectation parsePublicKeyInvalid invalidPublicKeyText = parsePublicKey invalidPublicKeyText `shouldSatisfy` isLeft parseSignatureSample :: Text -> Expectation parseSignatureSample publicKeyText = parseSignature publicKeyText `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 = partialParse parsePublicKey (sdPublicKey sd) signature = partialParse parseSignature (sdSignature sd) ---------------------------------------------------------------------------- -- Hashing ---------------------------------------------------------------------------- test_Bytes_Hashing :: [TestTree] test_Bytes_Hashing = [ testGroup "blake2b" $ hashingTest blake2b blake2bHashes , testGroup "sha256" $ hashingTest sha256 sha256Hashes , testGroup "sha512" $ hashingTest sha512 sha512Hashes ] -- These values have been computed using the following contract: {- parameter string; storage bytes; code { CDR; SHA512; # replace with desired function NIL operation; PAIR;}; -} blake2bHashes, sha256Hashes, sha512Hashes :: [(ByteString, Text)] blake2bHashes = [ ("\0", "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314") -- 0x00 , ("\0\0", "9ee6dfb61a2fb903df487c401663825643bb825d41695e63df8af6162ab145a6") -- 0x0000 ] sha256Hashes = [ ("\0", "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d") -- 0x00 , ("\0\0", "96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7") -- 0x0000 ] sha512Hashes = [ ("\0", "b8244d028981d693af7b456af8efa4cad63d282e19ff14942c246e50d9351d22704a802a71c3580b6370de4ceb293c324a8423342557d4e5c38438f0e36910ee") -- 0x00 , ("#", "d369286ac86b60fa920f6464d26becacd9f4c8bd885b783407cdcaa74fafd45a8b56b364b63f6256c3ceef26278a1c7799d4243a8149b5ede5ce1d890b5c7236") -- 0x23 ] hashingTest :: (ByteString -> ByteString) -> [(ByteString, Text)] -> [TestTree] hashingTest hashFunc pairs = do flip map pairs $ \(bs, bsHashHex) -> do testCase ("correctly computes hash of 0x" <> fmt (hexF bs)) $ fmt (hexF (hashFunc bs)) @?= bsHashHex ---------------------------------------------------------------------------- -- Key hashing ---------------------------------------------------------------------------- unit_Key_Hashing :: Assertion unit_Key_Hashing = mapM_ hashKeySample sampleKeyHashes sampleKeyHashes :: [(Text, Text)] sampleKeyHashes = [ ("edpkupH22qrz1sNQt5HSvWfRJFfyJ9dhNbZLptE6GR4JbMoBcACZZH" , "tz1NaZzLvdDBLfV2LWC6F4SJfNV2jHdZJXkJ" ) , ("edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3V" , "tz1Yz3VPaCNB5FjhdEVnSoN8Xv3ZM8g2LYhw" ) ] hashKeySample :: (Text, Text) -> Assertion hashKeySample (pkText, keyHashText) = hashKey pk @?= keyHash where pk = partialParse parsePublicKey pkText keyHash = partialParse parseKeyHash keyHashText ---------------------------------------------------------------------------- -- Utils ---------------------------------------------------------------------------- -- If passed textual data is invalid, it's a programmer mistake. partialParse :: (Text -> Either CryptoParseError a) -> Text -> a partialParse parse = either (error . pretty) id . parse