From 06ad384946f9c36793f834f4241834372291131a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 23 Oct 2019 10:14:45 +0200 Subject: [PATCH 01/24] decision coverage: make git checkout work reliably also stop copying diff-instrumental.py, it's now in master so it will be present when we checkout the base commit --- .travis.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f2cbba52..195150c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,7 @@ before_install: TRAVIS_COMMIT_RANGE=$PR_FIRST^..$TRAVIS_COMMIT fi # sanity check current commit - - git rev-parse HEAD + - BRANCH=$(git rev-parse HEAD) - echo "TRAVIS_COMMIT_RANGE=$TRAVIS_COMMIT_RANGE" - git fetch origin master:refs/remotes/origin/master @@ -78,7 +78,6 @@ install: script: - if [[ $TOX_ENV ]]; then tox -e $TOX_ENV; fi - tox -e speed - - cp diff-instrumental.py diff-instrumental-2.py - | if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST != "false" ]]; then git checkout $PR_FIRST^ @@ -86,8 +85,8 @@ script: files="$(ls src/ecdsa/test*.py | grep -v test_malformed_sigs.py)" instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files instrumental -f .instrumental.cov -s - instrumental -f .instrumental.cov -s | python diff-instrumental-2.py --save .diff-instrumental - git checkout $TRAVIS_COMMIT + instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental + git checkout $BRANCH instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files instrumental -f .instrumental.cov -sr fi @@ -98,11 +97,11 @@ script: instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files instrumental -f .instrumental.cov -s # just log the values when merging - instrumental -f .instrumental.cov -s | python diff-instrumental-2.py + instrumental -f .instrumental.cov -s | python diff-instrumental.py fi - | if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST != "false" ]]; then - instrumental -f .instrumental.cov -s | python diff-instrumental-2.py --read .diff-instrumental --fail-under 70 --max-difference -0.1 + instrumental -f .instrumental.cov -s | python diff-instrumental.py --read .diff-instrumental --fail-under 70 --max-difference -0.1 fi after_success: - if [[ -z $INSTRUMENTAL ]]; then coveralls; fi From 881274a0c0d32e844ffc0b8a011c20d6784a3586 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 14:18:37 +0200 Subject: [PATCH 02/24] add missing SECP256k1 to __all__ the curve is oficially supported, add it to star import --- src/ecdsa/curves.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/curves.py b/src/ecdsa/curves.py index b61f4b45..d0874e64 100644 --- a/src/ecdsa/curves.py +++ b/src/ecdsa/curves.py @@ -8,7 +8,7 @@ # will need to mark it as deprecated later __all__ = ["UnknownCurveError", "orderlen", "Curve", "NIST192p", "NIST224p", "NIST256p", "NIST384p", "NIST521p", "curves", - "find_curve"] + "find_curve", "SECP256k1"] class UnknownCurveError(Exception): From bce756251b4bd6902dec54519a5d7f589d2e2be5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 14:19:45 +0200 Subject: [PATCH 03/24] curves: add repr() support for easier diagnostics with hypothesis, it's nice to have the curves named also fix formatting in the module --- src/ecdsa/curves.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/curves.py b/src/ecdsa/curves.py index d0874e64..2555d938 100644 --- a/src/ecdsa/curves.py +++ b/src/ecdsa/curves.py @@ -15,7 +15,6 @@ class UnknownCurveError(Exception): pass -# the NIST curves class Curve: def __init__(self, name, curve, generator, oid, openssl_name=None): self.name = name @@ -29,25 +28,41 @@ def __init__(self, name, curve, generator, oid, openssl_name=None): self.oid = oid self.encoded_oid = der.encode_oid(*oid) + def __repr__(self): + return self.name + + +# the NIST curves NIST192p = Curve("NIST192p", ecdsa.curve_192, ecdsa.generator_192, (1, 2, 840, 10045, 3, 1, 1), "prime192v1") + + NIST224p = Curve("NIST224p", ecdsa.curve_224, ecdsa.generator_224, (1, 3, 132, 0, 33), "secp224r1") + + NIST256p = Curve("NIST256p", ecdsa.curve_256, ecdsa.generator_256, (1, 2, 840, 10045, 3, 1, 7), "prime256v1") + + NIST384p = Curve("NIST384p", ecdsa.curve_384, ecdsa.generator_384, (1, 3, 132, 0, 34), "secp384r1") + + NIST521p = Curve("NIST521p", ecdsa.curve_521, ecdsa.generator_521, (1, 3, 132, 0, 35), "secp521r1") + + SECP256k1 = Curve("SECP256k1", ecdsa.curve_secp256k1, ecdsa.generator_secp256k1, (1, 3, 132, 0, 10), "secp256k1") + curves = [NIST192p, NIST224p, NIST256p, NIST384p, NIST521p, SECP256k1] From de83837110104a32d20581bd0926865c1e5019a0 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 15:30:49 +0200 Subject: [PATCH 04/24] support for repr(VerifyingKey) for hypothesis, falsifying examples are easier to check and reproduce when the key can be printed in form that can be put into code --- src/ecdsa/keys.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 4f926429..b0898411 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -125,6 +125,11 @@ def __init__(self, _error__please_use_generate=None): self.default_hashfunc = None self.pubkey = None + def __repr__(self): + pub_key = self.to_string("compressed") + return "VerifyingKey.from_string({0!r}, {1!r}, {2})".format( + pub_key, self.curve, self.default_hashfunc().name) + @classmethod def from_public_point(cls, point, curve=NIST192p, hashfunc=sha1): """ From 18e9cd49ff253f2bfcfdc8332240fb14ca7b096a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 16:01:54 +0200 Subject: [PATCH 05/24] fuzzing of DER signatures with hypothesis use hypothesis to generate malformed signatures by introducing different changes for different curves made with different hashes --- .gitignore | 1 + build-requirements-2.6.txt | 1 + build-requirements-3.3.txt | 2 + build-requirements.txt | 2 + src/ecdsa/test_malformed_sigs.py | 146 +++++++++++++++++++++++-------- tox.ini | 5 ++ 6 files changed, 120 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 49bbeffb..05fbfd4a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ coverage-html .tox nosetests.xml t/ +.hypothesis/ # Translations *.mo diff --git a/build-requirements-2.6.txt b/build-requirements-2.6.txt index 980e00df..9f39acd0 100644 --- a/build-requirements-2.6.txt +++ b/build-requirements-2.6.txt @@ -2,3 +2,4 @@ tox coveralls<1.3.0 idna<2.8 unittest2 +hypothesis<3 diff --git a/build-requirements-3.3.txt b/build-requirements-3.3.txt index 68f8c292..28dd401b 100644 --- a/build-requirements-3.3.txt +++ b/build-requirements-3.3.txt @@ -3,3 +3,5 @@ pluggy<0.6 tox<3 wheel<0.30 virtualenv==15.2.0 +enum34 +hypothesis<3.44 diff --git a/build-requirements.txt b/build-requirements.txt index 6918d2e8..072f7a9d 100644 --- a/build-requirements.txt +++ b/build-requirements.txt @@ -1,2 +1,4 @@ tox python-coveralls +hypothesis +pytest>=4.6.0 diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index fffb1b3b..d75bc5a8 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -1,51 +1,123 @@ from __future__ import with_statement, division -import pytest import hashlib +try: + from hashlib import algorithms_available +except ImportError: + algorithms_available = [ + "md5", "sha1", "sha224", "sha256", "sha384", "sha512"] +from functools import partial +import pytest +import sys +from six import binary_type +import hypothesis.strategies as st +from hypothesis import note, assume, given, settings -from six import b, binary_type -from .keys import SigningKey, VerifyingKey +from .keys import SigningKey from .keys import BadSignatureError from .util import sigencode_der, sigencode_string from .util import sigdecode_der, sigdecode_string -from .curves import curves, NIST256p, NIST521p +from .curves import curves, NIST256p -der_sigs = [] -example_data = b("some data to sign") -# Just NIST256p with SHA256 is 560 test cases, all curves with all hashes is -# few thousand slow test cases; execute the most interesting only +example_data = b"some data to sign" +"""Since the data is hashed for processing, really any string will do.""" -#for curve in curves: -for curve in [NIST521p]: - #for hash_alg in ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"]: - for hash_alg in ["sha256"]: - key = SigningKey.generate(curve) - signature = key.sign(example_data, hashfunc=getattr(hashlib, hash_alg), - sigencode=sigencode_der) - for pos in range(len(signature)): - for xor in (1<= (2, 7): + # deadline=2s because NIST521p are slow to verify + params["deadline"] = 2000 + + +@settings(**params) +@given(st_fuzzed_sig()) +def test_fuzzed_der_signatures(params): + verifying_key, sig = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) #### diff --git a/tox.ini b/tox.ini index a10f3965..4f31ae61 100644 --- a/tox.ini +++ b/tox.ini @@ -6,9 +6,14 @@ envlist = py26, py27, py33, py34, py35, py36, py37, py38, py, pypy, pypy3 deps = py{33}: py<1.5 py{33}: pytest<3.3 + py{33}: enum34 + py{33}: hypothesis<3.44 py{26}: unittest2 + py{26}: hypothesis<3 py{26,27,34,35,36,37,38,py,py3}: pytest + py{27,34,35,36,37,38,py,py3}: hypothesis py: pytest + py: hypothesis py{33}: wheel<0.30 coverage commands = coverage run --branch -m pytest {posargs:src/ecdsa} From 5fda5db6caece0f50ce48195be2eb5d2162b4307 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 17:32:10 +0200 Subject: [PATCH 06/24] run all tests in instrumental since now test_malformed_sigs.py executes quickly, we can run it under instrumental --- .travis.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 195150c0..8d1f6a89 100644 --- a/.travis.yml +++ b/.travis.yml @@ -87,14 +87,12 @@ script: instrumental -f .instrumental.cov -s instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental git checkout $BRANCH - instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files + instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` src/ecdsa instrumental -f .instrumental.cov -sr fi - | if [[ $INSTRUMENTAL && $TRAVIS_PULL_REQUEST == "false" ]]; then - # exclude the super slow test_malformed_sigs.py, until #127 is merged - files="$(ls src/ecdsa/test*.py | grep -v test_malformed_sigs.py)" - instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` $files + instrumental -t ecdsa -i 'test.*|.*_version' `which pytest` src/ecdsa instrumental -f .instrumental.cov -s # just log the values when merging instrumental -f .instrumental.cov -s | python diff-instrumental.py From 2f97ceca168a400ac205529ae2d26042775222d4 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 20:20:12 +0200 Subject: [PATCH 07/24] test random ECDSA-Sig-Value --- src/ecdsa/test_malformed_sigs.py | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index d75bc5a8..e6162380 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -18,6 +18,7 @@ from .util import sigencode_der, sigencode_string from .util import sigdecode_der, sigdecode_string from .curves import curves, NIST256p +from .der import encode_integer, encode_sequence example_data = b"some data to sign" @@ -120,6 +121,46 @@ def test_fuzzed_der_signatures(params): verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) +@st.composite +def st_random_der_ecdsa_sig_value(draw): + """ + Hypothesis strategy for selecting random values and encoding them + to ECDSA-Sig-Value object:: + + ECDSA-Sig-Value ::= SEQUENCE { + r INTEGER, + s INTEGER + } + """ + name, verifying_key, _ = draw(st.sampled_from(keys_and_sigs)) + note("Configuration: {0}".format(name)) + order = verifying_key.curve.order + + # the encode_integer doesn't suport negative numbers, would be nice + # to generate them too, but we have coverage for remove_integer() + # verifying that it doesn't accept them, so meh + r = draw(st.integers(min_value=0) | + st.integers(min_value=order >> 2, max_value=order+1)) + s = draw(st.integers(min_value=0) | + st.integers(min_value=order >> 2, max_value=order+1)) + + sig = encode_sequence(encode_integer(r), encode_integer(s)) + + return verifying_key, sig + + +@given(st_random_der_ecdsa_sig_value()) +def test_random_der_ecdsa_sig_value(params): + """ + Check if random values encoded in ECDSA-Sig-Value structure are rejected + as signature. + """ + verifying_key, sig = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) + + #### # # For string encoded signatures, only the length of string is important From 18ad79a2b5a0c8d1520bd930579d803b14754bd3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 21:03:24 +0200 Subject: [PATCH 08/24] generalise st_fuzzed_sig() --- src/ecdsa/test_malformed_sigs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index e6162380..229967fa 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -61,7 +61,7 @@ def test_signatures(verifying_key, signature): @st.composite -def st_fuzzed_sig(draw): +def st_fuzzed_sig(draw, keys_and_sigs): """ Hypothesis strategy that generates pairs of VerifyingKey and malformed signatures created by fuzzing of a valid signature. @@ -113,7 +113,7 @@ def st_fuzzed_sig(draw): @settings(**params) -@given(st_fuzzed_sig()) +@given(st_fuzzed_sig(keys_and_sigs)) def test_fuzzed_der_signatures(params): verifying_key, sig = params From 6b99bcdb7f00205d37a37d78044910a10016d9e7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 21:04:18 +0200 Subject: [PATCH 09/24] test malformed string singatues with hypothesis --- src/ecdsa/test_malformed_sigs.py | 55 +++++++++++--------------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index 229967fa..222315c6 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -161,40 +161,21 @@ def test_random_der_ecdsa_sig_value(params): verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) -#### -# -# For string encoded signatures, only the length of string is important -# -#### - -str_sigs = [] - -#for curve in curves: -for curve in [NIST256p]: - #for hash_alg in ["md5", "sha1", "sha224", "sha256", "sha384", "sha512"]: - for hash_alg in ["sha256"]: - key = SigningKey.generate(curve) - signature = key.sign(example_data, hashfunc=getattr(hashlib, hash_alg), - sigencode=sigencode_string) - for trunc in range(len(signature)): - str_sigs.append(pytest.param( - key.verifying_key, hash_alg, - signature, trunc, - id="{0}-{1}-trunc-{2}".format( - curve.name, hash_alg, trunc))) - - -@pytest.mark.parametrize("verifying_key,hash_alg,signature,trunc", str_sigs) -def test_truncated_string_signatures(verifying_key, hash_alg, signature, trunc): - # check if a malformed string encoded signature causes the same exception - # to be raised irrespective of the type of error - sig = bytearray(signature) - sig = sig[:trunc] - sig = binary_type(sig) - - try: - verifying_key.verify(sig, example_data, getattr(hashlib, hash_alg), - sigdecode_string) - assert False - except BadSignatureError: - assert True +keys_and_string_sigs = [ + (name, verifying_key, + sigencode_string(*sigdecode_der(sig, verifying_key.curve.order), + order=verifying_key.curve.order)) + for name, verifying_key, sig in keys_and_sigs] +""" +Name of the curve+hash combination, VerifyingKey and signature as a +byte string. +""" + + +@settings(**params) +@given(st_fuzzed_sig(keys_and_string_sigs)) +def test_fuzzed_string_signatures(params): + verifying_key, sig = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(sig, example_data, sigdecode=sigdecode_string) From 4f9e8916012dc9b33d815047de502fcf8d18690c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 21:42:28 +0200 Subject: [PATCH 10/24] test signature decoding with random DER objects --- src/ecdsa/test_malformed_sigs.py | 88 +++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index 222315c6..ee8494a0 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -18,7 +18,8 @@ from .util import sigencode_der, sigencode_string from .util import sigdecode_der, sigdecode_string from .curves import curves, NIST256p -from .der import encode_integer, encode_sequence +from .der import encode_integer, encode_bitstring, encode_octet_string, \ + encode_oid, encode_sequence, encode_constructed example_data = b"some data to sign" @@ -161,6 +162,91 @@ def test_random_der_ecdsa_sig_value(params): verifying_key.verify(sig, example_data, sigdecode=sigdecode_der) +def st_der_integer(*args, **kwargs): + """ + Hypothesis strategy that returns a random positive integer as DER + INTEGER. + Parameters are passed to hypothesis.strategy.integer. + """ + if "min_value" not in kwargs: + kwargs["min_value"] = 0 + return st.builds(encode_integer, st.integers(*args, **kwargs)) + + +@st.composite +def st_der_bit_string(draw, *args, **kwargs): + """ + Hypothesis strategy that returns a random DER BIT STRING. + Parameters are passed to hypothesis.strategy.binary. + """ + data = draw(st.binary(*args, **kwargs)) + if data: + unused = draw(st.integers(min_value=0, max_value=7)) + data = bytearray(data) + data[-1] &= - (2**unused) + data = bytes(data) + else: + unused = 0 + return encode_bitstring(data, unused) + + +def st_der_octet_string(*args, **kwargs): + """ + Hypothesis strategy that returns a random DER OCTET STRING object. + Parameters are passed to hypothesis.strategy.binary + """ + return st.builds(encode_octet_string, st.binary(*args, **kwargs)) + + +def st_der_null(): + """ + Hypothesis strategy that returns DER NULL object. + """ + return st.just(b'\x05\x00') + + +@st.composite +def st_der_oid(draw): + """ + Hypothesis strategy that returns DER OBJECT IDENTIFIER objects. + """ + first = draw(st.integers(min_value=0, max_value=2)) + second = draw(st.integers(min_value=0, max_value=39)) + rest = draw(st.lists(st.integers(min_value=0))) + return encode_oid(first, second, *rest) + + +def st_der(): + """ + Hypothesis strategy that returns random DER structures. + + A valid DER structure is any primitive object, an octet encoding + of a valid DER structure, sequence of valid DER objects or a constructed + encoding of any of the above. + """ + return st.recursive( + st.just(b'') | st_der_integer() | st_der_bit_string() | + st_der_octet_string() | st_der_null() | st_der_oid(), + lambda children: + st.builds(lambda x: encode_octet_string(x), st.one_of(children)) | + st.builds(lambda x: encode_bitstring(x, 0), st.one_of(children)) | + st.builds(lambda x: encode_sequence(*x), st.lists(children)) | + st.builds(lambda tag, x: + encode_constructed(tag, x), + st.integers(min_value=0, max_value=0x3f), + st.one_of(children)) + ) + + +@given(st.sampled_from(keys_and_sigs), st_der()) +def test_random_der_as_signature(params, der): + """Check if random DER structures are rejected as signature""" + name, verifying_key, _ = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(der, example_data, sigdecode=sigdecode_der) + + keys_and_string_sigs = [ (name, verifying_key, sigencode_string(*sigdecode_der(sig, verifying_key.curve.order), From a47b641020ff4f0a8e337f60ec4ecdbfb391665d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 21:44:15 +0200 Subject: [PATCH 11/24] test verify with random byte strings --- src/ecdsa/test_malformed_sigs.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index ee8494a0..dfb85f34 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -247,6 +247,15 @@ def test_random_der_as_signature(params, der): verifying_key.verify(der, example_data, sigdecode=sigdecode_der) +@given(st.sampled_from(keys_and_sigs), st.binary()) +def test_random_bytes_as_signature(params, der): + """Check if random bytes are rejected as signature""" + name, verifying_key, _ = params + + with pytest.raises(BadSignatureError): + verifying_key.verify(der, example_data, sigdecode=sigdecode_der) + + keys_and_string_sigs = [ (name, verifying_key, sigencode_string(*sigdecode_der(sig, verifying_key.curve.order), From f964553cb5095e467871ffe90df710db985bc52c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 21:50:38 +0200 Subject: [PATCH 12/24] set the settings.deadline for all signature tests --- src/ecdsa/test_malformed_sigs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index dfb85f34..bf9b17fe 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -150,6 +150,7 @@ def st_random_der_ecdsa_sig_value(draw): return verifying_key, sig +@settings(**params) @given(st_random_der_ecdsa_sig_value()) def test_random_der_ecdsa_sig_value(params): """ @@ -238,6 +239,7 @@ def st_der(): ) +@settings(**params) @given(st.sampled_from(keys_and_sigs), st_der()) def test_random_der_as_signature(params, der): """Check if random DER structures are rejected as signature""" @@ -247,6 +249,7 @@ def test_random_der_as_signature(params, der): verifying_key.verify(der, example_data, sigdecode=sigdecode_der) +@settings(**params) @given(st.sampled_from(keys_and_sigs), st.binary()) def test_random_bytes_as_signature(params, der): """Check if random bytes are rejected as signature""" From f8494175255da19febd10884c538f8794079c100 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 26 Oct 2019 22:07:18 +0200 Subject: [PATCH 13/24] report hypothesis stats in run since hypothesis executes good few hundred test cases, do report that they have been run when the user runs the test suite specified in readme... --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4f31ae61..fbaf823e 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ commands = coverage run --branch -m pytest {posargs:src/ecdsa} [testenv:coverage] sitepackages=True -commands = coverage run --branch -m pytest {posargs:src/ecdsa} +commands = coverage run --branch -m pytest --hypothesis-show-statistics {posargs:src/ecdsa} [testenv:speed] commands = {envpython} speed.py From 1dba4f80a95c13f74bdf509fd51c2dedf569d65b Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 01:51:47 +0200 Subject: [PATCH 14/24] ensure consistent coverage for sigdecode_der make sure we hit the currently defined two checks in the sigdecode_der() function --- src/ecdsa/test_malformed_sigs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/test_malformed_sigs.py b/src/ecdsa/test_malformed_sigs.py index bf9b17fe..1e800456 100644 --- a/src/ecdsa/test_malformed_sigs.py +++ b/src/ecdsa/test_malformed_sigs.py @@ -11,7 +11,7 @@ import sys from six import binary_type import hypothesis.strategies as st -from hypothesis import note, assume, given, settings +from hypothesis import note, assume, given, settings, example from .keys import SigningKey from .keys import BadSignatureError @@ -251,6 +251,15 @@ def test_random_der_as_signature(params, der): @settings(**params) @given(st.sampled_from(keys_and_sigs), st.binary()) +@example( + keys_and_sigs[0], + encode_sequence(encode_integer(0), encode_integer(0))) +@example( + keys_and_sigs[0], + encode_sequence(encode_integer(1), encode_integer(1)) + b'\x00') +@example( + keys_and_sigs[0], + encode_sequence(*[encode_integer(1)] * 3)) def test_random_bytes_as_signature(params, der): """Check if random bytes are rejected as signature""" name, verifying_key, _ = params From 2f3a59e8e9bec7ce20356a13a496e21c5047aaa3 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 02:05:24 +0200 Subject: [PATCH 15/24] test_ecdsa: cleanup imports don't use six.print_ - not needed on py2.6 sort imports in conventional way --- src/ecdsa/test_ecdsa.py | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index cb7d837f..be28adb3 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -1,8 +1,8 @@ -from .ecdsa import (Private_key, Public_key, Signature, - curve_192, generator_192, - digest_integer, ellipticcurve, point_is_valid) -from six import print_ +from __future__ import print_function import random +from .ecdsa import Private_key, Public_key, Signature, \ + curve_192, generator_192, digest_integer, ellipticcurve, point_is_valid + def test_ecdsa(): class TestFailure(Exception): @@ -12,7 +12,7 @@ def test_point_validity(generator, x, y, expected): """generator defines the curve; is (x,y) a point on this curve? "expected" is True if the right answer is Yes.""" if point_is_valid(generator, x, y) == expected: - print_("Point validity tested as expected.") + print("Point validity tested as expected.") else: raise TestFailure("*** Point validity test gave wrong result.") @@ -24,8 +24,8 @@ def test_signature_validity(Msg, Qx, Qy, R, S, expected): ellipticcurve.Point(curve_192, Qx, Qy)) got = pubk.verifies(digest_integer(Msg), Signature(R, S)) if got == expected: - print_("Signature tested as expected: got %s, expected %s." % \ - (got, expected)) + print("Signature tested as expected: got %s, expected %s." % \ + (got, expected)) else: raise TestFailure("*** Signature test failed: got %s, expected %s." % \ (got, expected)) @@ -35,21 +35,21 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): sign = Signature(R,S) pks = sign.recover_public_keys(digest_integer(Msg), generator_192) - print_("Test pk recover") + print("Test pk recover") if pks: # Test if the signature is valid for all found public keys for pk in pks: q = pk.point - print_("Recovered q: %s" % q) + print("Recovered q: %s" % q) test_signature_validity(Msg, q.x(), q.y(), R, S, True) # Test if the original public key is in the set of found keys original_q = ellipticcurve.Point(curve_192, Qx, Qy) points = [pk.point for pk in pks] if original_q in points: - print_("Original q was found") + print("Original q was found") else: raise TestFailure("Original q is not in the list of recovered public keys") @@ -57,7 +57,7 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): raise TestFailure("*** NO valid public key returned") - print_("NIST Curve P-192:") + print("NIST Curve P-192:") p192 = generator_192 @@ -68,7 +68,7 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: raise TestFailure("*** p192 * d came out wrong.") else: - print_("p192 * d came out right.") + print("p192 * d came out right.") k = 6140507067065001063065065565667405560006161556565665656654 R = k * p192 @@ -76,7 +76,7 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: raise TestFailure("*** k * p192 came out wrong.") else: - print_("k * p192 came out right.") + print("k * p192 came out right.") u1 = 2563697409189434185194736134579731015366492496392189760599 u2 = 6266643813348617967186477710235785849136406323338782220568 @@ -85,7 +85,7 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: raise TestFailure("*** u1 * p192 + u2 * Q came out wrong.") else: - print_("u1 * p192 + u2 * Q came out right.") + print("u1 * p192 + u2 * Q came out right.") e = 968236873715988614170569073515315707566766479517 pubk = Public_key(generator_192, generator_192 * d) @@ -96,21 +96,21 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): or s != 5735822328888155254683894997897571951568553642892029982342: raise TestFailure("*** r or s came out wrong.") else: - print_("r and s came out right.") + print("r and s came out right.") valid = pubk.verifies(e, sig) if valid: - print_("Signature verified OK.") + print("Signature verified OK.") else: raise TestFailure("*** Signature failed verification.") valid = pubk.verifies(e - 1, sig) if not valid: - print_("Forgery was correctly rejected.") + print("Forgery was correctly rejected.") else: raise TestFailure("*** Forgery was erroneously accepted.") - print_("Testing point validity, as per ECDSAVS.pdf B.2.2:") + print("Testing point validity, as per ECDSAVS.pdf B.2.2:") test_point_validity( \ p192, \ @@ -184,8 +184,8 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, \ False) - print_("Trying signature-verification tests from ECDSAVS.pdf B.2.4:") - print_("P-192:") + print("Trying signature-verification tests from ECDSAVS.pdf B.2.4:") + print("P-192:") Msg = 0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff798cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d158 Qx = 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7ac Qy = 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4 @@ -293,7 +293,7 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9 test_signature_validity(Msg, Qx, Qy, R, S, False) - print_("Testing the example code:") + print("Testing the example code:") # Building a public/private key pair from the NIST Curve P-192: @@ -320,11 +320,11 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): # Verifying a signature for a hash value: if pubkey.verifies(hash, signature): - print_("Demo verification succeeded.") + print("Demo verification succeeded.") else: raise TestFailure("*** Demo verification failed.") if pubkey.verifies(hash - 1, signature): raise TestFailure("**** Demo verification failed to reject tampered hash.") else: - print_("Demo verification correctly rejected tampered hash.") + print("Demo verification correctly rejected tampered hash.") From ea192f14560b388405c46d0ecf5178336a5d1337 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 02:39:18 +0200 Subject: [PATCH 16/24] test_ecdsa: use hypothesis for random sign and verify make the test with different keys, messages and nonces reproducible use all the curves/generators defined in the test (not only P-192) --- src/ecdsa/test_ecdsa.py | 90 ++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 36 deletions(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index be28adb3..6c823e47 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -1,7 +1,18 @@ from __future__ import print_function -import random +import sys +import hypothesis.strategies as st +from hypothesis import given, settings, note from .ecdsa import Private_key, Public_key, Signature, \ - curve_192, generator_192, digest_integer, ellipticcurve, point_is_valid + curve_192, generator_192, digest_integer, ellipticcurve, point_is_valid, \ + generator_224, generator_256, generator_384, generator_521, \ + generator_secp256k1 + + +HYP_SETTINGS = {} +# old hypothesis doesn't have the "deadline" setting +if sys.version_info > (2, 7): + # SEC521p is slow, allow long execution for it + HYP_SETTINGS["deadline"] = 5000 def test_ecdsa(): @@ -293,38 +304,45 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9 test_signature_validity(Msg, Qx, Qy, R, S, False) - print("Testing the example code:") - - # Building a public/private key pair from the NIST Curve P-192: - - g = generator_192 - n = g.order() - - # (random.SystemRandom is supposed to provide - # crypto-quality random numbers, but as Debian recently - # illustrated, a systems programmer can accidentally - # demolish this security, so in serious applications - # further precautions are appropriate.) - - randrange = random.SystemRandom().randrange - - secret = randrange(1, n) - pubkey = Public_key(g, g * secret) - privkey = Private_key(pubkey, secret) - # Signing a hash value: - - hash = randrange(1, n) - signature = privkey.sign(hash, randrange(1, n)) - - # Verifying a signature for a hash value: - - if pubkey.verifies(hash, signature): - print("Demo verification succeeded.") - else: - raise TestFailure("*** Demo verification failed.") - - if pubkey.verifies(hash - 1, signature): - raise TestFailure("**** Demo verification failed to reject tampered hash.") - else: - print("Demo verification correctly rejected tampered hash.") +@st.composite +def st_random_gen_key_msg_nonce(draw): + """Hypothesis strategy for test_sig_verify().""" + name_gen = { + "generator_192": generator_192, + "generator_224": generator_224, + "generator_256": generator_256, + "generator_secp256k1": generator_secp256k1, + "generator_384": generator_384, + "generator_521": generator_521} + name = draw(st.sampled_from(sorted(name_gen.keys()))) + note("Generator used: {0}".format(name)) + generator = name_gen[name] + order = generator.order() + + key = draw(st.integers(min_value=1, max_value=order)) + msg = draw(st.integers(min_value=1, max_value=order)) + nonce = draw(st.integers(min_value=1, max_value=order+1) | + st.integers(min_value=order>>1, max_value=order)) + return generator, key, msg, nonce + + +SIG_VER_SETTINGS = dict(HYP_SETTINGS) +SIG_VER_SETTINGS["max_examples"] = 10 +@settings(**SIG_VER_SETTINGS) +@given(st_random_gen_key_msg_nonce()) +def test_sig_verify(args): + """ + Check if signing and verification works for arbitrary messages and + that signatures for other messages are rejected. + """ + generator, sec_mult, msg, nonce = args + + pubkey = Public_key(generator, generator * sec_mult) + privkey = Private_key(pubkey, sec_mult) + + signature = privkey.sign(msg, nonce) + + assert pubkey.verifies(msg, signature) + + assert not pubkey.verifies(msg - 1, signature) From 98a47d55e1e0d885b45e5cd441964c5da9726c51 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 13:49:42 +0100 Subject: [PATCH 17/24] test_ecdsa: parametrise KATs for P-192 use pytest.parameterize to execute the Known-Answer Tests for the NIST P-256 curve use a bit more sane formatting for the messages in the tests --- src/ecdsa/test_ecdsa.py | 361 ++++++++++++++++++++++++---------------- 1 file changed, 213 insertions(+), 148 deletions(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 6c823e47..7ce212a5 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -2,6 +2,7 @@ import sys import hypothesis.strategies as st from hypothesis import given, settings, note +import pytest from .ecdsa import Private_key, Public_key, Signature, \ curve_192, generator_192, digest_integer, ellipticcurve, point_is_valid, \ generator_224, generator_256, generator_384, generator_521, \ @@ -27,46 +28,6 @@ def test_point_validity(generator, x, y, expected): else: raise TestFailure("*** Point validity test gave wrong result.") - def test_signature_validity(Msg, Qx, Qy, R, S, expected): - """Msg = message, Qx and Qy represent the base point on - elliptic curve c192, R and S are the signature, and - "expected" is True iff the signature is expected to be valid.""" - pubk = Public_key(generator_192, - ellipticcurve.Point(curve_192, Qx, Qy)) - got = pubk.verifies(digest_integer(Msg), Signature(R, S)) - if got == expected: - print("Signature tested as expected: got %s, expected %s." % \ - (got, expected)) - else: - raise TestFailure("*** Signature test failed: got %s, expected %s." % \ - (got, expected)) - - def test_pk_recovery(Msg, R, S, Qx, Qy): - - sign = Signature(R,S) - pks = sign.recover_public_keys(digest_integer(Msg), generator_192) - - print("Test pk recover") - - if pks: - - # Test if the signature is valid for all found public keys - for pk in pks: - q = pk.point - print("Recovered q: %s" % q) - test_signature_validity(Msg, q.x(), q.y(), R, S, True) - - # Test if the original public key is in the set of found keys - original_q = ellipticcurve.Point(curve_192, Qx, Qy) - points = [pk.point for pk in pks] - if original_q in points: - print("Original q was found") - else: - raise TestFailure("Original q is not in the list of recovered public keys") - - else: - raise TestFailure("*** NO valid public key returned") - print("NIST Curve P-192:") @@ -195,114 +156,218 @@ def test_pk_recovery(Msg, R, S, Qx, Qy): 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, \ False) - print("Trying signature-verification tests from ECDSAVS.pdf B.2.4:") - print("P-192:") - Msg = 0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff798cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d158 - Qx = 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7ac - Qy = 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4 - R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916 - S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479 - test_signature_validity(Msg, Qx, Qy, R, S, True) - test_pk_recovery(Msg, R, S, Qx, Qy) - - Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4 - Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7 - Qy = 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7 - R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555af - S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c - test_signature_validity(Msg, Qx, Qy, R, S, True) - test_pk_recovery(Msg, R, S, Qx, Qy) - - Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42dd - Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7 - Qy = 0x421b13379c59bc9dce38a1099ca79bbd06d647c7f6242336 - R = 0x4141bd5d64ea36c5b0bd21ef28c02da216ed9d04522b1e91 - S = 0x159a6aa852bcc579e821b7bb0994c0861fb08280c38daa09 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0x16b5f93afd0d02246f662761ed8e0dd9504681ed02a253006eb36736b563097ba39f81c8e1bce7a16c1339e345efabbc6baa3efb0612948ae51103382a8ee8bc448e3ef71e9f6f7a9676694831d7f5dd0db5446f179bcb737d4a526367a447bfe2c857521c7f40b6d7d7e01a180d92431fb0bbd29c04a0c420a57b3ed26ccd8a - Qx = 0xfd14cdf1607f5efb7b1793037b15bdf4baa6f7c16341ab0b - Qy = 0x83fa0795cc6c4795b9016dac928fd6bac32f3229a96312c4 - R = 0x8dfdb832951e0167c5d762a473c0416c5c15bc1195667dc1 - S = 0x1720288a2dc13fa1ec78f763f8fe2ff7354a7e6fdde44520 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0x08a2024b61b79d260e3bb43ef15659aec89e5b560199bc82cf7c65c77d39192e03b9a895d766655105edd9188242b91fbde4167f7862d4ddd61e5d4ab55196683d4f13ceb90d87aea6e07eb50a874e33086c4a7cb0273a8e1c4408f4b846bceae1ebaac1b2b2ea851a9b09de322efe34cebe601653efd6ddc876ce8c2f2072fb - Qx = 0x674f941dc1a1f8b763c9334d726172d527b90ca324db8828 - Qy = 0x65adfa32e8b236cb33a3e84cf59bfb9417ae7e8ede57a7ff - R = 0x9508b9fdd7daf0d8126f9e2bc5a35e4c6d800b5b804d7796 - S = 0x36f2bf6b21b987c77b53bb801b3435a577e3d493744bfab0 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0x1843aba74b0789d4ac6b0b8923848023a644a7b70afa23b1191829bbe4397ce15b629bf21a8838298653ed0c19222b95fa4f7390d1b4c844d96e645537e0aae98afb5c0ac3bd0e4c37f8daaff25556c64e98c319c52687c904c4de7240a1cc55cd9756b7edaef184e6e23b385726e9ffcba8001b8f574987c1a3fedaaa83ca6d - Qx = 0x10ecca1aad7220b56a62008b35170bfd5e35885c4014a19f - Qy = 0x04eb61984c6c12ade3bc47f3c629ece7aa0a033b9948d686 - R = 0x82bfa4e82c0dfe9274169b86694e76ce993fd83b5c60f325 - S = 0xa97685676c59a65dbde002fe9d613431fb183e8006d05633 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0x5a478f4084ddd1a7fea038aa9732a822106385797d02311aeef4d0264f824f698df7a48cfb6b578cf3da416bc0799425bb491be5b5ecc37995b85b03420a98f2c4dc5c31a69a379e9e322fbe706bbcaf0f77175e05cbb4fa162e0da82010a278461e3e974d137bc746d1880d6eb02aa95216014b37480d84b87f717bb13f76e1 - Qx = 0x6636653cb5b894ca65c448277b29da3ad101c4c2300f7c04 - Qy = 0xfdf1cbb3fc3fd6a4f890b59e554544175fa77dbdbeb656c1 - R = 0xeac2ddecddfb79931a9c3d49c08de0645c783a24cb365e1c - S = 0x3549fee3cfa7e5f93bc47d92d8ba100e881a2a93c22f8d50 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0xc598774259a058fa65212ac57eaa4f52240e629ef4c310722088292d1d4af6c39b49ce06ba77e4247b20637174d0bd67c9723feb57b5ead232b47ea452d5d7a089f17c00b8b6767e434a5e16c231ba0efa718a340bf41d67ea2d295812ff1b9277daacb8bc27b50ea5e6443bcf95ef4e9f5468fe78485236313d53d1c68f6ba2 - Qx = 0xa82bd718d01d354001148cd5f69b9ebf38ff6f21898f8aaa - Qy = 0xe67ceede07fc2ebfafd62462a51e4b6c6b3d5b537b7caf3e - R = 0x4d292486c620c3de20856e57d3bb72fcde4a73ad26376955 - S = 0xa85289591a6081d5728825520e62ff1c64f94235c04c7f95 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0xca98ed9db081a07b7557f24ced6c7b9891269a95d2026747add9e9eb80638a961cf9c71a1b9f2c29744180bd4c3d3db60f2243c5c0b7cc8a8d40a3f9a7fc910250f2187136ee6413ffc67f1a25e1c4c204fa9635312252ac0e0481d89b6d53808f0c496ba87631803f6c572c1f61fa049737fdacce4adff757afed4f05beb658 - Qx = 0x7d3b016b57758b160c4fca73d48df07ae3b6b30225126c2f - Qy = 0x4af3790d9775742bde46f8da876711be1b65244b2b39e7ec - R = 0x95f778f5f656511a5ab49a5d69ddd0929563c29cbc3a9e62 - S = 0x75c87fc358c251b4c83d2dd979faad496b539f9f2ee7a289 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0x31dd9a54c8338bea06b87eca813d555ad1850fac9742ef0bbe40dad400e10288acc9c11ea7dac79eb16378ebea9490e09536099f1b993e2653cd50240014c90a9c987f64545abc6a536b9bd2435eb5e911fdfde2f13be96ea36ad38df4ae9ea387b29cced599af777338af2794820c9cce43b51d2112380a35802ab7e396c97a - Qx = 0x9362f28c4ef96453d8a2f849f21e881cd7566887da8beb4a - Qy = 0xe64d26d8d74c48a024ae85d982ee74cd16046f4ee5333905 - R = 0xf3923476a296c88287e8de914b0b324ad5a963319a4fe73b - S = 0xf0baeed7624ed00d15244d8ba2aede085517dbdec8ac65f5 - test_signature_validity(Msg, Qx, Qy, R, S, True) - - Msg = 0xb2b94e4432267c92f9fdb9dc6040c95ffa477652761290d3c7de312283f6450d89cc4aabe748554dfb6056b2d8e99c7aeaad9cdddebdee9dbc099839562d9064e68e7bb5f3a6bba0749ca9a538181fc785553a4000785d73cc207922f63e8ce1112768cb1de7b673aed83a1e4a74592f1268d8e2a4e9e63d414b5d442bd0456d - Qx = 0xcc6fc032a846aaac25533eb033522824f94e670fa997ecef - Qy = 0xe25463ef77a029eccda8b294fd63dd694e38d223d30862f1 - R = 0x066b1d07f3a40e679b620eda7f550842a35c18b80c5ebe06 - S = 0xa0b0fb201e8f2df65e2c4508ef303bdc90d934016f16b2dc - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0x4366fcadf10d30d086911de30143da6f579527036937007b337f7282460eae5678b15cccda853193ea5fc4bc0a6b9d7a31128f27e1214988592827520b214eed5052f7775b750b0c6b15f145453ba3fee24a085d65287e10509eb5d5f602c440341376b95c24e5c4727d4b859bfe1483d20538acdd92c7997fa9c614f0f839d7 - Qx = 0x955c908fe900a996f7e2089bee2f6376830f76a19135e753 - Qy = 0xba0c42a91d3847de4a592a46dc3fdaf45a7cc709b90de520 - R = 0x1f58ad77fc04c782815a1405b0925e72095d906cbf52a668 - S = 0xf2e93758b3af75edf784f05a6761c9b9a6043c66b845b599 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0x543f8af57d750e33aa8565e0cae92bfa7a1ff78833093421c2942cadf9986670a5ff3244c02a8225e790fbf30ea84c74720abf99cfd10d02d34377c3d3b41269bea763384f372bb786b5846f58932defa68023136cd571863b304886e95e52e7877f445b9364b3f06f3c28da12707673fecb4b8071de06b6e0a3c87da160cef3 - Qx = 0x31f7fa05576d78a949b24812d4383107a9a45bb5fccdd835 - Qy = 0x8dc0eb65994a90f02b5e19bd18b32d61150746c09107e76b - R = 0xbe26d59e4e883dde7c286614a767b31e49ad88789d3a78ff - S = 0x8762ca831c1ce42df77893c9b03119428e7a9b819b619068 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0xd2e8454143ce281e609a9d748014dcebb9d0bc53adb02443a6aac2ffe6cb009f387c346ecb051791404f79e902ee333ad65e5c8cb38dc0d1d39a8dc90add5023572720e5b94b190d43dd0d7873397504c0c7aef2727e628eb6a74411f2e400c65670716cb4a815dc91cbbfeb7cfe8c929e93184c938af2c078584da045e8f8d1 - Qx = 0x66aa8edbbdb5cf8e28ceb51b5bda891cae2df84819fe25c0 - Qy = 0x0c6bc2f69030a7ce58d4a00e3b3349844784a13b8936f8da - R = 0xa4661e69b1734f4a71b788410a464b71e7ffe42334484f23 - S = 0x738421cf5e049159d69c57a915143e226cac8355e149afe9 - test_signature_validity(Msg, Qx, Qy, R, S, False) - - Msg = 0x6660717144040f3e2f95a4e25b08a7079c702a8b29babad5a19a87654bc5c5afa261512a11b998a4fb36b5d8fe8bd942792ff0324b108120de86d63f65855e5461184fc96a0a8ffd2ce6d5dfb0230cbbdd98f8543e361b3205f5da3d500fdc8bac6db377d75ebef3cb8f4d1ff738071ad0938917889250b41dd1d98896ca06fb - Qx = 0xbcfacf45139b6f5f690a4c35a5fffa498794136a2353fc77 - Qy = 0x6f4a6c906316a6afc6d98fe1f0399d056f128fe0270b0f22 - R = 0x9db679a3dafe48f7ccad122933acfe9da0970b71c94c21c1 - S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9 - test_signature_validity(Msg, Qx, Qy, R, S, False) + +# Trying signature-verification tests from ECDSAVS.pdf B.2.4: +CURVE_192_KATS = [ + (generator_192, + int("0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee" + "425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30" + "d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff79" + "8cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d1" + "58", 16), + 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7ac, + 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4, + 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916, + 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479, + True), + + (generator_192, + int("0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db1" + "2e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a" + "91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db3" + "26ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63" + "f4", 16), + 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7, + 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7, + 0x8285261607283ba18f335026130bab31840dcfd9c3e555af, + 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c, + True), + + (generator_192, + int("0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911" + "b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cd" + "d41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d30" + "3f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42" + "dd", 16), + 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7, + 0x421b13379c59bc9dce38a1099ca79bbd06d647c7f6242336, + 0x4141bd5d64ea36c5b0bd21ef28c02da216ed9d04522b1e91, + 0x159a6aa852bcc579e821b7bb0994c0861fb08280c38daa09, + False), + + (generator_192, + int("0x16b5f93afd0d02246f662761ed8e0dd9504681ed02a253006eb36736b56309" + "7ba39f81c8e1bce7a16c1339e345efabbc6baa3efb0612948ae51103382a8ee8" + "bc448e3ef71e9f6f7a9676694831d7f5dd0db5446f179bcb737d4a526367a447" + "bfe2c857521c7f40b6d7d7e01a180d92431fb0bbd29c04a0c420a57b3ed26ccd" + "8a", 16), + 0xfd14cdf1607f5efb7b1793037b15bdf4baa6f7c16341ab0b, + 0x83fa0795cc6c4795b9016dac928fd6bac32f3229a96312c4, + 0x8dfdb832951e0167c5d762a473c0416c5c15bc1195667dc1, + 0x1720288a2dc13fa1ec78f763f8fe2ff7354a7e6fdde44520, + False), + + (generator_192, + int("0x08a2024b61b79d260e3bb43ef15659aec89e5b560199bc82cf7c65c77d3919" + "2e03b9a895d766655105edd9188242b91fbde4167f7862d4ddd61e5d4ab55196" + "683d4f13ceb90d87aea6e07eb50a874e33086c4a7cb0273a8e1c4408f4b846bc" + "eae1ebaac1b2b2ea851a9b09de322efe34cebe601653efd6ddc876ce8c2f2072" + "fb", 16), + 0x674f941dc1a1f8b763c9334d726172d527b90ca324db8828, + 0x65adfa32e8b236cb33a3e84cf59bfb9417ae7e8ede57a7ff, + 0x9508b9fdd7daf0d8126f9e2bc5a35e4c6d800b5b804d7796, + 0x36f2bf6b21b987c77b53bb801b3435a577e3d493744bfab0, + False), + + (generator_192, + int("0x1843aba74b0789d4ac6b0b8923848023a644a7b70afa23b1191829bbe4397c" + "e15b629bf21a8838298653ed0c19222b95fa4f7390d1b4c844d96e645537e0aa" + "e98afb5c0ac3bd0e4c37f8daaff25556c64e98c319c52687c904c4de7240a1cc" + "55cd9756b7edaef184e6e23b385726e9ffcba8001b8f574987c1a3fedaaa83ca" + "6d", 16), + 0x10ecca1aad7220b56a62008b35170bfd5e35885c4014a19f, + 0x04eb61984c6c12ade3bc47f3c629ece7aa0a033b9948d686, + 0x82bfa4e82c0dfe9274169b86694e76ce993fd83b5c60f325, + 0xa97685676c59a65dbde002fe9d613431fb183e8006d05633, + False), + + (generator_192, + int("0x5a478f4084ddd1a7fea038aa9732a822106385797d02311aeef4d0264f824f" + "698df7a48cfb6b578cf3da416bc0799425bb491be5b5ecc37995b85b03420a98" + "f2c4dc5c31a69a379e9e322fbe706bbcaf0f77175e05cbb4fa162e0da82010a2" + "78461e3e974d137bc746d1880d6eb02aa95216014b37480d84b87f717bb13f76" + "e1", 16), + 0x6636653cb5b894ca65c448277b29da3ad101c4c2300f7c04, + 0xfdf1cbb3fc3fd6a4f890b59e554544175fa77dbdbeb656c1, + 0xeac2ddecddfb79931a9c3d49c08de0645c783a24cb365e1c, + 0x3549fee3cfa7e5f93bc47d92d8ba100e881a2a93c22f8d50, + False), + + (generator_192, + int("0xc598774259a058fa65212ac57eaa4f52240e629ef4c310722088292d1d4af6" + "c39b49ce06ba77e4247b20637174d0bd67c9723feb57b5ead232b47ea452d5d7" + "a089f17c00b8b6767e434a5e16c231ba0efa718a340bf41d67ea2d295812ff1b" + "9277daacb8bc27b50ea5e6443bcf95ef4e9f5468fe78485236313d53d1c68f6b" + "a2", 16), + 0xa82bd718d01d354001148cd5f69b9ebf38ff6f21898f8aaa, + 0xe67ceede07fc2ebfafd62462a51e4b6c6b3d5b537b7caf3e, + 0x4d292486c620c3de20856e57d3bb72fcde4a73ad26376955, + 0xa85289591a6081d5728825520e62ff1c64f94235c04c7f95, + False), + + (generator_192, + int("0xca98ed9db081a07b7557f24ced6c7b9891269a95d2026747add9e9eb80638a" + "961cf9c71a1b9f2c29744180bd4c3d3db60f2243c5c0b7cc8a8d40a3f9a7fc91" + "0250f2187136ee6413ffc67f1a25e1c4c204fa9635312252ac0e0481d89b6d53" + "808f0c496ba87631803f6c572c1f61fa049737fdacce4adff757afed4f05beb6" + "58", 16), + 0x7d3b016b57758b160c4fca73d48df07ae3b6b30225126c2f, + 0x4af3790d9775742bde46f8da876711be1b65244b2b39e7ec, + 0x95f778f5f656511a5ab49a5d69ddd0929563c29cbc3a9e62, + 0x75c87fc358c251b4c83d2dd979faad496b539f9f2ee7a289, + False), + + (generator_192, + int("0x31dd9a54c8338bea06b87eca813d555ad1850fac9742ef0bbe40dad400e102" + "88acc9c11ea7dac79eb16378ebea9490e09536099f1b993e2653cd50240014c9" + "0a9c987f64545abc6a536b9bd2435eb5e911fdfde2f13be96ea36ad38df4ae9e" + "a387b29cced599af777338af2794820c9cce43b51d2112380a35802ab7e396c9" + "7a", 16), + 0x9362f28c4ef96453d8a2f849f21e881cd7566887da8beb4a, + 0xe64d26d8d74c48a024ae85d982ee74cd16046f4ee5333905, + 0xf3923476a296c88287e8de914b0b324ad5a963319a4fe73b, + 0xf0baeed7624ed00d15244d8ba2aede085517dbdec8ac65f5, + True), + + (generator_192, + int("0xb2b94e4432267c92f9fdb9dc6040c95ffa477652761290d3c7de312283f645" + "0d89cc4aabe748554dfb6056b2d8e99c7aeaad9cdddebdee9dbc099839562d90" + "64e68e7bb5f3a6bba0749ca9a538181fc785553a4000785d73cc207922f63e8c" + "e1112768cb1de7b673aed83a1e4a74592f1268d8e2a4e9e63d414b5d442bd045" + "6d", 16), + 0xcc6fc032a846aaac25533eb033522824f94e670fa997ecef, + 0xe25463ef77a029eccda8b294fd63dd694e38d223d30862f1, + 0x066b1d07f3a40e679b620eda7f550842a35c18b80c5ebe06, + 0xa0b0fb201e8f2df65e2c4508ef303bdc90d934016f16b2dc, + False), + + (generator_192, + int("0x4366fcadf10d30d086911de30143da6f579527036937007b337f7282460eae" + "5678b15cccda853193ea5fc4bc0a6b9d7a31128f27e1214988592827520b214e" + "ed5052f7775b750b0c6b15f145453ba3fee24a085d65287e10509eb5d5f602c4" + "40341376b95c24e5c4727d4b859bfe1483d20538acdd92c7997fa9c614f0f839" + "d7", 16), + 0x955c908fe900a996f7e2089bee2f6376830f76a19135e753, + 0xba0c42a91d3847de4a592a46dc3fdaf45a7cc709b90de520, + 0x1f58ad77fc04c782815a1405b0925e72095d906cbf52a668, + 0xf2e93758b3af75edf784f05a6761c9b9a6043c66b845b599, + False), + + (generator_192, + int("0x543f8af57d750e33aa8565e0cae92bfa7a1ff78833093421c2942cadf99866" + "70a5ff3244c02a8225e790fbf30ea84c74720abf99cfd10d02d34377c3d3b412" + "69bea763384f372bb786b5846f58932defa68023136cd571863b304886e95e52" + "e7877f445b9364b3f06f3c28da12707673fecb4b8071de06b6e0a3c87da160ce" + "f3", 16), + 0x31f7fa05576d78a949b24812d4383107a9a45bb5fccdd835, + 0x8dc0eb65994a90f02b5e19bd18b32d61150746c09107e76b, + 0xbe26d59e4e883dde7c286614a767b31e49ad88789d3a78ff, + 0x8762ca831c1ce42df77893c9b03119428e7a9b819b619068, + False), + + (generator_192, + int("0xd2e8454143ce281e609a9d748014dcebb9d0bc53adb02443a6aac2ffe6cb009f" + "387c346ecb051791404f79e902ee333ad65e5c8cb38dc0d1d39a8dc90add502357" + "2720e5b94b190d43dd0d7873397504c0c7aef2727e628eb6a74411f2e400c65670" + "716cb4a815dc91cbbfeb7cfe8c929e93184c938af2c078584da045e8f8d1", 16), + 0x66aa8edbbdb5cf8e28ceb51b5bda891cae2df84819fe25c0, + 0x0c6bc2f69030a7ce58d4a00e3b3349844784a13b8936f8da, + 0xa4661e69b1734f4a71b788410a464b71e7ffe42334484f23, + 0x738421cf5e049159d69c57a915143e226cac8355e149afe9, + False), + + (generator_192, + int("0x6660717144040f3e2f95a4e25b08a7079c702a8b29babad5a19a87654bc5c5af" + "a261512a11b998a4fb36b5d8fe8bd942792ff0324b108120de86d63f65855e5461" + "184fc96a0a8ffd2ce6d5dfb0230cbbdd98f8543e361b3205f5da3d500fdc8bac6d" + "b377d75ebef3cb8f4d1ff738071ad0938917889250b41dd1d98896ca06fb", 16), + 0xbcfacf45139b6f5f690a4c35a5fffa498794136a2353fc77, + 0x6f4a6c906316a6afc6d98fe1f0399d056f128fe0270b0f22, + 0x9db679a3dafe48f7ccad122933acfe9da0970b71c94c21c1, + 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9, + False) + ] + + +@pytest.mark.parametrize("gen,msg,qx,qy,r,s,expected", CURVE_192_KATS) +def test_signature_validity(gen, msg, qx, qy, r, s, expected): + """ + `msg` = message, `qx` and `qy` represent the base point on + elliptic curve of `gen`, `r` and `s` are the signature, and + `expected` is True iff the signature is expected to be valid.""" + pubk = Public_key(gen, + ellipticcurve.Point(gen.curve(), qx, qy)) + assert expected == pubk.verifies(digest_integer(msg), Signature(r, s)) + + +@pytest.mark.parametrize("gen,msg,qx,qy,r,s,expected", + [x for x in CURVE_192_KATS if x[6]]) +def test_pk_recovery(gen, msg, r, s, qx, qy, expected): + del expected + sign = Signature(r, s) + pks = sign.recover_public_keys(digest_integer(msg), gen) + + assert pks + + # Test if the signature is valid for all found public keys + for pk in pks: + q = pk.point + test_signature_validity(gen, msg, q.x(), q.y(), r, s, True) + + # Test if the original public key is in the set of found keys + original_q = ellipticcurve.Point(gen.curve(), qx, qy) + points = [pk.point for pk in pks] + assert original_q in points @st.composite From 89846aabe997eefd5ea1b0c29e09726b67604535 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 14:00:47 +0100 Subject: [PATCH 18/24] test_ecdsa: parametrize point validity test --- src/ecdsa/test_ecdsa.py | 155 +++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 83 deletions(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index 7ce212a5..add27dcc 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -4,7 +4,7 @@ from hypothesis import given, settings, note import pytest from .ecdsa import Private_key, Public_key, Signature, \ - curve_192, generator_192, digest_integer, ellipticcurve, point_is_valid, \ + generator_192, digest_integer, ellipticcurve, point_is_valid, \ generator_224, generator_256, generator_384, generator_521, \ generator_secp256k1 @@ -20,15 +20,6 @@ def test_ecdsa(): class TestFailure(Exception): pass - def test_point_validity(generator, x, y, expected): - """generator defines the curve; is (x,y) a point on - this curve? "expected" is True if the right answer is Yes.""" - if point_is_valid(generator, x, y) == expected: - print("Point validity tested as expected.") - else: - raise TestFailure("*** Point validity test gave wrong result.") - - print("NIST Curve P-192:") p192 = generator_192 @@ -82,79 +73,77 @@ def test_point_validity(generator, x, y, expected): else: raise TestFailure("*** Forgery was erroneously accepted.") - print("Testing point validity, as per ECDSAVS.pdf B.2.2:") - - test_point_validity( \ - p192, \ - 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, \ - 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, \ - False) - - test_point_validity( - p192, \ - 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, \ - 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, \ - False) - - test_point_validity( - p192, \ - 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, \ - 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, \ - False) - - test_point_validity( - p192, \ - 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, \ - 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, \ - True) - - test_point_validity( - p192, \ - 0xcdf56c1aa3d8afc53c521adf3ffb96734a6a630a4a5b5a70, \ - 0x97c1c44a5fb229007b5ec5d25f7413d170068ffd023caa4e, \ - True) - - test_point_validity( - p192, \ - 0x89009c0dc361c81e99280c8e91df578df88cdf4b0cdedced, \ - 0x27be44a529b7513e727251f128b34262a0fd4d8ec82377b9, \ - True) - - test_point_validity( - p192, \ - 0x6a223d00bd22c52833409a163e057e5b5da1def2a197dd15, \ - 0x7b482604199367f1f303f9ef627f922f97023e90eae08abf, \ - True) - - test_point_validity( - p192, \ - 0x6dccbde75c0948c98dab32ea0bc59fe125cf0fb1a3798eda, \ - 0x0001171a3e0fa60cf3096f4e116b556198de430e1fbd330c8835, \ - False) - - test_point_validity( - p192, \ - 0xd266b39e1f491fc4acbbbc7d098430931cfa66d55015af12, \ - 0x193782eb909e391a3148b7764e6b234aa94e48d30a16dbb2, \ - False) - - test_point_validity( - p192, \ - 0x9d6ddbcd439baa0c6b80a654091680e462a7d1d3f1ffeb43, \ - 0x6ad8efc4d133ccf167c44eb4691c80abffb9f82b932b8caa, \ - False) - - test_point_validity( - p192, \ - 0x146479d944e6bda87e5b35818aa666a4c998a71f4e95edbc, \ - 0xa86d6fe62bc8fbd88139693f842635f687f132255858e7f6, \ - False) - - test_point_validity( - p192, \ - 0xe594d4a598046f3598243f50fd2c7bd7d380edb055802253, \ - 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, \ - False) + +# Testing point validity, as per ECDSAVS.pdf B.2.2: +P192_POINTS = [ + (generator_192, + 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, + 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, + False), + + (generator_192, + 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, + 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, + False), + + (generator_192, + 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, + 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, + False), + + (generator_192, + 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, + 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, + True), + + (generator_192, + 0xcdf56c1aa3d8afc53c521adf3ffb96734a6a630a4a5b5a70, + 0x97c1c44a5fb229007b5ec5d25f7413d170068ffd023caa4e, + True), + + (generator_192, + 0x89009c0dc361c81e99280c8e91df578df88cdf4b0cdedced, + 0x27be44a529b7513e727251f128b34262a0fd4d8ec82377b9, + True), + + (generator_192, + 0x6a223d00bd22c52833409a163e057e5b5da1def2a197dd15, + 0x7b482604199367f1f303f9ef627f922f97023e90eae08abf, + True), + + (generator_192, + 0x6dccbde75c0948c98dab32ea0bc59fe125cf0fb1a3798eda, + 0x0001171a3e0fa60cf3096f4e116b556198de430e1fbd330c8835, + False), + + (generator_192, + 0xd266b39e1f491fc4acbbbc7d098430931cfa66d55015af12, + 0x193782eb909e391a3148b7764e6b234aa94e48d30a16dbb2, + False), + + (generator_192, + 0x9d6ddbcd439baa0c6b80a654091680e462a7d1d3f1ffeb43, + 0x6ad8efc4d133ccf167c44eb4691c80abffb9f82b932b8caa, + False), + + (generator_192, + 0x146479d944e6bda87e5b35818aa666a4c998a71f4e95edbc, + 0xa86d6fe62bc8fbd88139693f842635f687f132255858e7f6, + False), + + (generator_192, + 0xe594d4a598046f3598243f50fd2c7bd7d380edb055802253, + 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, + False)] + + +@pytest.mark.parametrize("generator,x,y,expected", P192_POINTS) +def test_point_validity(generator, x, y, expected): + """ + `generator` defines the curve; is `(x, y)` a point on + this curve? `expected` is True if the right answer is Yes. + """ + assert point_is_valid(generator, x, y) == expected # Trying signature-verification tests from ECDSAVS.pdf B.2.4: From 1c5f2e0460b5434052dc0fbc0ba30f1103406109 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 14:20:06 +0100 Subject: [PATCH 19/24] test_ecdsa: clean up the tests from X9.62 --- src/ecdsa/test_ecdsa.py | 98 ++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 56 deletions(-) diff --git a/src/ecdsa/test_ecdsa.py b/src/ecdsa/test_ecdsa.py index add27dcc..f852d293 100644 --- a/src/ecdsa/test_ecdsa.py +++ b/src/ecdsa/test_ecdsa.py @@ -2,6 +2,10 @@ import sys import hypothesis.strategies as st from hypothesis import given, settings, note +try: + import unittest2 as unittest +except ImportError: + import unittest import pytest from .ecdsa import Private_key, Public_key, Signature, \ generator_192, digest_integer, ellipticcurve, point_is_valid, \ @@ -16,62 +20,44 @@ HYP_SETTINGS["deadline"] = 5000 -def test_ecdsa(): - class TestFailure(Exception): - pass - - print("NIST Curve P-192:") - - p192 = generator_192 - - # From X9.62: - - d = 651056770906015076056810763456358567190100156695615665659 - Q = d * p192 - if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: - raise TestFailure("*** p192 * d came out wrong.") - else: - print("p192 * d came out right.") - - k = 6140507067065001063065065565667405560006161556565665656654 - R = k * p192 - if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - raise TestFailure("*** k * p192 came out wrong.") - else: - print("k * p192 came out right.") - - u1 = 2563697409189434185194736134579731015366492496392189760599 - u2 = 6266643813348617967186477710235785849136406323338782220568 - temp = u1 * p192 + u2 * Q - if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - raise TestFailure("*** u1 * p192 + u2 * Q came out wrong.") - else: - print("u1 * p192 + u2 * Q came out right.") - - e = 968236873715988614170569073515315707566766479517 - pubk = Public_key(generator_192, generator_192 * d) - privk = Private_key(pubk, d) - sig = privk.sign(e, k) - r, s = sig.r, sig.s - if r != 3342403536405981729393488334694600415596881826869351677613 \ - or s != 5735822328888155254683894997897571951568553642892029982342: - raise TestFailure("*** r or s came out wrong.") - else: - print("r and s came out right.") - - valid = pubk.verifies(e, sig) - if valid: - print("Signature verified OK.") - else: - raise TestFailure("*** Signature failed verification.") - - valid = pubk.verifies(e - 1, sig) - if not valid: - print("Forgery was correctly rejected.") - else: - raise TestFailure("*** Forgery was erroneously accepted.") +class TestP192FromX9_62(unittest.TestCase): + """Check test vectors from X9.62""" + @classmethod + def setUpClass(cls): + cls.d = 651056770906015076056810763456358567190100156695615665659 + cls.Q = cls.d * generator_192 + cls.k = 6140507067065001063065065565667405560006161556565665656654 + cls.R = cls.k * generator_192 + + cls.msg = 968236873715988614170569073515315707566766479517 + cls.pubk = Public_key(generator_192, generator_192 * cls.d) + cls.privk = Private_key(cls.pubk, cls.d) + cls.sig = cls.privk.sign(cls.msg, cls.k) + + def test_point_multiplication(self): + assert self.Q.x() == 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5 + + def test_point_multiplication_2(self): + assert self.R.x() == 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD + assert self.R.y() == 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835 + + def test_mult_and_addition(self): + u1 = 2563697409189434185194736134579731015366492496392189760599 + u2 = 6266643813348617967186477710235785849136406323338782220568 + temp = u1 * generator_192 + u2 * self.Q + assert temp.x() == 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD + assert temp.y() == 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835 + + def test_signature(self): + r, s = self.sig.r, self.sig.s + assert r == 3342403536405981729393488334694600415596881826869351677613 + assert s == 5735822328888155254683894997897571951568553642892029982342 + + def test_verification(self): + assert self.pubk.verifies(self.msg, self.sig) + + def test_rejection(self): + assert not self.pubk.verifies(self.msg - 1, self.sig) # Testing point validity, as per ECDSAVS.pdf B.2.2: From 010853fa2f19c689d0e2a243da414ed81ca1f5be Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 21:27:21 +0100 Subject: [PATCH 20/24] test_numbertheory: hypothesis for inverse_mod use hypothesis (and larger numbers) for testing the inverse_mod function --- src/ecdsa/test_numbertheory.py | 46 +++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 8eea51f5..eaca6cd2 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -1,8 +1,14 @@ +from six import print_ +try: + import unittest2 as unittest +except ImportError: + import unittest +import hypothesis.strategies as st +from hypothesis import given from .numbertheory import (SquareRootError, factorization, gcd, lcm, jacobi, inverse_mod, is_prime, next_prime, smallprimes, square_root_mod_prime) -from six import print_ def test_numbertheory(): @@ -99,26 +105,30 @@ def test_numbertheory(): print_("%d != jacobi( %d, %d )" % (c, a, m)) -# Test the inverse_mod function: - print_("Testing inverse_mod . . .") - import random - n_tests = 0 - for i in range(100): - m = random.randint(20, 10000) - for j in range(100): - a = random.randint(1, m - 1) - if gcd(a, m) == 1: - n_tests = n_tests + 1 - inv = inverse_mod(a, m) - if inv <= 0 or inv >= m or (a * inv) % m != 1: - error_tally = error_tally + 1 - print_("%d = inverse_mod( %d, %d ) is wrong." % (inv, a, m)) - assert n_tests > 1000 - print_(n_tests, " tests of inverse_mod completed.") - class FailedTest(Exception): pass print_(error_tally, "errors detected.") if error_tally != 0: raise FailedTest("%d errors detected" % error_tally) + + +@st.composite +def st_two_nums_rel_prime(draw): + # 521-bit is the biggest curve we operate on, use 1024 for a bit + # of breathing space + mod = draw(st.integers(min_value=2, max_value=2**1024)) + num = draw(st.integers(min_value=1, max_value=mod-1) + .filter(lambda x: gcd(x, mod) == 1)) + return num, mod + + +class TestNumbertheory(unittest.TestCase): + @given(st_two_nums_rel_prime()) + def test_inverse_mod(self, nums): + num, mod = nums + + inv = inverse_mod(num, mod) + + assert 0 < inv < mod + assert num * inv % mod == 1 From 67058172aed5e296dab1b9645b0e149771b54ff7 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 21:52:37 +0100 Subject: [PATCH 21/24] test_numbertheory: test jacobi and factorization functions make the jacobi test into a hypothesis test increase the range tested add test to verify factorization, since it's used in the jacobi test --- src/ecdsa/test_numbertheory.py | 52 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index eaca6cd2..5b9d97cf 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -79,32 +79,6 @@ def test_numbertheory(): print_("Failed to report no root for sqrt( %d ) mod %d." % \ (nonsquare, p)) - # Test the jacobi function: - for m in range(3, 400, 2): - print_("Testing jacobi for modulus m = %d." % m) - if is_prime(m): - squares = [] - for root in range(1, m): - if jacobi(root * root, m) != 1: - error_tally = error_tally + 1 - print_("jacobi( %d * %d, %d) != 1" % (root, root, m)) - squares.append(root * root % m) - for i in range(1, m): - if i not in squares: - if jacobi(i, m) != -1: - error_tally = error_tally + 1 - print_("jacobi( %d, %d ) != -1" % (i, m)) - else: # m is not prime. - f = factorization(m) - for a in range(1, m): - c = 1 - for i in f: - c = c * jacobi(a, i[0]) ** i[1] - if c != jacobi(a, m): - error_tally = error_tally + 1 - print_("%d != jacobi( %d, %d )" % (c, a, m)) - - class FailedTest(Exception): pass @@ -124,6 +98,32 @@ def st_two_nums_rel_prime(draw): class TestNumbertheory(unittest.TestCase): + @given(st.integers(min_value=1, max_value=10**12)) + def test_factorization(self, num): + factors = factorization(num) + mult = 1 + for i in factors: + mult *= i[0] ** i[1] + assert mult == num + + @given(st.integers(min_value=3, max_value=1000).filter(lambda x: x % 2)) + def test_jacobi(self, mod): + if is_prime(mod): + squares = set() + for root in range(1, mod): + assert jacobi(root * root, mod) == 1 + squares.add(root * root % mod) + for i in range(1, mod): + if i not in squares: + assert jacobi(i, mod) == -1 + else: + factors = factorization(mod) + for a in range(1, mod): + c = 1 + for i in factors: + c *= jacobi(a, i[0]) ** i[1] + assert c == jacobi(a, mod) + @given(st_two_nums_rel_prime()) def test_inverse_mod(self, nums): num, mod = nums From 086a4eb1bda928394c6058fc0e60cc1f9fc62d74 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 27 Oct 2019 22:47:42 +0100 Subject: [PATCH 22/24] test_numbertheory: test square_root_mod_prime extend the testing for large primes with hypothesis use pytest.parametrize to test with small primes --- src/ecdsa/test_numbertheory.py | 85 +++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 5b9d97cf..13ffd52c 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -4,7 +4,13 @@ except ImportError: import unittest import hypothesis.strategies as st +import pytest from hypothesis import given +try: + from hypothesis import HealthCheck + HC_PRESENT=True +except ImportError: + HC_PRESENT=False from .numbertheory import (SquareRootError, factorization, gcd, lcm, jacobi, inverse_mod, is_prime, next_prime, smallprimes, @@ -51,40 +57,22 @@ def test_numbertheory(): for i in range(len(bigprimes) - 1): assert next_prime(bigprimes[i]) == bigprimes[i + 1] - error_tally = 0 - - # Test the square_root_mod_prime function: - - for p in smallprimes: - print_("Testing square_root_mod_prime for modulus p = %d." % p) - squares = [] - - for root in range(0, 1 + p // 2): - sq = (root * root) % p - squares.append(sq) - calculated = square_root_mod_prime(sq, p) - if (calculated * calculated) % p != sq: - error_tally = error_tally + 1 - print_("Failed to find %d as sqrt( %d ) mod %d. Said %d." % \ - (root, sq, p, calculated)) - - for nonsquare in range(0, p): - if nonsquare not in squares: - try: - calculated = square_root_mod_prime(nonsquare, p) - except SquareRootError: - pass - else: - error_tally = error_tally + 1 - print_("Failed to report no root for sqrt( %d ) mod %d." % \ - (nonsquare, p)) - class FailedTest(Exception): - pass +@pytest.mark.parametrize("prime", smallprimes) +def test_square_root_mod_prime_for_small_primes(prime): + squares = set() + for num in range(0, 1 + prime // 2): + sq = num * num % prime + squares.add(sq) + root = square_root_mod_prime(sq, prime) + # tested for real with TestNumbertheory.test_square_root_mod_prime + assert root * root % prime == sq - print_(error_tally, "errors detected.") - if error_tally != 0: - raise FailedTest("%d errors detected" % error_tally) + for nonsquare in range(0, prime): + if nonsquare in squares: + continue + with pytest.raises(SquareRootError): + square_root_mod_prime(nonsquare, prime) @st.composite @@ -97,7 +85,40 @@ def st_two_nums_rel_prime(draw): return num, mod +@st.composite +def st_primes(draw, *args, **kwargs): + if "min_value" not in kwargs: + kwargs["min_value"] = 1 + prime = draw(st.integers(*args, **kwargs) + .filter(lambda x: is_prime(x))) + return prime + + +@st.composite +def st_num_square_prime(draw): + prime = draw(st_primes(max_value=2**1024)) + num = draw(st.integers(min_value=0, max_value=1 + prime // 2)) + sq = num * num % prime + return sq, prime + + +HYP_SETTINGS = {} +if HC_PRESENT: + HYP_SETTINGS['suppress_health_check']=[HealthCheck.filter_too_much] + + class TestNumbertheory(unittest.TestCase): + @unittest.skipUnless(HC_PRESENT, + "Hypothesis 2.0.0 can't be made tolerant of hard to " + "meet requirements (like `is_prime()`)") + @settings(**HYP_SETTINGS) + @given(st_num_square_prime()) + def test_square_root_mod_prime(self, vals): + square, prime = vals + + calc = square_root_mod_prime(square, prime) + assert calc * calc % prime == square + @given(st.integers(min_value=1, max_value=10**12)) def test_factorization(self, num): factors = factorization(num) From 17d187fbdb09a6dbb7c8c304fca814f96971f2df Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 28 Oct 2019 00:16:06 +0100 Subject: [PATCH 23/24] test_numbertheory: test next_prime --- src/ecdsa/test_numbertheory.py | 52 ++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 13ffd52c..73a4e798 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -32,30 +32,34 @@ def test_numbertheory(): assert lcm([3, 5 * 3, 7 * 3]) == 3 * 5 * 7 assert lcm(3) == 3 - print_("Testing next_prime...") - bigprimes = (999671, - 999683, - 999721, - 999727, - 999749, - 999763, - 999769, - 999773, - 999809, - 999853, - 999863, - 999883, - 999907, - 999917, - 999931, - 999953, - 999959, - 999961, - 999979, - 999983) - - for i in range(len(bigprimes) - 1): - assert next_prime(bigprimes[i]) == bigprimes[i + 1] + +BIGPRIMES = (999671, + 999683, + 999721, + 999727, + 999749, + 999763, + 999769, + 999773, + 999809, + 999853, + 999863, + 999883, + 999907, + 999917, + 999931, + 999953, + 999959, + 999961, + 999979, + 999983) + + +@pytest.mark.parametrize( + "prime, next_p", + [(p, q) for p, q in zip(BIGPRIMES[:-1], BIGPRIMES[1:])]) +def test_next_prime(prime, next_p): + assert next_prime(prime) == next_p @pytest.mark.parametrize("prime", smallprimes) From 2a8195a5b14accf4f83d920485fc7f86a78daec1 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Mon, 28 Oct 2019 00:39:28 +0100 Subject: [PATCH 24/24] test_numbertheory: test gcd move sanity checks for gcd() to unittest add hypothesis tests for gcd --- src/ecdsa/test_numbertheory.py | 91 +++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/src/ecdsa/test_numbertheory.py b/src/ecdsa/test_numbertheory.py index 73a4e798..38af38ab 100644 --- a/src/ecdsa/test_numbertheory.py +++ b/src/ecdsa/test_numbertheory.py @@ -1,11 +1,14 @@ +import operator from six import print_ +from functools import reduce +import operator try: import unittest2 as unittest except ImportError: import unittest import hypothesis.strategies as st import pytest -from hypothesis import given +from hypothesis import given, settings try: from hypothesis import HealthCheck HC_PRESENT=True @@ -22,11 +25,6 @@ def test_numbertheory(): # p = modular_exp(2, -2, 3) # p = square_root_mod_prime(2, 3) - print_("Testing gcd...") - assert gcd(3 * 5 * 7, 3 * 5 * 11, 3 * 5 * 13) == 3 * 5 - assert gcd([3 * 5 * 7, 3 * 5 * 11, 3 * 5 * 13]) == 3 * 5 - assert gcd(3) == 3 - print_("Testing lcm...") assert lcm(3, 5 * 3, 7 * 3) == 3 * 5 * 7 assert lcm([3, 5 * 3, 7 * 3]) == 3 * 5 * 7 @@ -94,7 +92,7 @@ def st_primes(draw, *args, **kwargs): if "min_value" not in kwargs: kwargs["min_value"] = 1 prime = draw(st.integers(*args, **kwargs) - .filter(lambda x: is_prime(x))) + .filter(is_prime)) return prime @@ -106,12 +104,91 @@ def st_num_square_prime(draw): return sq, prime +@st.composite +def st_comp_with_com_fac(draw): + """ + Strategy that returns lists of numbers, all having a common factor. + """ + primes = draw(st.lists(st_primes(max_value=2**512), min_size=1, + max_size=10)) + # select random primes that will make the common factor of composites + com_fac_primes = draw(st.lists(st.sampled_from(primes))) + com_fac = reduce(operator.mul, com_fac_primes, 1) + + # select at most 20 lists, each having arbitrary number of primes + comp_primes = draw( + st.integers(min_value=1, max_value=20). + flatmap(lambda n: st.lists(st.lists(st.sampled_from(primes)), + min_size=1, max_size=n))) + + return [reduce(operator.mul, nums, 1) * com_fac for nums in comp_primes] + + +@st.composite +def st_comp_no_com_fac(draw): + """ + Strategy that returns lists of numbers that don't have a common factor. + """ + primes = draw(st.lists(st_primes(max_value=2**512), + min_size=2, max_size=10, unique=True)) + # first select the primes that will create the uncommon factor + # between returned numbers + uncom_fac_primes = draw(st.lists( + st.sampled_from(primes), + min_size=1, max_size=len(primes)-1, unique=True)) + uncom_fac = reduce(operator.mul, uncom_fac_primes, 1) + + # then build composites from leftover primes + leftover_primes = [i for i in primes if i not in uncom_fac_primes] + + assert leftover_primes + assert uncom_fac_primes + + # select at most 20 lists, each having arbitrary number of primes + # selected from the leftover_primes list + number_primes = draw( + st.integers(min_value=1, max_value=20). + flatmap(lambda n: st.lists(st.lists(st.sampled_from(leftover_primes)), + min_size=1, max_size=n))) + + numbers = [reduce(operator.mul, nums, 1) for nums in number_primes] + + insert_at = draw(st.integers(min_value=0, max_value=len(numbers))) + numbers.insert(insert_at, uncom_fac) + return numbers + + HYP_SETTINGS = {} if HC_PRESENT: HYP_SETTINGS['suppress_health_check']=[HealthCheck.filter_too_much] class TestNumbertheory(unittest.TestCase): + def test_gcd(self): + assert gcd(3 * 5 * 7, 3 * 5 * 11, 3 * 5 * 13) == 3 * 5 + assert gcd([3 * 5 * 7, 3 * 5 * 11, 3 * 5 * 13]) == 3 * 5 + assert gcd(3) == 3 + + @unittest.skipUnless(HC_PRESENT, + "Hypothesis 2.0.0 can't be made tolerant of hard to " + "meet requirements (like `is_prime()`)") + @settings(**HYP_SETTINGS) + @given(st_comp_with_com_fac()) + def test_gcd_with_com_factor(self, numbers): + n = gcd(numbers) + assert 1 in numbers or n != 1 + for i in numbers: + assert i % n == 0 + + @unittest.skipUnless(HC_PRESENT, + "Hypothesis 2.0.0 can't be made tolerant of hard to " + "meet requirements (like `is_prime()`)") + @settings(**HYP_SETTINGS) + @given(st_comp_no_com_fac()) + def test_gcd_with_uncom_factor(self, numbers): + n = gcd(numbers) + assert n == 1 + @unittest.skipUnless(HC_PRESENT, "Hypothesis 2.0.0 can't be made tolerant of hard to " "meet requirements (like `is_prime()`)")