packages feed

web3-tools-0.1.0.0: test/Eth/AddressSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Eth.AddressSpec (spec) where

import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
import Eth.Address (verifySignature)
import Test.Hspec
import Test.QuickCheck
import Test.QuickCheck.Monadic

-- Arbitrary instance for ByteString
instance Arbitrary BS.ByteString where
  arbitrary = BS.pack <$> arbitrary

spec :: Spec
spec = do
  describe "verifySignature" $ do
    describe "input validation" $ do
      it "rejects signature with wrong length" $ do
        let message = "test message"
            sig = BS.replicate 64 0 -- Should be 65
            addr = BS.replicate 20 0
        result <- verifySignature message sig addr
        result `shouldBe` False

      it "rejects address with wrong length" $ do
        let message = "test message"
            sig = BS.replicate 65 0
            addr = BS.replicate 19 0 -- Should be 20
        result <- verifySignature message sig addr
        result `shouldBe` False

      it "accepts correct lengths (even if signature is invalid)" $ do
        let message = "test message"
            sig = BS.replicate 65 0
            addr = BS.replicate 20 0
        -- Should not crash, just return False for invalid signature
        result <- verifySignature message sig addr
        result `shouldBe` False

    describe "property-based tests" $ do
      it "always returns a boolean" $
        property $
          \(message :: BS.ByteString) (sig :: BS.ByteString) (addr :: BS.ByteString) -> monadicIO $ do
            result <- run $ verifySignature message sig addr
            return $ result `elem` [True, False]

      it "returns False for signatures with wrong length" $
        property $
          forAll (choose (0, 128) `suchThat` (/= 65)) $ \sigLen -> monadicIO $ do
            let message = "test"
                sig = BS.replicate sigLen 0
                addr = BS.replicate 20 0
            result <- run $ verifySignature message sig addr
            return $ result == False

      it "returns False for addresses with wrong length" $
        property $
          forAll (choose (0, 40) `suchThat` (/= 20)) $ \addrLen -> monadicIO $ do
            let message = "test"
                sig = BS.replicate 65 0
                addr = BS.replicate addrLen 0
            result <- run $ verifySignature message sig addr
            return $ result == False

      it "is deterministic" $
        property $
          \(message :: BS.ByteString) -> monadicIO $ do
            let sig = BS.replicate 65 0
                addr = BS.replicate 20 0
            result1 <- run $ verifySignature message sig addr
            result2 <- run $ verifySignature message sig addr
            return $ result1 == result2

    describe "signature verification" $ do
      it "rejects an invalid signature for correct message/address" $ do
        let message = "Hello, Web3!"
            sig' = B16.decode "9d2db20f926fad885854f4bf659a72de9db2990b199ad300644e2b482f8583e169325c2a57bbfa4ef6897f63431f290716c58507af64d88554923410f38be2e31b"
            addr' = B16.decode "f406ffc8e58c5163e67e020dd1c5ce924923ecbf"
        result <- case (sig', addr') of
          (Right sig, Right addr) -> verifySignature message sig addr
          (_, _) -> error "Couldn't decode signature or address!"
        result `shouldBe` False

      it "rejects invalid message from with valid address/signature" $ do
        let message = "Bye, Web3!"
            sig' = B16.decode "8d2db20f926fad885854f4bf659a72de9db2990b199ad300644e2b482f8583e169325c2a57bbfa4ef6897f63431f290716c58507af64d88554923410f38be2e31b"
            addr' = B16.decode "f406ffc8e58c5163e67e020dd1c5ce924923ecbf"
        result <- case (sig', addr') of
          (Right sig, Right addr) -> verifySignature message sig addr
          (_, _) -> error "Couldn't decode signature or address!"
        result `shouldBe` False

      it "rejects invalid address from with valid message/signature [from another address]" $ do
        let message = "Hello, Web3!"
            sig' = B16.decode "8d2db20f926fad885854f4bf659a72de9db2990b199ad300644e2b482f8583e169325c2a57bbfa4ef6897f63431f290716c58507af64d88554923410f38be2e31b"
            addr' = B16.decode "0406ffc8e58c5163e67e020dd1c5ce924923ecbf"
        result <- case (sig', addr') of
          (Right sig, Right addr) -> verifySignature message sig addr
          (_, _) -> error "Couldn't decode signature or address!"
        result `shouldBe` False

      it "verifies a valid Ethereum signature from real wallet" $ do
        let message = "Hello, Web3!"
            sig' = B16.decode "8d2db20f926fad885854f4bf659a72de9db2990b199ad300644e2b482f8583e169325c2a57bbfa4ef6897f63431f290716c58507af64d88554923410f38be2e31b"
            addr' = B16.decode "f406ffc8e58c5163e67e020dd1c5ce924923ecbf"
        result <- case (sig', addr') of
          (Right sig, Right addr) -> verifySignature message sig addr
          (_, _) -> error "Couldn't decode signature or address!"
        result `shouldBe` True

    describe "edge cases" $ do
      it "handles empty message" $ do
        let message = ""
            sig = BS.replicate 65 0
            addr = BS.replicate 20 0
        result <- verifySignature message sig addr
        result `shouldSatisfy` const True -- Just shouldn't crash
      it "handles very long message" $ do
        let message = BS.replicate 10000 0xff
            sig = BS.replicate 65 0
            addr = BS.replicate 20 0
        result <- verifySignature message sig addr
        result `shouldSatisfy` const True -- Just shouldn't crash