├── src ├── tests │ ├── __init__.py │ ├── test_issues.py │ ├── test_helpers.py │ ├── test_main.py │ └── test_ec.py ├── _version.py └── __init__.py ├── MANIFEST.in ├── setup.cfg ├── .gitignore ├── .travis.yml ├── CHANGES.rst ├── setup.py ├── README.rst └── LICENSE /src/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.5.1.dev0' 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include CHANGES.rst 3 | include LICENSE 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [zest.releaser] 2 | python-file-with-version = src/_version.py 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyo 2 | *.pyc 3 | *.swp 4 | /*.egg-info 5 | /build 6 | /.eggs 7 | /dist 8 | MANIFEST 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial # required for Python >= 3.7 2 | language: python 3 | python: 4 | - "2.7" 5 | - "3.4" 6 | - "3.5" 7 | - "3.6" 8 | - "3.7" 9 | - "3.8" 10 | install: 11 | - "sudo apt-get update -qq" 12 | - "sudo apt-get install -y libgmp-dev libmpfr-dev libmpc-dev" 13 | - "sudo pip install flake8" 14 | - "python setup.py install" 15 | script: 16 | - "python setup.py test" 17 | - "flake8 --ignore=E741,W503,E704,W504" 18 | -------------------------------------------------------------------------------- /src/tests/test_issues.py: -------------------------------------------------------------------------------- 1 | """ Tests for specific issues that were reported on Github. 2 | 3 | See https://github.com/bwesterb/py-seccure/issues """ 4 | 5 | import seccure 6 | 7 | import unittest 8 | 9 | import gmpy2 10 | 11 | 12 | class TestIssues(unittest.TestCase): 13 | 14 | def test_issue5(self): 15 | self.assertEqual(repr(seccure.passphrase_to_pubkey(b'my private key')), 16 | 'i^H0qi|J&$coR5MFpR*Vn>') 17 | 18 | def test_issue10(self): 19 | for curvename in seccure.curves: 20 | curve = seccure.Curve.by_name(curvename) 21 | for pt in (curve.base, curve.base * gmpy2.mpz(1337)): 22 | self.assertEqual(pt + pt, pt * gmpy2.mpz(2)) 23 | 24 | 25 | if __name__ == '__main__': 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | py-seccure Changelog 2 | ==================== 3 | 4 | 0.5.1 (unreleased) 5 | ------------------ 6 | 7 | - Nothing changed yet. 8 | 9 | 10 | 0.5.0 (2020-02-04) 11 | ------------------ 12 | 13 | - Switch from PyCrypto to pycryptodome 14 | - Add support for Python 3.8 15 | 16 | 17 | 0.4.1 (2020-02-04) 18 | ------------------ 19 | 20 | - Fix `generate_keypair`. #19 (Thanks-to: @Dschorim) 21 | 22 | 23 | 0.4.0 (2019-07-28) 24 | ------------------ 25 | 26 | - Use gmpy2 instead of gmpy 27 | 28 | 29 | 0.3.3 (2019-04-28) 30 | ------------------ 31 | 32 | - Bow to flake8 33 | - py-seccure did not pad a signature with zeroes as seccure expected. 34 | This caused some signatures generated by py-seccure not being accepted 35 | by seccure. This has been fixed. Old unpadded signatures are still 36 | accepted by py-seccure to keep backwards compatibility. 37 | See #16 (Thanks-to: Tanner Collin) 38 | - Add support for Python 3.7 39 | - Drop support for Python 2.6 & Python 3.3 40 | 41 | 42 | 0.3.2 (2016-08-07) 43 | ------------------ 44 | 45 | - Add ``generate_keypair`` 46 | - Fix doubling of affine points. #10 (Thanks-to: Joep Peeters) 47 | 48 | 49 | 0.3.1.3 (2016-01-03) 50 | -------------------- 51 | 52 | - Small fix for Python 2.6 53 | 54 | 55 | 0.3.1.2 (2015-12-29) 56 | -------------------- 57 | 58 | - Use static __version__ attribute. 59 | - Use RST for README 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import os.path 5 | 6 | from setuptools import setup 7 | 8 | install_requires = [ 9 | 'pycryptodome', 10 | 'gmpy2 >=2', 11 | 'six >=1.2', 12 | ] 13 | 14 | base_path = os.path.dirname(os.path.abspath(__file__)) 15 | 16 | with open(os.path.join(base_path, 'src', '_version.py')) as f: 17 | exec(f.read()) 18 | 19 | with open(os.path.join(base_path, 'README.rst')) as f: 20 | with open(os.path.join(base_path, 'CHANGES.rst')) as g: 21 | long_description = '{0}\n{1}'.format(f.read(), g.read()) 22 | 23 | setup( 24 | name='seccure', 25 | version=__version__, # noqa: F821 26 | description='SECCURE compatible Elliptic Curve cryptography', 27 | long_description=long_description, 28 | author='Bas Westerbaan', 29 | author_email='bas@westerbaan.name', 30 | url='http://github.com/bwesterb/py-seccure', 31 | packages=['seccure', 'seccure.tests'], 32 | package_dir={'seccure': 'src'}, 33 | license='LGPL 3.0', 34 | zip_safe=True, 35 | install_requires=install_requires, 36 | classifiers=[ 37 | 'Development Status :: 4 - Beta', 38 | 'License :: OSI Approved ::' 39 | ' GNU Lesser General Public License v3 (LGPLv3)', 40 | 'Operating System :: POSIX', 41 | 'Topic :: Security', 42 | 'Programming Language :: Python :: 2.7', 43 | 'Programming Language :: Python :: 3.4', 44 | 'Programming Language :: Python :: 3.5', 45 | 'Programming Language :: Python :: 3.6', 46 | 'Programming Language :: Python :: 3.7', 47 | 'Programming Language :: Python :: 3.8', 48 | ], 49 | test_suite='seccure.tests', 50 | ), 51 | -------------------------------------------------------------------------------- /src/tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import binascii 3 | 4 | from six.moves import xrange 5 | 6 | import six 7 | import gmpy2 8 | import seccure 9 | 10 | 11 | class TestHelpers(unittest.TestCase): 12 | 13 | def test_serialize_number_binary(self): 14 | for number, string in ( 15 | (1231234, b'12c982'), 16 | (0, b''), 17 | (255, b'ff'), 18 | (1231231231232, b'011eab19a500'), 19 | (13371337, b'cc07c9'), 20 | (256, b'0100')): 21 | self.assertEqual( 22 | binascii.hexlify( 23 | seccure.serialize_number(number)), 24 | string) 25 | self.assertEqual(binascii.hexlify(seccure.serialize_number( 26 | gmpy2.mpz(number))), string) 27 | self.assertEqual(seccure.deserialize_number( 28 | binascii.unhexlify(string)), number) 29 | for number, string in ( 30 | (1231234, b'0012c982'), 31 | (0, b'00000000'), 32 | (255, b'000000ff'), 33 | (13371337, b'00cc07c9'), 34 | (256, b'00000100')): 35 | self.assertEqual(binascii.hexlify( 36 | seccure.serialize_number(number, outlen=4)), string) 37 | self.assertEqual(binascii.hexlify( 38 | seccure.serialize_number(gmpy2.mpz(number), 39 | outlen=4)), string) 40 | self.assertEqual(seccure.deserialize_number( 41 | binascii.unhexlify(string)), number) 42 | 43 | def test_deserialize_number_compact_unicode(self): 44 | for number, string in ( 45 | (1231234, '#c!E'), 46 | (0, ''), 47 | (255, '$p'), 48 | (1231231231232, '$?Pvfdc'), 49 | (13371337, '5AkH'), 50 | (90, '#!'), 51 | (89, '~'), 52 | (256, '$q')): 53 | self.assertEqual(seccure.deserialize_number( 54 | six.u(string), fmt=seccure.SER_COMPACT), number) 55 | 56 | def test_serialize_number_compact(self): 57 | for number, string in ( 58 | (1231234, b'#c!E'), 59 | (0, b''), 60 | (255, b'$p'), 61 | (1231231231232, b'$?Pvfdc'), 62 | (13371337, b'5AkH'), 63 | (90, b'#!'), 64 | (89, b'~'), 65 | (256, b'$q')): 66 | self.assertEqual( 67 | seccure.serialize_number( 68 | number, 69 | fmt=seccure.SER_COMPACT), 70 | string) 71 | self.assertEqual( 72 | seccure.serialize_number( 73 | gmpy2.mpz(number), 74 | fmt=seccure.SER_COMPACT), 75 | string) 76 | self.assertEqual(seccure.deserialize_number( 77 | string, fmt=seccure.SER_COMPACT), number) 78 | for number, string in ( 79 | (1231234, b'!!!!!!#c!E'), 80 | (0, b'!!!!!!!!!!'), 81 | (255, b'!!!!!!!!$p'), 82 | (1231231231232, b'!!!$?Pvfdc'), 83 | (13371337, b'!!!!!!5AkH'), 84 | (90, b'!!!!!!!!#!'), 85 | (89, b'!!!!!!!!!~'), 86 | (256, b'!!!!!!!!$q')): 87 | self.assertEqual( 88 | seccure.serialize_number(number, outlen=10, 89 | fmt=seccure.SER_COMPACT), string) 90 | self.assertEqual( 91 | seccure.deserialize_number( 92 | string, 93 | fmt=seccure.SER_COMPACT), 94 | number) 95 | 96 | def test_mod_issquare(self): 97 | for p in (7, 37, 1489): 98 | p = gmpy2.mpz(p) 99 | had = set() 100 | for n in xrange(p - 1): 101 | sq = (n * n) % p 102 | if sq in had: 103 | continue 104 | had.add(sq) 105 | self.assertTrue(seccure.mod_issquare(sq, p)) 106 | for n in xrange(p - 1): 107 | if n not in had: 108 | self.assertFalse(seccure.mod_issquare(n, p)) 109 | 110 | def test_mod_root(self): 111 | for p in (7, 37, 1489): 112 | p = gmpy2.mpz(p) 113 | for n in xrange(p - 1): 114 | if seccure.mod_issquare(n, p): 115 | self.assertEqual((seccure.mod_root(n, p) ** 2) % p, n) 116 | 117 | 118 | if __name__ == '__main__': 119 | unittest.main() 120 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | py-seccure 2 | ========== 3 | 4 | Simple Elliptic Curve Cryptography for Python compatible with the 5 | excellent `SECCURE`_ command 6 | line utility (version 0.5). It's licensed under LGPLv3. See LICENSE. 7 | 8 | **Do not use ``py-seccure`` when its operation can be timed by an 9 | attacker.** See `timing attack`_. 10 | 11 | Usage 12 | ----- 13 | 14 | Public key from private 15 | ~~~~~~~~~~~~~~~~~~~~~~~ 16 | 17 | To get the public key from the private, you can use the original 18 | commandline utility: 19 | 20 | :: 21 | 22 | $ seccure-key 23 | Assuming curve p160. 24 | Enter private key: my private key 25 | The public key is: 8W;>i^H0qi|J&$coR5MFpR*Vn 26 | 27 | In Python: 28 | 29 | .. code:: python 30 | 31 | >>> import seccure 32 | >>> str(seccure.passphrase_to_pubkey(b'my private key')) 33 | '8W;>i^H0qi|J&$coR5MFpR*Vn' 34 | 35 | Encrypting a string 36 | ~~~~~~~~~~~~~~~~~~~ 37 | 38 | To encrypt for a public key, one would use the original commandline 39 | utility as follows. 40 | 41 | :: 42 | 43 | $ seccure-encrypt -o private.msg '8W;>i^H0qi|J&$coR5MFpR*Vn' 44 | Assuming MAC length of 80 bits. 45 | Go ahead and type your message ... 46 | This is a very very secret message! 47 | ^D 48 | 49 | In Python: 50 | 51 | .. code:: python 52 | 53 | >>> ciphertext = seccure.encrypt(b'This is a very secret message\n', b'8W;>i^H0qi|J&$coR5MFpR*Vn') 54 | >>> ciphertext 55 | '\x00\x146\x17\xe9\xc1\x1a\x7fkX\xec\xa0n,h\xb4\xd0\x98\xeaO[\xf8\xfa\x85\xaa\xb37!\xf0j\x0e\xd4\xd0\x8b\xfe}\x8a\xd2+\xf2\xceu\x07\x90K2E\x12\x1d\xf1\xd8\x8f\xc6\x91\t>> seccure.encrypt_file('/path/to/file', '/path/to/file.enc', '8W;>i^H0qi|J&$coR5MFpR*Vn') 62 | 63 | Decrypting 64 | ~~~~~~~~~~ 65 | 66 | To decrypt the message with the original utility: 67 | 68 | :: 69 | 70 | $ seccure-decrypt -i private.msg 71 | Assuming MAC length of 80 bits. 72 | Assuming curve p160. 73 | Enter private key: my private key 74 | This is a very very secret message! 75 | Integrity check successful, message unforged! 76 | 77 | In Python: 78 | 79 | .. code:: python 80 | 81 | >>> seccure.decrypt(ciphertext, b'my private key') 82 | 'This is a very secret message\n' 83 | 84 | And to decrypt a file: 85 | 86 | .. code:: python 87 | 88 | >>> seccure.decrypt_file('/path/to/file.enc', '/path/to/file', b'my private key') 89 | 90 | Creating a signature 91 | ~~~~~~~~~~~~~~~~~~~~ 92 | 93 | To create a signature: 94 | 95 | :: 96 | 97 | $ seccure-sign 98 | Assuming curve p160. 99 | Enter private key: my private key 100 | Go ahead and type your message ... 101 | This message will be signed 102 | ^D 103 | Signature: $HPI?t(I*1vAYsl$|%21WXND=6Br*[>k(OR9B!GOwHqL0s+3Uq 104 | 105 | In Python: 106 | 107 | .. code:: python 108 | 109 | >>> seccure.sign(b'This message will be signed\n', b'my private key') 110 | '$HPI?t(I*1vAYsl$|%21WXND=6Br*[>k(OR9B!GOwHqL0s+3Uq' 111 | 112 | Verifying a signature 113 | ~~~~~~~~~~~~~~~~~~~~~ 114 | 115 | To verify a signature: 116 | 117 | :: 118 | 119 | $ seccure-verify '8W;>i^H0qi|J&$coR5MFpR*Vn' '$HPI?t(I*1vAYsl$|%21WXND=6Br*[>k(OR9B!GOwHqL0s+3Uq' 120 | Go ahead and type your message ... 121 | This message will be signed 122 | ^D 123 | Signature successfully verified! 124 | 125 | In Python: 126 | 127 | .. code:: python 128 | 129 | >>> seccure.verify(b'This message will be signed\n', b'$HPI?t(I*1vAYsl$|%21WXND=6Br*[>k(OR9B!GOwHqL0s+3Uq', b'8W;>i^H0qi|J&$coR5MFpR*Vn') 130 | True 131 | 132 | Installation 133 | ------------ 134 | 135 | On Debian Wheezy 136 | ~~~~~~~~~~~~~~~~ 137 | 138 | :: 139 | 140 | $ apt-get install libgmp3-dev build-essential python-dev python-pip libmpfr-dev libmpc-dev 141 | $ pip install seccure 142 | 143 | On Ubuntu 144 | ~~~~~~~~~ 145 | 146 | :: 147 | 148 | $ apt-get install libgmp-dev build-essential python-dev python-pip libmpfr-dev libmpc-dev 149 | $ pip install seccure 150 | 151 | On Mac with MacPorts 152 | ~~~~~~~~~~~~~~~~~~~~ 153 | 154 | :: 155 | 156 | $ port install py27-gmpy2 157 | $ pip install seccure 158 | 159 | Please contribute! 160 | ------------------ 161 | 162 | To help out, you could: 163 | 164 | 1. Test and report any bugs or other difficulties. 165 | 2. Implement missing features, such as ``seccure-dh``, 166 | ``seccure-veridec`` and ``seccure-signcrypt``. 167 | 3. Package py-seccure (or the original SECCURE itself) for your 168 | platform. 169 | 4. Write more unit tests. 170 | 171 | .. image:: https://travis-ci.org/bwesterb/py-seccure.png 172 | :target: https://travis-ci.org/py-seccure/py-seccure 173 | 174 | .. _SECCURE: http://point-at-infinity.org/seccure/ 175 | .. _timing attack: http://en.wikipedia.org/wiki/Timing_attack 176 | -------------------------------------------------------------------------------- /src/tests/test_main.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import tempfile 3 | 4 | import seccure 5 | import six 6 | 7 | 8 | class TestMain(unittest.TestCase): 9 | 10 | def test_passphrase_to_pubkey(self): 11 | self.assertEqual(str(seccure.passphrase_to_pubkey(b'test')), 12 | '*jMVCU^[QC&q*v_8C1ZAFBAgD') 13 | self.assertEqual(str(seccure.passphrase_to_pubkey(b'my private key')), 14 | '8W;>i^H0qi|J&$coR5MFpR*Vn') 15 | self.assertRaises(ValueError, seccure.passphrase_to_pubkey, 16 | six.u('test')) 17 | for curvename in seccure.curves: 18 | seccure.passphrase_to_pubkey(b'test', curve=curvename) 19 | 20 | def test_generate_keypair(self): 21 | for curvename in seccure.curves: 22 | privkey, pubkey = seccure.generate_keypair(curve=curvename) 23 | self.assertEqual( 24 | str(seccure.passphrase_to_pubkey( 25 | privkey, 26 | curve=curvename, 27 | )), pubkey) 28 | 29 | def test_encrypt(self): 30 | msg = b'My private message' 31 | pw = b'my private key' 32 | pk = str(seccure.passphrase_to_pubkey(pw)) 33 | self.assertEqual(seccure.decrypt(seccure.encrypt(msg, pk), pw), msg) 34 | for c in seccure.curves: 35 | self.assertEqual( 36 | seccure.decrypt( 37 | seccure.encrypt( 38 | msg, 39 | str(seccure.passphrase_to_pubkey(pw, curve=c)), 40 | curve=c), 41 | pw, curve=c), 42 | msg) 43 | 44 | def test_verify(self): 45 | msg = b'This message will be signed\n' 46 | sig = b'$HPI?t(I*1vAYsl$|%21WXND=6Br*[>k(OR9B!GOwHqL0s+3Uq' 47 | pubkey = '8W;>i^H0qi|J&$coR5MFpR*Vn' 48 | self.assertTrue(seccure.verify(msg, sig, pubkey)) 49 | 50 | def test_sign(self): 51 | msg = b'This message will be signed\n' 52 | pw = b'my private key' 53 | self.assertEqual(seccure.sign(msg, pw), 54 | b'$HPI?t(I*1vAYsl$|%21WXND=6Br*[>k(OR9B!GOwHqL0s+3Uq') 55 | 56 | def test_sign_length(self): 57 | msg = b'ttrMWDqBxjC8UAl8X4TRDSSpd1IyYMh4\n' 58 | pw = b'my private key' 59 | self.assertEqual(seccure.sign(msg, pw), 60 | b'!!!5{LV[=|t~46wS2y 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | """ Elliptic Curve cryptography compatible with SECCURE: 2 | http://point-at-infinity.org/seccure/ """ 3 | 4 | import hmac 5 | import hashlib 6 | import logging 7 | import binascii 8 | import contextlib 9 | import collections 10 | 11 | from ._version import __version__ # noqa: F401 12 | 13 | # pycryptodome 14 | import Crypto.Util.Counter 15 | import Crypto.Cipher.AES 16 | import Crypto.Random.random 17 | 18 | # gmpy 19 | import gmpy2 20 | 21 | # six 22 | import six 23 | 24 | # TODO replace with six.byte2int, when it is released 25 | if six.PY3: 26 | from io import BytesIO as BytesIO 27 | 28 | def byte2int(b): return b 29 | 30 | def stringlike(x): return isinstance(x, (str, bytes)) 31 | else: 32 | from cStringIO import StringIO as BytesIO 33 | 34 | def byte2int(b): return ord(b) 35 | 36 | def stringlike(x): return isinstance(x, basestring) 37 | 38 | l = logging.getLogger(__name__) 39 | 40 | 41 | class IntegrityError(ValueError): 42 | pass 43 | 44 | 45 | # Serialization of numbers 46 | # ######################################################### 47 | SER_COMPACT = 0 48 | SER_BINARY = 1 49 | 50 | COMPACT_DIGITS = (b'!#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ' 51 | b'[]^_abcdefghijklmnopqrstuvwxyz{|}~') 52 | R_COMPACT_DIGITS = {} # TODO is a tuple/list faster? 53 | for i, c in enumerate(COMPACT_DIGITS): 54 | R_COMPACT_DIGITS[c] = i 55 | 56 | 57 | def serialize_number(x, fmt=SER_BINARY, outlen=None): 58 | """ Serializes `x' to a string of length `outlen' in format `fmt' """ 59 | ret = b'' 60 | if fmt == SER_BINARY: 61 | while x: 62 | x, r = divmod(x, 256) 63 | ret = six.int2byte(int(r)) + ret 64 | if outlen is not None: 65 | assert len(ret) <= outlen 66 | ret = ret.rjust(outlen, b'\0') 67 | return ret 68 | assert fmt == SER_COMPACT 69 | while x: 70 | x, r = divmod(x, len(COMPACT_DIGITS)) 71 | ret = COMPACT_DIGITS[r:r + 1] + ret 72 | if outlen is not None: 73 | assert len(ret) <= outlen 74 | ret = ret.rjust(outlen, COMPACT_DIGITS[0:1]) 75 | return ret 76 | 77 | 78 | def deserialize_number(s, fmt=SER_BINARY): 79 | """ Deserializes a number from a string `s' in format `fmt' """ 80 | ret = gmpy2.mpz(0) 81 | if fmt == SER_BINARY: 82 | if isinstance(s, six.text_type): 83 | raise ValueError( 84 | "Encode `s` to a bytestring yourself to" + 85 | " prevent problems with different default encodings") 86 | for c in s: 87 | ret *= 256 88 | ret += byte2int(c) 89 | return ret 90 | assert fmt == SER_COMPACT 91 | if isinstance(s, six.text_type): 92 | s = s.encode('ascii') 93 | for c in s: 94 | ret *= len(COMPACT_DIGITS) 95 | ret += R_COMPACT_DIGITS[c] 96 | return ret 97 | 98 | 99 | def get_serialized_number_len(x, fmt=SER_BINARY): 100 | if fmt == SER_BINARY: 101 | return (x.num_digits(2) + 7) // 8 102 | assert fmt == SER_COMPACT 103 | res = 0 104 | while x != 0: 105 | x = x // len(COMPACT_DIGITS) 106 | res += 1 107 | return res 108 | 109 | # Some modular arithmetic 110 | # ######################################################### 111 | 112 | 113 | def mod_issquare(a, p): 114 | """ Returns whether `a' is a square modulo p """ 115 | if not a: 116 | return True 117 | p1 = p // 2 118 | p2 = pow(a, p1, p) 119 | return p2 == 1 120 | 121 | 122 | def mod_root(a, p): 123 | """ Return a root of `a' modulo p """ 124 | if a == 0: 125 | return 0 126 | if not mod_issquare(a, p): 127 | raise ValueError 128 | n = 2 129 | while mod_issquare(n, p): 130 | n += 1 131 | q = p - 1 132 | r = 0 133 | while not q.bit_test(r): 134 | r += 1 135 | q = q >> r 136 | y = pow(n, q, p) 137 | h = q >> 1 138 | b = pow(a, h, p) 139 | x = (a * b) % p 140 | b = (b * x) % p 141 | while b != 1: 142 | h = (b * b) % p 143 | m = 1 144 | while h != 1: 145 | h = (h * h) % p 146 | m += 1 147 | h = gmpy2.mpz(0) 148 | h = h.bit_set(r - m - 1) 149 | t = pow(y, h, p) 150 | y = (t * t) % p 151 | r = m 152 | x = (x * t) % p 153 | b = (b * y) % p 154 | return x 155 | 156 | 157 | # Raw curve parameters 158 | # ######################################################### 159 | 160 | raw_curve_parameters = collections.namedtuple( 161 | 'raw_curve_parameters', 162 | ('name', 163 | 'a', 164 | 'b', 165 | 'm', 166 | 'base_x', 167 | 'base_y', 168 | 'order', 169 | 'cofactor', 170 | 'pk_len_compact')) 171 | RAW_CURVES = ( 172 | ("secp112r1", 173 | b"db7c2abf62e35e668076bead2088", 174 | b"659ef8ba043916eede8911702b22", 175 | b"db7c2abf62e35e668076bead208b", 176 | b"09487239995a5ee76b55f9c2f098", 177 | b"a89ce5af8724c0a23e0e0ff77500", 178 | b"db7c2abf62e35e7628dfac6561c5", 1, 18), 179 | ("secp128r1", 180 | b"fffffffdfffffffffffffffffffffffc", 181 | b"e87579c11079f43dd824993c2cee5ed3", 182 | b"fffffffdffffffffffffffffffffffff", 183 | b"161ff7528b899b2d0c28607ca52c5b86", 184 | b"cf5ac8395bafeb13c02da292dded7a83", 185 | b"fffffffe0000000075a30d1b9038a115", 1, 20), 186 | ("secp160r1", 187 | b"ffffffffffffffffffffffffffffffff7ffffffc", 188 | b"1c97befc54bd7a8b65acf89f81d4d4adc565fa45", 189 | b"ffffffffffffffffffffffffffffffff7fffffff", 190 | b"4a96b5688ef573284664698968c38bb913cbfc82", 191 | b"23a628553168947d59dcc912042351377ac5fb32", 192 | b"0100000000000000000001f4c8f927aed3ca752257", 1, 25), 193 | ("secp192r1/nistp192", 194 | b"fffffffffffffffffffffffffffffffefffffffffffffffc", 195 | b"64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 196 | b"fffffffffffffffffffffffffffffffeffffffffffffffff", 197 | b"188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012", 198 | b"07192b95ffc8da78631011ed6b24cdd573f977a11e794811", 199 | b"ffffffffffffffffffffffff99def836146bc9b1b4d22831", 1, 30), 200 | ("secp224r1/nistp224", 201 | b"fffffffffffffffffffffffffffffffefffffffffffffffffffffffe", 202 | b"b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4", 203 | b"ffffffffffffffffffffffffffffffff000000000000000000000001", 204 | b"b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21", 205 | b"bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34", 206 | b"ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", 1, 35), 207 | ("secp256r1/nistp256", 208 | b"ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 209 | b"5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 210 | b"ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 211 | b"6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 212 | b"4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 213 | b"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 214 | 1, 40), 215 | ("secp384r1/nistp384", 216 | b"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" 217 | b"ffffffff0000000000000000fffffffc", 218 | b"b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875a" 219 | b"c656398d8a2ed19d2a85c8edd3ec2aef", 220 | b"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" 221 | b"ffffffff0000000000000000ffffffff", 222 | b"aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a38" 223 | b"5502f25dbf55296c3a545e3872760ab7", 224 | b"3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c0" 225 | b"0a60b1ce1d7e819d7a431d7c90ea0e5f", 226 | b"ffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf" 227 | b"581a0db248b0a77aecec196accc52973", 1, 60), 228 | ("secp521r1/nistp521", 229 | b"01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 230 | b"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 231 | b"fffffffc", 232 | b"0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef1" 233 | b"09e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd4" 234 | b"6b503f00", 235 | b"01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 236 | b"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 237 | b"ffffffff", 238 | b"00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d" 239 | b"3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31" 240 | b"c2e5bd66", 241 | b"011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e" 242 | b"662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be9476" 243 | b"9fd16650", 244 | b"01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 245 | b"fffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e" 246 | b"91386409", 1, 81), 247 | ("brainpoolp160r1", 248 | b"340e7be2a280eb74e2be61bada745d97e8f7c300", 249 | b"1e589a8595423412134faa2dbdec95c8d8675e58", 250 | b"e95e4a5f737059dc60dfc7ad95b3d8139515620f", 251 | b"bed5af16ea3f6a4f62938c4631eb5af7bdbcdbc3", 252 | b"1667cb477a1a8ec338f94741669c976316da6321", 253 | b"e95e4a5f737059dc60df5991d45029409e60fc09", 1, 25), 254 | ("brainpoolp192r1", 255 | b"6a91174076b1e0e19c39c031fe8685c1cae040e5c69a28ef", 256 | b"469a28ef7c28cca3dc721d044f4496bcca7ef4146fbf25c9", 257 | b"c302f41d932a36cda7a3463093d18db78fce476de1a86297", 258 | b"c0a0647eaab6a48753b033c56cb0f0900a2f5c4853375fd6", 259 | b"14b690866abd5bb88b5f4828c1490002e6773fa2fa299b8f", 260 | b"c302f41d932a36cda7a3462f9e9e916b5be8f1029ac4acc1", 1, 30), 261 | ("brainpoolp224r1", 262 | b"68a5e62ca9ce6c1c299803a6c1530b514e182ad8b0042a59cad29f43", 263 | b"2580f63ccfe44138870713b1a92369e33e2135d266dbb372386c400b", 264 | b"d7c134aa264366862a18302575d1d787b09f075797da89f57ec8c0ff", 265 | b"0d9029ad2c7e5cf4340823b2a87dc68c9e4ce3174c1e6efdee12c07d", 266 | b"58aa56f772c0726f24c6b89e4ecdac24354b9e99caa3f6d3761402cd", 267 | b"d7c134aa264366862a18302575d0fb98d116bc4b6ddebca3a5a7939f", 268 | 1, 35), 269 | ("brainpoolp256r1", 270 | b"7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9", 271 | b"26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6", 272 | b"a9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377", 273 | b"8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262", 274 | b"547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997", 275 | b"a9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7", 276 | 1, 40), 277 | ("brainpoolp320r1", 278 | b"3ee30b568fbab0f883ccebd46d3f3bb8a2a73513f5eb79da66190eb085ffa9f49" 279 | b"2f375a97d860eb4", 280 | b"520883949dfdbc42d3ad198640688a6fe13f41349554b49acc31dccd884539816" 281 | b"f5eb4ac8fb1f1a6", 282 | b"d35e472036bc4fb7e13c785ed201e065f98fcfa6f6f40def4f92b9ec7893ec28f" 283 | b"cd412b1f1b32e27", 284 | b"43bd7e9afb53d8b85289bcc48ee5bfe6f20137d10a087eb6e7871e2a10a599c71" 285 | b"0af8d0d39e20611", 286 | b"14fdd05545ec1cc8ab4093247f77275e0743ffed117182eaa9c77877aaac6ac7d" 287 | b"35245d1692e8ee1", 288 | b"d35e472036bc4fb7e13c785ed201e065f98fcfa5b68f12a32d482ec7ee8658e98" 289 | b"691555b44c59311", 1, 50), 290 | ("brainpoolp384r1", 291 | b"7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8" 292 | b"aa5814a503ad4eb04a8c7dd22ce2826", 293 | b"04a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57" 294 | b"cb4390295dbc9943ab78696fa504c11", 295 | b"8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123a" 296 | b"cd3a729901d1a71874700133107ec53", 297 | b"1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e" 298 | b"826e03436d646aaef87b2e247d4af1e", 299 | b"8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280" 300 | b"e4646217791811142820341263c5315", 301 | b"8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7c" 302 | b"f3ab6af6b7fc3103b883202e9046565", 1, 60), 303 | ("brainpoolp512r1", 304 | b"7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2" 305 | b"ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94" 306 | b"ca", 307 | b"3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72" 308 | b"bf2c7b9e7c1ac4d77fc94cadc083e67984050b75ebae5dd2809bd638016f7" 309 | b"23", 310 | b"aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717" 311 | b"d4d9b009bc66842aecda12ae6a380e62881ff2f2d82c68528aa6056583a48" 312 | b"f3", 313 | b"81aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098ef" 314 | b"f3b1f78e2d0d48d50d1687b93b97d5f7c6d5047406a5e688b352209bcb9f8" 315 | b"22", 316 | b"7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b" 317 | b"2dcde494a5f485e5bca4bd88a2763aed1ca2b2fa8f0540678cd1e0f3ad808" 318 | b"92", 319 | b"aadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308705" 320 | b"53e5c414ca92619418661197fac10471db1d381085ddaddb58796829ca900" 321 | b"69", 322 | 1, 79), 323 | ) 324 | curves = [r[0] for r in RAW_CURVES] 325 | 326 | # Arithmetic on elliptic curves 327 | # ######################################################### 328 | 329 | 330 | class JacobianPoint(object): 331 | 332 | def __init__(self, x, y, z, curve): 333 | self.x = x 334 | self.y = y 335 | self.z = z 336 | self.curve = curve 337 | 338 | def to_affine(self): 339 | if self.z == 0: 340 | return AffinePoint(x=0, y=0, curve=self.curve) 341 | m = self.curve.m 342 | h = gmpy2.invert(self.z, m) 343 | y = (h * h) % m 344 | x = (self.x * y) % m 345 | y = (y * h) % m 346 | y = (y * self.y) % m 347 | return AffinePoint(x=x, y=y, curve=self.curve) 348 | 349 | def double(self): 350 | if not self.z: 351 | return self 352 | if not self.y: 353 | return JacobianPoint(x=self.x, y=self.y, z=0, curve=self.curve) 354 | m = self.curve.m 355 | a = self.curve.a 356 | t1 = (self.x * self.x) % m 357 | t2 = (t1 + t1) % m 358 | t2 = (t2 + t1) % m 359 | t1 = (self.z * self.z) % m 360 | t1 = (t1 * t1) % m 361 | t1 = (t1 * a) % m 362 | t1 = (t1 + t2) % m 363 | z = (self.z * self.y) % m 364 | z = (z + z) % m 365 | y = (self.y * self.y) % m 366 | y = (y + y) % m 367 | t2 = (self.x * y) % m 368 | t2 = (t2 + t2) % m 369 | x = (t1 * t1) % m 370 | x = (x - t2) % m 371 | x = (x - t2) % m 372 | t2 = (t2 - x) % m 373 | t1 = (t1 * t2) % m 374 | t2 = (y * y) % m 375 | t2 = (t2 + t2) % m 376 | y = (t1 - t2) % m 377 | return JacobianPoint(x=x, y=y, z=z, curve=self.curve) 378 | 379 | def __add__(self, other): 380 | if not isinstance(other, AffinePoint): 381 | raise NotImplementedError 382 | if not other: 383 | return self 384 | if not self.z: 385 | return other.to_jacobian() 386 | m = self.curve.m 387 | t1 = (self.z * self.z) % m 388 | t2 = (t1 * other.x) % m 389 | t1 = (t1 * self.z) % m 390 | t1 = (t1 * other.y) % m 391 | if self.x == t2: 392 | if self.y == t1: 393 | return self.double() 394 | return JacobianPoint(x=self.x, y=self.y, z=0, curve=self.curve) 395 | x = (self.x - t2) % m 396 | y = (self.y - t1) % m 397 | z = (self.z * x) % m 398 | t3 = (x * x) % m 399 | t2 = (t2 * t3) % m 400 | t3 = (t3 * x) % m 401 | t1 = (t1 * t3) % m 402 | x = (y * y) % m 403 | x = (x - t3) % m 404 | x = (x - t2) % m 405 | x = (x - t2) % m 406 | t2 = (t2 - x) % m 407 | y = (y * t2) % m 408 | y = (y - t1) % m 409 | return JacobianPoint(x=x, y=y, z=z, curve=self.curve) 410 | 411 | def __repr__(self): 412 | return "" % ( 413 | self.x, self.y, self.z, self.curve.name) 414 | 415 | 416 | class AffinePoint(object): 417 | 418 | def __init__(self, x, y, curve): 419 | self.x = x 420 | self.y = y 421 | self.curve = curve 422 | 423 | @property 424 | def on_curve(self): 425 | if not self: 426 | return True 427 | m = self.curve.m 428 | a = self.curve.a 429 | b = self.curve.b 430 | h1 = (self.x * self.x) % m 431 | h1 = (h1 + a) % m 432 | h1 = (h1 * self.x) % m 433 | h1 = (h1 + b) % m 434 | h2 = (self.y * self.y) % m 435 | return h1 == h2 436 | 437 | def to_jacobian(self): 438 | if not self: 439 | return JacobianPoint(x=0, y=0, z=0, curve=self.curve) 440 | return JacobianPoint(x=self.x, y=self.y, z=1, curve=self.curve) 441 | 442 | def double(self): 443 | if not self.y: 444 | return AffinePoint(x=0, y=0, curve=self.curve) 445 | m = self.curve.m 446 | a = self.curve.a 447 | t2 = (self.x * self.x) % m 448 | t1 = (t2 + t2) % m 449 | t1 = (t1 + t2) % m 450 | t1 = (t1 + a) % m 451 | t2 = (self.y + self.y) % m 452 | t2 = gmpy2.invert(t2, m) 453 | t1 = (t1 * t2) % m 454 | t2 = (t1 * t1) % m 455 | t2 = (t2 - self.x) % m 456 | t2 = (t2 - self.x) % m 457 | x = (self.x - t2) % m 458 | t1 = (t1 * x) % m 459 | y = (t1 - self.y) % m 460 | x = t2 461 | return AffinePoint(x=x, y=y, curve=self.curve) 462 | 463 | def __mul__(self, exp): 464 | n = exp.num_digits(2) 465 | r = JacobianPoint(x=0, y=0, z=0, curve=self.curve) 466 | while n: 467 | r = r.double() 468 | n -= 1 469 | if exp.bit_test(n): 470 | r = r + self 471 | R = r.to_affine() 472 | assert R.on_curve 473 | return R 474 | 475 | def __add__(self, other): 476 | if not isinstance(other, AffinePoint): 477 | raise NotImplementedError 478 | if not other: 479 | return self 480 | if not self: 481 | return other 482 | if self.x == other.x: 483 | if self.y == other.y: 484 | return self.double() 485 | return AffinePoint(x=0, y=0, curve=self.curve) 486 | m = self.curve.m 487 | t = (self.y - other.y) % m 488 | y = (self.x - other.x) % m 489 | y = gmpy2.invert(y, m) 490 | y = (t * y) % m 491 | t = (y * y) % m 492 | x = (self.x + other.x) % m 493 | x = (t - x) % m 494 | t = (other.x - x) % m 495 | y = (y * t) % m 496 | y = (y - other.y) % m 497 | return AffinePoint(x=x, y=y, curve=self.curve) 498 | 499 | def __nonzero__(self): 500 | return bool(self.x or self.y) 501 | __bool__ = __nonzero__ 502 | 503 | def __repr__(self): 504 | return "" % ( 505 | self.x, self.y, self.curve.name) 506 | 507 | def __eq__(self, other): 508 | if not isinstance(other, AffinePoint): 509 | return False 510 | return self.x == other.x and self.y == other.y 511 | 512 | def __ne__(self, other): 513 | return not (self == other) 514 | 515 | def __str__(self): 516 | return self.to_string(SER_COMPACT) 517 | 518 | def to_bytes(self, fmt=SER_BINARY): 519 | outlen = (self.curve.pk_len_compact if fmt == SER_COMPACT 520 | else self.curve.pk_len_bin) 521 | if self._point_compress(): 522 | return serialize_number(self.x + self.curve.m, fmt, outlen) 523 | return serialize_number(self.x, fmt, outlen) 524 | 525 | def to_string(self, fmt=SER_BINARY): 526 | return self.to_bytes(fmt).decode() 527 | 528 | def _point_compress(self): 529 | return self.y.bit_test(0) 530 | 531 | def _ECIES_KDF(self, R): 532 | h = hashlib.sha512() 533 | h.update(serialize_number(self.x, SER_BINARY, self.curve.elem_len_bin)) 534 | h.update(serialize_number(R.x, SER_BINARY, self.curve.elem_len_bin)) 535 | h.update(serialize_number(R.y, SER_BINARY, self.curve.elem_len_bin)) 536 | return h.digest() 537 | 538 | def _ECIES_encryption(self): 539 | while True: 540 | k = gmpy2.mpz( 541 | Crypto.Random.random.randrange( 542 | 0, int(self.curve.order - 1))) 543 | R = self.curve.base * k 544 | k = k * self.curve.cofactor 545 | Z = self * k 546 | if Z: 547 | break 548 | return (Z._ECIES_KDF(R), R) 549 | 550 | def _ECIES_decryption(self, d): 551 | if isinstance(d, PrivKey): 552 | d = d.e 553 | e = d * self.curve.cofactor 554 | if not self.valid_embedded_key: 555 | raise ValueError 556 | Z = self * e 557 | if not Z: 558 | raise ValueError 559 | return Z._ECIES_KDF(self) 560 | 561 | def _ECDSA_verify(self, md, sig): 562 | order = self.curve.order 563 | s, r = divmod(sig, order) 564 | if s <= 0 or order <= s or r <= 0 or order <= r: 565 | return False 566 | e = deserialize_number(md, SER_BINARY) % order 567 | s = gmpy2.invert(s, order) 568 | e = (e * s) % order 569 | X1 = self.curve.base * e 570 | e = (r * s) % order 571 | X2 = self * e 572 | X1 = X1 + X2 573 | if not X1: 574 | return False 575 | s = X1.x % order 576 | return s == r 577 | 578 | @property 579 | def valid_embedded_key(self): 580 | if (self.x < 0 or self.x >= self.curve.m or self.y < 0 or 581 | self.y > self.curve.m): 582 | return False 583 | if not self: 584 | return False 585 | if not self.on_curve: 586 | return False 587 | return True 588 | 589 | 590 | class PubKey(object): 591 | """ A public affine point """ 592 | 593 | def __init__(self, p): 594 | self.p = p 595 | 596 | def verify(self, h, sig, sig_fmt=SER_BINARY): 597 | """ Verifies that `sig' is a signature for a message with 598 | SHA-512 hash `h'. """ 599 | s = deserialize_number(sig, sig_fmt) 600 | return self.p._ECDSA_verify(h, s) 601 | 602 | @contextlib.contextmanager 603 | def encrypt_to(self, f, mac_bytes=10): 604 | """ Returns a file like object `ef'. Anything written to `ef' 605 | will be encrypted for this pubkey and written to `f'. """ 606 | ctx = EncryptionContext(f, self.p, mac_bytes) 607 | yield ctx 608 | ctx.finish() 609 | 610 | def encrypt(self, s, mac_bytes=10): 611 | """ Encrypt `s' for this pubkey. """ 612 | if isinstance(s, six.text_type): 613 | raise ValueError( 614 | "Encode `s` to a bytestring yourself to" + 615 | " prevent problems with different default encodings") 616 | out = BytesIO() 617 | with self.encrypt_to(out, mac_bytes) as f: 618 | f.write(s) 619 | return out.getvalue() 620 | 621 | def to_bytes(self, fmt=SER_BINARY): 622 | return self.p.to_bytes(fmt) 623 | 624 | def to_string(self, fmt=SER_BINARY): 625 | return self.p.to_string(fmt) 626 | 627 | def __str__(self): 628 | return self.to_string(SER_COMPACT) 629 | 630 | def __repr__(self): 631 | return "" % self 632 | 633 | 634 | class PrivKey(object): 635 | """ A secret exponent """ 636 | 637 | def __init__(self, e, curve): 638 | self.e = e 639 | self.curve = curve 640 | 641 | @contextlib.contextmanager 642 | def decrypt_from(self, f, mac_bytes=10): 643 | """ Decrypts a message from f. """ 644 | ctx = DecryptionContext(self.curve, f, self, mac_bytes) 645 | yield ctx 646 | ctx.read() 647 | 648 | def decrypt(self, s, mac_bytes=10): 649 | if isinstance(s, six.text_type): 650 | raise ValueError("s should be bytes") 651 | instream = BytesIO(s) 652 | with self.decrypt_from(instream, mac_bytes) as f: 653 | return f.read() 654 | 655 | def sign(self, h, sig_format=SER_BINARY): 656 | """ Signs the message with SHA-512 hash `h' with this private key. """ 657 | outlen = (self.curve.sig_len_compact if sig_format == SER_COMPACT 658 | else self.curve.sig_len_bin) 659 | sig = self._ECDSA_sign(h) 660 | return serialize_number(sig, sig_format, outlen) 661 | 662 | def __repr__(self): 663 | return "" % self.e 664 | 665 | def __str__(self): 666 | return str(self.e) 667 | 668 | def _ECDSA_sign(self, md): 669 | # Get the pseudo-random exponent from the messagedigest 670 | # and the private key. 671 | order = self.curve.order 672 | hmk = serialize_number(self.e, SER_BINARY, self.curve.order_len_bin) 673 | h = hmac.new(hmk, digestmod=hashlib.sha256) 674 | h.update(md) 675 | ctr = Crypto.Util.Counter.new(128, initial_value=0) 676 | cprng = Crypto.Cipher.AES.new(h.digest(), 677 | Crypto.Cipher.AES.MODE_CTR, counter=ctr) 678 | r = 0 679 | s = 0 680 | while s == 0: 681 | while r == 0: 682 | buf = cprng.encrypt(b'\0' * self.curve.order_len_bin) 683 | k = self.curve._buf_to_exponent(buf) 684 | p1 = self.curve.base * k 685 | r = p1.x % order 686 | e = deserialize_number(md, SER_BINARY) 687 | e = (e % order) 688 | s = (self.e * r) % order 689 | s = (s + e) % order 690 | e = gmpy2.invert(k, order) 691 | s = (s * e) % order 692 | s = s * order 693 | s = s + r 694 | return s 695 | 696 | # Encryption and decryption contexts 697 | # ######################################################### 698 | 699 | 700 | class EncryptionContext(object): 701 | """ Holds state of encryption. Use AffinePoint.encrypt_to """ 702 | 703 | def __init__(self, f, p, mac_bytes=10): 704 | self.f = f 705 | self.mac_bytes = mac_bytes 706 | key, R = p._ECIES_encryption() 707 | self.h = hmac.new(key[32:], digestmod=hashlib.sha256) 708 | f.write(R.to_bytes(SER_BINARY)) 709 | ctr = Crypto.Util.Counter.new(128, initial_value=0) 710 | self.cipher = Crypto.Cipher.AES.new( 711 | key[:32], Crypto.Cipher.AES.MODE_CTR, counter=ctr) 712 | 713 | def write(self, s): 714 | if not self.f: 715 | raise IOError("closed") 716 | ct = self.cipher.encrypt(s) 717 | self.f.write(ct) 718 | self.h.update(ct) 719 | 720 | def finish(self): 721 | if not self.f: 722 | raise IOError("closed") 723 | self.f.write(self.h.digest()[:self.mac_bytes]) 724 | self.f = None 725 | 726 | 727 | class DecryptionContext(object): 728 | """ Holds state of decryption. Use Curve.decrypt_from """ 729 | 730 | def __init__(self, curve, f, privkey, mac_bytes=10): 731 | self.f = f 732 | self.mac_bytes = mac_bytes 733 | R = curve.point_from_string(f.read(curve.pk_len_bin), SER_BINARY) 734 | key = R._ECIES_decryption(privkey) 735 | self.h = hmac.new(key[32:], digestmod=hashlib.sha256) 736 | ctr = Crypto.Util.Counter.new(128, initial_value=0) 737 | self.cipher = Crypto.Cipher.AES.new( 738 | key[:32], Crypto.Cipher.AES.MODE_CTR, counter=ctr) 739 | self.ahead = f.read(mac_bytes) 740 | 741 | def read(self, n=None): 742 | if not self.f: 743 | return '' 744 | if n is None: 745 | tmp = self.ahead + self.f.read() 746 | else: 747 | tmp = self.ahead + self.f.read(n) 748 | ct = tmp[:-self.mac_bytes] 749 | self.ahead = tmp[-self.mac_bytes:] 750 | self.h.update(ct) 751 | pt = self.cipher.decrypt(ct) 752 | if n is None or len(ct) < n: 753 | if self.h.digest()[:self.mac_bytes] != self.ahead: 754 | raise IntegrityError 755 | self.f = None 756 | return pt 757 | 758 | # The main Curve objects 759 | # ######################################################### 760 | 761 | 762 | class Curve(object): 763 | """ Represents a Elliptic Curve """ 764 | 765 | @staticmethod 766 | def by_name_substring(substring): 767 | substring = substring.lower() 768 | candidates = [] 769 | for raw_curve in RAW_CURVES: 770 | if substring in raw_curve[0]: 771 | candidates.append(raw_curve) 772 | if len(candidates) != 1: 773 | raise KeyError 774 | return Curve(candidates[0]) 775 | 776 | @staticmethod 777 | def by_name(name): 778 | for raw_curve in RAW_CURVES: 779 | if raw_curve[0] == name: 780 | return Curve(raw_curve) 781 | raise KeyError 782 | 783 | @staticmethod 784 | def by_pk_len(pk_len): 785 | for raw_curve in RAW_CURVES: 786 | if raw_curve[8] == pk_len: 787 | return Curve(raw_curve) 788 | raise KeyError 789 | 790 | def __init__(self, raw_curve_params): 791 | """ Initialize a new curve from raw curve parameters. 792 | 793 | Use `Curve.by_pk_len' instead """ 794 | r = raw_curve_parameters(*raw_curve_params) 795 | 796 | # Store domain parameters 797 | self.name = r.name 798 | self.a = deserialize_number(binascii.unhexlify(r.a), SER_BINARY) 799 | self.b = deserialize_number(binascii.unhexlify(r.b), SER_BINARY) 800 | self.m = deserialize_number(binascii.unhexlify(r.m), SER_BINARY) 801 | self.order = deserialize_number( 802 | binascii.unhexlify(r.order), SER_BINARY) 803 | self.base = AffinePoint( 804 | curve=self, x=deserialize_number( 805 | binascii.unhexlify( 806 | r.base_x), SER_BINARY), y=deserialize_number( 807 | binascii.unhexlify( 808 | r.base_y), SER_BINARY)) 809 | self.cofactor = r.cofactor 810 | 811 | # Calculate some other parameters 812 | self.pk_len_bin = get_serialized_number_len( 813 | (2 * self.m) - 1, SER_BINARY) 814 | self.pk_len_compact = get_serialized_number_len( 815 | (2 * self.m) - 1, SER_COMPACT) 816 | assert self.pk_len_compact == r.pk_len_compact 817 | self.sig_len_bin = get_serialized_number_len( 818 | (self.order * self.order) - 1, SER_BINARY) 819 | self.sig_len_compact = get_serialized_number_len( 820 | (self.order * self.order) - 1, SER_COMPACT) 821 | self.dh_len_bin = min((self.order.num_digits(2) // 2 + 7) // 8, 32) 822 | self.dh_len_compact = get_serialized_number_len( 823 | 2 ** self.dh_len_bin - 1, SER_COMPACT) 824 | self.elem_len_bin = get_serialized_number_len(self.m, SER_BINARY) 825 | self.order_len_bin = get_serialized_number_len(self.order, SER_BINARY) 826 | 827 | @property 828 | def key_bytes(self): 829 | """ The approximate number of bytes of information in a key. """ 830 | return self.pk_len_bin 831 | 832 | def __repr__(self): 833 | return "" % self.name 834 | 835 | def point_from_string(self, s, fmt=SER_BINARY): 836 | x = deserialize_number(s, fmt) 837 | yflag = x >= self.m 838 | if yflag: 839 | x = x - self.m 840 | assert 0 < x and x <= self.m 841 | return self._point_decompress(x, yflag) 842 | 843 | def pubkey_from_string(self, s, fmt=SER_BINARY): 844 | return PubKey(self.point_from_string(s, fmt)) 845 | 846 | def _point_decompress(self, x, yflag): 847 | m = self.m 848 | h = (x * x) % m 849 | h = (h + self.a) % m 850 | h = (h * x) % m 851 | h = (h + self.b) % m 852 | y = mod_root(h, m) 853 | if y or not yflag: 854 | if y.bit_test(0) == yflag: 855 | return AffinePoint(x=x, y=y, curve=self) 856 | return AffinePoint(x=x, y=m - y, curve=self) 857 | 858 | def hash_to_exponent(self, h): 859 | """ Converts a 32 byte hash to an exponent """ 860 | ctr = Crypto.Util.Counter.new(128, initial_value=0) 861 | cipher = Crypto.Cipher.AES.new(h, 862 | Crypto.Cipher.AES.MODE_CTR, counter=ctr) 863 | buf = cipher.encrypt(b'\0' * self.order_len_bin) 864 | return self._buf_to_exponent(buf) 865 | 866 | def _buf_to_exponent(self, buf): 867 | a = deserialize_number(buf, SER_BINARY) 868 | a = (a % (self.order - 1)) + 1 869 | return a 870 | 871 | def passphrase_to_pubkey(self, passphrase): 872 | return PubKey(self.base * self.passphrase_to_privkey(passphrase).e) 873 | 874 | def passphrase_to_privkey(self, passphrase): 875 | if isinstance(passphrase, six.text_type): 876 | raise ValueError( 877 | "Encode `passphrase` to a bytestring yourself to" + 878 | " prevent problems with different default encodings") 879 | h = _passphrase_to_hash(passphrase) 880 | return PrivKey(self.hash_to_exponent(h), self) 881 | 882 | @contextlib.contextmanager 883 | def decrypt_from(self, f, privkey, mac_bytes=10): 884 | ctx = DecryptionContext(self, f, privkey, mac_bytes) 885 | yield ctx 886 | ctx.read() 887 | 888 | def decrypt(self, s, privkey, mac_bytes=10): 889 | instream = BytesIO(s) 890 | with self.decrypt_from(instream, privkey, mac_bytes) as f: 891 | return f.read() 892 | 893 | # Helpers 894 | # ######################################################### 895 | 896 | 897 | def _passphrase_to_hash(passphrase): 898 | """ Converts a passphrase to a hash. """ 899 | return hashlib.sha256(passphrase).digest() 900 | 901 | 902 | def encrypt(s, pk, pk_format=SER_COMPACT, mac_bytes=10, curve=None): 903 | """ Encrypts `s' for public key `pk' """ 904 | curve = (Curve.by_pk_len(len(pk)) if curve is None 905 | else Curve.by_name(curve)) 906 | p = curve.pubkey_from_string(pk, pk_format) 907 | return p.encrypt(s, mac_bytes) 908 | 909 | 910 | def decrypt(s, passphrase, curve='secp160r1', mac_bytes=10): 911 | """ Decrypts `s' with passphrase `passphrase' """ 912 | curve = Curve.by_name(curve) 913 | privkey = curve.passphrase_to_privkey(passphrase) 914 | return privkey.decrypt(s, mac_bytes) 915 | 916 | 917 | def encrypt_file(in_path_or_file, out_path_or_file, pk, pk_format=SER_COMPACT, 918 | mac_bytes=10, chunk_size=4096, curve=None): 919 | """ Encrypts `in_file' to `out_file' for pubkey `pk' """ 920 | close_in, close_out = False, False 921 | in_file, out_file = in_path_or_file, out_path_or_file 922 | try: 923 | if stringlike(in_path_or_file): 924 | in_file = open(in_path_or_file, 'rb') 925 | close_in = True 926 | if stringlike(out_path_or_file): 927 | out_file = open(out_path_or_file, 'wb') 928 | close_out = True 929 | _encrypt_file(in_file, out_file, pk, pk_format, mac_bytes, chunk_size, 930 | curve) 931 | finally: 932 | if close_out: 933 | out_file.close() 934 | if close_in: 935 | in_file.close() 936 | 937 | 938 | def decrypt_file(in_path_or_file, out_path_or_file, passphrase, 939 | curve='secp160r1', mac_bytes=10, chunk_size=4096): 940 | """ Decrypts `in_file' to `out_file' with passphrase `passphrase' """ 941 | close_in, close_out = False, False 942 | in_file, out_file = in_path_or_file, out_path_or_file 943 | try: 944 | if stringlike(in_path_or_file): 945 | in_file = open(in_path_or_file, 'rb') 946 | close_in = True 947 | if stringlike(out_path_or_file): 948 | out_file = open(out_path_or_file, 'wb') 949 | close_out = True 950 | _decrypt_file(in_file, out_file, passphrase, curve, mac_bytes, 951 | chunk_size) 952 | finally: 953 | if close_out: 954 | out_file.close() 955 | if close_in: 956 | in_file.close() 957 | 958 | 959 | def _encrypt_file(in_file, out_file, pk, pk_format=SER_COMPACT, 960 | mac_bytes=10, chunk_size=4096, curve=None): 961 | curve = (Curve.by_pk_len(len(pk)) if curve is None 962 | else Curve.by_name(curve)) 963 | p = curve.pubkey_from_string(pk, pk_format) 964 | with p.encrypt_to(out_file, mac_bytes) as encrypted_out: 965 | while True: 966 | buff = in_file.read(chunk_size) 967 | if not buff: 968 | break 969 | encrypted_out.write(buff) 970 | 971 | 972 | def _decrypt_file(in_file, out_file, passphrase, curve='secp160r1', 973 | mac_bytes=10, chunk_size=4096): 974 | curve = Curve.by_name(curve) 975 | privkey = curve.passphrase_to_privkey(passphrase) 976 | with privkey.decrypt_from(in_file, mac_bytes) as decrypted_in: 977 | while True: 978 | buff = decrypted_in.read(chunk_size) 979 | if not buff: 980 | break 981 | out_file.write(buff) 982 | 983 | 984 | def verify(s, sig, pk, sig_format=SER_COMPACT, pk_format=SER_COMPACT, 985 | curve=None): 986 | """ Verifies that `sig' is a signature of pubkey `pk' for the 987 | message `s'. """ 988 | if isinstance(s, six.text_type): 989 | raise ValueError("Encode `s` to a bytestring yourself to" + 990 | " prevent problems with different default encodings") 991 | curve = (Curve.by_pk_len(len(pk)) if curve is None 992 | else Curve.by_name(curve)) 993 | p = curve.pubkey_from_string(pk, pk_format) 994 | return p.verify(hashlib.sha512(s).digest(), sig, sig_format) 995 | 996 | 997 | def sign(s, passphrase, sig_format=SER_COMPACT, curve='secp160r1'): 998 | """ Signs `s' with passphrase `passphrase' """ 999 | if isinstance(s, six.text_type): 1000 | raise ValueError("Encode `s` to a bytestring yourself to" + 1001 | " prevent problems with different default encodings") 1002 | curve = Curve.by_name(curve) 1003 | privkey = curve.passphrase_to_privkey(passphrase) 1004 | return privkey.sign(hashlib.sha512(s).digest(), sig_format) 1005 | 1006 | 1007 | def passphrase_to_pubkey(passphrase, curve='secp160r1'): 1008 | curve = Curve.by_name(curve) 1009 | return curve.passphrase_to_pubkey(passphrase) 1010 | 1011 | 1012 | def generate_keypair(curve='secp160r1', randfunc=None): 1013 | """ Convenience function to generate a random 1014 | new keypair (passphrase, pubkey). """ 1015 | if randfunc is None: 1016 | randfunc = Crypto.Random.new().read 1017 | curve = Curve.by_name(curve) 1018 | raw_privkey = randfunc(curve.order_len_bin) 1019 | privkey = serialize_number(deserialize_number(raw_privkey), SER_COMPACT) 1020 | pubkey = str(curve.passphrase_to_pubkey(privkey)) 1021 | return (privkey, pubkey) 1022 | -------------------------------------------------------------------------------- /src/tests/test_ec.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import seccure 3 | 4 | 5 | def nist_test_vectors(): 6 | # These test vectors have been derived from 7 | # http://point-at-infinity.org/ecc/nisttv 8 | return { 9 | 'secp192r1/nistp192': [ 10 | ['#', 11 | '#<%E/4#.v,7<^TdueIy@]IU5S%7,8?', 12 | 'D:/]|&5!rtYmWN,>BDR$OQNC&}Bxl'], 13 | ['$', 14 | '.G%F|2_Dl5)AnEw@|/0m/_Q!v/Z1.^', 15 | '.RGK=RHIy3ewGT-PG.P_z&jS@&TGbD'], 16 | ['%', 17 | ')4#*~G=.7Ls:LXwQ.SEf-RWyd*2?He', 18 | '):%*slCL)UG[E'], 22 | ['(', 23 | 'sF]%z{=H,e7nh!hAn4){e{Hx;DCOu', 24 | '$UUneyI5=Fup0BP0I>Uq)inTprX=-b'], 25 | [')', 26 | '+Py2hL#3v^//Wf(SjzCiz#2W6-!{H3', 27 | ')x[m~OT_[%gYQIJ>?6i', 42 | '/K4D~^>n-zfyED5)Q.qIvuh(/(MeP5'], 43 | ['/', 44 | 'qgmrXtieD+Wh,7<&.V!g-iDY4DAq%', 45 | '&bkU|9#D)(f:9qL$i0=dhk-=(&A1p~'], 46 | ['0', 47 | 'uJ,/kZ(gk=#*WUrE>8z!98*fT)aq$', 48 | '(fYT~];|R68nMVYPCIP-0fUSfwCZPu'], 49 | ['1', 50 | '#$Ggz#2y&Jxo^QWRH77v>~&lA3V#1]', 51 | '#YXyt/-Pj_d)H6z=SBx%HJbRGPa9yG'], 52 | ['2', 53 | '*?a#[CS2dxE:&78Rxz-VQ4HB~Iq{^p', 54 | '$!!H^>bjKWo(,{Y,VRdmR-UDr!YVKH'], 55 | ['3', 56 | ',S=281l8hjzlj?T_wY5)TLwdHGQOI%', 57 | '0>[w!!>fCD]Rae6Ci'], 58 | ['4', 59 | '%T??1?wCL!J@!_YX#MyPhG6smV'], 64 | ['6', 65 | '-#CQhmXm}V~_8}E!!1v9]cVHx{@vSt', 66 | '+1xc4~S3Dvfl.{FUX0P}h=ZxNVX6vq'], 67 | ['7', 68 | ',i1z8tG2uj9.Vg(*O}CN25%,vQE/}Z', 69 | '*XSz9v)sc_1YIXw5Ys.-!tqhGK7!|:'], 70 | ['=)R$U0!8,', 71 | ')iZUa%AO~)7Pf_jjlN3oe0hWVI<=AL', 72 | '/k7BDtG}oUDJumbb~rR_R7f@Gk=&]P'], 73 | ['h=k])#StiU[0IMl;b,', 74 | ',A:0c|XASm%!Fi#aZ~O88vt^q}*XG_'], 76 | ['%J6g^PG&e$cqy(ZsU[l$NilC2+7ubr', 77 | ')+051*qwTf5&{WO=2JC2,|%q#5T4!j', 78 | '%xJwH{bc8_>])TF3blo,B$}bR<:=[<'], 79 | ['P?Y?', 80 | ']Fa.z7VOeXA|4%K([4B@Ye;1%*Gp~', 81 | '&}czo:Op!f=E.(3r1K?$EsI[Rg]Zf&'], 82 | ['%@qm2T9*#5MsoI~r;nfCO(~CPRZE&l', 83 | '$,YlC3H5cJZLJDW$@9<>V>[3TBm8hH', 84 | '+ZOd#Uk4&cj%5@F;PF1', 86 | '%]U-y}tZr*rC*n4/e,m97E3'], 88 | [')_dX3jMhRJ;o{~b<*', 90 | '%A#T&2>(=)nOi9%NW&aJF[=BBfqhj~'], 91 | ['&ba4+:I>#QwUJu[a+fU1l%ti(i!C3', 92 | '/Q;>~77~A^dNQGJdpqptpXThsUErEY', 93 | '%LBD(p0U:&1o|grzTz#>uOBF51eJX('], 94 | [')Q2EAWWqv09a+Aq*r%~8k4dD}.', 95 | '.N-|P2_[S,G=|z?K3,Y@17#Cs3C-,4', 96 | '.&mDr^|jeayq0-7(S*$lLO~wr>w0OW'], 97 | [')_ck7)YDM#V)q0B([7#.k9NzFo$ZdP', 98 | '.>Z7_:3/D>1&EOgq-W#ugFR(R,$Qqg', 99 | '*t,i5h<~Jn}))dn!xZ(L55:508))j_'], 100 | ['o-xs/xXptrZPWKFyF!f%VgQbxQd', 101 | '&R}up{cDIz%pH):5WFgJ07~fhz', 104 | '%}!fIA[/#PG]GHZr]vQT<}k6&ibNVC', 105 | '$2XD?dpY(Rccvs*y$LU6AMC,JX,8kk'], 106 | ['0@H5c!z$l7mG6fc6P500Pfc67rPcq8', 107 | ',i1z8tG2uj9.Vg(*O}CN25%,vQE/}Z', 108 | '(fs:J,s1+V]ll.k1dmT5{8;MQg25n_=LdlZlPc_Ufz'], 118 | ['0@H5c!z$l7mG6fc6P500Pfc67rPcq<', 119 | ',S=281l8hjzlj?T_wY5)TLwdHGQOI%', 120 | '#k=c!]>Iz@yr!=0&XzUKrqt>n&c$V'], 121 | ['0@H5c!z$l7mG6fc6P500Pfc67rPcq=', 122 | '*?a#[CS2dxE:&78Rxz-VQ4HB~Iq{^p', 123 | '.@Gl%d::A]}B,j+:h/{QK&a/}N1@yw'], 124 | ['0@H5c!z$l7mG6fc6P500Pfc67rPcq>', 125 | '#$Ggz#2y&Jxo^QWRH77v>~&lA3V#1]', 126 | '.en:msoR#V,@mPh)k?h8z!98*fT)aq$', 129 | '*Xm^cDa(;#UWh1*r{925nJ_},VG>?6i', 135 | 't6ocC]6a<)LpCO=mSnu(;NLdI=3u.'], 136 | ['0@H5c!z$l7mG6fc6P500Pfc67rPcqB', 137 | '+siKUZ.HUi.;hwI|TVJvx(FUs=)PW<', 138 | '-en)3|.fl?p|i>bDl4~DKI+y0]PT|@'], 139 | ['0@H5c!z$l7mG6fc6P500Pfc67rPcqC', 140 | ')gx)i3PopPmdZhH{B|Ep9JvDLt/s^%', 141 | '07u4)E#1C|svi8.}F3vS:*Pye1BiTrCZUfr~'], 151 | ['0@H5c!z$l7mG6fc6P500Pfc67rPcqG', 152 | 'sF]%z{=H,e7nh!hAn4){e{Hx;DCOu', 153 | '-iqE|)QoOovV)E46uD,MuGG{~[2Z;^'], 154 | ['0@H5c!z$l7mG6fc6P500Pfc67rPcqH', 155 | '$jT2E44Id)YLfU|:ek?KKo(z83N::@Gl[jq', 172 | '#(~jAx%9-X){!1V;@PIwF$xe|z/_Ov@7%Ol'], 173 | ['%', 174 | '+K>D@oaZxra];/jnr/6>hD:Zy~IOr(1:2zQ', 175 | ')5jef&A5$q9*_q,YdPSZy4$d9ZtM_B;', 181 | '#Q!$~Ss+,_n3,V7Fk@i9t'], 194 | ['-', 195 | ')Z6yYS93O8~b?[h01/_f3BV,Z_YU=v^i>C4', 196 | '$3gD$9d,hbHC5-f/svB%9lWawGZ{>jhK0st'], 197 | ['.', 198 | ',(WI|1Cfx^aSNuDVUFo?xcDL2+T5YdzKr[+', 199 | '#8LJOJq1kOHNh-H@Sh2:^r['], 200 | ['/', 201 | '&2R5XW-8fIK7{g:tzIr3T(XP?Vwh@d|lbuA', 202 | '#7f>m(,OI]#m.ol#Wzepz|=>dY}vU7],c|7py2#0c2^7>Sck', 205 | '#G[)b)nn>1u}g0,]UbftdF7@it]w=5gcV=*'], 206 | ['1', 207 | '):#*/s=Kq#m?u40qaruleDeB>bdEC+{R40]@S[U]rK*hhUqu?v'], 212 | ['3', 213 | 'I{K:3q;G2q[8<+Upcju4-uSzDn_>-|Z{/8', 214 | '#zq2w0%-g8H/otU!3fv?U$vQDQcKqFC0W>q'], 215 | ['4', 216 | ')|hRDrDq,0$l6xjIvsfYMqvK}z%kS[v=vk?', 217 | ',_;,;=7]F?rO+2(iP&Ftp,C4($-FJg,,Z+E'], 218 | ['5', 219 | '*^DD@GhQ&y%3=#9?Xuwoyw]s6I5^rlo;5rU', 220 | '+tLfjdVC*h9Sx?E~F4s[{Q.fmU}j0[3)0SA'], 221 | ['6', 222 | ').hB@$S-_i}}P{,|VXgj|_D%BTU<[.pvy@$', 223 | '+Cy_W;]IRQ>JO8k?p+)_+pj>MKkXzZ.sk$f'], 224 | ['7', 225 | ',VIpAcCrWdFa/D><%.a}*U:Z:v0o:M'], 239 | ['&q5&/{&LWEq>n-1.f]3rP*Ug}{8t0w^ZWf@', 240 | '+*epC|++8w*nN#Zjg0KZB:E$15@L=YKRe2p', 241 | '*{^D^+BALVbsEy%Y0sktLXtr_JnqBPH>ig4'], 242 | ['&q5&KlSs<+RkPNB8|Kaulbg2AY^B^J,n*X,', 243 | '+Bq&OdY1N,mrc+09U_U;Z^IY4T~l!7%Op_L', 244 | ',!a?nW}V{1/7G(hpQbW;$Q;Cebo)6K!|~fr'], 245 | ['&8d-G))Lc#Uu.F(fQ,S*8)uv139]=so0ZDg', 246 | '$ks,AV;(Q#N>&oy=m>+:th(9)3Cmo(x{L1o', 247 | '*9p}tp+)DB9Sfa$zFalW?b6s/,GS^f}[%_Q'], 248 | ['#_qn-=gwi$qiOfTe6x?/0.7KPCNlmJ62e]c@93Uy4^8j', 250 | '+kn!6=mWX6Qn+i]I%!:dFJ5IK7%=pd-A2x0'], 251 | ['&q4K^.>hKb>llLsVc}PR(VxgQ}5JC2*&K@b', 252 | '%t+qP)Ib4|v4Vv*n}J3p1Z7CZvG,02rqGV@', 253 | ',3GZ?[d0q5S_yX<-V>BIg(Vd&r;R_TpG0Xs'], 254 | ['Xw0+uL@}ofSoBI(%YlO#7i^ypxgZ', 258 | ',(2wg+mjehE8rAQ4WoglR|5W!oFnIKJ/iS0', 259 | '#;%w?K^Z1;OMSA]d[jG8QUlv]o#DMsA#(L]'], 260 | ['#3Y0)*g<}-^a)fT7{ZyrL?]sf=fLiwG', 261 | '$Inh$]K|RL+P?V2?[Sp<^edaxs(4Lx>sonb', 262 | '+1({EvtO=rGViNjIR5AZwCiO1TnjQ8y1y.z'], 263 | [',cG,-SnkG4$U=o$>]o]&4Lv.'], 266 | [',cG,-Sn#'], 272 | [',cG,-Sn-|Z{/8', 277 | '*gTx5Fk2*l2!Ht-uxkh~tj&^Z!!kWc,_,RP'], 278 | [',cG,-SnazEb1@)}mm(@=gp=UQB%;'], 284 | [',cG,-SnWzepz|=>dY}vU7],c|7py2#0c2^7>Sck', 286 | '+Sb8GC4', 295 | '*P_g+=-3)B1l&]zi8Z?<4!DO)-*<.>eCR{M'], 296 | [',cG,-SngIKd#zQgeo,}EPWh.$MI-[,v]7Z3q9AF'], 302 | [',cG,-SnZy4$d9ZtM_B;', 310 | '+3FnGht&-I(7/@x4}~0yJX$E0b3(-?TAwbO'], 311 | [',cG,-SnTMaaQc!3'], 314 | [',cG,-SnD@oaZxra];/jnr/6>hD:Zy~IOr(1:2zQ', 316 | '%N[EFON*l3A(WwV=H#.${gB*uP/&9_r+V65'], 317 | [',cG,-SnfU|:ek?KKo(z83N::@Gl[jq', 319 | '+[G@jZk%dJP49Y,[l#7G&j$K!VUVy3/W^AU'], 320 | [',cG,-Sn-', 329 | '5PV+tLJ7]*e[Ew@H/SiC{h@-%E?KrF@_[', 335 | ')4UqxYfrv.B2nB85NVxz.,AQ[=C.I0//@?ZJ/@=;F2+nWppawODqR(10b+RZGv.Si?X', 338 | ')4,1/koiBEU$XBJnx2#MX&(Hqw)>b1q@9#^M,-,-'], 339 | [')', 340 | '&qP>aO(&%N*ligxF]8m~n#iq4%kkP*NH2KYT|+EP', 341 | ')FzJ-L!WCtg)litRREnC#&6Ph8Gxf,B(y~CQ+]_e'], 342 | ['*', 343 | '%wk5'], 345 | ['+', 346 | '$f#Za!D~Q_y^;$xQ/0;K_C|X1tV4k3SW/BV-XFGixH;9?dOcMO^&zasMlE-a0(_/HBt-N}S#5gMnDw', 350 | '#1Ncdq?h:7Gf-hw3awZG22ipNs*1QC!c$Mxfq/i5'], 351 | ['-', 352 | '(c~ld$8QjoEx?Hr%R&Xema=_}opTUn:;dw|K:n@v', 353 | '%fxUkh=#bj7PmyF'], 354 | ['.', 355 | '#fd@E{oO7Cs|>ivEnDRCB|U~$+{RCaXRMZPDX3JF', 356 | '%}HCHAOg#djH6N]O4so;P*wHhPXi[jKj5bUYJ~XE'], 357 | ['/', 358 | '%4nY9TZC7Ca3&,/8<#XvUQP/>CXx_z;kI]M2TRiK', 359 | '5Jl?TpvpW/%&pw]oVWVZikiA5BpK|R2&~YH+Yb]'], 360 | ['0', 361 | '^6Gb/4q6&yz3p-&06[^(:Y!Z!-)(OchO]6?*0Bt', 362 | '$h5Kq^T)mR4xW*WSUO65G7b$|AKZ&h;WdBAdT?t-'], 363 | ['1', 364 | '$AKa(XZgoIi.V=CC6ee2u^>I6RiM0uH++=$v^vaR', 365 | ')ijJ*8afSZ4/^,OsGHceoN4n', 368 | '(!JH*&,sv[yTu9m4wlP*gwRMIjkmw&vnH>@4NK_S'], 369 | ['3', 370 | '%;.>/ggDgNlmV8hh^m?n*?N|by:.5bHUoSw)H@}$', 371 | '&a4i+F&Mfe4z.(=X*cc+XgPt<#p.TP=?8!Ye|URM'], 372 | ['4', 373 | '#|/y,O]Y=/QItv%7|I}@30ZqE{p6%VHfTak?/NM1', 374 | '&bEFo/ZgdQ@jX|Q1uQTD^xeIGGIrQp9uoaEp,]>$'], 375 | ['5', 376 | 'KPv9HV?,*&E&QmTlQ/k(OR8gBamM6=x&FGYmt=^', 377 | ')m+epy9~Nb1wpL)sX*7FX>J2F$:W2sMh@~D7*C6a'], 378 | ['6', 379 | '(Y7xZxpItlhE&=|seu|h4p3O)x~?IStu^|og&'], 384 | ['=)R$U0!8,', 385 | '#HnqbgPpMcA/&Qc1M-}w..L5*]', 386 | '&uY,RS1|-gX!sIJ:8=MH[7SD'], 387 | ['h=k])#StiU[0IMl;b,', 388 | 'i0&H/dQXh=WK}uMWXX-e>,Fk&bRS*h)N9Z_l3&7', 389 | '(=(-Wv>}X*/0DQu]si>X', 391 | '&D>i8H+?VVI_]JGEplJLYw+Wzim=Rd/2:E7*$u6#', 392 | 'iiRS+PFluXQ+xI%v$[e^]I:2!)FAV,OezHf|u#{'], 393 | ['%R;Xe*59<7(kU(z6Xa+*E-3ykGfE*6G((NyWL4zv', 394 | '%g!vhB45oRSrCH9k?5sR;W*7YVU|y{1rvXUGsQ5v', 395 | '%-n1=K9;VgFC6j}=uUJ9{1(O2m|OY!.iG2TBbEfi'], 396 | ['sIMQ(}(NF/?95s2fyi5TSDhWW/y[({@{OK@/V', 397 | '$lphM&:C_b~v}~5kix*F(c8m}&C6b1B&blQfl/tFH', 400 | '(Z2v!O5/ps4El(y;(QA~fYs9*2gRifS[Ui]!KYWG', 401 | '$*~/ws[1vNmE=Z[Zz5/g^GX:E50-AX->6/{yP9[M'], 402 | ['ZjC^[)=j#jpyASa/8JJ)Kc]qwd7W*{g+0S}jay', 403 | ')[l6;[9m)cPh%>]{}g>SlqRdOcqVH$0QPg0!2X*g', 404 | '$Q-D&~%8~@2kV[$v:kCh{[p*x6l)_mR8;,~U.%rS=UY3', 410 | ')zknWub#b=!$Y^J7?O8lj3Hv6[:z4BWG2nvnb$L^'], 411 | ['%R;WV*w|{1iNpt96sy6h'], 414 | ['$N*K[pZ!yI9!BoA&OjRcu{uKJ41ClEhb!-&vc:,', 415 | '(AXbn&PodWyMEZ164oGIGW0L@2Y,f]l(1f8yS:Fw', 416 | '#Wv3iZOMmHTfRg!haIK+GeI?NE}_ab0(fA|OoA?>'], 417 | ['&{/O,Z}!.4=sVV]vw*R?jtj||0?sfhpp2]Q-.y;', 418 | '#L(;I;mJ.W&p9>UaB_9fU^Rw1W:VB~r2CEeSSc_%b|,{v3R'], 420 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSb_', 421 | '%[:Cc9B}f6BA2U/3_STk=9:ve7bjsi-*7Lv:N%Be', 422 | '%jq65HfVpdLJ4OlH,cEEn%sN>Hz!C97s9qvWKP/>'], 423 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSba', 424 | '(Y7xZxpItlhE&=|seu|h4p3O)x~?IStuj05ogZ*_pK*;uxq/ggDgNlmV8hh^m?n*?N|by:.5bHUoSw)H@}$', 434 | '$EBJNFUj8z^JkbCk]UMCf$^[G8afSZ4/^,OsGHceoN4n', 437 | '$&,kOgPD)$vp#Nr2mL_DVq]%9RVuu?X;%.u%yt5n'], 438 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbf', 439 | '$AKa(XZgoIi.V=CC6ee2u^>I6RiM0uH++=$v^vaR', 440 | 'Agf/)22/^L@_)p1iz9wSNMd{w+h[7Oh*sRt!~8'], 444 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbh', 445 | '%4nY9TZC7Ca3&,/8<#XvUQP/>CXx_z;kI]M2TRiK', 446 | ')q,G:7jA/*eBqugfubVtd!DhB,!sBF|wFJYo@f3e'], 447 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbi', 448 | '#fd@E{oO7Cs|>ivEnDRCB|U~$+{RCaXRMZPDX3JF', 449 | '%).p1K-Pz{)|c:#tREA3nb9*xliz2Y&?8h^[}A;|'], 450 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbj', 451 | '(c~ld$8QjoEx?Hr%R&Xema=_}opTUn:;dw|K:n@v', 452 | '%?Zw-bC62;(tgWpCtN>{6r&7}2/G8WhlIhK!wQx{'], 453 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbk', 454 | ')L$g4RQ0X@->^&zasMlE-a0(_/HBt-N}S#5gMnDw', 455 | '(u(Ory=OeJJ_k}h4&AT(0XFa4JaO(&%N*ligxF]8m~n#iq4%kkP*NH2KYT|+EP', 464 | '^XiL@Y_Yk-?,|jq4sB,>ey~y(zk*;1#PJqf@c5]'], 465 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbo', 466 | '$8]>/@?ZJ/@=;F2+nWppawODqR(10b+RZGv.Si?X', 467 | 'rK%I~kN[<=C?F6Tm+/}ff,*oF=G.5^i5HUj@6j8'], 468 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbp', 469 | ')7~=9h]R86mPdb0MC,9J71,r^q#*>@-%E?KrF@_[', 470 | 'q}A^2tE)SP6+FI28b7Q4_n~(#!WE7Cljk|ie@6|'], 471 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbq', 472 | '$Y|L@fDog=5xfNC&sQHMK)j(iz,VPQn&-c1;y@3}', 473 | '%@HCg{Hif1XZ1}qf*yCvc=J9%*3,KMDI*o.6KWij'], 474 | ['*&T4VkZ8|_qEvg_Df:1L91z@|8h@+%NsnSWckSbr', 475 | '%JdV1Ct4}9KQ9A,/.MGBfBeL%C@rByHRz|8s-', 476 | ')q%[Mv0mh$iat.fBAM']], 480 | 'secp384r1/nistp384': [ 481 | ['#', 482 | '#?>g,R[~H^zenwA+.lO&EvJ.AU0X%mO&n4,j#y(2xT1.BeDn@9cmfH', 483 | 'HRR&noUlCUO{vD]C/Jjr_ZK7TY.<=[L-cqIULK]gy&:pS7Zn|.DG}Y[E-,b'], 484 | ['$', 485 | ')/WS~1MF6%Z+c4,s}LZV8Ph<0_s@^[r{v/BNXR}Ih,@H#K-dYrrab|FL,Uv', 486 | '#+rjmCG?0&n(I[Vb}Wx4kPOp@UqOb|sT9M7[A8G]#4gi@U+Zt*|$ScVg:E:-'], 487 | ['%', 488 | '(3u5@Z|2?_[QsnW.5*TX>LMd/byEjh5sK;[a*BZ-zQwv3R(!ndz5KOPN|$]', 489 | '#Tqlv>mwUgPFVqkiBH-z@eu<6)X*v!.%q|X}@zhF)0ev*=JV:sUb*bbjby6_'], 490 | ['&', 491 | '0S.5hvE;?q&:xy9w>/,g25JW2fGbTNSOc~tg-b}:78*Q=5OG!yr#UlaV@Mj', 492 | '#Uc0!)^_H>xq@}P~NBqKLO0_10!M11#e3@O=2J;?e@!{_-=ifw}ykD$*Ih*H'], 493 | ['(', 494 | '/F]pWSV#h*D|kX:x@%Z~HfwA6<1}0F5@Jl*P$F|_xn29[GSwMGHAP2~,b-/', 495 | '#,_F[k{Z(6452OhM*DP2,U.dm'], 496 | [')', 497 | 'i@7U%5OR;W)r:(mS@~b8_rLZ46fP!WK0p^9}E@ijJ-kf(mB5C];k#>eLHi_', 498 | ')UosYO>NiWB[fhBe#3D}tpHVflR%k&uNXSX,Pcj_B38i=^PA(zT]y?.)9~U'], 499 | ['*', 500 | '>w6bCKpDx=h03EnCL>WAk8H_p?e?{7ts=s-Y6Y4ecs@6V(eac8+:', 507 | 'iRa>PGgTh8gx76XC+6kR[E|,R#ZQxUU&~+nWPxD.vcUn4|/@ZX$pPeaU{nydS_j/jNO4ey8xrq,'], 511 | ['.', 512 | ')]0ySL)C)m$}+<3eY|XDYfSf^?czef1m5)4k;Sv6I:*bi^pxF#;QojKK5?;', 513 | 'C-gIYrC_Gz0gr:r1S]vZ?.C:5?)M=a,[00', 519 | '#e+*#f~faGCUm:])1YTDHC,$VlPpsy*ohY]T[Jv_}4M0Jw7g{jHdKBs3.dyS'], 520 | ['1', 521 | '#lOq,sb)}o4io~h;}iUBa.)kX3:ghBOHH!#4}EkeYT*SqFAjSV(/hQKYa=S}', 522 | '#(-c+etEj{v;k^X1nXRjPu*3+AmND~r!w3D4OI,CM>u=:Wa/1yPtx:8xY9p5'], 523 | ['2', 524 | '#EhY0J-T!KSQ1/!z]?p3wmSWSUdS^{o8{u%)bY*_RB=E(h!HvU2)GujiRKu6', 525 | '1b8io&:m42j!WzT6g!2Ae>&>GL2FI9RDMjT-7Mx:y0DP=h696@z^s,!==g{'], 526 | ['3', 527 | '#^C?ma>A9[5LLsWWn5B3DWV8.flwpVcsJGA.O-sTic=H-AC:9q1t&N80Sk>n', 528 | '#_v{GPUQFPFEc^a5E-Fr3AM=RZNG77aIFwv_paLB!l#R%^+1h;ao;Ml2p{Hv'], 529 | ['4', 530 | 'OnTK|}7s%8x+PzIyp>VXS}i@JRy]D_FIiZFHK4.~_]', 531 | 'fjtoBqPY5h!S#*|^uJfJbGm(4^*Cytn8!cS4EgzD*aG-Uo.5ySaObUNR0]^'], 532 | ['5', 533 | '#f4=/5pEsv.A3~)84&ShQFPty3:|AQa;mRy1kD&fPk&_?m/TgFf8JlvIKW?X', 534 | 'S$[/to}:5n&D;JwH6VL+?:^R%okl@eL{9XXM{([4v^9~zg:l=+ut5?oF>$-'], 535 | ['6', 536 | '#+#MolSLbcCH/qqnD3^2&CS.XF8${q|,', 540 | '[J8ogr2W0Feb&q$YP&{k{8n9nNet~b[3nAd!eQymaZ.FKADe6orDq?L;B6%'], 541 | ['=)R$U0!8,', 542 | '#;4RK_e,UNYLp.xj.uxCCXOu%E!>D.U*U@GGCOsE!a3_&88{5h1cPZ#-JQhg', 543 | 'L;=Q%^,Moo5Vwpo&}]lrHOL&9Sf#Ql|[iht*t3aK*p?KcSM.N*,7a3_H+{9'], 544 | ['h=k])#StiU[0IMl;b,', 545 | '#-AFgmk/.Il.[4fISD7O?a}Lio[+/pYUyYE;k=8~b?>tpMbo~>F:B%O98z;}', 546 | 'EGSFImGM[@Z1+pZP*F$u_CpF#JRGc'], 550 | ['OG+/AhyOvMJz_yODX|yI77rGa<]acz#1X>v&(RZrLt#s31{rr2[my4tH[.MX:mjOBvJ!-H;KM.[0<.s{i', 554 | '#dUQUW3NTg8MjvF-b;V5{ugtPLf05>~qy((3iq}d>&yHP0L;!&B0!3E4l=((', 555 | 'FN8^MN]aV^6c~]Q0^:~(=ZjZEcz7:Ca<^*bcUZcrbrJr^J2Y|]-/m/,,|e+'], 556 | ['AP^vHy&1Ync#)uwt{x$UX4WbG+So)?vMxA{q~CY0IR.]&qc[t$#+3;', 557 | '#V6yVV,Q-Z~iW0ir|-lQ-i~NSGKCKg8k&$|rZi;nrzRo)6A>VQ2R3r?CM45W', 558 | 'Q*&FU]Y/XQ}%,+ac$Bshf,q)Eq0xuh-a(c5@T%2WoTHt_4C.g%g3.s/k0vf'], 559 | ['%v5hOg,mwwL=~.+!PLO4-m^G3M=zW@+AR_(d@ArI*wOQZ<64U$BX:aXm);', 560 | 'm.MD|.I8|[LiXjP:EJ^biXnnF?Z~Ju3bVX%Vv3UF$WxAHT:n8BM/hMcC0rE', 561 | '#-QXdl8q7A/n[GB[jf~pH3gzVF3!wqX|7L/aRJ+cTM>_wB_UC%[XQTGT6/wpgsBvH8nuKonK}.J0l%xV)@/Ld(~vEo]r=i2Lo:R+Pz', 564 | '#IXDc32~^^|^^m$dDu;-fpj[*tZY(l_(L<)R$2:r9r)5B/d~>m<~#K=Z&7+/'], 565 | ['#x[vX3t}*DK8aK>X[Ds~||qago*>I}K?Tj&BFTRG}>jV}TO=0gVI', 567 | '#=28VS+x)+Z}LJR_n;6O1/=(yI|XnA4XeC|$ILZ^Xac5U#gWa%1F3sD[ck{q'], 568 | ['cJP1[}n;TrX9F^&uNIe6^K~UB][FCy/c:>-L$,KPKB7fP]%(M(&nhE=c6G/oXEigduk-k#=YHsd$C;b9YB!thb}PS#|>dh5=ONSJ5Ru', 570 | '#smlypNz/+f,_?@.,acZ>{N1/G|+}EX]8UY+DmrtM%3LdBgZuU3^2&CS.XF8${q|,', 579 | '#B2-eXks*9.Kk?RI-(??mV5*(EL^cgrGS~KFj#_.;-#AL%DrU3^v!jWV3lS{'], 580 | ['#|YCTA^%_FR2LDDKeUDAloP638l1-WlGoUjU4P=k-Jss}5qR||N-ErPHW5(pft'], 586 | ['#|YCTA^%_FR2LDDKeUDVXS}i@JRy]D_FIiZFHK4.~_]', 588 | '#8oMe}lS(3k1xC=6l_6Om+UWBJ-dc{(fwu-(Lz*tyASx~-B'], 589 | ['#|YCTA^%_FR2LDDKeUDA9[5LLsWWn5B3DWV8.flwpVcsJGA.O-sTic=H-AC:9q1t&N80Sk>n', 591 | '>cG/p*R:u.kied9AJ|IH3|XkWL|CPsY@ttIy(ef(~Yymp[)R.my-0,qZ3A+'], 592 | ['#|YCTA^%_FR2LDDKeUD5?)M=a,[00', 600 | ':Q}~1Y_,gETznw32ArgGJRdmL4}50T0=;G+X1?GVNO>])&y;$q@InL'], 601 | ['#|YCTA^%_FR2LDDKeUD/n0,GwqR:Z-1XSMK2:ba7nQ^t~NY{=/elh@,xO/KnP_W-6a&LcN8UH2'], 604 | ['#|YCTA^%_FR2LDDKeUD=s-Y6Y4ecs@6V(eac8+:', 612 | '#6)c8p8>,^*4+.'], 613 | ['#|YCTA^%_FR2LDDKeUDtsw6bCKpDx=h03EnCL>WAk8H_p?e?{7eZn{M)hi{8^2]{C[662~u?Z56(z,K_B%_#'], 619 | ['#|YCTA^%_FR2LDDKeUDeLHi_', 621 | '#v%Rag0g2[yno][,!T3vY]YMgM0qS{O/969Pa7N=HJK7+4(fyAS5gbWvEtiJ'], 622 | ['#|YCTA^%_FR2LDDKeUD/,g25JW2fGbTNSOc~tg-b}:78*Q=5OG!yr#UlaV@Mj', 627 | 'Gw6T:~E9*X@.ErL85Qp/%=81&yvIWR?TN=mY=vhCLZP4DHMSNOnY9sz$FaW'], 628 | ['#|YCTA^%_FR2LDDKeUDLMd/byEjh5sK;[a*BZ-zQwv3R(!ndz5KOPN|$]', 630 | 'HhU^$p.+_#jtQWcD09A:mTZ,.Cg,R[~H^zenwA+.lO&EvJ.AU0X%mO&n4,j#y(2xT1.BeDn@9cmfH', 636 | '#U)pOQnMs${aOL~nCHxPfroK*^B9>JwVZ,ya6;fI@qW5!zNZKJCE|]=G*$^?']], 637 | 'secp521r1/nistp521': [ 638 | ['#', 639 | '#6^MdvRYdx-!l7|sVny0W(X3PJQ_Mv(|5jxgkcT<' 640 | + '&Bm@8M3f%VL+U3??dsA5juQ6X|g3[!c1oFT(tK5O{', 641 | '#ejq!OonINDDR4Y.1x[SRM*U9rHhJ+tJz!PiGwl8' 642 | + '8!ScmlJ@W#*/PfgVK6NIsNMNd+P&/{++vmj)Omu+e'], 643 | ['$', 644 | 'H/7|Bzs(HQYe-4v(oica2kTVGN+9RS{l[JhVLzHL;' 645 | + '~U(hdMn8SX}NtGz_1iOR2YK#/~mKRW/vs.@=(dF', 646 | '#P2YwkRNpI&rasEJ<7y>V3/kUH&~u[<3#k_l%C_Jy' 647 | + '}n,f9reVG2!a_W?rwrt,3D:r6#NpuuxI%$}[]2Ti'], 648 | ['%', 649 | '$Xj.s|Opfj#lHCG5-!+eM57ws#+*..AP.aun1c+yp' 650 | + '#tS]_iVfezh7jRYeCW%ZG&cB0HaPRD!rY$JYs),.', 651 | '#z|nZVPU-iH9o=u?9@Es%EJ5$0O@Lg5aJc}g+AsYG' 652 | + '^z@kNRepbQ};kmby95>g7L$y~QYCtI,=izIOp,]B'], 653 | ['&', 654 | '@_y3gE.1(/yzrxriVoPNP:&42NaJ3H|(kvtl0k8Zn[-DIc*FC$mO49pd_Yuye0;-*', 656 | 'lo,XTYG&u{[ZAYG0=V_nOGSQh)GV;2R'], 658 | ['(', 659 | 'Zt776Pfc4CuZZ@u#A1J5:8@0Vd^HX3XL7MuLz;D7L' 660 | + 'DDgH>BT4a:JPm<1gL0+.][E[oZ;KUu', 661 | '$/1/}6}ek*p&:$iRx2VFus?zT!(x0Y~BOqO/.u8i>' 662 | + 'TpK)J*Vu^cH(KF5V8TV[w1GT:vneb.1GFK,@yB9F'], 663 | [')', 664 | '%%!-xBSk)F]Qbr/r?zs_&#p-5<|%rbrweFqsBjB!w' 665 | + 'KUOAl*le:b(wW?HxN;O!MpR{vU:U$]?eOk+3', 676 | '$33YK:L^L6BvvecSZ8z!?s?5k2NpXOakK8DE/OlDT' 677 | + 'I|;yoB)Wo7KxX|/]>w}a}8w|;E2LG|[jf#XCrGh9'], 678 | [',', 679 | '$-3FJ;JTx@4a~k@g]kR*Of27qBU_l1g-*~~Q<*wB['], 683 | ['-', 684 | '$LJ_V@3&iQ0b!8#75Rh*3edSjkyz:?xkL0TtV|V;c' 685 | + 'gY}_M]6*/yO#O6102OV^am>=%/uJO-v@QCucFh=[', 686 | '%#Fbj:Dn|lz^[/CP[.GP#$-%$%-@n-]Nz;s/;;0In' 687 | + '/X1(&tE9f-zA$yrq7A2feO}qV5d7},XWQ9p7KqRh'], 688 | ['.', 689 | 'qMjMmblIv+L~m7)VG?zN3lJx/rpLN(^WUECz4#ZYJ' 690 | + '7KTu!Xq!0&XDUfCl%);]NpbNSXjut|o#wsCsQ,+', 691 | 'xF9/Mls*%;@egja@Y}j)la-zS]=]FPGrl2M}ow;4s%O8x^U&%<~[~)'], 693 | ['/', 694 | '$hw=_K,lII^JSuCLz|p?*]hv84ow-s2w&|_Z0G@ebJy:?q2/LO!*<2DVU;kI$%' 697 | + '4E3$6GGcL~A42.ePxim*5pPww)EBZS5Q/MRrU9W3'], 698 | ['0', 699 | 'jfX._&HYs5bZ@:3DdwdjLcbBg)lQ~BqieFCt:;(UO' 700 | + 'U55#g$-x-f?Aj5m4Q.LhbR]VFks@mYdMJjRl[p#', 701 | '#[@a>V~,Ya75=2^DdtA72.|/u?QY}ns5Ex@Y#' 705 | + 'b_%{/a{5WZsJ(=]3]Jys}$aqu]v,*-5x/&X:,mhs', 706 | 'V2:7w#8Q>Xb:H<+yYXdjq71B7x_,F2w-Rv?y[P82#' 707 | + 'T/1ixjqV3j_gou}KxGq!CJaoHL2tqdwM'], 708 | ['2', 709 | '_@c_9K.jB98-ey}Ig;zk:e!WB(@YJg*Iu+4^?NaGf' 710 | + '6fX[g=$Hk_-6Xal28(wY_!CH4H~(Yw[6Y^a,awL', 711 | '$b%p*WR7gR%Ss/:I(Jbr9gt6=+_+6>l475N!E/^cj' 712 | + 'tO.X5hS5A}V/#Gq~)TAR1}*LV%;(Z{*m2p_OIGb6'], 713 | ['3', 714 | '$r,AxDQ0|!]VDf-Y]$~?(V_T&#z-P]J0=R>sZ]4xW' 715 | + '~c#MvGV94n^3FxY{>?VS(_T@Q,X{?P|)X(54<}T@', 716 | 'hP?F0ot#Q9:gWwiHe#pu(j4INw', 721 | 'nb^2)[CAVz*!(h0l/.y)WWD:R>,KA0,OkpY)BL'], 723 | ['5', 724 | '$fB,T;=xbz05fG_XPetN{>!8#Mp~tvZ->nI@vZ9P?' 725 | + '=o_TUajMH1Y5t2.P7tUJ#uu~zhc^22HFa;SGjN[~', 726 | '#^XdN*Pt$$,lbvJPKk#NAB' 727 | + 'tZ@D~(S-hwj>WrPKk|KTA2}6)hvz[^Z@#xTx4FkM'], 728 | ['6', 729 | 'yn/,b]~#K8B~]maDLgePR7,-Z.uqjoTd}ebGv1T1|' 730 | + 'EO4/vb%hTt&D|g>Zv]25T2P.nV4UIh.|zASKe<4', 731 | '#w8;b[k:HloVi]5DZe3Dy^Bg]?:VfFBaeHeH~3-G!C([pQ' 737 | + 'f.OJ?m@T^^lC4lNozs2_-T-3@SpQbDjW~^I{XE3i'], 738 | ['=)R$U0!8,', 739 | '$43K{=:MACOx&!2cW>w#&lp{{V9L)y}rw+GUQu/Qe' 740 | + 'zEj:bTg4S~ZR,^)o:Hg=>~S;SMHWqkDkn3rSl?pw', 741 | '#^CXXP-zV.qjuAnH$U;>e#Js&hbDMxz>>FuX9AfHx' 742 | + '6&q-XM;vk=q9+8XNZ1|Z|=P0kSURXsc-Rp8cMU.<4@X@>>ENFmx154]$5dr8R/O:CU1U(zi;K', 762 | '#3Y$GMT1>u4%C&:0nTJU89P6&H_H2zwDXkzHG.)re' 763 | + 'DD]D6*=C:8L@F[BEJX&Cf!.q=&N$ZY}T7fA;?>x_', 764 | '$2/n$g:-lz),3*Gcji)z#6{T|9p/2Coi$JV~i3or+f1K%%obwqH'], 766 | [')66_g@;43JumBGNXv^;?{=JNHkxgB%&@d6zL=I5#+' 767 | + 'p3uvD/ceUzZg3q^Hjl8I[%E(%*[-R+Xexw@l$m', 768 | '#_Wl!7+c<*a(hT5xw@D_%QIq$bAMZ<}!td$6xe2y3' 769 | + 'J<46/$,A!JGVwfHT|R4[{l=F2H:XGf?JkeI#}MRvg' 771 | + 'n%0C@rxL?eAmzdAS2hrt8Bd#csaHUeDwu0m]5Lg#'], 772 | ['ET~M3_zxy:EG|WgD>ozjVD;tkxRRd7X%^tNV+n.--' 773 | + 'v16gj^)i4%pskU/p/[#YlwFl71@/nF=wk8X', 774 | '#s0^]RWkPQ*(#i0k7G/Ti}+UV8ut1k<%1Vu$iMntk' 775 | + 'Sbtua8I+:_,!zw}-CD}<-xYD6yxL>iX9;{m.Swur', 776 | '6[a?,#LZ3xcEUsXOByE}1%I]RQg,|ZlT@VFI>F/?0' 777 | + '3fzS08RysK(RnMkcfZ|9gEVtcwyHNCDa+Y|8Cy4'], 778 | ['+pC+{J@Pf0lQ;{~{#S;Z%D?HbiW0]CzPDvdI@g?Jl' 779 | + '8z[q{.', 780 | '$VuKjXu}+jjV>c!C)ei@|i={aNAQ_KbHS>(SF}*/L' 781 | + 'TW5;7qNppDI4p=LEvVD!e8ha%&J)fqWcP|;TEZV^', 782 | '#,4E*Bg6)~#[$p(R:KkJRp$I{,Bf-H#oBtA%n?}k0' 783 | + '4xfRAUIyjaj?=Q86@Z%(&tL|y~P=o~.>x;We{1,?'], 784 | ['#S6{p]=>Yj=U}$1e(O8,+Kj6?]/xZA,znh:R5rhl7' 785 | + 'wu;VK|BD/+kx^+x_ER,ua?V/x.WHu[bP@G6~', 786 | '$r(tmaOoNzz+@[laHAca206Kl[=o12wT?#8TeUPK|' 787 | + 'W9YeH8{4%A[5,4zsO+h1q:;_^aY$dY+!]AR9#gI=', 788 | 'asjDQQ!c!xo%NCzi5X[Qx)Qi&CMK=:<^{*pzPiXG*' 789 | + 'h%G2@;E;-_/OHl-d-zR7cLa:Rk-FH]6$3OaEn7q'], 790 | [';_Lqo|b(^n<.JK0Yk3bg>c+tiGpIh)%C6]H<;[4{g' 791 | + 'F.m=eqh],ZSppR:-A;R7/#v-VO4La,Qj9kBEI:', 792 | '$_7{;9_9MVEHC8@u$0;V{KEC3ybH>+yc/wV_ze,8/' 793 | + 'KA*hxF@z#b2t1]R,jj$;&-Qyt!Iozm1xIBTn5{={', 794 | 'a.0KGy437UhJ6e=^,IFx4gnAJ@A>lL@;1.OE4r]pS' 795 | + 'lPnwt(~_HHh@UzR8mkArtJprCbc/3=s1y4x7>FRe/ttGAf3;u8jee!/', 800 | '$<%geRzL[}i(x|~{Wq~%dE^0QbeWEk{~SHsUbrL0&' 801 | + 'XK)243VwogF4{afbi6$8K=i/bA/XVQJ*hqkGM@s*'], 802 | ['%%Cj07!Q*?,LNbkZIK0U|_MRmsrhfWrQ5,9v/TFs6u.j(Wn;c.:' 805 | + 'U1D&lD*zS=w%PeS+@Rf2.s)atppq)dJ(k7??N;|{', 806 | '#=0F6DF^,nO&8w2L]Nr>Nz/n!Ht-Y-LV}+=6.bv&m' 807 | + '4/Sc{b9@MZy5*GWD6}@)[]wgH=tB8qxqu{_|JB*.'], 808 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 809 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&j', 810 | '$I^EFx_]WcXYMK7zFrRY*E{*bZgUI/BKzv]Q@PTQl' 811 | + 'V5mm]0C]n7o^WMNt]8@-R[vcc9fGLh:<7NL~[Y(j', 812 | '#xZG|H_/,uf|9hYskFf$/6urOG,)/MZ:VAqf3Y9Tl' 813 | + 'Jh%NPtok6|^n#]pG_*6-0[QosnV%8[L.PI*,GZLX'], 814 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 815 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&k', 816 | 'yn/,b]~#K8B~]maDLgePR7,-Z.uqjoTd}ebGv1T1|' 817 | + 'EO4/vb%hTt&D|g>Zv]25T2P.nV4UIh.|zASKe<4', 818 | '#7U0&15,<7-V8*gGn~<FB' 819 | + '$hUWc(Q{l9~KrY{zK7KL+=dUi$#G1L7PmWM1IUjS'], 820 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 821 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&l', 822 | '$fB,T;=xbz05fG_XPetN{>!8#Mp~tvZ->nI@vZ9P?' 823 | + '=o_TUajMH1Y5t2.P7tUJ#uu~zhc^22HFa;SGjN[~', 824 | '#O5/X=|Q9yvhg%P@2A[q]2j,m8uiS*8Uq#h;i^G&{' 825 | + '<:5Sp[[6-dar[Vnkn|y6x~^m.YOY=BZFN/{/mXrt'], 826 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 827 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&m', 828 | '$^]@?W_D/wy?P,z*T&?DN+8xyb#}kl@m{aG$nT(D[' 829 | + '.!E19,#^q7g.dH%4(9#iLB+-YoW64iOP~(O>4INw', 830 | '$@,jUeC!CK!(~_4[^V2h$1Pl>x(TA_(p5YHQ}cF-]' 831 | + 'Ethp;c_Px8aoaa+S(v*]A,&Ip+t9nTuVFVf7Fx=u'], 832 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 833 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&n', 834 | '$r,AxDQ0|!]VDf-Y]$~?(V_T&#z-P]J0=R>sZ]4xW' 835 | + '~c#MvGV94n^3FxY{>?VS(_T@Q,X{?P|)X(54<}T@', 836 | '$F=,A]0NbPct:/$$#~JnUHd>)2@*F94v,9Q(l,mgY' 837 | + '[Q-a@5eu.Zuz|f5Mwl,rUN.?&_-.E4/g(' 839 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&o', 840 | '_@c_9K.jB98-ey}Ig;zk:e!WB(@YJg*Iu+4^?NaGf' 841 | + '6fX[g=$Hk_-6Xal28(wY_!CH4H~(Yw[6Y^a,awL', 842 | 'LhW^5L.{OvY.WbCE:jm4gXDX_-cAt9(R9jf1R7cS<' 843 | + 'EG@Zy[.R]t%5$M9SG&9,4TT]@/O>&/w@7qVVW|/'], 844 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 845 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&p', 846 | '$G1Byu[+EKx+wPd/n8Icspj&XM>/u?QY}ns5Ex@Y#' 847 | + 'b_%{/a{5WZsJ(=]3]Jys}$aqu]v,*-5x/&X:,mhs', 848 | '$V[1Os|.3eBLg>_bP+rza]9ISQq.KlrA[xBGY%E10' 849 | + '0@Fi(iEN>JaPMYI:1#|x:nB)]WBcZUTu*ZA30' 851 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&q', 852 | 'jfX._&HYs5bZ@:3DdwdjLcbBg)lQ~BqieFCt:;(UO' 853 | + 'U55#g$-x-f?Aj5m4Q.LhbR]VFks@mYdMJjRl[p#', 854 | '#QLhI5~:+BeydT=Gfo.JVe~VqiiyZbxipMx&a' 855 | + '|!q7O1Ba|*!v$o|ZQL[Do4o9[+E6#}Sva8,rbD5)'], 856 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 857 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&r', 858 | '$hw=_K,lII^JSuCLz|p?*]hv84ow-s2w&|GNc(@]%Dg}!T6;%W]@)3z|W*b2:tLD;' 861 | + '|OBuZYfb3Wc(A.+==$5>M$5CX}5Jh)2'], 862 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 863 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&s', 864 | 'qMjMmblIv+L~m7)VG?zN3lJx/rpLN(^WUECz4#ZYJ' 865 | + '7KTu!Xq!0&XDUfCl%);]NpbNSXjut|o#wsCsQ,+', 866 | '$6G2X?3OZ}an;}2--+MuE4}Em6nFG2>m1-Hm}xHwG' 867 | + 'k161$EC_havRwlxg5*XXl3m+y1SQIi>(z$Mk!C_<'], 868 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 869 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&t', 870 | '$LJ_V@3&iQ0b!8#75Rh*3edSjkyz:?xkL0TtV|V;c' 871 | + 'gY}_M]6*/yO#O6102OV^am>=%/uJO-v@QCucFh=[', 872 | '.Ff{RYSf6~ODWW;nW(1JLCTre_Kh)Fim3EX;Ff{P$' 873 | + '' 875 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&u', 876 | '$-3FJ;JTx@4a~k@g]kR*Of27qBU_' 879 | + '^U' 881 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&v', 882 | '&OI[d{V_+$nz5AOk+3', 884 | '{Yn6tBG2+#if' 885 | + 'v9}!B*h&F~8ZM3Y>$G+' 887 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&w', 888 | 'R{UZ+gXq;mDpn2~zX:-dR:NNL(h0h5[FKqa#$~]Y5' 889 | + 'pWm$@E(j+i+5Z/&yqC*ZMEJP)&v5M?9nUP1_Gqy', 890 | '$j!CYwHK05z^h|=|s4uJxFxOtvAbdh5HEPx=k8N#o' 891 | + '?{ve^g;.r|5Nl=!9pjSG@mkm2A{+:ic55k)VyBdw'], 892 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 893 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&x', 894 | '%%!-xBSk)F]Qbr/r?zs_&#p-5<|%rbrweFqsBjB!w' 895 | + 'KUOAl*le:b(wW?HxN;O!MpR{vU:U$]?e' 899 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&y', 900 | 'Zt776Pfc4CuZZ@u#A1J5:8@0Vd^HX3XL7MuLz;D7L' 901 | + 'DDgH>BT4a:JPm<1gL0+.][E[oZ;KUu', 902 | '#!];hU~]wy-+gc39QRu:TZ0]AhdrGW%v9YiXGj]]!' 903 | + 'Z%*qFYWJ6y%,j&-aDFn/C#7LyKWp8t)?,[Gg(^F{'], 904 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 905 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&z', 906 | '@_y3gE.1(/yzrxriVoPNP:&42NaJ3H|(kvtl0k8Zn[-DIc*FC$mO49pd_Yuye0;-*', 908 | '$A|?/8D{^-}Qe,4%sn|QB}{3uqJ7Pyc+gBQE.Y)=/' 909 | + '3(4t=B9&={:6@U1ay(kL^p$Z((ou,Po2}@J_IfMo'], 910 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 911 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&{', 912 | '$Xj.s|Opfj#lHCG5-!+eM57ws#+*..AP.aun1c+yp' 913 | + '#tS]_iVfezh7jRYeCW%ZG&cB0HaPRD!rY$JYs),.', 914 | '#3nY-6MmW:Ru2I&M4E)lH,%ErYi:~K@!kv' 915 | + 'Px5/B1JO4,LuI[]>Ch*%%eY)5pm4%W.Hg.*V1u#!'], 916 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 917 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&|', 918 | 'H/7|Bzs(HQYe-4v(oica2kTVGN+9RS{l[JhVLzHL;' 919 | + '~U(hdMn8SX}NtGz_1iOR2YK#/~mKRW/vs.@=(dF', 920 | '#]Zmn!KsrXu@m@Aek_Ui)faWyP>6zD' 921 | + '3(I4VoJiMLJOTr!Dc*Pa*mD0~Awe$,>=M%RJCo+X'], 922 | ['%/lHfk}Cd#z/~eykJdL_KNMWthjkU4%9hL9fS_tF>' 923 | + 'en@bh3bsv/<8Bz-3iXbbkCg|k/xooigfaHF/Mk&}', 924 | '#6^MdvRYdx-!l7|sVny0W(X3PJQ_Mv(|5jxgkcT<&' 925 | + 'Bm@8M3f%VL+U3??dsA5juQ6X|g3[!c1oFT(tK5O{', 926 | '#I#Vf=0T;SVjMRA_;jp-x#F$[uC%.+0mmKg|.h+1*' 927 | + '1@p-%9ohrT>_NchlFLzukd0?,rCG{w.mc>JV4,T]']]} 928 | 929 | 930 | class TestEC(unittest.TestCase): 931 | 932 | def test_nist_testvectors(self): 933 | vectors = nist_test_vectors() 934 | for curvename in vectors: 935 | curve = seccure.Curve.by_name(curvename) 936 | for k, x, y in vectors[curvename]: 937 | k = seccure.deserialize_number(k, seccure.SER_COMPACT) 938 | x = seccure.deserialize_number(x, seccure.SER_COMPACT) 939 | y = seccure.deserialize_number(y, seccure.SER_COMPACT) 940 | self.assertEqual(curve.base * k, 941 | seccure.AffinePoint(x, y, curve)) 942 | 943 | 944 | if __name__ == '__main__': 945 | unittest.main() 946 | --------------------------------------------------------------------------------