packages feed

crypton-2.0.0: tests/ECDSASpec.hs

-- The binary curves are deprecated and still supported, so the tests
-- that hold them to their behaviour name them on purpose.
{-# OPTIONS_GHC -Wno-deprecations #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}

module ECDSASpec (spec) where

import qualified Crypto.ECC as ECDSA
import Crypto.Error
import Crypto.Hash
import qualified Crypto.PubKey.ECC.ECDSA as ECC
import qualified Crypto.PubKey.ECC.Generate as ECC
import qualified Crypto.PubKey.ECC.Types as ECC
import qualified Crypto.PubKey.ECDSA as ECDSA
import qualified Data.ByteString as B
import Data.Maybe

import Imports

data Curve
    = forall curve.
        (ECDSA.EllipticCurveECDSA curve, Show (ECDSA.Scalar curve)) =>
      Curve curve ECC.Curve ECC.CurveName

instance Show Curve where
    showsPrec d (Curve _ _ name) = showsPrec d name

instance Arbitrary Curve where
    arbitrary =
        elements
            [ makeCurve ECDSA.Curve_P256R1 ECC.SEC_p256r1
            , makeCurve ECDSA.Curve_P384R1 ECC.SEC_p384r1
            , makeCurve ECDSA.Curve_P521R1 ECC.SEC_p521r1
            ]
      where
        makeCurve c name = Curve c (ECC.getCurveByName name) name

arbitraryScalar :: ECC.Curve -> Gen Integer
arbitraryScalar curve = choose (1, n - 1)
  where
    n = ECC.ecc_n (ECC.common_curve curve)

sigECDSAtoECC
    :: ECDSA.EllipticCurveECDSA curve
    => proxy curve -> ECDSA.Signature curve -> ECC.Signature
sigECDSAtoECC prx (ECDSA.Signature r s) = ECC.Signature (ECDSA.scalarToInteger prx r) (ECDSA.scalarToInteger prx s)

normalizeECC :: ECC.Curve -> ECC.Signature -> ECC.Signature
normalizeECC curve (ECC.Signature r s)
    | s <= n `div` 2 = ECC.Signature r s
    | otherwise = ECC.Signature r (n - s)
  where
    n = ECC.ecc_n $ ECC.common_curve curve

testRecover :: ECC.CurveName -> Spec
testRecover name = prop (show name) $ \(ArbitraryBS0_2901 msg) -> do
    let curve = ECC.getCurveByName name
    let n = ECC.ecc_n $ ECC.common_curve curve
    k <- choose (1, n - 1)
    d <- choose (1, n - 1)
    let key = ECC.PrivateKey curve d
    let digest = hashWith SHA256 msg
    let pub =
            ECC.signExtendedDigestWith k key digest >>= \signature -> ECC.recoverDigest curve signature digest
    pure $
        propertyHold
            [eqTest "recovery" (Just $ ECC.generateQ curve d) (ECC.public_q <$> pub)]

testNormalize :: ECC.CurveName -> Spec
testNormalize name = prop (show name) $ \(ArbitraryBS0_2901 msg) -> do
    let curve = ECC.getCurveByName name
    let n = ECC.ecc_n $ ECC.common_curve curve
    k <- choose (1, n - 1)
    d <- choose (1, n - 1)
    let key = ECC.PrivateKey curve d
    let digest = hashWith SHA256 msg
    let check =
            ECC.signExtendedDigestWith k key digest >>= \s -> pure $ ECC.sign_s (ECC.signature s) <= n `div` 2
    pure $ propertyHold [eqTest "normalized" (Just True) check]

spec :: Spec
spec = do
    modifyMaxSuccess (const 5) $
        describe "verification" $ do
            prop "SHA1" $ propertyECDSA SHA1
            prop "SHA224" $ propertyECDSA SHA224
            prop "SHA256" $ propertyECDSA SHA256
            prop "SHA384" $ propertyECDSA SHA384
            prop "SHA512" $ propertyECDSA SHA512
    describe "recovery" $ do
        modifyMaxSuccess (const 100) $ testRecover ECC.SEC_p128r1
        modifyMaxSuccess (const 100) $ testRecover ECC.SEC_p128r2
        modifyMaxSuccess (const 100) $ testRecover ECC.SEC_p256k1
        modifyMaxSuccess (const 100) $ testRecover ECC.SEC_p256r1
        modifyMaxSuccess (const 50) $ testRecover ECC.SEC_t131r1
        modifyMaxSuccess (const 50) $ testRecover ECC.SEC_t131r2
        modifyMaxSuccess (const 20) $ testRecover ECC.SEC_t233k1
        modifyMaxSuccess (const 20) $ testRecover ECC.SEC_t233r1
    describe "normalize" $ do
        modifyMaxSuccess (const 100) $ testNormalize ECC.SEC_p128r1
        modifyMaxSuccess (const 100) $ testNormalize ECC.SEC_p128r2
        modifyMaxSuccess (const 100) $ testNormalize ECC.SEC_p256k1
        modifyMaxSuccess (const 100) $ testNormalize ECC.SEC_p256r1
        modifyMaxSuccess (const 50) $ testNormalize ECC.SEC_t131r1
        modifyMaxSuccess (const 50) $ testNormalize ECC.SEC_t131r2
        modifyMaxSuccess (const 20) $ testNormalize ECC.SEC_t233k1
        modifyMaxSuccess (const 20) $ testNormalize ECC.SEC_t233r1
  where
    propertyECDSA hashAlg (Curve c curve _) (ArbitraryBS0_2901 msg) = do
        d <- arbitraryScalar curve
        kECC <- arbitraryScalar curve
        let privECC = ECC.PrivateKey curve d
            prx = Just c -- using Maybe as Proxy
            kECDSA = throwCryptoError $ ECDSA.scalarFromInteger prx kECC
            privECDSA = throwCryptoError $ ECDSA.scalarFromInteger prx d
            pubECDSA = ECDSA.toPublic prx privECDSA
            sigECC = fromJust $ ECC.signWith kECC privECC hashAlg msg
            sigECDSA = fromJust $ ECDSA.signWith prx kECDSA privECDSA hashAlg msg
            msg' = msg `B.append` B.singleton 42
        return $
            propertyHold
                [ eqTest "signature" sigECC $ normalizeECC curve $ sigECDSAtoECC prx sigECDSA
                , eqTest "verification" True (ECDSA.verify prx hashAlg pubECDSA sigECDSA msg)
                , eqTest "alteration" False (ECDSA.verify prx hashAlg pubECDSA sigECDSA msg')
                ]