Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ECDSA Signature Normalization #45

Merged
merged 5 commits into from
Mar 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Crypto/PubKey/ECC/ECDSA.hs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module Crypto.PubKey.ECC.ECDSA (

import Control.Monad
import Data.Data
import Data.Bits

import Crypto.Hash
import Crypto.Internal.ByteArray (ByteArrayAccess)
Expand Down Expand Up @@ -101,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.
--
Expand Down
41 changes: 31 additions & 10 deletions tests/ECDSA.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,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
Expand All @@ -57,6 +58,17 @@ 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 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]

tests :: TestTree
tests = testGroup "ECDSA"
[ localOption (QuickCheckTests 5) $
Expand All @@ -78,6 +90,16 @@ 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
, 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
propertyECDSA hashAlg (Curve c curve _) (ArbitraryBS0_2901 msg) = do
Expand All @@ -90,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')
]
11 changes: 9 additions & 2 deletions tests/KAT_PubKey/ECDSA.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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]
Loading