Skip to content

Commit b1d5d35

Browse files
authored
Merge pull request #234 from tomato42/unlucky_k_tests
test for r==0 and s==0 in signature creation
2 parents 533bc74 + 5a4f658 commit b1d5d35

File tree

2 files changed

+67
-7
lines changed

2 files changed

+67
-7
lines changed

src/ecdsa/keys.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,8 +1394,10 @@ def sign(
13941394
default.
13951395
13961396
:raises RSZeroError: in the unlikely event when "r" parameter or
1397-
"s" parameter is equal 0 as that would leak the key. Calee should
1398-
try a better entropy source or different 'k' in such case.
1397+
"s" parameter of the created signature is equal 0, as that would
1398+
leak the key. Caller should try a better entropy source, retry with
1399+
different 'k', or use the
1400+
:func:`~SigningKey.sign_deterministic` in such case.
13991401
14001402
:return: encoded signature of the hash of `data`
14011403
:rtype: bytes or sigencode function dependant type
@@ -1444,8 +1446,10 @@ def sign_digest(
14441446
SHA-384 output using NIST256p or in similar situations.
14451447
14461448
:raises RSZeroError: in the unlikely event when "r" parameter or
1447-
"s" parameter is equal 0 as that would leak the key. Calee should
1448-
try a better entropy source in such case.
1449+
"s" parameter of the created signature is equal 0, as that would
1450+
leak the key. Caller should try a better entropy source, retry with
1451+
different 'k', or use the
1452+
:func:`~SigningKey.sign_digest_deterministic` in such case.
14491453
14501454
:return: encoded signature for the `digest` hash
14511455
:rtype: bytes or sigencode function dependant type
@@ -1472,8 +1476,10 @@ def sign_number(self, number, entropy=None, k=None):
14721476
it will be selected at random using the entropy source.
14731477
14741478
:raises RSZeroError: in the unlikely event when "r" parameter or
1475-
"s" parameter is equal 0 as that would leak the key. Calee should
1476-
try a different 'k' in such case.
1479+
"s" parameter of the created signature is equal 0, as that would
1480+
leak the key. Caller should try a better entropy source, retry with
1481+
different 'k', or use the
1482+
:func:`~SigningKey.sign_digest_deterministic` in such case.
14771483
14781484
:return: the "r" and "s" parameters of the signature
14791485
:rtype: tuple of ints

src/ecdsa/test_keys.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
except NameError:
99
buffer = memoryview
1010

11+
import os
1112
import array
1213
import pytest
1314
import hashlib
@@ -23,7 +24,7 @@
2324
sigdecode_strings,
2425
)
2526
from .curves import NIST256p, Curve, BRAINPOOLP160r1
26-
from .ellipticcurve import Point
27+
from .ellipticcurve import Point, PointJacobi, CurveFp, INFINITY
2728
from .ecdsa import generator_brainpoolp160r1
2829

2930

@@ -296,6 +297,59 @@ def test_inequality_on_signing_keys_not_implemented(self):
296297
self.assertNotEqual(self.sk1, None)
297298

298299

300+
class TestTrivialCurve(unittest.TestCase):
301+
@classmethod
302+
def setUpClass(cls):
303+
# To test what happens with r or s in signing happens to be zero we
304+
# need to find a scalar that creates one of the points on a curve that
305+
# has x coordinate equal to zero.
306+
# Even for secp112r2 curve that's non trivial so use this toy
307+
# curve, for which we can iterate over all points quickly
308+
curve = CurveFp(163, 84, 58)
309+
gen = PointJacobi(curve, 2, 87, 1, 167, generator=True)
310+
311+
cls.toy_curve = Curve("toy_p8", curve, gen, (1, 2, 0))
312+
313+
cls.sk = SigningKey.from_secret_exponent(
314+
140, cls.toy_curve, hashfunc=hashlib.sha1,
315+
)
316+
317+
def test_generator_sanity(self):
318+
gen = self.toy_curve.generator
319+
320+
self.assertEqual(gen * gen.order(), INFINITY)
321+
322+
def test_public_key_sanity(self):
323+
self.assertEqual(self.sk.verifying_key.to_string(), b"\x98\x1e")
324+
325+
def test_deterministic_sign(self):
326+
sig = self.sk.sign_deterministic(b"message")
327+
328+
self.assertEqual(sig, b"-.")
329+
330+
self.assertTrue(self.sk.verifying_key.verify(sig, b"message"))
331+
332+
def test_deterministic_sign_random_message(self):
333+
msg = os.urandom(32)
334+
sig = self.sk.sign_deterministic(msg)
335+
self.assertEqual(len(sig), 2)
336+
self.assertTrue(self.sk.verifying_key.verify(sig, msg))
337+
338+
def test_deterministic_sign_that_rises_R_zero_error(self):
339+
# the raised RSZeroError is caught and handled internally by
340+
# sign_deterministic methods
341+
msg = b"\x00\x4f"
342+
sig = self.sk.sign_deterministic(msg)
343+
self.assertEqual(sig, b"\x36\x9e")
344+
self.assertTrue(self.sk.verifying_key.verify(sig, msg))
345+
346+
def test_deterministic_sign_that_rises_S_zero_error(self):
347+
msg = b"\x01\x6d"
348+
sig = self.sk.sign_deterministic(msg)
349+
self.assertEqual(sig, b"\x49\x6c")
350+
self.assertTrue(self.sk.verifying_key.verify(sig, msg))
351+
352+
299353
# test VerifyingKey.verify()
300354
prv_key_str = (
301355
"-----BEGIN EC PRIVATE KEY-----\n"

0 commit comments

Comments
 (0)