From f011dc5e2fa649af965c46f010286354f3f6495e Mon Sep 17 00:00:00 2001 From: AntonKueltz Date: Sun, 28 Jul 2019 12:10:29 -0700 Subject: [PATCH] split up tests and update test runner --- .gitignore | 1 + .travis.yml | 4 +- fastecdsa/test.py | 780 ------------------ fastecdsa/tests/__init__.py | 9 + fastecdsa/tests/test_asn1.py | 23 + fastecdsa/tests/test_brainpool_ecdh.py | 120 +++ fastecdsa/tests/test_key_encoding.py | 72 ++ fastecdsa/tests/test_key_recovery.py | 18 + fastecdsa/tests/test_keygen.py | 40 + fastecdsa/tests/test_nonce_generation.py | 31 + fastecdsa/tests/test_p256_ecdsa.py | 99 +++ .../tests/test_prime_field_curve_math.py | 153 ++++ fastecdsa/tests/test_rfc6979_ecdsa.py | 130 +++ fastecdsa/tests/test_signature_encoding.py | 53 ++ fastecdsa/tests/test_whitespace_parsing.py | 32 + setup.py | 38 +- 16 files changed, 796 insertions(+), 807 deletions(-) delete mode 100644 fastecdsa/test.py create mode 100644 fastecdsa/tests/__init__.py create mode 100644 fastecdsa/tests/test_asn1.py create mode 100644 fastecdsa/tests/test_brainpool_ecdh.py create mode 100644 fastecdsa/tests/test_key_encoding.py create mode 100644 fastecdsa/tests/test_key_recovery.py create mode 100644 fastecdsa/tests/test_keygen.py create mode 100644 fastecdsa/tests/test_nonce_generation.py create mode 100644 fastecdsa/tests/test_p256_ecdsa.py create mode 100644 fastecdsa/tests/test_prime_field_curve_math.py create mode 100644 fastecdsa/tests/test_rfc6979_ecdsa.py create mode 100644 fastecdsa/tests/test_signature_encoding.py create mode 100644 fastecdsa/tests/test_whitespace_parsing.py diff --git a/.gitignore b/.gitignore index 75fb7d4..0ef722f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ src/test.c *.egg-info MANIFEST binaryField.* +.coverage diff --git a/.travis.yml b/.travis.yml index e3acbe9..0644672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ python: before_install: - sudo apt-get install python-dev libgmp3-dev install: - - pip install pycodestyle + - pip install pycodestyle pytest pytest-cov before_script: - pycodestyle --max-line-length=200 --ignore=E402,W605 */*.py - pip install -e . script: - - python -m fastecdsa.test + - pytest --cov=fastecdsa diff --git a/fastecdsa/test.py b/fastecdsa/test.py deleted file mode 100644 index 8334fe4..0000000 --- a/fastecdsa/test.py +++ /dev/null @@ -1,780 +0,0 @@ -from binascii import unhexlify, hexlify -from hashlib import sha1, sha224, sha256, sha384, sha512 -from os import remove -from random import choice, randint -from re import findall, DOTALL -from six.moves.urllib.request import urlopen -import unittest - -from .curve import ( - P192, P224, P256, P384, P521, secp192k1, secp224k1, secp256k1, brainpoolP160r1, brainpoolP192r1, - brainpoolP224r1, brainpoolP256r1, brainpoolP320r1, brainpoolP384r1, brainpoolP512r1 -) -from .ecdsa import sign, verify -from .encoding.der import DEREncoder, InvalidDerSignature -from .encoding.pem import PEMEncoder -from .encoding.sec1 import InvalidSEC1PublicKey, SEC1Encoder -from .keys import export_key, gen_keypair, get_public_keys_from_sig, import_key, gen_private_key -from .point import Point -from .util import RFC6979 - -CURVES = [ - P192, P224, P256, P384, P521, secp192k1, secp224k1, secp256k1, brainpoolP160r1, brainpoolP192r1, - brainpoolP224r1, brainpoolP256r1, brainpoolP320r1, brainpoolP384r1, brainpoolP512r1 -] - - -class TestPrimeFieldCurve(unittest.TestCase): - """ cases taken from https://www.nsa.gov/ia/_files/nist-routines.pdf """ - - def test_P192_arith(self): - S = Point( - 0xd458e7d127ae671b0c330266d246769353a012073e97acf8, - 0x325930500d851f336bddc050cf7fb11b5673a1645086df3b, - curve=P192 - ) - d = 0xa78a236d60baec0c5dd41b33a542463a8255391af64c74ee - expected = Point( - 0x1faee4205a4f669d2d0a8f25e3bcec9a62a6952965bf6d31, - 0x5ff2cdfa508a2581892367087c696f179e7a4d7e8260fb06, - curve=P192 - ) - R = d * S - self.assertEqual(R, expected) - - def test_P224_arith(self): - S = Point( - 0x6eca814ba59a930843dc814edd6c97da95518df3c6fdf16e9a10bb5b, - 0xef4b497f0963bc8b6aec0ca0f259b89cd80994147e05dc6b64d7bf22, - curve=P224 - ) - d = 0xa78ccc30eaca0fcc8e36b2dd6fbb03df06d37f52711e6363aaf1d73b - expected = Point( - 0x96a7625e92a8d72bff1113abdb95777e736a14c6fdaacc392702bca4, - 0x0f8e5702942a3c5e13cd2fd5801915258b43dfadc70d15dbada3ed10, - curve=P224 - ) - R = d * S - self.assertEqual(R, expected) - - def test_P256_arith(self): - S = Point( - 0xde2444bebc8d36e682edd27e0f271508617519b3221a8fa0b77cab3989da97c9, - 0xc093ae7ff36e5380fc01a5aad1e66659702de80f53cec576b6350b243042a256, - curve=P256 - ) - d = 0xc51e4753afdec1e6b6c6a5b992f43f8dd0c7a8933072708b6522468b2ffb06fd - expected = Point( - 0x51d08d5f2d4278882946d88d83c97d11e62becc3cfc18bedacc89ba34eeca03f, - 0x75ee68eb8bf626aa5b673ab51f6e744e06f8fcf8a6c0cf3035beca956a7b41d5, - curve=P256 - ) - R = d * S - self.assertEqual(R, expected) - - def test_P384_arith(self): - S = Point( - int('fba203b81bbd23f2b3be971cc23997e1ae4d89e69cb6f92385dda82768ada415ebab4167459da98e6' - '2b1332d1e73cb0e', 16), - int('5ffedbaefdeba603e7923e06cdb5d0c65b22301429293376d5c6944e3fa6259f162b4788de6987fd5' - '9aed5e4b5285e45', 16), - curve=P384 - ) - d = int('a4ebcae5a665983493ab3e626085a24c104311a761b5a8fdac052ed1f111a5c44f76f45659d2d111a' - '61b5fdd97583480', 16) - expected = Point( - int('e4f77e7ffeb7f0958910e3a680d677a477191df166160ff7ef6bb5261f791aa7b45e3e653d151b95d' - 'ad3d93ca0290ef2', 16), - int('ac7dee41d8c5f4a7d5836960a773cfc1376289d3373f8cf7417b0c6207ac32e913856612fc9ff2e35' - '7eb2ee05cf9667f', 16), - curve=P384 - ) - R = d * S - self.assertEqual(R, expected) - - def test_P521_arith(self): - S = Point( - int('000001d5c693f66c08ed03ad0f031f937443458f601fd098d3d0227b4bf62873af50740b0bb84aa15' - '7fc847bcf8dc16a8b2b8bfd8e2d0a7d39af04b089930ef6dad5c1b4', 16), - int('00000144b7770963c63a39248865ff36b074151eac33549b224af5c8664c54012b818ed037b2b7c1a' - '63ac89ebaa11e07db89fcee5b556e49764ee3fa66ea7ae61ac01823', 16), - curve=P521 - ) - d = int('000001eb7f81785c9629f136a7e8f8c674957109735554111a2a866fa5a166699419bfa9936c78b62' - '653964df0d6da940a695c7294d41b2d6600de6dfcf0edcfc89fdcb1', 16) - expected = Point( - int('00000091b15d09d0ca0353f8f96b93cdb13497b0a4bb582ae9ebefa35eee61bf7b7d041b8ec34c6c0' - '0c0c0671c4ae063318fb75be87af4fe859608c95f0ab4774f8c95bb', 16), - int('00000130f8f8b5e1abb4dd94f6baaf654a2d5810411e77b7423965e0c7fd79ec1ae563c207bd255ee' - '9828eb7a03fed565240d2cc80ddd2cecbb2eb50f0951f75ad87977f', 16), - curve=P521 - ) - R = d * S - self.assertEqual(R, expected) - - def test_secp256k1_arith(self): - # http://crypto.stackexchange.com/a/787/17884 - m = 0xAA5E28D6A97A2479A65527F7290311A3624D4CC0FA1578598EE3C2613BF99522 - expected = Point( - 0x34F9460F0E4F08393D192B3C5133A6BA099AA0AD9FD54EBCCFACDFA239FF49C6, - 0x0B71EA9BD730FD8923F6D25A7A91E7DD7728A960686CB5A901BB419E0F2CA232, - curve=secp256k1 - ) - R = m * secp256k1.G - self.assertEqual(R, expected) - - m = 0x7E2B897B8CEBC6361663AD410835639826D590F393D90A9538881735256DFAE3 - expected = Point( - 0xD74BF844B0862475103D96A611CF2D898447E288D34B360BC885CB8CE7C00575, - 0x131C670D414C4546B88AC3FF664611B1C38CEB1C21D76369D7A7A0969D61D97D, - curve=secp256k1 - ) - R = m * secp256k1.G - self.assertEqual(R, expected) - - m = 0x6461E6DF0FE7DFD05329F41BF771B86578143D4DD1F7866FB4CA7E97C5FA945D - expected = Point( - 0xE8AECC370AEDD953483719A116711963CE201AC3EB21D3F3257BB48668C6A72F, - 0xC25CAF2F0EBA1DDB2F0F3F47866299EF907867B7D27E95B3873BF98397B24EE1, - curve=secp256k1 - ) - R = m * secp256k1.G - self.assertEqual(R, expected) - - m = 0x376A3A2CDCD12581EFFF13EE4AD44C4044B8A0524C42422A7E1E181E4DEECCEC - expected = Point( - 0x14890E61FCD4B0BD92E5B36C81372CA6FED471EF3AA60A3E415EE4FE987DABA1, - 0x297B858D9F752AB42D3BCA67EE0EB6DCD1C2B7B0DBE23397E66ADC272263F982, - curve=secp256k1 - ) - R = m * secp256k1.G - self.assertEqual(R, expected) - - m = 0x1B22644A7BE026548810C378D0B2994EEFA6D2B9881803CB02CEFF865287D1B9 - expected = Point( - 0xF73C65EAD01C5126F28F442D087689BFA08E12763E0CEC1D35B01751FD735ED3, - 0xF449A8376906482A84ED01479BD18882B919C140D638307F0C0934BA12590BDE, - curve=secp256k1 - ) - R = m * secp256k1.G - self.assertEqual(R, expected) - - def test_arbitrary_arithmetic(self): - for _ in range(100): - curve = choice(CURVES) - a, b = randint(0, curve.q), randint(0, curve.q) - c = (a + b) % curve.q - P, Q = a * curve.G, b * curve.G - R = c * curve.G - pq_sum, qp_sum = P + Q, Q + P - self.assertTrue(pq_sum == qp_sum) - self.assertTrue(qp_sum == R) - - -class TestNonceGeneration(unittest.TestCase): - def test_rfc_6979(self): - msg = 'sample' - x = 0x09A4D6792295A7F730FC3F2B49CBC0F62E862272F - q = 0x4000000000000000000020108A2E0CC0D99F8A5EF - - expected = 0x09744429FA741D12DE2BE8316E35E84DB9E5DF1CD - nonce = RFC6979(msg, x, q, sha1).gen_nonce() - self.assertTrue(nonce == expected) - - expected = 0x323E7B28BFD64E6082F5B12110AA87BC0D6A6E159 - nonce = RFC6979(msg, x, q, sha224).gen_nonce() - self.assertTrue(nonce == expected) - - expected = 0x23AF4074C90A02B3FE61D286D5C87F425E6BDD81B - nonce = RFC6979(msg, x, q, sha256).gen_nonce() - self.assertTrue(nonce == expected) - - expected = 0x2132ABE0ED518487D3E4FA7FD24F8BED1F29CCFCE - nonce = RFC6979(msg, x, q, sha384).gen_nonce() - self.assertTrue(nonce == expected) - - expected = 0x00BBCC2F39939388FDFE841892537EC7B1FF33AA3 - nonce = RFC6979(msg, x, q, sha512).gen_nonce() - self.assertTrue(nonce == expected) - - -class TestPrimeFieldECDSA(unittest.TestCase): - """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ - - def test_ecdsa_P256_SHA1_sign(self): - d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 - expected = ( - 0x61340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D32, - 0x6D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB, - ) - sig = sign('sample', d, curve=P256, hashfunc=sha1) - self.assertEqual(sig, expected) - - Q = d * P256.G - self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha1)) - - """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ - - def test_ecdsa_P256_SHA224_sign(self): - d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 - expected = ( - 0x53B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F, - 0xB9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C - ) - sig = sign('sample', d, curve=P256, hashfunc=sha224) - self.assertEqual(sig, expected) - - Q = d * P256.G - self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha224)) - - """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ - - def test_ecdsa_P256_SHA2_sign(self): - d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 - expected = ( - 0xEFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716, - 0xF7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8 - ) - sig = sign('sample', d, curve=P256, hashfunc=sha256) - self.assertEqual(sig, expected) - - Q = d * P256.G - self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha256)) - - """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ - - def test_ecdsa_P256_SHA384_sign(self): - d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 - expected = ( - 0x0EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF7719, - 0x4861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954 - ) - sig = sign('sample', d, curve=P256, hashfunc=sha384) - self.assertEqual(sig, expected) - - Q = d * P256.G - self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha384)) - - """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ - - def test_ecdsa_P256_SHA512_sign(self): - d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 - expected = ( - 0x8496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F00, - 0x2362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE - ) - sig = sign('sample', d, curve=P256, hashfunc=sha512) - self.assertEqual(sig, expected) - - Q = d * P256.G - self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha512)) - - """ case taken from https://www.nsa.gov/ia/_files/ecdsa.pdf """ - - def test_ecdsa_P256_verify(self): - Q = Point( - 0x8101ece47464a6ead70cf69a6e2bd3d88691a3262d22cba4f7635eaff26680a8, - 0xd8a12ba61d599235f67d9cb4d58f1783d3ca43e78f0a5abaa624079936c0c3a9, - curve=P256 - ) - msg = 'This is only a test message. It is 48 bytes long' - sig = ( - 0x7214bc9647160bbd39ff2f80533f5dc6ddd70ddf86bb815661e805d5d4e6f27c, - 0x7d1ff961980f961bdaa3233b6209f4013317d3e3f9e1493592dbeaa1af2bc367 - ) - self.assertTrue(verify(sig, msg, Q, curve=P256, hashfunc=sha256)) - - sig = ( - 0x7214bc9647160bbd39ff2f80533f5dc6ddd70ddf86bb815661e805d5d4e6fbad, - 0x7d1ff961980f961bdaa3233b6209f4013317d3e3f9e1493592dbeaa1af2bc367 - ) - self.assertFalse(verify(sig, msg, Q, curve=P256, hashfunc=sha256)) - - -class TestP192ECDSA(unittest.TestCase): - def test_rfc6979(self): - text = urlopen('https://tools.ietf.org/rfc/rfc6979.txt').read().decode() - curve_tests = findall(r'curve: NIST P-192(.*)curve: NIST P-224', text, flags=DOTALL)[0] - - q = int(findall(r'q = ([0-9A-F]*)', curve_tests)[0], 16) - x = int(findall(r'x = ([0-9A-F]*)', curve_tests)[0], 16) - - test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ - r'\s*k = ([0-9A-F]*)\n' \ - r'\s*r = ([0-9A-F]*)\n' \ - r'\s*s = ([0-9A-F]*)\n' - - hash_lookup = { - '1': sha1, - '224': sha224, - '256': sha256, - '384': sha384, - '512': sha512 - } - - for test in findall(test_regex, curve_tests): - h = hash_lookup[test[0]] - msg = test[1] - k = int(test[2], 16) - r = int(test[3], 16) - s = int(test[4], 16) - - self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) - self.assertEqual((r, s), sign(msg, x, curve=P192, hashfunc=h)) - - -class TestP224ECDSA(unittest.TestCase): - def test_rfc6979(self): - text = urlopen('https://tools.ietf.org/rfc/rfc6979.txt').read().decode() - curve_tests = findall(r'curve: NIST P-224(.*)curve: NIST P-256', text, flags=DOTALL)[0] - - q = int(findall(r'q = ([0-9A-F]*)', curve_tests)[0], 16) - x = int(findall(r'x = ([0-9A-F]*)', curve_tests)[0], 16) - - test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ - r'\s*k = ([0-9A-F]*)\n' \ - r'\s*r = ([0-9A-F]*)\n' \ - r'\s*s = ([0-9A-F]*)\n' - - hash_lookup = { - '1': sha1, - '224': sha224, - '256': sha256, - '384': sha384, - '512': sha512 - } - - for test in findall(test_regex, curve_tests): - h = hash_lookup[test[0]] - msg = test[1] - k = int(test[2], 16) - r = int(test[3], 16) - s = int(test[4], 16) - - self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) - self.assertEqual((r, s), sign(msg, x, curve=P224, hashfunc=h)) - - -class TestP256ECDSA(unittest.TestCase): - def test_rfc6979(self): - text = urlopen('https://tools.ietf.org/rfc/rfc6979.txt').read().decode() - curve_tests = findall(r'curve: NIST P-256(.*)curve: NIST P-384', text, flags=DOTALL)[0] - - q = int(findall(r'q = ([0-9A-F]*)', curve_tests)[0], 16) - x = int(findall(r'x = ([0-9A-F]*)', curve_tests)[0], 16) - - test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ - r'\s*k = ([0-9A-F]*)\n' \ - r'\s*r = ([0-9A-F]*)\n' \ - r'\s*s = ([0-9A-F]*)\n' - - hash_lookup = { - '1': sha1, - '224': sha224, - '256': sha256, - '384': sha384, - '512': sha512 - } - - for test in findall(test_regex, curve_tests): - h = hash_lookup[test[0]] - msg = test[1] - k = int(test[2], 16) - r = int(test[3], 16) - s = int(test[4], 16) - - self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) - self.assertEqual((r, s), sign(msg, x, curve=P256, hashfunc=h)) - - -class TestP384ECDSA(unittest.TestCase): - def test_rfc6979(self): - text = urlopen('https://tools.ietf.org/rfc/rfc6979.txt').read().decode() - curve_tests = findall(r'curve: NIST P-384(.*)curve: NIST P-521', text, flags=DOTALL)[0] - - q_parts = findall(r'q = ([0-9A-F]*)\n\s*([0-9A-F]*)', curve_tests)[0] - q = int(q_parts[0] + q_parts[1], 16) - x_parts = findall(r'x = ([0-9A-F]*)\n\s*([0-9A-F]*)', curve_tests)[0] - x = int(x_parts[0] + x_parts[1], 16) - - test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ - r'\s*k = ([0-9A-F]*)\n\s*([0-9A-F]*)\n' \ - r'\s*r = ([0-9A-F]*)\n\s*([0-9A-F]*)\n' \ - r'\s*s = ([0-9A-F]*)\n\s*([0-9A-F]*)\n' - - hash_lookup = { - '1': sha1, - '224': sha224, - '256': sha256, - '384': sha384, - '512': sha512 - } - - for test in findall(test_regex, curve_tests): - h = hash_lookup[test[0]] - msg = test[1] - k = int(test[2] + test[3], 16) - r = int(test[4] + test[5], 16) - s = int(test[6] + test[7], 16) - - self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) - self.assertEqual((r, s), sign(msg, x, curve=P384, hashfunc=h)) - - -class TestP521ECDSA(unittest.TestCase): - def test_rfc6979(self): - text = urlopen('https://tools.ietf.org/rfc/rfc6979.txt').read().decode() - curve_tests = findall(r'curve: NIST P-521(.*)curve: NIST K-163', text, flags=DOTALL)[0] - - q_parts = findall(r'q = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)', curve_tests)[0] - q = int(q_parts[0] + q_parts[1] + q_parts[2], 16) - x_parts = findall(r'x = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)', curve_tests)[0] - x = int(x_parts[0] + x_parts[1] + x_parts[2], 16) - - test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ - r'\s*k = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)\n' \ - r'\s*r = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)\n' \ - r'\s*s = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)\n' - - hash_lookup = { - '1': sha1, - '224': sha224, - '256': sha256, - '384': sha384, - '512': sha512 - } - - for test in findall(test_regex, curve_tests): - h = hash_lookup[test[0]] - msg = test[1] - k = int(test[2] + test[3] + test[4], 16) - r = int(test[5] + test[6] + test[7], 16) - s = int(test[8] + test[9] + test[10], 16) - - self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) - self.assertEqual((r, s), sign(msg, x, curve=P521, hashfunc=h)) - - -class TestBrainpoolECDH(unittest.TestCase): - def test_256bit(self): - # https://tools.ietf.org/html/rfc7027#appendix-A.1 - dA = 0x81DB1EE100150FF2EA338D708271BE38300CB54241D79950F77B063039804F1D - qA = dA * brainpoolP256r1.G - - self.assertEqual(qA.x, 0x44106E913F92BC02A1705D9953A8414DB95E1AAA49E81D9E85F929A8E3100BE5) - self.assertEqual(qA.y, 0x8AB4846F11CACCB73CE49CBDD120F5A900A69FD32C272223F789EF10EB089BDC) - - dB = 0x55E40BC41E37E3E2AD25C3C6654511FFA8474A91A0032087593852D3E7D76BD3 - qB = dB * brainpoolP256r1.G - - self.assertEqual(qB.x, 0x8D2D688C6CF93E1160AD04CC4429117DC2C41825E1E9FCA0ADDD34E6F1B39F7B) - self.assertEqual(qB.y, 0x990C57520812BE512641E47034832106BC7D3E8DD0E4C7F1136D7006547CEC6A) - - self.assertEqual((dA * qB).x, (dB * qA).x) - self.assertEqual((dA * qB).y, (dB * qA).y) - - Z = dA * qB - self.assertEqual(Z.x, 0x89AFC39D41D3B327814B80940B042590F96556EC91E6AE7939BCE31F3A18BF2B) - self.assertEqual(Z.y, 0x49C27868F4ECA2179BFD7D59B1E3BF34C1DBDE61AE12931648F43E59632504DE) - - def test_384bit(self): - # https://tools.ietf.org/html/rfc7027#appendix-A.2 - dA = int('1E20F5E048A5886F1F157C74E91BDE2B98C8B52D58E5003D57053FC4B0BD6' - '5D6F15EB5D1EE1610DF870795143627D042', 16) - qA = dA * brainpoolP384r1.G - - self.assertEqual( - qA.x, - int('68B665DD91C195800650CDD363C625F4E742E8134667B767B1B47679358' - '8F885AB698C852D4A6E77A252D6380FCAF068', 16) - ) - self.assertEqual( - qA.y, - int('55BC91A39C9EC01DEE36017B7D673A931236D2F1F5C83942D049E3FA206' - '07493E0D038FF2FD30C2AB67D15C85F7FAA59', 16) - ) - - dB = int('032640BC6003C59260F7250C3DB58CE647F98E1260ACCE4ACDA3DD869F74E' - '01F8BA5E0324309DB6A9831497ABAC96670', 16) - qB = dB * brainpoolP384r1.G - - self.assertEqual( - qB.x, - int('4D44326F269A597A5B58BBA565DA5556ED7FD9A8A9EB76C25F46DB69D19' - 'DC8CE6AD18E404B15738B2086DF37E71D1EB4', 16) - ) - self.assertEqual( - qB.y, - int('62D692136DE56CBE93BF5FA3188EF58BC8A3A0EC6C1E151A21038A42E91' - '85329B5B275903D192F8D4E1F32FE9CC78C48', 16) - ) - - self.assertEqual((dA * qB).x, (dB * qA).x) - self.assertEqual((dA * qB).y, (dB * qA).y) - - Z = dA * qB - self.assertEqual( - Z.x, - int('0BD9D3A7EA0B3D519D09D8E48D0785FB744A6B355E6304BC51C229FBBCE2' - '39BBADF6403715C35D4FB2A5444F575D4F42', 16) - ) - self.assertEqual( - Z.y, - int('0DF213417EBE4D8E40A5F76F66C56470C489A3478D146DECF6DF0D94BAE9' - 'E598157290F8756066975F1DB34B2324B7BD', 16) - ) - - def test_512bit(self): - # https://tools.ietf.org/html/rfc7027#appendix-A.2 - dA = int('16302FF0DBBB5A8D733DAB7141C1B45ACBC8715939677F6A56850A38BD87B' - 'D59B09E80279609FF333EB9D4C061231FB26F92EEB04982A5F1D1764CAD57665422', 16) - qA = dA * brainpoolP512r1.G - - self.assertEqual( - qA.x, - int('0A420517E406AAC0ACDCE90FCD71487718D3B953EFD7FBEC5F7F27E28C6' - '149999397E91E029E06457DB2D3E640668B392C2A7E737A7F0BF04436D11640FD09FD', 16) - ) - self.assertEqual( - qA.y, - int('72E6882E8DB28AAD36237CD25D580DB23783961C8DC52DFA2EC138AD472' - 'A0FCEF3887CF62B623B2A87DE5C588301EA3E5FC269B373B60724F5E82A6AD147FDE7', 16) - ) - - dB = int('230E18E1BCC88A362FA54E4EA3902009292F7F8033624FD471B5D8ACE49D1' - '2CFABBC19963DAB8E2F1EBA00BFFB29E4D72D13F2224562F405CB80503666B25429', 16) - qB = dB * brainpoolP512r1.G - - self.assertEqual( - qB.x, - int('9D45F66DE5D67E2E6DB6E93A59CE0BB48106097FF78A081DE781CDB31FC' - 'E8CCBAAEA8DD4320C4119F1E9CD437A2EAB3731FA9668AB268D871DEDA55A5473199F', 16) - ) - self.assertEqual( - qB.y, - int('2FDC313095BCDD5FB3A91636F07A959C8E86B5636A1E930E8396049CB48' - '1961D365CC11453A06C719835475B12CB52FC3C383BCE35E27EF194512B71876285FA', 16) - ) - - self.assertEqual((dA * qB).x, (dB * qA).x) - self.assertEqual((dA * qB).y, (dB * qA).y) - - Z = dA * qB - self.assertEqual( - Z.x, - int('A7927098655F1F9976FA50A9D566865DC530331846381C87256BAF322624' - '4B76D36403C024D7BBF0AA0803EAFF405D3D24F11A9B5C0BEF679FE1454B21C4CD1F', 16) - ) - self.assertEqual( - Z.y, - int('7DB71C3DEF63212841C463E881BDCF055523BD368240E6C3143BD8DEF8B3' - 'B3223B95E0F53082FF5E412F4222537A43DF1C6D25729DDB51620A832BE6A26680A2', 16) - ) - - -class TestKeys(unittest.TestCase): - def test_gen_private_key(self): - class FakeCurve(): - def __init__(self, q): - self.q = q - - class FakeRandom(): - def __init__(self, values=b"\x00"): - self.values = values - self.pos = 0 - - def __call__(self, nb): - result = self.values[self.pos:self.pos + nb] - self.pos += nb - return result - self.assertEqual(gen_private_key(FakeCurve(2), randfunc=FakeRandom(b"\x00")), 0) - # 1 byte / 6 bits shaved off + the first try is lower than the order - self.assertEqual(gen_private_key(FakeCurve(2), randfunc=FakeRandom(b"\x40")), 1) - # 1 byte / 6 bits shaved off + the first try is higher than the order - self.assertEqual(gen_private_key(FakeCurve(2), randfunc=FakeRandom(b"\xc0\x40")), 1) - self.assertEqual(gen_private_key(FakeCurve(2), randfunc=FakeRandom(b"\xc0\x00")), 0) - # 2 byte / 3 are shaved off, the first try is lower than the order. - self.assertEqual(gen_private_key(FakeCurve(8191), randfunc=FakeRandom(b"\xff\xf0")), 8190) - # 2 byte / 3 are shaved off - # first try : _bytes_to_int("\xff\xf8") >> 3 == 8191 (too high for order 8191) - # second try : _bytes_to_int("\xff\xf0") >> 3 == 8190 (ok for order 8191) - self.assertEqual(gen_private_key(FakeCurve(8191), randfunc=FakeRandom(b"\xff\xf8\xff\xf0")), 8190) - # Same but with a different second try value - self.assertEqual(gen_private_key(FakeCurve(8191), randfunc=FakeRandom(b"\xff\xf8\xff\xef")), 8189) - - -class TestAsn1(unittest.TestCase): - def test_generate_and_parse_pem(self): - d, Q = gen_keypair(P256) - export_key(d, curve=P256, filepath='p256.key') - export_key(Q, curve=P256, filepath='p256.pub') - - parsed_d, parsed_Q = import_key('p256.key') - self.assertEqual(parsed_d, d) - self.assertEqual(parsed_Q, Q) - - parsed_d, parsed_Q = import_key('p256.pub') - self.assertTrue(parsed_d is None) - self.assertEqual(parsed_Q, Q) - - remove('p256.key') - remove('p256.pub') - - -class TestSignatureDERencoding(unittest.TestCase): - def test_encode_der_signature(self): - self.assertEqual(DEREncoder.encode_signature(r=1, s=2), b"\x30" # SEQUENCE - b"\x06" # Length of Sequence - b"\x02" # INTEGER - b"\x01" # Length of r - b"\x01" # r - b"\x02" # INTEGER - b"\x01" # Length of s - b"\x02") # s - - # Check that we add a zero byte when the number's highest bit is set - self.assertEqual(DEREncoder.encode_signature(r=128, s=128), - b"0\x08\x02\x02\x00\x80\x02\x02\x00\x80") - - def test_decode_der_signature(self): - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"") # length to shot - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x31\x06\x02\x01\x01\x02\x01\x02") # invalid SEQUENCE marker - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x07\x02\x01\x01\x02\x01\x02") # invalid length - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x06\x02\x03\x01\x02\x01\x02") # invalid length of r - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x03\x01\x02") # invalid length of s - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x06\x03\x01\x01\x02\x01\x02") # invalid INTEGER marker for r - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x06\x02\x00\x02\x01\x02") # length of r is 0 - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x06\x02\x01\x81\x02\x01\x02") # value of r is negative - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x07\x02\x02\x00\x01\x02\x01\x02") # value of r starts with a zero byte - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x03\x01\x02") # invalid INTEGER marker for s - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x00") # value of s is 0 - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x01\x81") # value of s is negative - with self.assertRaises(InvalidDerSignature): - DEREncoder.decode_signature(b"\x30\x07\x02\x01\x01\x02\x02\x00\x02") # value of s starts with a zero byte - - self.assertEqual(DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x01\x02"), (1, 2)) - self.assertEqual(DEREncoder.decode_signature(b"0\x08\x02\x02\x00\x80\x02\x02\x00\x80"), - (128, 128)) # verify zero bytes - self.assertEqual(DEREncoder.decode_signature(b"0\x08\x02\x02\x03\xE8\x02\x02\x03\xE8"), - (1000, 1000)) # verify byte order - - -class TestEncodePublicKey(unittest.TestCase): - def test_SEC1_encode_public_key(self): - # 1/ PrivateKey generated using openssl "openssl ecparam -name secp256k1 -genkey -out ec-priv.pem" - # 2/ Printed using "openssl ec -in ec-priv.pem -text -noout" and converted to numeric using "asn1._bytes_to_int" - priv_key = 7002880736699640265110069622773736733141182416793484574964618597954446769264 - pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(secp256k1.G * priv_key)) - pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(secp256k1.G * priv_key, compressed=False)) - # 3/ PublicKey extracted using "openssl ec -in ec-priv.pem -pubout -out ec-pub.pem" - # 4/ Encoding verified using openssl "openssl ec -in ec-pub.pem -pubin -text -noout -conv_form compressed" - self.assertEqual(pubkey_compressed, b'02e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062') - self.assertEqual(pubkey_uncompressed, b'04e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f3583506' - b'23dad76df888abde5ed0cc5af1b83968edffcae5d70bedb24fdc18bb5f79499d0') - # Same with P256 Curve - priv_P256 = 807015861248675637760562792774171551137308512372870683367415858378856470633 - pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(P256.G * priv_P256)) - pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(P256.G * priv_P256, compressed=False)) - self.assertEqual(pubkey_compressed, b'0212c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824c') - self.assertEqual(pubkey_uncompressed, b'0412c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824' - b'c9a7d581bcf2aba680b53cedbade03be62fe95869da04a168a458f369ac6a823e') - # And secp192k1 Curve - priv_secp192k1 = 5345863567856687638748079156318679969014620278806295592453 - pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(secp192k1.G * priv_secp192k1)) - pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(secp192k1.G * priv_secp192k1, compressed=False)) - self.assertEqual(pubkey_compressed, b'03a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22d') - self.assertEqual(pubkey_uncompressed, b'04a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22' - b'df07a73819149e8d903aa983e52ab1cff38f0d381f940d361') - - def test_SEC1_decode_public_key(self): - expected_public = Point(x=0xe5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062, - y=0x3dad76df888abde5ed0cc5af1b83968edffcae5d70bedb24fdc18bb5f79499d0, - curve=secp256k1) - public_from_compressed = SEC1Encoder.decode_public_key( - unhexlify(b'02e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062'), secp256k1) - public_from_uncompressed = SEC1Encoder.decode_public_key( - unhexlify(b'04e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f3583506' - b'23dad76df888abde5ed0cc5af1b83968edffcae5d70bedb24fdc18bb5f79499d0'), secp256k1) - # Same values as in "test_SEC1_encode_public_key", verified using openssl - self.assertEqual(public_from_compressed, expected_public) - self.assertEqual(public_from_uncompressed, expected_public) - with self.assertRaises(InvalidSEC1PublicKey) as e: - SEC1Encoder.decode_public_key(b'\x02', secp256k1) # invalid compressed length - self.assertEqual(e.exception.args[0], "A compressed public key must be 33 bytes long") - with self.assertRaises(InvalidSEC1PublicKey) as e: - SEC1Encoder.decode_public_key(b'\x04', secp256k1) # invalid uncompressed length - self.assertEqual(e.exception.args[0], "An uncompressed public key must be 65 bytes long") - with self.assertRaises(InvalidSEC1PublicKey) as e: - # invalid prefix value - SEC1Encoder.decode_public_key( - unhexlify(b'05e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062'), secp256k1) - self.assertEqual(e.exception.args[0], "Wrong key format") - # With P256, same values as in "test_SEC1_encode_public_key", verified using openssl - expected_P256 = Point(x=0x12c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824c, - y=0x9a7d581bcf2aba680b53cedbade03be62fe95869da04a168a458f369ac6a823e, - curve=P256) - public_from_compressed = SEC1Encoder.decode_public_key( - unhexlify(b'0212c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824c'), P256) - self.assertEqual(public_from_compressed, expected_P256) - # With P256, same values as in "test_SEC1_encode_public_key", verified using openssl - expected_secp192k1 = Point(x=0xa3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22d, - y=0xf07a73819149e8d903aa983e52ab1cff38f0d381f940d361, - curve=secp192k1) - public_from_compressed = SEC1Encoder.decode_public_key( - unhexlify(b'03a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22d'), secp192k1) - self.assertEqual(public_from_compressed, expected_secp192k1) - - -class TestKeyRecovery(unittest.TestCase): - def test_key_recovery(self): - for curve in CURVES: - d, Q = gen_keypair(curve) - msg = 'https://crypto.stackexchange.com/questions/18105/how-does-recovering-the-' \ - 'public-key-from-an-ecdsa-signature-work' - sig = sign(msg, d, curve=curve) - - Qs = get_public_keys_from_sig(sig, msg, curve=curve, hashfunc=sha256) - self.assertTrue(Q in Qs) - - -class TestWhitespaceParsing(unittest.TestCase): - d = 0x3007df8e8bed6c3592a10f9d0495173dcecbc99a40e8c88b47d7d590f8b608dd - x = 0x2c071354794f38ff439eff52f8b475b0def34a210aa3b88a63f2a7295d35f41f - y = 0xb0f35d4f45d95657fa37d6a5431be9441a95638fc710884f43fff7938428056e - Q = Point(x, y, curve=secp256k1) - - def test_leading_newline(self): - keypem = "\n-----BEGIN EC PRIVATE KEY-----\n" \ - "MHQCAQEEIDAH346L7Ww1kqEPnQSVFz3Oy8maQOjIi0fX1ZD4tgjdoAcGBSuBBAAK\n" \ - "oUQDQgAELAcTVHlPOP9Dnv9S+LR1sN7zSiEKo7iKY/KnKV019B+w811PRdlWV/o3\n" \ - "1qVDG+lEGpVjj8cQiE9D//eThCgFbg==\n" \ - "-----END EC PRIVATE KEY-----\n" - key, pubkey = PEMEncoder.decode_private_key(keypem) - self.assertEqual(key, self.d) - self.assertEqual(pubkey, self.Q) - - def test_leading_trailing_newlines(self): - keypem = "\n\n\n\n-----BEGIN EC PRIVATE KEY-----\n" \ - "MHQCAQEEIDAH346L7Ww1kqEPnQSVFz3Oy8maQOjIi0fX1ZD4tgjdoAcGBSuBBAAK\n" \ - "oUQDQgAELAcTVHlPOP9Dnv9S+LR1sN7zSiEKo7iKY/KnKV019B+w811PRdlWV/o3\n" \ - "1qVDG+lEGpVjj8cQiE9D//eThCgFbg==\n" \ - "-----END EC PRIVATE KEY-----\n\n\n\n" - key, pubkey = PEMEncoder.decode_private_key(keypem) - self.assertEqual(key, self.d) - self.assertEqual(pubkey, self.Q) - - -if __name__ == '__main__': - unittest.main() diff --git a/fastecdsa/tests/__init__.py b/fastecdsa/tests/__init__.py new file mode 100644 index 0000000..474f355 --- /dev/null +++ b/fastecdsa/tests/__init__.py @@ -0,0 +1,9 @@ +from ..curve import ( + P192, P224, P256, P384, P521, secp192k1, secp224k1, secp256k1, brainpoolP160r1, brainpoolP192r1, + brainpoolP224r1, brainpoolP256r1, brainpoolP320r1, brainpoolP384r1, brainpoolP512r1 +) + +CURVES = [ + P192, P224, P256, P384, P521, secp192k1, secp224k1, secp256k1, brainpoolP160r1, brainpoolP192r1, + brainpoolP224r1, brainpoolP256r1, brainpoolP320r1, brainpoolP384r1, brainpoolP512r1 +] diff --git a/fastecdsa/tests/test_asn1.py b/fastecdsa/tests/test_asn1.py new file mode 100644 index 0000000..c17eef0 --- /dev/null +++ b/fastecdsa/tests/test_asn1.py @@ -0,0 +1,23 @@ +from os import remove +from unittest import TestCase + +from ..curve import P256 +from ..keys import export_key, import_key, gen_keypair + + +class TestAsn1(TestCase): + def test_generate_and_parse_pem(self): + d, Q = gen_keypair(P256) + export_key(d, curve=P256, filepath='p256.key') + export_key(Q, curve=P256, filepath='p256.pub') + + parsed_d, parsed_Q = import_key('p256.key') + self.assertEqual(parsed_d, d) + self.assertEqual(parsed_Q, Q) + + parsed_d, parsed_Q = import_key('p256.pub') + self.assertTrue(parsed_d is None) + self.assertEqual(parsed_Q, Q) + + remove('p256.key') + remove('p256.pub') \ No newline at end of file diff --git a/fastecdsa/tests/test_brainpool_ecdh.py b/fastecdsa/tests/test_brainpool_ecdh.py new file mode 100644 index 0000000..c89baee --- /dev/null +++ b/fastecdsa/tests/test_brainpool_ecdh.py @@ -0,0 +1,120 @@ +from unittest import TestCase + +from ..curve import brainpoolP256r1, brainpoolP384r1, brainpoolP512r1 + + +class TestBrainpoolECDH(TestCase): + def test_256bit(self): + # https://tools.ietf.org/html/rfc7027#appendix-A.1 + dA = 0x81DB1EE100150FF2EA338D708271BE38300CB54241D79950F77B063039804F1D + qA = dA * brainpoolP256r1.G + + self.assertEqual(qA.x, 0x44106E913F92BC02A1705D9953A8414DB95E1AAA49E81D9E85F929A8E3100BE5) + self.assertEqual(qA.y, 0x8AB4846F11CACCB73CE49CBDD120F5A900A69FD32C272223F789EF10EB089BDC) + + dB = 0x55E40BC41E37E3E2AD25C3C6654511FFA8474A91A0032087593852D3E7D76BD3 + qB = dB * brainpoolP256r1.G + + self.assertEqual(qB.x, 0x8D2D688C6CF93E1160AD04CC4429117DC2C41825E1E9FCA0ADDD34E6F1B39F7B) + self.assertEqual(qB.y, 0x990C57520812BE512641E47034832106BC7D3E8DD0E4C7F1136D7006547CEC6A) + + self.assertEqual((dA * qB).x, (dB * qA).x) + self.assertEqual((dA * qB).y, (dB * qA).y) + + Z = dA * qB + self.assertEqual(Z.x, 0x89AFC39D41D3B327814B80940B042590F96556EC91E6AE7939BCE31F3A18BF2B) + self.assertEqual(Z.y, 0x49C27868F4ECA2179BFD7D59B1E3BF34C1DBDE61AE12931648F43E59632504DE) + + def test_384bit(self): + # https://tools.ietf.org/html/rfc7027#appendix-A.2 + dA = int('1E20F5E048A5886F1F157C74E91BDE2B98C8B52D58E5003D57053FC4B0BD6' + '5D6F15EB5D1EE1610DF870795143627D042', 16) + qA = dA * brainpoolP384r1.G + + self.assertEqual( + qA.x, + int('68B665DD91C195800650CDD363C625F4E742E8134667B767B1B47679358' + '8F885AB698C852D4A6E77A252D6380FCAF068', 16) + ) + self.assertEqual( + qA.y, + int('55BC91A39C9EC01DEE36017B7D673A931236D2F1F5C83942D049E3FA206' + '07493E0D038FF2FD30C2AB67D15C85F7FAA59', 16) + ) + + dB = int('032640BC6003C59260F7250C3DB58CE647F98E1260ACCE4ACDA3DD869F74E' + '01F8BA5E0324309DB6A9831497ABAC96670', 16) + qB = dB * brainpoolP384r1.G + + self.assertEqual( + qB.x, + int('4D44326F269A597A5B58BBA565DA5556ED7FD9A8A9EB76C25F46DB69D19' + 'DC8CE6AD18E404B15738B2086DF37E71D1EB4', 16) + ) + self.assertEqual( + qB.y, + int('62D692136DE56CBE93BF5FA3188EF58BC8A3A0EC6C1E151A21038A42E91' + '85329B5B275903D192F8D4E1F32FE9CC78C48', 16) + ) + + self.assertEqual((dA * qB).x, (dB * qA).x) + self.assertEqual((dA * qB).y, (dB * qA).y) + + Z = dA * qB + self.assertEqual( + Z.x, + int('0BD9D3A7EA0B3D519D09D8E48D0785FB744A6B355E6304BC51C229FBBCE2' + '39BBADF6403715C35D4FB2A5444F575D4F42', 16) + ) + self.assertEqual( + Z.y, + int('0DF213417EBE4D8E40A5F76F66C56470C489A3478D146DECF6DF0D94BAE9' + 'E598157290F8756066975F1DB34B2324B7BD', 16) + ) + + def test_512bit(self): + # https://tools.ietf.org/html/rfc7027#appendix-A.2 + dA = int('16302FF0DBBB5A8D733DAB7141C1B45ACBC8715939677F6A56850A38BD87B' + 'D59B09E80279609FF333EB9D4C061231FB26F92EEB04982A5F1D1764CAD57665422', 16) + qA = dA * brainpoolP512r1.G + + self.assertEqual( + qA.x, + int('0A420517E406AAC0ACDCE90FCD71487718D3B953EFD7FBEC5F7F27E28C6' + '149999397E91E029E06457DB2D3E640668B392C2A7E737A7F0BF04436D11640FD09FD', 16) + ) + self.assertEqual( + qA.y, + int('72E6882E8DB28AAD36237CD25D580DB23783961C8DC52DFA2EC138AD472' + 'A0FCEF3887CF62B623B2A87DE5C588301EA3E5FC269B373B60724F5E82A6AD147FDE7', 16) + ) + + dB = int('230E18E1BCC88A362FA54E4EA3902009292F7F8033624FD471B5D8ACE49D1' + '2CFABBC19963DAB8E2F1EBA00BFFB29E4D72D13F2224562F405CB80503666B25429', 16) + qB = dB * brainpoolP512r1.G + + self.assertEqual( + qB.x, + int('9D45F66DE5D67E2E6DB6E93A59CE0BB48106097FF78A081DE781CDB31FC' + 'E8CCBAAEA8DD4320C4119F1E9CD437A2EAB3731FA9668AB268D871DEDA55A5473199F', 16) + ) + self.assertEqual( + qB.y, + int('2FDC313095BCDD5FB3A91636F07A959C8E86B5636A1E930E8396049CB48' + '1961D365CC11453A06C719835475B12CB52FC3C383BCE35E27EF194512B71876285FA', 16) + ) + + self.assertEqual((dA * qB).x, (dB * qA).x) + self.assertEqual((dA * qB).y, (dB * qA).y) + + Z = dA * qB + self.assertEqual( + Z.x, + int('A7927098655F1F9976FA50A9D566865DC530331846381C87256BAF322624' + '4B76D36403C024D7BBF0AA0803EAFF405D3D24F11A9B5C0BEF679FE1454B21C4CD1F', 16) + ) + self.assertEqual( + Z.y, + int('7DB71C3DEF63212841C463E881BDCF055523BD368240E6C3143BD8DEF8B3' + 'B3223B95E0F53082FF5E412F4222537A43DF1C6D25729DDB51620A832BE6A26680A2', 16) + ) \ No newline at end of file diff --git a/fastecdsa/tests/test_key_encoding.py b/fastecdsa/tests/test_key_encoding.py new file mode 100644 index 0000000..30ff066 --- /dev/null +++ b/fastecdsa/tests/test_key_encoding.py @@ -0,0 +1,72 @@ +from binascii import hexlify, unhexlify +from unittest import TestCase + +from ..curve import P256, secp192k1, secp256k1 +from ..encoding.sec1 import InvalidSEC1PublicKey, SEC1Encoder +from ..point import Point + + +class TestEncodePublicKey(TestCase): + def test_SEC1_encode_public_key(self): + # 1/ PrivateKey generated using openssl "openssl ecparam -name secp256k1 -genkey -out ec-priv.pem" + # 2/ Printed using "openssl ec -in ec-priv.pem -text -noout" and converted to numeric using "asn1._bytes_to_int" + priv_key = 7002880736699640265110069622773736733141182416793484574964618597954446769264 + pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(secp256k1.G * priv_key)) + pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(secp256k1.G * priv_key, compressed=False)) + # 3/ PublicKey extracted using "openssl ec -in ec-priv.pem -pubout -out ec-pub.pem" + # 4/ Encoding verified using openssl "openssl ec -in ec-pub.pem -pubin -text -noout -conv_form compressed" + self.assertEqual(pubkey_compressed, b'02e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062') + self.assertEqual(pubkey_uncompressed, b'04e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f3583506' + b'23dad76df888abde5ed0cc5af1b83968edffcae5d70bedb24fdc18bb5f79499d0') + # Same with P256 Curve + priv_P256 = 807015861248675637760562792774171551137308512372870683367415858378856470633 + pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(P256.G * priv_P256)) + pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(P256.G * priv_P256, compressed=False)) + self.assertEqual(pubkey_compressed, b'0212c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824c') + self.assertEqual(pubkey_uncompressed, b'0412c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824' + b'c9a7d581bcf2aba680b53cedbade03be62fe95869da04a168a458f369ac6a823e') + # And secp192k1 Curve + priv_secp192k1 = 5345863567856687638748079156318679969014620278806295592453 + pubkey_compressed = hexlify(SEC1Encoder.encode_public_key(secp192k1.G * priv_secp192k1)) + pubkey_uncompressed = hexlify(SEC1Encoder.encode_public_key(secp192k1.G * priv_secp192k1, compressed=False)) + self.assertEqual(pubkey_compressed, b'03a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22d') + self.assertEqual(pubkey_uncompressed, b'04a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22' + b'df07a73819149e8d903aa983e52ab1cff38f0d381f940d361') + + def test_SEC1_decode_public_key(self): + expected_public = Point(x=0xe5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062, + y=0x3dad76df888abde5ed0cc5af1b83968edffcae5d70bedb24fdc18bb5f79499d0, + curve=secp256k1) + public_from_compressed = SEC1Encoder.decode_public_key( + unhexlify(b'02e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062'), secp256k1) + public_from_uncompressed = SEC1Encoder.decode_public_key( + unhexlify(b'04e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f3583506' + b'23dad76df888abde5ed0cc5af1b83968edffcae5d70bedb24fdc18bb5f79499d0'), secp256k1) + # Same values as in "test_SEC1_encode_public_key", verified using openssl + self.assertEqual(public_from_compressed, expected_public) + self.assertEqual(public_from_uncompressed, expected_public) + with self.assertRaises(InvalidSEC1PublicKey) as e: + SEC1Encoder.decode_public_key(b'\x02', secp256k1) # invalid compressed length + self.assertEqual(e.exception.args[0], "A compressed public key must be 33 bytes long") + with self.assertRaises(InvalidSEC1PublicKey) as e: + SEC1Encoder.decode_public_key(b'\x04', secp256k1) # invalid uncompressed length + self.assertEqual(e.exception.args[0], "An uncompressed public key must be 65 bytes long") + with self.assertRaises(InvalidSEC1PublicKey) as e: + # invalid prefix value + SEC1Encoder.decode_public_key( + unhexlify(b'05e5e2c01985aafb6e2c3ad49f3db5ccc54b2e63343af405b521303d0f35835062'), secp256k1) + self.assertEqual(e.exception.args[0], "Wrong key format") + # With P256, same values as in "test_SEC1_encode_public_key", verified using openssl + expected_P256 = Point(x=0x12c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824c, + y=0x9a7d581bcf2aba680b53cedbade03be62fe95869da04a168a458f369ac6a823e, + curve=P256) + public_from_compressed = SEC1Encoder.decode_public_key( + unhexlify(b'0212c9ddf64b0d1f1d91d9bd729abfb880079fa889d66604cc0b78c9cbc271824c'), P256) + self.assertEqual(public_from_compressed, expected_P256) + # With P256, same values as in "test_SEC1_encode_public_key", verified using openssl + expected_secp192k1 = Point(x=0xa3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22d, + y=0xf07a73819149e8d903aa983e52ab1cff38f0d381f940d361, + curve=secp192k1) + public_from_compressed = SEC1Encoder.decode_public_key( + unhexlify(b'03a3bec5fba6d13e51fb55bd88dd097cb9b04f827bc151d22d'), secp192k1) + self.assertEqual(public_from_compressed, expected_secp192k1) \ No newline at end of file diff --git a/fastecdsa/tests/test_key_recovery.py b/fastecdsa/tests/test_key_recovery.py new file mode 100644 index 0000000..59f8005 --- /dev/null +++ b/fastecdsa/tests/test_key_recovery.py @@ -0,0 +1,18 @@ +from hashlib import sha256 +from unittest import TestCase + +from . import CURVES +from ..ecdsa import sign +from ..keys import gen_keypair, get_public_keys_from_sig + + +class TestKeyRecovery(TestCase): + def test_key_recovery(self): + for curve in CURVES: + d, Q = gen_keypair(curve) + msg = 'https://crypto.stackexchange.com/questions/18105/how-does-recovering-the-' \ + 'public-key-from-an-ecdsa-signature-work' + sig = sign(msg, d, curve=curve) + + Qs = get_public_keys_from_sig(sig, msg, curve=curve, hashfunc=sha256) + self.assertTrue(Q in Qs) \ No newline at end of file diff --git a/fastecdsa/tests/test_keygen.py b/fastecdsa/tests/test_keygen.py new file mode 100644 index 0000000..34b3aca --- /dev/null +++ b/fastecdsa/tests/test_keygen.py @@ -0,0 +1,40 @@ +from unittest import TestCase + +from ..keys import gen_private_key + + +class TestKeygen(TestCase): + def test_gen_private_key(self): + class FakeCurve(): + def __init__(self, q): + self.q = q + + class FakeRandom(): + def __init__(self, values=b"\x00"): + self.values = values + self.pos = 0 + + def __call__(self, nb): + result = self.values[self.pos:self.pos + nb] + self.pos += nb + return result + + self.assertEqual(gen_private_key(FakeCurve(2), randfunc=FakeRandom(b"\x00")), 0) + + # 1 byte / 6 bits shaved off + the first try is lower than the order + self.assertEqual(gen_private_key(FakeCurve(2), randfunc=FakeRandom(b"\x40")), 1) + + # 1 byte / 6 bits shaved off + the first try is higher than the order + self.assertEqual(gen_private_key(FakeCurve(2), randfunc=FakeRandom(b"\xc0\x40")), 1) + self.assertEqual(gen_private_key(FakeCurve(2), randfunc=FakeRandom(b"\xc0\x00")), 0) + + # 2 byte / 3 are shaved off, the first try is lower than the order. + self.assertEqual(gen_private_key(FakeCurve(8191), randfunc=FakeRandom(b"\xff\xf0")), 8190) + + # 2 byte / 3 are shaved off + # first try : _bytes_to_int("\xff\xf8") >> 3 == 8191 (too high for order 8191) + # second try : _bytes_to_int("\xff\xf0") >> 3 == 8190 (ok for order 8191) + self.assertEqual(gen_private_key(FakeCurve(8191), randfunc=FakeRandom(b"\xff\xf8\xff\xf0")), 8190) + + # Same but with a different second try value + self.assertEqual(gen_private_key(FakeCurve(8191), randfunc=FakeRandom(b"\xff\xf8\xff\xef")), 8189) \ No newline at end of file diff --git a/fastecdsa/tests/test_nonce_generation.py b/fastecdsa/tests/test_nonce_generation.py new file mode 100644 index 0000000..1cd0429 --- /dev/null +++ b/fastecdsa/tests/test_nonce_generation.py @@ -0,0 +1,31 @@ +from hashlib import sha1, sha224, sha256, sha384, sha512 +from unittest import TestCase + +from ..util import RFC6979 + + +class TestNonceGeneration(TestCase): + def test_rfc_6979(self): + msg = 'sample' + x = 0x09A4D6792295A7F730FC3F2B49CBC0F62E862272F + q = 0x4000000000000000000020108A2E0CC0D99F8A5EF + + expected = 0x09744429FA741D12DE2BE8316E35E84DB9E5DF1CD + nonce = RFC6979(msg, x, q, sha1).gen_nonce() + self.assertTrue(nonce == expected) + + expected = 0x323E7B28BFD64E6082F5B12110AA87BC0D6A6E159 + nonce = RFC6979(msg, x, q, sha224).gen_nonce() + self.assertTrue(nonce == expected) + + expected = 0x23AF4074C90A02B3FE61D286D5C87F425E6BDD81B + nonce = RFC6979(msg, x, q, sha256).gen_nonce() + self.assertTrue(nonce == expected) + + expected = 0x2132ABE0ED518487D3E4FA7FD24F8BED1F29CCFCE + nonce = RFC6979(msg, x, q, sha384).gen_nonce() + self.assertTrue(nonce == expected) + + expected = 0x00BBCC2F39939388FDFE841892537EC7B1FF33AA3 + nonce = RFC6979(msg, x, q, sha512).gen_nonce() + self.assertTrue(nonce == expected) \ No newline at end of file diff --git a/fastecdsa/tests/test_p256_ecdsa.py b/fastecdsa/tests/test_p256_ecdsa.py new file mode 100644 index 0000000..44a051e --- /dev/null +++ b/fastecdsa/tests/test_p256_ecdsa.py @@ -0,0 +1,99 @@ +from hashlib import sha1, sha224, sha256, sha384, sha512 +from unittest import TestCase + +from ..curve import P256 +from ..ecdsa import sign, verify +from ..point import Point + + +class TestP256ECDSA(TestCase): + """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ + + def test_ecdsa_P256_SHA1_sign(self): + d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 + expected = ( + 0x61340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D32, + 0x6D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB, + ) + sig = sign('sample', d, curve=P256, hashfunc=sha1) + self.assertEqual(sig, expected) + + Q = d * P256.G + self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha1)) + + """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ + + def test_ecdsa_P256_SHA224_sign(self): + d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 + expected = ( + 0x53B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F, + 0xB9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C + ) + sig = sign('sample', d, curve=P256, hashfunc=sha224) + self.assertEqual(sig, expected) + + Q = d * P256.G + self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha224)) + + """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ + + def test_ecdsa_P256_SHA2_sign(self): + d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 + expected = ( + 0xEFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716, + 0xF7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8 + ) + sig = sign('sample', d, curve=P256, hashfunc=sha256) + self.assertEqual(sig, expected) + + Q = d * P256.G + self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha256)) + + """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ + + def test_ecdsa_P256_SHA384_sign(self): + d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 + expected = ( + 0x0EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF7719, + 0x4861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954 + ) + sig = sign('sample', d, curve=P256, hashfunc=sha384) + self.assertEqual(sig, expected) + + Q = d * P256.G + self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha384)) + + """ case taken from http://tools.ietf.org/html/rfc6979#appendix-A.2.5 """ + + def test_ecdsa_P256_SHA512_sign(self): + d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 + expected = ( + 0x8496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F00, + 0x2362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE + ) + sig = sign('sample', d, curve=P256, hashfunc=sha512) + self.assertEqual(sig, expected) + + Q = d * P256.G + self.assertTrue(verify(sig, 'sample', Q, curve=P256, hashfunc=sha512)) + + """ case taken from https://www.nsa.gov/ia/_files/ecdsa.pdf """ + + def test_ecdsa_P256_verify(self): + Q = Point( + 0x8101ece47464a6ead70cf69a6e2bd3d88691a3262d22cba4f7635eaff26680a8, + 0xd8a12ba61d599235f67d9cb4d58f1783d3ca43e78f0a5abaa624079936c0c3a9, + curve=P256 + ) + msg = 'This is only a test message. It is 48 bytes long' + sig = ( + 0x7214bc9647160bbd39ff2f80533f5dc6ddd70ddf86bb815661e805d5d4e6f27c, + 0x7d1ff961980f961bdaa3233b6209f4013317d3e3f9e1493592dbeaa1af2bc367 + ) + self.assertTrue(verify(sig, msg, Q, curve=P256, hashfunc=sha256)) + + sig = ( + 0x7214bc9647160bbd39ff2f80533f5dc6ddd70ddf86bb815661e805d5d4e6fbad, + 0x7d1ff961980f961bdaa3233b6209f4013317d3e3f9e1493592dbeaa1af2bc367 + ) + self.assertFalse(verify(sig, msg, Q, curve=P256, hashfunc=sha256)) \ No newline at end of file diff --git a/fastecdsa/tests/test_prime_field_curve_math.py b/fastecdsa/tests/test_prime_field_curve_math.py new file mode 100644 index 0000000..5a96bd2 --- /dev/null +++ b/fastecdsa/tests/test_prime_field_curve_math.py @@ -0,0 +1,153 @@ +from random import choice, randint +from unittest import TestCase + +from . import CURVES +from ..curve import P192, P224, P256, P384, P521, secp256k1 +from ..point import Point + + +class TestPrimeFieldCurve(TestCase): + """ NIST P curve tests taken from https://www.nsa.gov/ia/_files/nist-routines.pdf """ + + def test_P192_arith(self): + S = Point( + 0xd458e7d127ae671b0c330266d246769353a012073e97acf8, + 0x325930500d851f336bddc050cf7fb11b5673a1645086df3b, + curve=P192 + ) + d = 0xa78a236d60baec0c5dd41b33a542463a8255391af64c74ee + expected = Point( + 0x1faee4205a4f669d2d0a8f25e3bcec9a62a6952965bf6d31, + 0x5ff2cdfa508a2581892367087c696f179e7a4d7e8260fb06, + curve=P192 + ) + R = d * S + self.assertEqual(R, expected) + + def test_P224_arith(self): + S = Point( + 0x6eca814ba59a930843dc814edd6c97da95518df3c6fdf16e9a10bb5b, + 0xef4b497f0963bc8b6aec0ca0f259b89cd80994147e05dc6b64d7bf22, + curve=P224 + ) + d = 0xa78ccc30eaca0fcc8e36b2dd6fbb03df06d37f52711e6363aaf1d73b + expected = Point( + 0x96a7625e92a8d72bff1113abdb95777e736a14c6fdaacc392702bca4, + 0x0f8e5702942a3c5e13cd2fd5801915258b43dfadc70d15dbada3ed10, + curve=P224 + ) + R = d * S + self.assertEqual(R, expected) + + def test_P256_arith(self): + S = Point( + 0xde2444bebc8d36e682edd27e0f271508617519b3221a8fa0b77cab3989da97c9, + 0xc093ae7ff36e5380fc01a5aad1e66659702de80f53cec576b6350b243042a256, + curve=P256 + ) + d = 0xc51e4753afdec1e6b6c6a5b992f43f8dd0c7a8933072708b6522468b2ffb06fd + expected = Point( + 0x51d08d5f2d4278882946d88d83c97d11e62becc3cfc18bedacc89ba34eeca03f, + 0x75ee68eb8bf626aa5b673ab51f6e744e06f8fcf8a6c0cf3035beca956a7b41d5, + curve=P256 + ) + R = d * S + self.assertEqual(R, expected) + + def test_P384_arith(self): + S = Point( + int('fba203b81bbd23f2b3be971cc23997e1ae4d89e69cb6f92385dda82768ada415ebab4167459da98e6' + '2b1332d1e73cb0e', 16), + int('5ffedbaefdeba603e7923e06cdb5d0c65b22301429293376d5c6944e3fa6259f162b4788de6987fd5' + '9aed5e4b5285e45', 16), + curve=P384 + ) + d = int('a4ebcae5a665983493ab3e626085a24c104311a761b5a8fdac052ed1f111a5c44f76f45659d2d111a' + '61b5fdd97583480', 16) + expected = Point( + int('e4f77e7ffeb7f0958910e3a680d677a477191df166160ff7ef6bb5261f791aa7b45e3e653d151b95d' + 'ad3d93ca0290ef2', 16), + int('ac7dee41d8c5f4a7d5836960a773cfc1376289d3373f8cf7417b0c6207ac32e913856612fc9ff2e35' + '7eb2ee05cf9667f', 16), + curve=P384 + ) + R = d * S + self.assertEqual(R, expected) + + def test_P521_arith(self): + S = Point( + int('000001d5c693f66c08ed03ad0f031f937443458f601fd098d3d0227b4bf62873af50740b0bb84aa15' + '7fc847bcf8dc16a8b2b8bfd8e2d0a7d39af04b089930ef6dad5c1b4', 16), + int('00000144b7770963c63a39248865ff36b074151eac33549b224af5c8664c54012b818ed037b2b7c1a' + '63ac89ebaa11e07db89fcee5b556e49764ee3fa66ea7ae61ac01823', 16), + curve=P521 + ) + d = int('000001eb7f81785c9629f136a7e8f8c674957109735554111a2a866fa5a166699419bfa9936c78b62' + '653964df0d6da940a695c7294d41b2d6600de6dfcf0edcfc89fdcb1', 16) + expected = Point( + int('00000091b15d09d0ca0353f8f96b93cdb13497b0a4bb582ae9ebefa35eee61bf7b7d041b8ec34c6c0' + '0c0c0671c4ae063318fb75be87af4fe859608c95f0ab4774f8c95bb', 16), + int('00000130f8f8b5e1abb4dd94f6baaf654a2d5810411e77b7423965e0c7fd79ec1ae563c207bd255ee' + '9828eb7a03fed565240d2cc80ddd2cecbb2eb50f0951f75ad87977f', 16), + curve=P521 + ) + R = d * S + self.assertEqual(R, expected) + + def test_secp256k1_arith(self): + # http://crypto.stackexchange.com/a/787/17884 + m = 0xAA5E28D6A97A2479A65527F7290311A3624D4CC0FA1578598EE3C2613BF99522 + expected = Point( + 0x34F9460F0E4F08393D192B3C5133A6BA099AA0AD9FD54EBCCFACDFA239FF49C6, + 0x0B71EA9BD730FD8923F6D25A7A91E7DD7728A960686CB5A901BB419E0F2CA232, + curve=secp256k1 + ) + R = m * secp256k1.G + self.assertEqual(R, expected) + + m = 0x7E2B897B8CEBC6361663AD410835639826D590F393D90A9538881735256DFAE3 + expected = Point( + 0xD74BF844B0862475103D96A611CF2D898447E288D34B360BC885CB8CE7C00575, + 0x131C670D414C4546B88AC3FF664611B1C38CEB1C21D76369D7A7A0969D61D97D, + curve=secp256k1 + ) + R = m * secp256k1.G + self.assertEqual(R, expected) + + m = 0x6461E6DF0FE7DFD05329F41BF771B86578143D4DD1F7866FB4CA7E97C5FA945D + expected = Point( + 0xE8AECC370AEDD953483719A116711963CE201AC3EB21D3F3257BB48668C6A72F, + 0xC25CAF2F0EBA1DDB2F0F3F47866299EF907867B7D27E95B3873BF98397B24EE1, + curve=secp256k1 + ) + R = m * secp256k1.G + self.assertEqual(R, expected) + + m = 0x376A3A2CDCD12581EFFF13EE4AD44C4044B8A0524C42422A7E1E181E4DEECCEC + expected = Point( + 0x14890E61FCD4B0BD92E5B36C81372CA6FED471EF3AA60A3E415EE4FE987DABA1, + 0x297B858D9F752AB42D3BCA67EE0EB6DCD1C2B7B0DBE23397E66ADC272263F982, + curve=secp256k1 + ) + R = m * secp256k1.G + self.assertEqual(R, expected) + + m = 0x1B22644A7BE026548810C378D0B2994EEFA6D2B9881803CB02CEFF865287D1B9 + expected = Point( + 0xF73C65EAD01C5126F28F442D087689BFA08E12763E0CEC1D35B01751FD735ED3, + 0xF449A8376906482A84ED01479BD18882B919C140D638307F0C0934BA12590BDE, + curve=secp256k1 + ) + R = m * secp256k1.G + self.assertEqual(R, expected) + + def test_arbitrary_arithmetic(self): + for _ in range(100): + curve = choice(CURVES) + a, b = randint(0, curve.q), randint(0, curve.q) + c = (a + b) % curve.q + P, Q = a * curve.G, b * curve.G + R = c * curve.G + pq_sum, qp_sum = P + Q, Q + P + self.assertTrue(pq_sum == qp_sum) + self.assertTrue(qp_sum == R) diff --git a/fastecdsa/tests/test_rfc6979_ecdsa.py b/fastecdsa/tests/test_rfc6979_ecdsa.py new file mode 100644 index 0000000..698fd4a --- /dev/null +++ b/fastecdsa/tests/test_rfc6979_ecdsa.py @@ -0,0 +1,130 @@ +from hashlib import sha1, sha224, sha256, sha384, sha512 +from re import findall, DOTALL +from six.moves.urllib.request import urlopen +from unittest import TestCase + +from ..curve import P192, P224, P256, P384, P521 +from ..ecdsa import sign +from ..util import RFC6979 + + +class TestRFC6979ECDSA(TestCase): + @classmethod + def setUpClass(cls): + cls.rfc6979_text = urlopen('https://tools.ietf.org/rfc/rfc6979.txt').read().decode() + cls.hash_lookup = { + '1': sha1, + '224': sha224, + '256': sha256, + '384': sha384, + '512': sha512 + } + + def test_p192_rfc6979_ecdsa(self): + curve_tests = findall(r'curve: NIST P-192(.*)curve: NIST P-224', self.rfc6979_text, flags=DOTALL)[0] + + q = int(findall(r'q = ([0-9A-F]*)', curve_tests)[0], 16) + x = int(findall(r'x = ([0-9A-F]*)', curve_tests)[0], 16) + + test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ + r'\s*k = ([0-9A-F]*)\n' \ + r'\s*r = ([0-9A-F]*)\n' \ + r'\s*s = ([0-9A-F]*)\n' + + for test in findall(test_regex, curve_tests): + h = self.hash_lookup[test[0]] + msg = test[1] + k = int(test[2], 16) + r = int(test[3], 16) + s = int(test[4], 16) + + self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) + self.assertEqual((r, s), sign(msg, x, curve=P192, hashfunc=h)) + + def test_p224_rfc6979_ecdsa(self): + curve_tests = findall(r'curve: NIST P-224(.*)curve: NIST P-256', self.rfc6979_text, flags=DOTALL)[0] + + q = int(findall(r'q = ([0-9A-F]*)', curve_tests)[0], 16) + x = int(findall(r'x = ([0-9A-F]*)', curve_tests)[0], 16) + + test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ + r'\s*k = ([0-9A-F]*)\n' \ + r'\s*r = ([0-9A-F]*)\n' \ + r'\s*s = ([0-9A-F]*)\n' + + for test in findall(test_regex, curve_tests): + h = self.hash_lookup[test[0]] + msg = test[1] + k = int(test[2], 16) + r = int(test[3], 16) + s = int(test[4], 16) + + self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) + self.assertEqual((r, s), sign(msg, x, curve=P224, hashfunc=h)) + + def test_p256_rfc6979_ecdsa(self): + curve_tests = findall(r'curve: NIST P-256(.*)curve: NIST P-384', self.rfc6979_text, flags=DOTALL)[0] + + q = int(findall(r'q = ([0-9A-F]*)', curve_tests)[0], 16) + x = int(findall(r'x = ([0-9A-F]*)', curve_tests)[0], 16) + + test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ + r'\s*k = ([0-9A-F]*)\n' \ + r'\s*r = ([0-9A-F]*)\n' \ + r'\s*s = ([0-9A-F]*)\n' + + for test in findall(test_regex, curve_tests): + h = self.hash_lookup[test[0]] + msg = test[1] + k = int(test[2], 16) + r = int(test[3], 16) + s = int(test[4], 16) + + self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) + self.assertEqual((r, s), sign(msg, x, curve=P256, hashfunc=h)) + + def test_p384_rfc6979_ecdsa(self): + curve_tests = findall(r'curve: NIST P-384(.*)curve: NIST P-521', self.rfc6979_text, flags=DOTALL)[0] + + q_parts = findall(r'q = ([0-9A-F]*)\n\s*([0-9A-F]*)', curve_tests)[0] + q = int(q_parts[0] + q_parts[1], 16) + x_parts = findall(r'x = ([0-9A-F]*)\n\s*([0-9A-F]*)', curve_tests)[0] + x = int(x_parts[0] + x_parts[1], 16) + + test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ + r'\s*k = ([0-9A-F]*)\n\s*([0-9A-F]*)\n' \ + r'\s*r = ([0-9A-F]*)\n\s*([0-9A-F]*)\n' \ + r'\s*s = ([0-9A-F]*)\n\s*([0-9A-F]*)\n' + + for test in findall(test_regex, curve_tests): + h = self.hash_lookup[test[0]] + msg = test[1] + k = int(test[2] + test[3], 16) + r = int(test[4] + test[5], 16) + s = int(test[6] + test[7], 16) + + self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) + self.assertEqual((r, s), sign(msg, x, curve=P384, hashfunc=h)) + + def test_p521_rfc6979_ecdsa(self): + curve_tests = findall(r'curve: NIST P-521(.*)curve: NIST K-163', self.rfc6979_text, flags=DOTALL)[0] + + q_parts = findall(r'q = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)', curve_tests)[0] + q = int(q_parts[0] + q_parts[1] + q_parts[2], 16) + x_parts = findall(r'x = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)', curve_tests)[0] + x = int(x_parts[0] + x_parts[1] + x_parts[2], 16) + + test_regex = r'With SHA-(\d+), message = "([a-zA-Z]*)":\n' \ + r'\s*k = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)\n' \ + r'\s*r = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)\n' \ + r'\s*s = ([0-9A-F]*)\n\s*([0-9A-F]*)\n\s*([0-9A-F]*)\n' + + for test in findall(test_regex, curve_tests): + h = self.hash_lookup[test[0]] + msg = test[1] + k = int(test[2] + test[3] + test[4], 16) + r = int(test[5] + test[6] + test[7], 16) + s = int(test[8] + test[9] + test[10], 16) + + self.assertEqual(k, RFC6979(msg, x, q, h).gen_nonce()) + self.assertEqual((r, s), sign(msg, x, curve=P521, hashfunc=h)) diff --git a/fastecdsa/tests/test_signature_encoding.py b/fastecdsa/tests/test_signature_encoding.py new file mode 100644 index 0000000..85f221e --- /dev/null +++ b/fastecdsa/tests/test_signature_encoding.py @@ -0,0 +1,53 @@ +from unittest import TestCase + +from ..encoding.der import DEREncoder, InvalidDerSignature + + +class TestSignatureEncoding(TestCase): + def test_encode_der_signature(self): + self.assertEqual(DEREncoder.encode_signature(r=1, s=2), b"\x30" # SEQUENCE + b"\x06" # Length of Sequence + b"\x02" # INTEGER + b"\x01" # Length of r + b"\x01" # r + b"\x02" # INTEGER + b"\x01" # Length of s + b"\x02") # s + + # Check that we add a zero byte when the number's highest bit is set + self.assertEqual(DEREncoder.encode_signature(r=128, s=128), + b"0\x08\x02\x02\x00\x80\x02\x02\x00\x80") + + def test_decode_der_signature(self): + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"") # length to shot + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x31\x06\x02\x01\x01\x02\x01\x02") # invalid SEQUENCE marker + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x07\x02\x01\x01\x02\x01\x02") # invalid length + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x06\x02\x03\x01\x02\x01\x02") # invalid length of r + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x03\x01\x02") # invalid length of s + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x06\x03\x01\x01\x02\x01\x02") # invalid INTEGER marker for r + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x06\x02\x00\x02\x01\x02") # length of r is 0 + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x06\x02\x01\x81\x02\x01\x02") # value of r is negative + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x07\x02\x02\x00\x01\x02\x01\x02") # value of r starts with a zero byte + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x03\x01\x02") # invalid INTEGER marker for s + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x00") # value of s is 0 + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x01\x81") # value of s is negative + with self.assertRaises(InvalidDerSignature): + DEREncoder.decode_signature(b"\x30\x07\x02\x01\x01\x02\x02\x00\x02") # value of s starts with a zero byte + + self.assertEqual(DEREncoder.decode_signature(b"\x30\x06\x02\x01\x01\x02\x01\x02"), (1, 2)) + self.assertEqual(DEREncoder.decode_signature(b"0\x08\x02\x02\x00\x80\x02\x02\x00\x80"), + (128, 128)) # verify zero bytes + self.assertEqual(DEREncoder.decode_signature(b"0\x08\x02\x02\x03\xE8\x02\x02\x03\xE8"), + (1000, 1000)) # verify byte order \ No newline at end of file diff --git a/fastecdsa/tests/test_whitespace_parsing.py b/fastecdsa/tests/test_whitespace_parsing.py new file mode 100644 index 0000000..4b1cf2b --- /dev/null +++ b/fastecdsa/tests/test_whitespace_parsing.py @@ -0,0 +1,32 @@ +from unittest import TestCase + +from ..curve import secp256k1 +from ..encoding.pem import PEMEncoder +from ..point import Point + + +class TestWhitespaceParsing(TestCase): + d = 0x3007df8e8bed6c3592a10f9d0495173dcecbc99a40e8c88b47d7d590f8b608dd + x = 0x2c071354794f38ff439eff52f8b475b0def34a210aa3b88a63f2a7295d35f41f + y = 0xb0f35d4f45d95657fa37d6a5431be9441a95638fc710884f43fff7938428056e + Q = Point(x, y, curve=secp256k1) + + def test_leading_newline(self): + keypem = "\n-----BEGIN EC PRIVATE KEY-----\n" \ + "MHQCAQEEIDAH346L7Ww1kqEPnQSVFz3Oy8maQOjIi0fX1ZD4tgjdoAcGBSuBBAAK\n" \ + "oUQDQgAELAcTVHlPOP9Dnv9S+LR1sN7zSiEKo7iKY/KnKV019B+w811PRdlWV/o3\n" \ + "1qVDG+lEGpVjj8cQiE9D//eThCgFbg==\n" \ + "-----END EC PRIVATE KEY-----\n" + key, pubkey = PEMEncoder.decode_private_key(keypem) + self.assertEqual(key, self.d) + self.assertEqual(pubkey, self.Q) + + def test_leading_trailing_newlines(self): + keypem = "\n\n\n\n-----BEGIN EC PRIVATE KEY-----\n" \ + "MHQCAQEEIDAH346L7Ww1kqEPnQSVFz3Oy8maQOjIi0fX1ZD4tgjdoAcGBSuBBAAK\n" \ + "oUQDQgAELAcTVHlPOP9Dnv9S+LR1sN7zSiEKo7iKY/KnKV019B+w811PRdlWV/o3\n" \ + "1qVDG+lEGpVjj8cQiE9D//eThCgFbg==\n" \ + "-----END EC PRIVATE KEY-----\n\n\n\n" + key, pubkey = PEMEncoder.decode_private_key(keypem) + self.assertEqual(key, self.d) + self.assertEqual(pubkey, self.Q) \ No newline at end of file diff --git a/setup.py b/setup.py index 04416c7..ddd3162 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,6 @@ from setuptools import find_packages, setup, Extension, Command -class TestCommand(Command): - user_options = [] - description = "Run all tests" - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - from subprocess import call - call(['python', '-m', 'fastecdsa.test']) - - class BenchmarkCommand(Command): user_options = [] description = "Benchmark this package" @@ -48,18 +33,8 @@ def run(self): ) setup( - name='fastecdsa', - version='1.7.3', author='Anton Kueltz', author_email='kueltz.anton@gmail.com', - license='CC0 1.0 Universal (CC0 1.0) Public Domain Dedication', - keywords='elliptic curve cryptography ecdsa ecc', - description='Fast elliptic curve digital signatures', - long_description=''.join(open('README.rst', 'r').readlines()), - url='https://github.com/AntonKueltz/fastecdsa', - packages=find_packages(), - ext_modules=[curvemath, _ecdsa], - cmdclass={'test': TestCommand, 'benchmark': BenchmarkCommand}, classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', @@ -70,7 +45,20 @@ def run(self): 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Operating System :: MacOS :: MacOS X', 'Operating System :: POSIX :: Linux', ], + cmdclass={'benchmark': BenchmarkCommand}, + description='Fast elliptic curve digital signatures', + ext_modules=[curvemath, _ecdsa], + install_requires=['six'], + keywords='elliptic curve cryptography ecdsa ecc', + license='CC0 1.0 Universal (CC0 1.0) Public Domain Dedication', + long_description=''.join(open('README.rst', 'r').readlines()), + name='fastecdsa', + packages=find_packages(), + tests_require=['pytest', 'pytest-cov'], + url='https://github.com/AntonKueltz/fastecdsa', + version='1.7.4', )