packages feed

cleveland-0.1.1: morley-test/Test/Tezos/Crypto.hs

-- 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"
      ]