From 1b18637574d8486010fe68d8e65a5e7da690fdd8 Mon Sep 17 00:00:00 2001 From: Julian Brunner Date: Sun, 2 Mar 2025 14:52:58 +0100 Subject: [PATCH 1/5] add normalize functions --- Crypto/PubKey/ECC/ECDSA.hs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Crypto/PubKey/ECC/ECDSA.hs b/Crypto/PubKey/ECC/ECDSA.hs index be29cca1..a70fe6de 100644 --- a/Crypto/PubKey/ECC/ECDSA.hs +++ b/Crypto/PubKey/ECC/ECDSA.hs @@ -22,6 +22,8 @@ module Crypto.PubKey.ECC.ECDSA ( verifyDigest, recover, recoverDigest, + normalize, + normalizeExtended, ) where import Control.Monad @@ -205,3 +207,15 @@ recover :: (ByteArrayAccess msg, HashAlgorithm hash) => hash -> Curve -> ExtendedSignature -> msg -> Maybe PublicKey recover hashAlg curve sig msg = recoverDigest curve sig $ hashWith hashAlg msg + +normalize :: Curve -> Signature -> Signature +normalize curve (Signature r s) + | s <= n `div` 2 = Signature r s + | otherwise = Signature r (n - s) + where n = ecc_n $ common_curve curve + +normalizeExtended :: Curve -> ExtendedSignature -> ExtendedSignature +normalizeExtended curve (ExtendedSignature i p (Signature r s)) + | s <= n `div` 2 = ExtendedSignature i p (Signature r s) + | otherwise = ExtendedSignature i (not p) (Signature r (n - s)) + where n = ecc_n $ common_curve curve From 140d080277961527533e39aa25e6a65195695045 Mon Sep 17 00:00:00 2001 From: Julian Brunner Date: Sun, 2 Mar 2025 15:18:54 +0100 Subject: [PATCH 2/5] add normalize tests --- tests/ECDSA.hs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/ECDSA.hs b/tests/ECDSA.hs index 29368c7e..50f56395 100644 --- a/tests/ECDSA.hs +++ b/tests/ECDSA.hs @@ -11,6 +11,7 @@ import qualified Crypto.PubKey.ECC.Generate as ECC import qualified Crypto.PubKey.ECC.ECDSA as ECC import qualified Crypto.PubKey.ECC.Types as ECC import qualified Crypto.PubKey.ECDSA as ECDSA +import Data.Functor import qualified Data.ByteString as B import Imports @@ -57,6 +58,26 @@ testRecover name = testProperty (show name) $ \ (ArbitraryBS0_2901 msg) -> do 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 -> TestTree +testNormalize name = testProperty (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 pub = ECC.PublicKey curve $ ECC.generateQ curve d + let digest = hashWith SHA256 msg + let sig = ECC.signExtendedDigestWith k key digest + let sigN = ECC.normalize curve . ECC.signature <$> sig + let sigE = ECC.normalizeExtended curve <$> sig + let checkN = sigN <&> \ s -> ECC.verifyDigest pub s digest + let checkE = sigE <&> \ s -> ECC.verifyDigest pub (ECC.signature s) digest + let recover = sigE >>= \ s -> ECC.recoverDigest curve s digest + pure $ propertyHold [ + eqTest "verification" (Just True) checkN, + eqTest "verification-extended" (Just True) checkE, + eqTest "recovery" (Just $ ECC.public_q pub) (ECC.public_q <$> recover)] + tests :: TestTree tests = testGroup "ECDSA" [ localOption (QuickCheckTests 5) $ @@ -78,6 +99,12 @@ tests = testGroup "ECDSA" , localOption (QuickCheckTests 20) $ testRecover ECC.SEC_t233k1 , localOption (QuickCheckTests 20) $ testRecover ECC.SEC_t233r1 ] + , testGroup "normalize" + [ localOption (QuickCheckTests 100) $ testNormalize ECC.SEC_p128r1 + , localOption (QuickCheckTests 100) $ testNormalize ECC.SEC_p128r2 + , localOption (QuickCheckTests 100) $ testNormalize ECC.SEC_p256k1 + , localOption (QuickCheckTests 100) $ testNormalize ECC.SEC_p256r1 + ] ] where propertyECDSA hashAlg (Curve c curve _) (ArbitraryBS0_2901 msg) = do From 71588ccd7aa09869b13920c805e8e170bce223fd Mon Sep 17 00:00:00 2001 From: Julian Brunner Date: Wed, 5 Mar 2025 20:19:10 +0100 Subject: [PATCH 3/5] use unsafeShiftR instead of div --- Crypto/PubKey/ECC/ECDSA.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Crypto/PubKey/ECC/ECDSA.hs b/Crypto/PubKey/ECC/ECDSA.hs index a70fe6de..f08c9267 100644 --- a/Crypto/PubKey/ECC/ECDSA.hs +++ b/Crypto/PubKey/ECC/ECDSA.hs @@ -28,6 +28,7 @@ module Crypto.PubKey.ECC.ECDSA ( import Control.Monad import Data.Data +import Data.Bits import Crypto.Hash import Crypto.Internal.ByteArray (ByteArrayAccess) @@ -210,12 +211,12 @@ recover hashAlg curve sig msg = recoverDigest curve sig $ hashWith hashAlg msg normalize :: Curve -> Signature -> Signature normalize curve (Signature r s) - | s <= n `div` 2 = Signature r s + | s <= n `unsafeShiftR` 1 = Signature r s | otherwise = Signature r (n - s) where n = ecc_n $ common_curve curve normalizeExtended :: Curve -> ExtendedSignature -> ExtendedSignature normalizeExtended curve (ExtendedSignature i p (Signature r s)) - | s <= n `div` 2 = ExtendedSignature i p (Signature r s) + | s <= n `unsafeShiftR` 1 = ExtendedSignature i p (Signature r s) | otherwise = ExtendedSignature i (not p) (Signature r (n - s)) where n = ecc_n $ common_curve curve From 8d876e049d5af5fe62aa383fa8ddab71e86c5a02 Mon Sep 17 00:00:00 2001 From: Julian Brunner Date: Thu, 6 Mar 2025 03:11:13 +0100 Subject: [PATCH 4/5] add documentation to normalize functions --- Crypto/PubKey/ECC/ECDSA.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Crypto/PubKey/ECC/ECDSA.hs b/Crypto/PubKey/ECC/ECDSA.hs index f08c9267..08a52c64 100644 --- a/Crypto/PubKey/ECC/ECDSA.hs +++ b/Crypto/PubKey/ECC/ECDSA.hs @@ -209,12 +209,14 @@ recover => hash -> Curve -> ExtendedSignature -> msg -> Maybe PublicKey recover hashAlg curve sig msg = recoverDigest curve sig $ hashWith hashAlg msg +-- | Normalize a signature to be in low-S form. normalize :: Curve -> Signature -> Signature normalize curve (Signature r s) | s <= n `unsafeShiftR` 1 = Signature r s | otherwise = Signature r (n - s) where n = ecc_n $ common_curve curve +-- | Normalize an extended signature to be in low-S form. normalizeExtended :: Curve -> ExtendedSignature -> ExtendedSignature normalizeExtended curve (ExtendedSignature i p (Signature r s)) | s <= n `unsafeShiftR` 1 = ExtendedSignature i p (Signature r s) From f67cf07b87aa914d67564d2d05178ae41af1daa3 Mon Sep 17 00:00:00 2001 From: Julian Brunner Date: Fri, 7 Mar 2025 22:33:20 +0100 Subject: [PATCH 5/5] integrate normalization into signing and adjust tests to accomodate --- Crypto/PubKey/ECC/ECDSA.hs | 20 +++----------------- tests/ECDSA.hs | 38 ++++++++++++++++---------------------- tests/KAT_PubKey/ECDSA.hs | 11 +++++++++-- 3 files changed, 28 insertions(+), 41 deletions(-) diff --git a/Crypto/PubKey/ECC/ECDSA.hs b/Crypto/PubKey/ECC/ECDSA.hs index 08a52c64..52a79cbf 100644 --- a/Crypto/PubKey/ECC/ECDSA.hs +++ b/Crypto/PubKey/ECC/ECDSA.hs @@ -22,8 +22,6 @@ module Crypto.PubKey.ECC.ECDSA ( verifyDigest, recover, recoverDigest, - normalize, - normalizeExtended, ) where import Control.Monad @@ -104,7 +102,9 @@ signExtendedDigestWith k (PrivateKey curve d) digest = do kInv <- inverse k n let s = kInv * (z + r * d) `mod` n when (r == 0 || s == 0) Nothing - return $ ExtendedSignature i p $ Signature r s + return $ if s <= n `unsafeShiftR` 1 + then ExtendedSignature i p $ Signature r s + else ExtendedSignature i (not p) $ Signature r (n - s) -- | Sign digest using the private key and an explicit k number. -- @@ -208,17 +208,3 @@ recover :: (ByteArrayAccess msg, HashAlgorithm hash) => hash -> Curve -> ExtendedSignature -> msg -> Maybe PublicKey recover hashAlg curve sig msg = recoverDigest curve sig $ hashWith hashAlg msg - --- | Normalize a signature to be in low-S form. -normalize :: Curve -> Signature -> Signature -normalize curve (Signature r s) - | s <= n `unsafeShiftR` 1 = Signature r s - | otherwise = Signature r (n - s) - where n = ecc_n $ common_curve curve - --- | Normalize an extended signature to be in low-S form. -normalizeExtended :: Curve -> ExtendedSignature -> ExtendedSignature -normalizeExtended curve (ExtendedSignature i p (Signature r s)) - | s <= n `unsafeShiftR` 1 = ExtendedSignature i p (Signature r s) - | otherwise = ExtendedSignature i (not p) (Signature r (n - s)) - where n = ecc_n $ common_curve curve diff --git a/tests/ECDSA.hs b/tests/ECDSA.hs index 50f56395..4c8e4e5f 100644 --- a/tests/ECDSA.hs +++ b/tests/ECDSA.hs @@ -11,7 +11,6 @@ import qualified Crypto.PubKey.ECC.Generate as ECC import qualified Crypto.PubKey.ECC.ECDSA as ECC import qualified Crypto.PubKey.ECC.Types as ECC import qualified Crypto.PubKey.ECDSA as ECDSA -import Data.Functor import qualified Data.ByteString as B import Imports @@ -39,13 +38,14 @@ arbitraryScalar curve = choose (1, n - 1) where n = ECC.ecc_n (ECC.common_curve curve) -sigECCToECDSA - :: ECDSA.EllipticCurveECDSA curve - => proxy curve -> ECC.Signature -> ECDSA.Signature curve -sigECCToECDSA prx (ECC.Signature r s) = - ECDSA.Signature - (throwCryptoError $ ECDSA.scalarFromInteger prx r) - (throwCryptoError $ ECDSA.scalarFromInteger prx s) +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 -> TestTree testRecover name = testProperty (show name) $ \ (ArbitraryBS0_2901 msg) -> do @@ -65,18 +65,9 @@ testNormalize name = testProperty (show name) $ \ (ArbitraryBS0_2901 msg) -> do k <- choose (1, n - 1) d <- choose (1, n - 1) let key = ECC.PrivateKey curve d - let pub = ECC.PublicKey curve $ ECC.generateQ curve d let digest = hashWith SHA256 msg - let sig = ECC.signExtendedDigestWith k key digest - let sigN = ECC.normalize curve . ECC.signature <$> sig - let sigE = ECC.normalizeExtended curve <$> sig - let checkN = sigN <&> \ s -> ECC.verifyDigest pub s digest - let checkE = sigE <&> \ s -> ECC.verifyDigest pub (ECC.signature s) digest - let recover = sigE >>= \ s -> ECC.recoverDigest curve s digest - pure $ propertyHold [ - eqTest "verification" (Just True) checkN, - eqTest "verification-extended" (Just True) checkE, - eqTest "recovery" (Just $ ECC.public_q pub) (ECC.public_q <$> recover)] + 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] tests :: TestTree tests = testGroup "ECDSA" @@ -104,6 +95,10 @@ tests = testGroup "ECDSA" , localOption (QuickCheckTests 100) $ testNormalize ECC.SEC_p128r2 , localOption (QuickCheckTests 100) $ testNormalize ECC.SEC_p256k1 , localOption (QuickCheckTests 100) $ testNormalize ECC.SEC_p256r1 + , localOption (QuickCheckTests 50) $ testNormalize ECC.SEC_t131r1 + , localOption (QuickCheckTests 50) $ testNormalize ECC.SEC_t131r2 + , localOption (QuickCheckTests 20) $ testNormalize ECC.SEC_t233k1 + , localOption (QuickCheckTests 20) $ testNormalize ECC.SEC_t233r1 ] ] where @@ -117,11 +112,10 @@ tests = testGroup "ECDSA" pubECDSA = ECDSA.toPublic prx privECDSA sigECC = fromJust $ ECC.signWith kECC privECC hashAlg msg sigECDSA = fromJust $ ECDSA.signWith prx kECDSA privECDSA hashAlg msg - sigECDSA' = sigECCToECDSA prx sigECC msg' = msg `B.append` B.singleton 42 return $ propertyHold - [ eqTest "signature" sigECDSA sigECDSA' - , eqTest "verification" True (ECDSA.verify prx hashAlg pubECDSA sigECDSA' msg) + [ 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') ] diff --git a/tests/KAT_PubKey/ECDSA.hs b/tests/KAT_PubKey/ECDSA.hs index 797da9b1..3835e651 100644 --- a/tests/KAT_PubKey/ECDSA.hs +++ b/tests/KAT_PubKey/ECDSA.hs @@ -32,6 +32,13 @@ instance Show Entry where (show $ B.take 8 $ message entry) (show $ hashAlgorithm entry) +normalize :: Entry -> Entry +normalize entry + | s <= n `div` 2 = entry + | otherwise = entry { signature = Signature r (n - s) } where + Signature r s = signature entry + n = ecc_n $ common_curve $ getCurveByName $ curveName entry + -- taken from GEC 2: Test Vectors for SEC 1 gec2Entries :: [Entry] gec2Entries = [ @@ -874,5 +881,5 @@ testEntry entry = testGroup (show entry) tests where ecdsaTests :: TestTree ecdsaTests = testGroup "ECDSA" [ - testGroup "GEC 2" $ testEntry <$> gec2Entries, - testGroup "RFC 6979" $ testEntry <$> flatten rfc6979Entries] + testGroup "GEC 2" $ testEntry . normalize <$> gec2Entries, + testGroup "RFC 6979" $ testEntry . normalize <$> flatten rfc6979Entries]