├── pure25519 ├── __init__.py ├── dh.py ├── test_dh.py ├── speed_dh.py ├── test_spake2.py ├── speed_spake2.py ├── speed_ed25519.py ├── spake2.py ├── slow_basic.py ├── do_ed25519_kat.py ├── eddsa.py ├── test_orders.py ├── _ed25519.py ├── speed_basic.py ├── test_basic.py ├── test_replacements.py ├── ed25519_oop.py ├── test_ed25519.py └── basic.py ├── .gitignore ├── LICENSE ├── misc ├── speed_orig_ed25519.py ├── orig_ed25519.py └── djbec.py ├── setup.py └── README.md /pure25519/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pure25519/dh.py: -------------------------------------------------------------------------------- 1 | from pure25519.basic import random_scalar, Base, bytes_to_element 2 | from hashlib import sha256 3 | 4 | # In practice, you should use the Curve25519 function, which is better in 5 | # every way. But this is an example of what Diffie-Hellman looks like. 6 | 7 | def dh_start(entropy_f): 8 | x = random_scalar(entropy_f) 9 | X = Base.scalarmult(x) 10 | return x,X.to_bytes() 11 | 12 | def dh_finish(x, Y_s): 13 | Y = bytes_to_element(Y_s) 14 | XY = Y.scalarmult(x) 15 | return sha256(XY.to_bytes()).digest() 16 | -------------------------------------------------------------------------------- /pure25519/test_dh.py: -------------------------------------------------------------------------------- 1 | import os, unittest 2 | from binascii import hexlify 3 | from pure25519.basic import encodepoint 4 | from pure25519.dh import dh_start, dh_finish 5 | 6 | class DH(unittest.TestCase): 7 | def assertElementsEqual(self, e1, e2): 8 | self.assertEqual(hexlify(encodepoint(e1)), hexlify(encodepoint(e2))) 9 | def assertBytesEqual(self, e1, e2): 10 | self.assertEqual(hexlify(e1), hexlify(e2)) 11 | 12 | def test_dh(self): 13 | for i in range(10): 14 | x,X_s = dh_start(os.urandom) 15 | y,Y_s = dh_start(os.urandom) 16 | z1 = dh_finish(x, Y_s) 17 | z2 = dh_finish(y, X_s) 18 | self.assertBytesEqual(z1, z2) 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | "python-pure25519" Copyright (c) 2015 Brian Warner and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /pure25519/speed_dh.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | def do(setup_statements, statement): 4 | # extracted from timeit.py 5 | t = timeit.Timer(stmt=statement, 6 | setup="\n".join(setup_statements)) 7 | # determine number so that 0.2 <= total time < 2.0 8 | for i in range(1, 10): 9 | number = 10**i 10 | x = t.timeit(number) 11 | if x >= 0.5: 12 | break 13 | return x / number 14 | 15 | def abbrev(t): 16 | if t > 1.0: 17 | return "%.3fs" % t 18 | if t > 1e-3: 19 | return "%.2fms" % (t*1e3) 20 | return "%.2fus" % (t*1e6) 21 | 22 | def p(name, setup_statements, statements): 23 | t = sorted([do(setup_statements, statements) for i in range(3)]) 24 | print("%12s: %s (%s)" % (name, 25 | abbrev(min(t)), 26 | " ".join([abbrev(s) for s in t]))) 27 | 28 | def run(): 29 | S1 = "import os; from pure25519 import dh" 30 | S2 = "x,X_s = dh.dh_start(os.urandom)" 31 | S3 = "y,Y_s = dh.dh_start(os.urandom)" 32 | S4 = "dh.dh_finish(x,Y_s)" 33 | 34 | print("speed_dh") 35 | p("start", [S1], S2) 36 | p("finish", [S1, S2, S3], S4) 37 | 38 | if __name__ == "__main__": 39 | run() 40 | -------------------------------------------------------------------------------- /pure25519/test_spake2.py: -------------------------------------------------------------------------------- 1 | import os, unittest 2 | from binascii import hexlify 3 | from pure25519.spake2 import start_U, finish_U, start_V, finish_V, U, V 4 | 5 | class SPAKE2(unittest.TestCase): 6 | def assertBytesEqual(self, e1, e2): 7 | self.assertEqual(hexlify(e1), hexlify(e2)) 8 | 9 | def test_success(self): 10 | pw = b"password" 11 | sd_U,X = start_U(pw, os.urandom, b"idA", b"idB") 12 | sd_V,Y = start_V(pw, os.urandom, b"idA", b"idB") 13 | K1 = finish_U(sd_U,Y) 14 | K2 = finish_V(sd_V,X) 15 | self.assertBytesEqual(K1, K2) 16 | 17 | def test_failure(self): 18 | sd_U,X = start_U(b"password", os.urandom, b"idA", b"idB") 19 | sd_V,Y = start_V(b"wrong", os.urandom, b"idA", b"idB") 20 | K1 = finish_U(sd_U,Y) 21 | K2 = finish_V(sd_V,X) 22 | self.assertNotEqual(hexlify(K1), hexlify(K2)) 23 | 24 | def test_blinding_factors(self): 25 | self.assertEqual(hexlify(U.to_bytes()).decode("ascii"), expected_U) 26 | self.assertEqual(hexlify(V.to_bytes()).decode("ascii"), expected_V) 27 | 28 | expected_U = "6d7107929f9fb8ddeb0788f4bd6cd0a39b5cdcf71b03c41029aae74eda5f64f3" 29 | expected_V = "48032d6a3406ad4860cca367750cea4f26ea14265ad57ffc6aefe9bf68994054" 30 | -------------------------------------------------------------------------------- /pure25519/speed_spake2.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | def do(setup_statements, statement): 4 | # extracted from timeit.py 5 | t = timeit.Timer(stmt=statement, 6 | setup="\n".join(setup_statements)) 7 | # determine number so that 0.2 <= total time < 2.0 8 | for i in range(1, 10): 9 | number = 10**i 10 | x = t.timeit(number) 11 | if x >= 0.5: 12 | break 13 | return x / number 14 | 15 | def abbrev(t): 16 | if t > 1.0: 17 | return "%.3fs" % t 18 | if t > 1e-3: 19 | return "%.2fms" % (t*1e3) 20 | return "%.2fus" % (t*1e6) 21 | 22 | def p(name, setup_statements, statements): 23 | t = sorted([do(setup_statements, statements) for i in range(3)]) 24 | print("%12s: %s (%s)" % (name, 25 | abbrev(min(t)), 26 | " ".join([abbrev(s) for s in t]))) 27 | 28 | def run(): 29 | S1 = "import os; from pure25519 import spake2; pw=b'pw'; A=b'idA'; B=b'idB'" 30 | S2 = "sdata_U,X_s = spake2.start_U(pw, os.urandom, A, B)" 31 | S3 = "sdata_V,Y_s = spake2.start_V(pw, os.urandom, A, B)" 32 | S4 = "k = spake2.finish_U(sdata_U,Y_s)" 33 | 34 | print("speed_spake2") 35 | p("start", [S1], S2) 36 | p("finish", [S1, S2, S3], S4) 37 | 38 | if __name__ == "__main__": 39 | run() 40 | -------------------------------------------------------------------------------- /misc/speed_orig_ed25519.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | def do(setup_statements, statement): 4 | # extracted from timeit.py 5 | t = timeit.Timer(stmt=statement, 6 | setup="\n".join(setup_statements)) 7 | # determine number so that 1.0 <= total time < 10.0 8 | for i in range(1, 10): 9 | number = 10**i 10 | x = t.timeit(number) 11 | if x >= 1.0: 12 | break 13 | return x / number 14 | 15 | def abbrev(t): 16 | if t > 1.0: 17 | return "%.3fs" % t 18 | if t > 1e-3: 19 | return "%.2fms" % (t*1e3) 20 | return "%.2fus" % (t*1e6) 21 | 22 | def p(name, setup_statements, statements): 23 | t = sorted([do(setup_statements, statements) for i in range(3)]) 24 | print("%12s: %s (%s)" % (name, 25 | abbrev(min(t)), 26 | " ".join([abbrev(s) for s in t]))) 27 | 28 | def run(): 29 | S1 = "import orig_ed25519 as orig; from hashlib import sha256; msg=b'hello world'; sk=b'32-ish random bytes'" 30 | S2 = "pk = orig.publickey(sk)" 31 | S3 = "sig = orig.signature(msg,sk,pk)" 32 | S4 = "orig.checkvalid(sig,msg,pk)" 33 | 34 | print("speed_orig") 35 | p("generate", [S1], S2) 36 | p("sign", [S1, S2], S3) 37 | p("verify", [S1, S2, S3], S4) 38 | 39 | if __name__ == "__main__": 40 | run() 41 | -------------------------------------------------------------------------------- /pure25519/speed_ed25519.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | def do(setup_statements, statement): 4 | # extracted from timeit.py 5 | t = timeit.Timer(stmt=statement, 6 | setup="\n".join(setup_statements)) 7 | # determine number so that 1.0 <= total time < 10.0 8 | for i in range(1, 10): 9 | number = 10**i 10 | x = t.timeit(number) 11 | if x >= 1.0: 12 | break 13 | return x / number 14 | 15 | def abbrev(t): 16 | if t > 1.0: 17 | return "%.3fs" % t 18 | if t > 1e-3: 19 | return "%.2fms" % (t*1e3) 20 | return "%.2fus" % (t*1e6) 21 | 22 | def p(name, setup_statements, statements): 23 | t = sorted([do(setup_statements, statements) for i in range(3)]) 24 | print("%12s: %s (%s)" % (name, 25 | abbrev(min(t)), 26 | " ".join([abbrev(s) for s in t]))) 27 | 28 | def run(): 29 | S1 = "from pure25519 import ed25519_oop; msg=b'hello world'" 30 | S2 = "sk,vk = ed25519_oop.create_keypair()" 31 | S3 = "sig = sk.sign(msg)" 32 | S4 = "vk.verify(sig, msg)" 33 | 34 | print("speed_ed25519") 35 | p("generate", [S1], S2) 36 | p("sign", [S1, S2], S3) 37 | p("verify", [S1, S2, S3], S4) 38 | 39 | S5 = "from pure25519 import eddsa" 40 | S6 = "h=eddsa.Hint(b'')" 41 | S7 = "eddsa.checkvalid(sig, msg, vk.vk_s)" 42 | 43 | p("Hint", [S5], S6) 44 | p("checkvalid", [S1,S2,S3,S5], S7) 45 | 46 | if __name__ == "__main__": 47 | run() 48 | -------------------------------------------------------------------------------- /pure25519/spake2.py: -------------------------------------------------------------------------------- 1 | from hashlib import sha256 2 | from pure25519.basic import (arbitrary_element, bytes_to_element, Base, 3 | random_scalar, password_to_scalar) 4 | 5 | # a,b random. X=G*a+U*pw. Y=G*b+V*pw. Z1=(Y-V*pw)*a. Z2=(X-U*pw)*b 6 | 7 | U = arbitrary_element(b"U") 8 | V = arbitrary_element(b"V") 9 | 10 | def _start(pw, entropy_f, blinding): 11 | a = random_scalar(entropy_f) 12 | pw_scalar = password_to_scalar(pw) 13 | X = Base.scalarmult(a).add(blinding.scalarmult(pw_scalar)) 14 | X_s = X.to_bytes() 15 | return (a, pw_scalar), X_s 16 | 17 | def _finish(start_data, Y_s, blinding): 18 | (a, pw_scalar) = start_data 19 | Y = bytes_to_element(Y_s) # rejects zero and non-group 20 | Z = Y.add(blinding.scalarmult(-pw_scalar)).scalarmult(a) 21 | return Z.to_bytes() 22 | 23 | 24 | def start_U(pw, entropy_f, idA, idB, U=U, V=V): 25 | sdata, X_s = _start(pw, entropy_f, U) 26 | start_data = (sdata, pw, X_s, idA, idB, U, V) 27 | return start_data, X_s 28 | 29 | def finish_U(start_data, Y_s): 30 | (sdata, pw, X_s, idA, idB, U, V) = start_data 31 | Z_s = _finish(sdata, Y_s, V) 32 | transcript = idA + idB + X_s + Y_s + Z_s + pw 33 | key = sha256(transcript).digest() 34 | return key 35 | 36 | 37 | def start_V(pw, entropy_f, idA, idB, U=U, V=V): 38 | sdata, Y_s = _start(pw, entropy_f, V) 39 | start_data = (sdata, pw, Y_s, idA, idB, U, V) 40 | return start_data, Y_s 41 | 42 | def finish_V(start_data, X_s): 43 | (sdata, pw, Y_s, idA, idB, U, V) = start_data 44 | Z_s = _finish(sdata, X_s, U) 45 | transcript = idA + idB + X_s + Y_s + Z_s + pw 46 | key = sha256(transcript).digest() 47 | return key 48 | 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import print_function 4 | import sys, unittest 5 | from distutils.core import setup, Command 6 | 7 | class Test(Command): 8 | description = "run tests" 9 | user_options = [] 10 | def initialize_options(self): 11 | pass 12 | def finalize_options(self): 13 | pass 14 | def run(self): 15 | test = unittest.defaultTestLoader.discover("pure25519") 16 | runner = unittest.TextTestRunner(verbosity=2) 17 | result = runner.run(test) 18 | sys.exit(not result.wasSuccessful()) 19 | 20 | class KnownAnswerTest(Test): 21 | description = "run known-answer-tests" 22 | def run(self): 23 | test = unittest.defaultTestLoader.loadTestsFromName("pure25519.do_ed25519_kat") 24 | runner = unittest.TextTestRunner(verbosity=2) 25 | result = runner.run(test) 26 | sys.exit(not result.wasSuccessful()) 27 | 28 | class Speed(Test): 29 | description = "run benchmark suite" 30 | def run(self): 31 | from pure25519 import speed_basic, speed_ed25519, speed_dh, speed_spake2 32 | speed_basic.run() 33 | speed_ed25519.run() 34 | speed_dh.run() 35 | speed_spake2.run() 36 | 37 | setup(name="pure25519", 38 | version="0", # not for publication 39 | description="pure-python curve25519/ed25519 routines", 40 | author="Brian Warner", 41 | author_email="warner-python-pure25519@lothar.com", 42 | license="MIT", 43 | url="https://github.com/warner/python-pure25519", 44 | packages=["pure25519"], 45 | package_dir={"pure25519": "pure25519"}, 46 | cmdclass={"test": Test, "speed": Speed, "test_kat": KnownAnswerTest}, 47 | ) 48 | -------------------------------------------------------------------------------- /pure25519/slow_basic.py: -------------------------------------------------------------------------------- 1 | 2 | from pure25519.basic import (inv, d, Q, L, 3 | xform_extended_to_affine, 4 | scalarmult_element, 5 | xform_affine_to_extended, 6 | double_element, _add_elements_nonunfied) 7 | 8 | # Affine Coordinates: only here to compare against faster versions 9 | 10 | def slow_add_affine(A,B): # affine->affine 11 | # Complete: works even when A==B. Slow: 50x slower than extended 12 | x1 = A[0] 13 | y1 = A[1] 14 | x2 = B[0] 15 | y2 = B[1] 16 | x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2) 17 | y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2) 18 | return (x3 % Q,y3 % Q) 19 | 20 | def slow_scalarmult_affine(A,e): # affine->affine 21 | e = e % L 22 | if e == 0: return [0,1] 23 | B = slow_scalarmult_affine(A,e//2) 24 | B = slow_add_affine(B,B) 25 | if e & 1: B = slow_add_affine(B,A) 26 | return B 27 | 28 | # other functions that are only here for speed comparisons 29 | 30 | def scalarmult_affine(pt, e): # affine->affine 31 | e = e % L 32 | return xform_extended_to_affine( 33 | scalarmult_element( 34 | xform_affine_to_extended(pt), 35 | e)) 36 | 37 | def scalarmult_affine_to_extended(pt, n): # affine->extended 38 | assert len(pt) == 2 # affine 39 | n = n % L 40 | if n==0: return xform_affine_to_extended((0,1)) 41 | xpt = xform_affine_to_extended(pt) # so Z=1 42 | return _scalarmult_affine_to_extended_inner(xpt, n) 43 | 44 | def _scalarmult_affine_to_extended_inner(xpt, n): 45 | if n==0: return xform_affine_to_extended((0,1)) 46 | _ = double_element(_scalarmult_affine_to_extended_inner(xpt, n>>1)) 47 | return _add_elements_nonunfied(_, xpt) if n&1 else _ 48 | -------------------------------------------------------------------------------- /pure25519/do_ed25519_kat.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import unittest 3 | from binascii import hexlify, unhexlify 4 | from pure25519.ed25519_oop import SigningKey, VerifyingKey 5 | 6 | class KnownAnswerTests(unittest.TestCase): 7 | def test_all(self): 8 | # kat-ed25519.txt comes from "sign.input" on ed25519.cr.yp.to . The 9 | # pure-python ed25519.py in the same distribution uses a very 10 | # different key format than the one used by NaCl. 11 | lines = list(open("pure25519/kat-ed25519.txt")) 12 | for i,line in enumerate(lines): 13 | if not i%50: print("%d/%d" % (i, len(lines))) 14 | x = line.split(":") 15 | A,B,C,D = [unhexlify(i) for i in x[:4]] 16 | # A[:32] is the 32 byte seed (the entropy input to H()) 17 | # A[32:] == B == the public point (pubkey) 18 | # C is the message 19 | # D is 64 bytes of signature (R+S) prepended to the message 20 | 21 | seed = A[:32] 22 | vk_s = B 23 | # the NaCl signature is R+S, which happens to be the same as ours 24 | msg = C 25 | sig = D[:64] # R+S 26 | # note that R depends only upon the second half of H(seed). S 27 | # depends upon both the first half (the exponent) and the second 28 | # half 29 | 30 | #if len(msg) % 16 == 1: 31 | # print "msg len = %d" % len(msg), time.time() 32 | 33 | sk = SigningKey(seed) 34 | vk = sk.get_verifying_key() 35 | self.failUnlessEqual(vk.to_bytes(), vk_s) 36 | vk2 = VerifyingKey(vk_s) 37 | self.failUnlessEqual(vk2, vk) # objects should compare equal 38 | self.failUnlessEqual(vk2.to_bytes(), vk_s) 39 | newsig = sk.sign(msg) # R+S 40 | self.failUnlessEqual(hexlify(newsig), hexlify(sig)) # deterministic sigs 41 | self.failUnlessEqual(vk.verify(sig, msg), None) # no exception 42 | 43 | 44 | if __name__ == '__main__': 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /pure25519/eddsa.py: -------------------------------------------------------------------------------- 1 | 2 | from pure25519.basic import (bytes_to_clamped_scalar, 3 | bytes_to_scalar, scalar_to_bytes, 4 | bytes_to_element, Base) 5 | import hashlib, binascii 6 | 7 | def H(m): 8 | return hashlib.sha512(m).digest() 9 | 10 | def publickey(seed): 11 | # turn first half of SHA512(seed) into scalar, then into point 12 | assert len(seed) == 32 13 | a = bytes_to_clamped_scalar(H(seed)[:32]) 14 | A = Base.scalarmult(a) 15 | return A.to_bytes() 16 | 17 | def Hint(m): 18 | h = H(m) 19 | return int(binascii.hexlify(h[::-1]), 16) 20 | 21 | def signature(m,sk,pk): 22 | assert len(sk) == 32 # seed 23 | assert len(pk) == 32 24 | h = H(sk[:32]) 25 | a_bytes, inter = h[:32], h[32:] 26 | a = bytes_to_clamped_scalar(a_bytes) 27 | r = Hint(inter + m) 28 | R = Base.scalarmult(r) 29 | R_bytes = R.to_bytes() 30 | S = r + Hint(R_bytes + pk + m) * a 31 | return R_bytes + scalar_to_bytes(S) 32 | 33 | def checkvalid(s, m, pk): 34 | if len(s) != 64: raise Exception("signature length is wrong") 35 | if len(pk) != 32: raise Exception("public-key length is wrong") 36 | R = bytes_to_element(s[:32]) 37 | A = bytes_to_element(pk) 38 | S = bytes_to_scalar(s[32:]) 39 | h = Hint(s[:32] + pk + m) 40 | v1 = Base.scalarmult(S) 41 | v2 = R.add(A.scalarmult(h)) 42 | return v1==v2 43 | 44 | # wrappers 45 | 46 | import os 47 | 48 | def create_signing_key(): 49 | seed = os.urandom(32) 50 | return seed 51 | def create_verifying_key(signing_key): 52 | return publickey(signing_key) 53 | 54 | def sign(skbytes, msg): 55 | """Return just the signature, given the message and just the secret 56 | key.""" 57 | if len(skbytes) != 32: 58 | raise ValueError("Bad signing key length %d" % len(skbytes)) 59 | vkbytes = create_verifying_key(skbytes) 60 | sig = signature(msg, skbytes, vkbytes) 61 | return sig 62 | 63 | def verify(vkbytes, sig, msg): 64 | if len(vkbytes) != 32: 65 | raise ValueError("Bad verifying key length %d" % len(vkbytes)) 66 | if len(sig) != 64: 67 | raise ValueError("Bad signature length %d" % len(sig)) 68 | rc = checkvalid(sig, msg, vkbytes) 69 | if not rc: 70 | raise ValueError("rc != 0", rc) 71 | return True 72 | -------------------------------------------------------------------------------- /pure25519/test_orders.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from binascii import hexlify 3 | from pure25519.basic import Zero, ElementOfUnknownGroup 4 | from pure25519.basic import xform_affine_to_extended, L, bytes_to_unknown_group_element 5 | 6 | ORDERS = {1: "1", 2: "2", 4: "4", 8: "8", 7 | 1*L: "1*L", 2*L: "2*L", 4*L: "4*L", 8*L: "8*L"} 8 | def get_order(e): 9 | for o in sorted(ORDERS): 10 | if e.scalarmult(o) == Zero: 11 | return o 12 | 13 | class Orders(unittest.TestCase): 14 | def assertElementsEqual(self, e1, e2, msg=None): 15 | self.assertEqual(hexlify(e1.to_bytes()), hexlify(e2.to_bytes()), 16 | msg) 17 | 18 | def collect(self, e): 19 | values = set() 20 | for i in range(400): 21 | values.add(e.scalarmult(i).to_bytes()) 22 | return values 23 | 24 | def test_orders(self): 25 | # all points should have an order that's listed in ORDERS. Test some 26 | # specific points. For low-order points, actually find the complete 27 | # subgroup and measure its size. 28 | 29 | p = Zero 30 | values = self.collect(p) 31 | self.assertEqual(len(values), 1) 32 | self.assertEqual(values, set([Zero.to_bytes()])) 33 | self.assertEqual(get_order(p), 1) 34 | 35 | # (0,-1) should be order 2 36 | p = ElementOfUnknownGroup(xform_affine_to_extended((0,-1))) 37 | values = self.collect(p) 38 | self.assertEqual(len(values), 2) 39 | self.assertEqual(values, set([Zero.to_bytes(), p.to_bytes()])) 40 | self.assertEqual(get_order(p), 2) 41 | 42 | # (..,26) is in the right group (order L) 43 | b = b"\x1a" + b"\x00"*31 44 | p = bytes_to_unknown_group_element(b) 45 | self.assertEqual(get_order(p), L) 46 | 47 | # (..,35) is maybe order 2*L 48 | b = b"\x23" + b"\x00"*31 49 | p = bytes_to_unknown_group_element(b) 50 | self.assertEqual(get_order(p), 2*L) 51 | 52 | # (..,48) is maybe order 4*L 53 | b = b"\x30" + b"\x00"*31 54 | p = bytes_to_unknown_group_element(b) 55 | self.assertEqual(get_order(p), 4*L) 56 | 57 | # (..,55) is maybe order 8*L 58 | b = b"\x37" + b"\x00"*31 59 | p = bytes_to_unknown_group_element(b) 60 | self.assertEqual(get_order(p), 8*L) 61 | -------------------------------------------------------------------------------- /pure25519/_ed25519.py: -------------------------------------------------------------------------------- 1 | from . import eddsa 2 | 3 | # adapt pure25519/ed25519.py to behave like (C/glue) ed25519/_ed25519.py, so 4 | # ed25519_oop.py doesn't have to change 5 | 6 | # ed25519 secret/private/signing keys can be built from a 32-byte random seed 7 | # (clamped and treated as a scalar). The public/verifying key is a 32-byte 8 | # encoded group element. Signing requires both, so a common space/time 9 | # tradeoff is to glue the two together and call the 64-byte combination the 10 | # "secret key". Signatures are 64 bytes (one encoded group element and one 11 | # encoded scalar). The NaCl code prefers to glue the signature to the 12 | # message, rather than pass around detacted signatures. 13 | # 14 | # for clarity, we use the following notation: 15 | # seed: 32-byte secret random seed (unclamped) 16 | # sk: = seed 17 | # vk: 32-byte verifying key (encoded group element) 18 | # seed+vk: 64-byte glue thing (sometimes stored as secret key) 19 | # sig: 64-byte detached signature (R+S) 20 | # sig+msg: signature concatenated to message 21 | 22 | # that glue provides: 23 | # SECRETKEYBYTES=64, PUBLICKEYBYTES=32, SIGNATUREBYTES=64 24 | # (vk,seed+vk)=publickey(seed) 25 | # sig+msg = sign(msg, seed+vk) 26 | # msg = open(sig+msg, vk) # or raise BadSignatureError 27 | 28 | # pure25519/ed25519.py provides: 29 | # vk = publickey(sk) 30 | # sig = signature(msg, sk or sk+vk, vk) 31 | # bool = checkvalid(sig, msg, vk) 32 | 33 | class BadSignatureError(Exception): 34 | pass 35 | 36 | SECRETKEYBYTES = 64 37 | PUBLICKEYBYTES = 32 38 | SIGNATUREKEYBYTES = 64 39 | 40 | def publickey(seed32): 41 | assert len(seed32) == 32 42 | vk32 = eddsa.publickey(seed32) 43 | return vk32, seed32+vk32 44 | 45 | def sign(msg, skvk): 46 | assert len(skvk) == 64 47 | sk = skvk[:32] 48 | vk = skvk[32:] 49 | sig = eddsa.signature(msg, sk, vk) 50 | return sig+msg 51 | 52 | def open(sigmsg, vk): 53 | assert len(vk) == 32 54 | sig = sigmsg[:64] 55 | msg = sigmsg[64:] 56 | try: 57 | valid = eddsa.checkvalid(sig, msg, vk) 58 | except ValueError as e: 59 | raise BadSignatureError(e) 60 | except Exception as e: 61 | if str(e) == "decoding point that is not on curve": 62 | raise BadSignatureError(e) 63 | raise 64 | if not valid: 65 | raise BadSignatureError() 66 | return msg 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-pure25519 2 | 3 | This contains a collection of pure-python functions to implement Curve25519-based cryptography, including: 4 | 5 | * Diffie-Hellman Key Agreement 6 | * Ed25519 digital signatures 7 | * SPAKE2 Password Authenticated Key Agreement 8 | 9 | You almost certainly want to use [pynacl](https://pypi.python.org/pypi/PyNaCl/) or [python-ed25519](https://pypi.python.org/pypi/ed25519) instead, which are python bindings to djb's C implementations of Curve25519/Ed25519 (and the rest of the NaCl suite). 10 | 11 | Bad things about this module: 12 | 13 | * much slower than C 14 | * not written by djb, so probably horribly buggy and insecure 15 | * very much not constant-time: leaks hamming weights like crazy 16 | 17 | Good things about this module: 18 | 19 | * can be used without a C compiler 20 | * compatible with python2 and python3 21 | * exposes enough point math (addition and scalarmult) to implement SPAKE2 22 | 23 | ## Slow 24 | 25 | The pure-python functions are considerably slower than their pynacl (libsodium) equivalents, using python-2.7.9 on my 2.6GHz Core-i7: 26 | 27 | | function | pure25519 | pynacl (C) | 28 | | -------------- | --------- | ---------- | 29 | | Ed25519 sign | 2.8 ms | 142 us | 30 | | Ed25519 verify | 10.8 ms | 240 us | 31 | | DH-start | 2.8 ms | 72 us | 32 | | DH-finish | 5.4 ms | 89 us | 33 | | SPAKE2 start | 5.4 ms | N/A | 34 | | SPAKE2 finish | 8.0 ms | N/A | 35 | 36 | This library is conservative, and performs full subgroup-membership checks on decoded points, which adds considerable overhead. The Curve25519/Ed25519 algorithms were designed to not require these checks, so a careful application might be able to improve on this slightly (Ed25519 verify down to 37 | 6.2ms, DH-finish to 3.2ms). 38 | 39 | # Compatibility, and the lack thereof 40 | 41 | The sample Diffie-Hellman key-agreement code in dh.py is not actually Curve25519: it uses the Ed25519 curve, which is sufficiently similar for security purposes, but won't interoperate with a proper Curve25519 implementation. It is included just to exercise the API and obtain a comparable performance number. 42 | 43 | The Ed25519 implementation *should* be compatible with other versions, and includes the known-answer-tests from http://ed25519.cr.yp.to/software.html to confirm this. 44 | 45 | The SPAKE2 implementation is new, and there's nothing else for it to interoperate with yet. 46 | 47 | ## Sources 48 | 49 | This code is adapted and modified from a number of original sources, 50 | including: 51 | 52 | * https://bitbucket.org/dholth/ed25519ll 53 | * http://ed25519.cr.yp.to/python/ed25519.py 54 | * http://ed25519.cr.yp.to/software.html 55 | * http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html 56 | 57 | Many thanks to Ron Garret, Daniel Holth, and Matthew Dempsky. 58 | 59 | ## License 60 | 61 | This software is released under the MIT license. 62 | -------------------------------------------------------------------------------- /misc/orig_ed25519.py: -------------------------------------------------------------------------------- 1 | # from http://ed25519.cr.yp.to/python/ed25519.py 2 | import hashlib 3 | 4 | b = 256 5 | q = 2**255 - 19 6 | l = 2**252 + 27742317777372353535851937790883648493 7 | 8 | def H(m): 9 | return hashlib.sha512(m).digest() 10 | 11 | def expmod(b,e,m): 12 | if e == 0: return 1 13 | t = expmod(b,e/2,m)**2 % m 14 | if e & 1: t = (t*b) % m 15 | return t 16 | 17 | def inv(x): 18 | return expmod(x,q-2,q) 19 | 20 | d = -121665 * inv(121666) 21 | I = expmod(2,(q-1)/4,q) 22 | 23 | def xrecover(y): 24 | xx = (y*y-1) * inv(d*y*y+1) 25 | x = expmod(xx,(q+3)/8,q) 26 | if (x*x - xx) % q != 0: x = (x*I) % q 27 | if x % 2 != 0: x = q-x 28 | return x 29 | 30 | By = 4 * inv(5) 31 | Bx = xrecover(By) 32 | B = [Bx % q,By % q] 33 | 34 | def edwards(P,Q): 35 | x1 = P[0] 36 | y1 = P[1] 37 | x2 = Q[0] 38 | y2 = Q[1] 39 | x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2) 40 | y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2) 41 | return [x3 % q,y3 % q] 42 | 43 | def scalarmult(P,e): 44 | if e == 0: return [0,1] 45 | Q = scalarmult(P,e/2) 46 | Q = edwards(Q,Q) 47 | if e & 1: Q = edwards(Q,P) 48 | return Q 49 | 50 | def encodeint(y): 51 | bits = [(y >> i) & 1 for i in range(b)] 52 | return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)]) 53 | 54 | def encodepoint(P): 55 | x = P[0] 56 | y = P[1] 57 | bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] 58 | return ''.join([chr(sum([bits[i * 8 + j] << j for j in range(8)])) for i in range(b/8)]) 59 | 60 | def bit(h,i): 61 | return (ord(h[i/8]) >> (i%8)) & 1 62 | 63 | def publickey(sk): 64 | h = H(sk) 65 | a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) 66 | A = scalarmult(B,a) 67 | return encodepoint(A) 68 | 69 | def Hint(m): 70 | h = H(m) 71 | return sum(2**i * bit(h,i) for i in range(2*b)) 72 | 73 | def signature(m,sk,pk): 74 | h = H(sk) 75 | a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) 76 | r = Hint(''.join([h[i] for i in range(b/8,b/4)]) + m) 77 | R = scalarmult(B,r) 78 | S = (r + Hint(encodepoint(R) + pk + m) * a) % l 79 | return encodepoint(R) + encodeint(S) 80 | 81 | def isoncurve(P): 82 | x = P[0] 83 | y = P[1] 84 | return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0 85 | 86 | def decodeint(s): 87 | return sum(2**i * bit(s,i) for i in range(0,b)) 88 | 89 | def decodepoint(s): 90 | y = sum(2**i * bit(s,i) for i in range(0,b-1)) 91 | x = xrecover(y) 92 | if x & 1 != bit(s,b-1): x = q-x 93 | P = [x,y] 94 | if not isoncurve(P): raise Exception("decoding point that is not on curve") 95 | return P 96 | 97 | def checkvalid(s,m,pk): 98 | if len(s) != b/4: raise Exception("signature length is wrong") 99 | if len(pk) != b/8: raise Exception("public-key length is wrong") 100 | R = decodepoint(s[0:b/8]) 101 | A = decodepoint(pk) 102 | S = decodeint(s[b/8:b/4]) 103 | h = Hint(encodepoint(R) + pk + m) 104 | if scalarmult(B,S) != edwards(R,scalarmult(A,h)): 105 | raise Exception("signature does not pass verification") 106 | -------------------------------------------------------------------------------- /pure25519/speed_basic.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | def do(setup_statements, statement): 4 | # extracted from timeit.py 5 | t = timeit.Timer(stmt=statement, 6 | setup="\n".join(setup_statements)) 7 | # determine number so that 0.2 <= total time < 2.0 8 | for i in range(1, 10): 9 | number = 10**i 10 | x = t.timeit(number) 11 | if x >= 1.0: 12 | break 13 | return x / number 14 | 15 | def abbrev(t): 16 | if t > 1.0: 17 | return "%.3fs" % t 18 | if t > 1e-3: 19 | return "%.2fms" % (t*1e3) 20 | if t > 1e-6: 21 | return "%.2fus" % (t*1e6) 22 | return "%.2fns" % (t*1e9) 23 | 24 | def p(name, setup_statements, statements): 25 | t = sorted([do(setup_statements, statements) for i in range(3)]) 26 | print("%-32s: %s (%s)" % (name, 27 | abbrev(min(t)), 28 | " ".join([abbrev(s) for s in t]))) 29 | 30 | # pure_ed25519.sign() is doing an extra publickey(), doubles the cost 31 | 32 | def run(): 33 | S1 = "from pure25519 import basic, slow_basic" 34 | S2 = "p=slow_basic.scalarmult_affine(basic.B, 16*1000000000)" 35 | S3 = "P=basic.encodepoint(p)" 36 | S4 = "basic.decodepoint(P)" 37 | S5big = r"i = b'\xf0'+b'\xff'*31" 38 | S5medium = r"i = b'\xf0'+b'\x55'*31" 39 | S5small = r"i = b'\xf0'+b'\x00'*31" 40 | S6 = "si=basic.bytes_to_scalar(i)" 41 | S7 = "basic.scalar_to_bytes(si)" 42 | S8 = "slow_basic.slow_scalarmult_affine(p, si)" 43 | S9 = "slow_basic.scalarmult_affine(p, si)" 44 | S10 = "x=basic.xform_affine_to_extended(p)" 45 | S11 = "basic.xform_extended_to_affine(x)" 46 | S12 = "p2 = slow_basic.scalarmult_affine(basic.B, 17)" 47 | S13 = "slow_basic.slow_add_affine(p, p2)" 48 | S14 = "x2=basic.xform_affine_to_extended(p2)" 49 | S15 = "basic.add_elements(x, x2)" 50 | S16 = "basic.xrecover(basic.Bx)" 51 | S17 = "basic.isoncurve(p)" 52 | S18 = "(si + si) % basic.Q" 53 | S19 = "(si * si) % basic.Q" 54 | S20 = "basic.inv(si)" 55 | S21 = "basic._add_elements_nonunfied(x, x2)" 56 | S22 = "e=basic.bytes_to_unknown_group_element(P)" 57 | S23 = "e=basic.bytes_to_element(P)" 58 | S24 = "e=basic.arbitrary_element(b'seed')" 59 | S25 = "e.scalarmult(si)" 60 | 61 | print("speed_basic") 62 | if 1: 63 | p("encodepoint", [S1,S2], S3) 64 | p("decodepoint", [S1,S2,S3], S4) 65 | p("scalar_to_bytes", [S1,S5big,S6], S7) 66 | p("bytes_to_scalar", [S1,S5big], S6) 67 | p("slow_scalarmult_affine (big)", [S1,S2,S5big,S6], S8) 68 | p("slow_scalarmult_affine (medium)", [S1,S2,S5medium,S6], S8) 69 | p("slow_scalarmult_affine (small)", [S1,S2,S5small,S6], S8) 70 | p("scalarmult_affine (big)", [S1,S2,S5big,S6], S9) 71 | p("scalarmult_affine (medium)", [S1,S2,S5medium,S6], S9) 72 | p("scalarmult_affine (small)", [S1,S2,S5small,S6], S9) 73 | p("xform_affine_to_extended", [S1,S2], S10) 74 | p("xform_extended_to_affine", [S1,S2,S10], S11) 75 | p("slow_add_affine", [S1,S2,S12], S13) 76 | p("add (extended)", [S1,S2,S12,S10,S14], S15) 77 | p("add_nonunified (extended)", [S1,S2,S12,S10,S14], S21) 78 | p("xrecover", [S1], S16) 79 | p("isoncurve", [S1,S2], S17) 80 | if 1: 81 | p("field_add", [S1,S5medium,S6], S18) 82 | p("field_mul (big)", [S1,S5big,S6], S19) 83 | p("field_mul (medium)", [S1,S5medium,S6], S19) 84 | p("field_mul (small)", [S1,S5small,S6], S19) 85 | p("field_inv", [S1,S5medium,S6], S20) 86 | if 1: 87 | p("bytes_to_unknown_group_element", [S1,S2,S3], S22) 88 | p("bytes_to_element", [S1,S2,S3], S23) 89 | p("arbitrary_element", [S1], S24) 90 | p("scalarmult(unknown-medium)", [S1,S2,S3,S22,S5medium,S6], S25) 91 | p("scalarmult(medium)", [S1,S2,S3,S23,S5medium,S6], S25) 92 | 93 | if __name__ == "__main__": 94 | run() 95 | -------------------------------------------------------------------------------- /pure25519/test_basic.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import random 3 | from binascii import hexlify 4 | from pure25519.basic import (Q, L, B, ElementOfUnknownGroup, 5 | arbitrary_element, password_to_scalar, 6 | bytes_to_element, bytes_to_unknown_group_element, 7 | _add_elements_nonunfied, add_elements, encodepoint, 8 | xform_extended_to_affine, xform_affine_to_extended) 9 | from pure25519.basic import Base, Element, Zero 10 | from pure25519.slow_basic import (slow_add_affine, scalarmult_affine, 11 | scalarmult_affine_to_extended) 12 | 13 | class Basic(unittest.TestCase): 14 | def assertElementsEqual(self, e1, e2, msg=None): 15 | self.assertEqual(hexlify(e1.to_bytes()), hexlify(e2.to_bytes()), 16 | msg) 17 | def assertBytesEqual(self, e1, e2, msg=None): 18 | self.assertEqual(hexlify(e1), hexlify(e2), msg) 19 | 20 | def test_arbitrary_element(self): 21 | for i in range(20): 22 | seed = str(i).encode("ascii") 23 | e = arbitrary_element(seed) 24 | e2 = arbitrary_element(seed) 25 | self.assertElementsEqual(e, e2) 26 | 27 | def test_password_to_scalar(self): 28 | for i in range(20): 29 | seed = str(i).encode("ascii") 30 | s = password_to_scalar(seed) 31 | s2 = password_to_scalar(seed) 32 | self.assertEqual(s, s2) 33 | 34 | def test_scalarmult(self): 35 | Bsm = Base.scalarmult 36 | e0 = Bsm(0) 37 | e1 = Bsm(1) 38 | e2 = Bsm(2) 39 | e5 = Bsm(5) 40 | e10 = Bsm(10) 41 | e15 = Bsm(15) 42 | em5 = Bsm(-5) 43 | self.assertElementsEqual(e0.add(e0), e0) 44 | self.assertElementsEqual(e1, Base) 45 | self.assertElementsEqual(e0.add(e1), e1) 46 | self.assertElementsEqual(Bsm(-5), Bsm(-5 % L)) 47 | # there was a bug, due to the add inside scalarmult being 48 | # non-unified, which caused e0.scalarmult(N) to not equal e0 49 | self.assertElementsEqual(e0.scalarmult(5), e0) 50 | self.assertElementsEqual(e2.scalarmult(0), e0) 51 | self.assertElementsEqual(e1.add(e1), e2) 52 | self.assertElementsEqual(e5.add(e5), e10) 53 | self.assertElementsEqual(e5.add(e10), e15) 54 | self.assertElementsEqual(e2.scalarmult(5), e10) 55 | self.assertElementsEqual(e15.add(em5), e10) 56 | self.assertElementsEqual(e5.add(em5), e0) 57 | 58 | sm2 = scalarmult_affine 59 | e = {} 60 | for i in range(-50, 100): 61 | e[i] = Bsm(i) 62 | self.assertElementsEqual(Element(xform_affine_to_extended(sm2(B, i))), e[i]) 63 | self.assertElementsEqual(Element(scalarmult_affine_to_extended(B, i)), 64 | e[i]) 65 | 66 | for i in range(20,30): 67 | for j in range(-10,10): 68 | x1 = e[i].add(e[j]) 69 | x2 = Bsm(i+j) 70 | self.assertElementsEqual(x1, x2, (x1,x2,i,j)) 71 | x3 = Element(xform_affine_to_extended(sm2(B, i+j))) 72 | self.assertElementsEqual(x1, x3, (x1,x3,i,j)) 73 | 74 | def test_orders(self): 75 | # the point (0,1) is the identity, and has order 1 76 | p0 = xform_affine_to_extended((0,1)) 77 | # p0+p0=p0 78 | # p0+anything=anything 79 | 80 | # The point (0,-1) has order 2 81 | p2 = xform_affine_to_extended((0,-1)) 82 | p3 = add_elements(p2, p2) # p3=p2+p2=p0 83 | p4 = add_elements(p3, p2) # p4=p3+p2=p2 84 | p5 = add_elements(p4, p2) # p5=p4+p2=p0 85 | self.assertBytesEqual(encodepoint(xform_extended_to_affine(p3)), 86 | encodepoint(xform_extended_to_affine(p0))) 87 | self.assertBytesEqual(encodepoint(xform_extended_to_affine(p4)), 88 | encodepoint(xform_extended_to_affine(p2))) 89 | self.assertBytesEqual(encodepoint(xform_extended_to_affine(p5)), 90 | encodepoint(xform_extended_to_affine(p0))) 91 | 92 | # now same thing, but with Element 93 | p0 = ElementOfUnknownGroup(xform_affine_to_extended((0,1))) 94 | self.assertElementsEqual(p0, Zero) 95 | p2 = ElementOfUnknownGroup(xform_affine_to_extended((0,-1))) 96 | p3 = p2.add(p2) 97 | p4 = p3.add(p2) 98 | p5 = p4.add(p2) 99 | self.assertElementsEqual(p3, p0) 100 | self.assertElementsEqual(p4, p2) 101 | self.assertElementsEqual(p5, p0) 102 | self.assertFalse(isinstance(p3, Element)) 103 | self.assertFalse(isinstance(p4, Element)) 104 | self.assertFalse(isinstance(p5, Element)) 105 | 106 | # and again, with .scalarmult instead of .add 107 | p3 = p2.scalarmult(2) # p3=2*p2=p0 108 | p4 = p2.scalarmult(3) # p4=3*p2=p2 109 | p5 = p2.scalarmult(4) # p5=4*p2=p0 110 | self.assertElementsEqual(p3, p0) # TODO: failing 111 | self.assertElementsEqual(p4, p2) 112 | self.assertElementsEqual(p5, p0) 113 | self.assertFalse(isinstance(p3, Element)) 114 | self.assertFalse(isinstance(p4, Element)) 115 | self.assertFalse(isinstance(p5, Element)) 116 | 117 | # I don't know if there are points of order 4. 118 | 119 | def test_add(self): 120 | e = [] 121 | for i in range(100): 122 | seed = str(i).encode("ascii") 123 | s = password_to_scalar(seed) 124 | e.append(Base.scalarmult(s)) 125 | for i in range(100): 126 | x = random.choice(e) 127 | y = random.choice(e) 128 | 129 | sum1 = element_from_affine( 130 | slow_add_affine(xform_extended_to_affine(x.XYTZ), 131 | xform_extended_to_affine(y.XYTZ))) 132 | sum2 = x.add(y) 133 | self.assertElementsEqual(sum1, sum2) 134 | if x != y: 135 | sum3 = Element(_add_elements_nonunfied(x.XYTZ, y.XYTZ)) 136 | self.assertElementsEqual(sum1, sum3) 137 | 138 | def test_bytes_to_element(self): 139 | b = encodepoint((0,1)) # order 1, aka Zero 140 | self.assertRaises(ValueError, bytes_to_element, b) 141 | p = bytes_to_unknown_group_element(b) 142 | self.assertFalse(isinstance(p, Element)) 143 | self.assertIs(p, Zero) 144 | 145 | b = encodepoint((0,-1%Q)) # order 2 146 | self.assertRaises(ValueError, bytes_to_element, b) 147 | p = bytes_to_unknown_group_element(b) 148 | self.assertFalse(isinstance(p, Element)) 149 | 150 | # (..,26) is in the right group 151 | b = b"\x1a" + b"\x00"*31 152 | p = bytes_to_element(b) 153 | self.assertTrue(isinstance(p, Element)) 154 | 155 | 156 | def element_from_affine(P): 157 | return Element(xform_affine_to_extended(P)) 158 | -------------------------------------------------------------------------------- /pure25519/test_replacements.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import unittest 4 | from binascii import hexlify, unhexlify 5 | import hashlib 6 | from pure25519 import basic 7 | 8 | if sys.version_info[0] == 2: 9 | def asbytes(b): 10 | """Convert array of integers to byte string""" 11 | return ''.join(chr(x) for x in b) 12 | def bit(h, i): 13 | """Return i'th bit of bytestring h""" 14 | return (ord(h[i//8]) >> (i%8)) & 1 15 | else: 16 | asbytes = bytes 17 | def bit(h, i): 18 | return (h[i//8] >> (i%8)) & 1 19 | 20 | b = 256 # =32*8 21 | def H(m): 22 | return hashlib.sha512(m).digest() 23 | 24 | class Compare(unittest.TestCase): 25 | # I want to replace some very manual (and slow) loops with builtins. 26 | # These tests are to ensure that the replacements work the same way 27 | 28 | # Hint turns bytes into a 512-bit integer, little-endian (the first byte 29 | # of H(m) is used for the low-order 8 bits of the integer, with the LSB 30 | # of H(m)[0] used as the LSB of the integer). 31 | def orig_Hint(self, m): 32 | h = H(m) 33 | return sum(2**i * bit(h,i) for i in range(2*b)) 34 | def new_Hint(self, m): 35 | h = H(m) 36 | return int(hexlify(h[::-1]), 16) 37 | def test_Hint(self): 38 | for i in range(200): 39 | m = str(i).encode("ascii") 40 | self.assertEqual(self.orig_Hint(m), self.new_Hint(m)) 41 | 42 | def orig_decodepoint_sum(self, s): 43 | # the original. 802us. 44 | y = sum(2**i * bit(s,i) for i in range(0,b-1)) 45 | #print("%064x" % y) 46 | return y 47 | 48 | def new_decodepoint_sum_1(self, s): 49 | # the range(0,b-1) means we don't use the MSB of the last byte, so 50 | # the high-order bit of the int will be zero. In this form, we clamp 51 | # the int. 337us 52 | unclamped = int(hexlify(s[:32][::-1]), 16) 53 | clamp = (1 << 255) - 1 54 | clamped = unclamped & clamp 55 | return clamped 56 | 57 | def new_decodepoint_sum_2(self, s): 58 | # In this form, we remap the last byte before conversion. 340us 59 | s = s[:32] 60 | clamped_byte = unhexlify("%02x" % (int(hexlify(s[-2:]), 16) & 0x7f)) 61 | clamped_s = s[:-1] + clamped_byte 62 | clamped = int(hexlify(clamped_s[::-1]), 16) 63 | #print("%064x" % clamped) 64 | return clamped 65 | 66 | def test_decodepoint_sum(self): 67 | for i in range(200): 68 | #print() 69 | s = H(str(i).encode("ascii"))[:32] 70 | self.assertEqual(self.orig_decodepoint_sum(s), 71 | self.new_decodepoint_sum_1(s)) 72 | self.assertEqual(self.new_decodepoint_sum_1(s), 73 | self.new_decodepoint_sum_2(s)) 74 | 75 | def orig_decodepoint_2(self, s): 76 | # already sped up once 77 | unclamped = int(hexlify(s[:32][::-1]), 16) 78 | clamp = (1 << 255) - 1 79 | y = unclamped & clamp 80 | x = basic.xrecover(y) 81 | if x & 1 != bit(s,b-1): x = basic.Q-x 82 | P = [x,y] 83 | if not basic.isoncurve(P): 84 | raise Exception("decoding point that is not on curve") 85 | return P 86 | 87 | def new_decodepoint_2(self, s): 88 | # already sped up once 89 | unclamped = int(hexlify(s[:32][::-1]), 16) 90 | clamp = (1 << 255) - 1 91 | y = unclamped & clamp 92 | x = basic.xrecover(y) 93 | if bool(x & 1) != bool(unclamped & (1<<255)): x = basic.Q-x 94 | P = [x,y] 95 | if not basic.isoncurve(P): 96 | raise Exception("decoding point that is not on curve") 97 | return P 98 | 99 | def test_decodepoint_2(self): 100 | for i in range(200): 101 | P_s = basic.Base.scalarmult(i).to_bytes() 102 | self.assertEqual(self.orig_decodepoint_2(P_s), 103 | self.new_decodepoint_2(P_s)) 104 | 105 | def orig_decodeint(self, s): 106 | return sum(2**i * bit(s,i) for i in range(0,b)) 107 | def new_decodeint(self, s): 108 | return int(hexlify(s[:32][::-1]), 16) 109 | 110 | def test_decodeint(self): 111 | for i in range(200): 112 | s = H(str(i).encode("ascii"))[:32] 113 | self.assertEqual(self.orig_decodeint(s), 114 | self.new_decodeint(s)) 115 | 116 | def orig_signature_sum(self, sk): 117 | h = H(sk) 118 | a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) 119 | #print("%064x" % a) 120 | return a 121 | 122 | def new_signature_sum(self, sk): 123 | h = H(sk) 124 | unclamped = int(hexlify(h[:32][::-1]), 16) 125 | # top byte is 01xxxxxx 126 | # middle bytes are xxxxxxxx 127 | # bottom byte is xxxxx000 128 | top_reset_clamp = (0x40 << (31*8)) - 1 129 | bottom_reset_clamp = ((1 << (32*8)) - 1) ^ 0x7 130 | set_clamp = 0x40 << (31*8) 131 | clamped = (unclamped & top_reset_clamp & bottom_reset_clamp) | set_clamp 132 | #print("%064x" % unclamped) 133 | #print("%064x" % set_clamp) 134 | #print("%064x" % top_reset_clamp) 135 | #print("%064x" % bottom_reset_clamp) 136 | #print("%064x" % clamped) 137 | return clamped 138 | 139 | def test_signature_sum(self): 140 | for i in range(200): 141 | #print() 142 | sk = H(str(i).encode("ascii"))[:32] 143 | self.assertEqual(self.orig_signature_sum(sk), 144 | self.new_signature_sum(sk)) 145 | 146 | def orig_encodeint(self, y): 147 | bits = [(y >> i) & 1 for i in range(b)] 148 | e = [(sum([bits[i * 8 + j] << j for j in range(8)])) 149 | for i in range(b//8)] 150 | return asbytes(e) 151 | 152 | def new_encodeint(self, y): 153 | assert 0 <= y < 2**256 154 | return unhexlify("%064x" % y)[::-1] 155 | 156 | def test_encodeint(self): 157 | for i in range(200): 158 | s = H(str(i).encode("ascii"))[:32] 159 | sint = self.orig_decodeint(s) 160 | self.assertEqual(self.orig_encodeint(sint), 161 | self.new_encodeint(sint)) 162 | 163 | def orig_encodepoint(self, P): 164 | x = P[0] 165 | y = P[1] 166 | bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] 167 | e = [(sum([bits[i * 8 + j] << j for j in range(8)])) 168 | for i in range(b//8)] 169 | return asbytes(e) 170 | 171 | def new_encodepoint(self, P): 172 | x = P[0] 173 | y = P[1] 174 | # MSB of output equals x.b0 (=x&1) 175 | # rest of output is little-endian y 176 | assert 0 <= y < (1<<255) # always < 0x7fff..ff 177 | if x & 1: 178 | y += 1<<255 179 | return unhexlify("%064x" % y)[::-1] 180 | 181 | def test_encodepoint(self): 182 | for i in range(200): 183 | P = basic.xform_extended_to_affine(basic.Base.scalarmult(i).XYTZ) 184 | self.assertEqual(self.orig_encodepoint(P), 185 | self.new_encodepoint(P)) 186 | -------------------------------------------------------------------------------- /pure25519/ed25519_oop.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | from . import _ed25519 4 | BadSignatureError = _ed25519.BadSignatureError 5 | 6 | def create_keypair(entropy=os.urandom): 7 | SEEDLEN = int(_ed25519.SECRETKEYBYTES/2) 8 | assert SEEDLEN == 32 9 | seed = entropy(SEEDLEN) 10 | sk = SigningKey(seed) 11 | vk = sk.get_verifying_key() 12 | return sk, vk 13 | 14 | class BadPrefixError(Exception): 15 | pass 16 | 17 | def remove_prefix(s_bytes, prefix): 18 | assert(type(s_bytes) == type(prefix)) 19 | if s_bytes[:len(prefix)] != prefix: 20 | raise BadPrefixError("did not see expected '%s' prefix" % (prefix,)) 21 | return s_bytes[len(prefix):] 22 | 23 | def to_ascii(s_bytes, prefix="", encoding="base64"): 24 | """Return a version-prefixed ASCII representation of the given binary 25 | string. 'encoding' indicates how to do the encoding, and can be one of: 26 | * base64 27 | * base32 28 | * base16 (or hex) 29 | 30 | This function handles bytes, not bits, so it does not append any trailing 31 | '=' (unlike standard base64.b64encode). It also lowercases the base32 32 | output. 33 | 34 | 'prefix' will be prepended to the encoded form, and is useful for 35 | distinguishing the purpose and version of the binary string. E.g. you 36 | could prepend 'pub0-' to a VerifyingKey string to allow the receiving 37 | code to raise a useful error if someone pasted in a signature string by 38 | mistake. 39 | """ 40 | assert isinstance(s_bytes, bytes) 41 | if not isinstance(prefix, bytes): 42 | prefix = prefix.encode('ascii') 43 | if encoding == "base64": 44 | s_ascii = base64.b64encode(s_bytes).decode('ascii').rstrip("=") 45 | elif encoding == "base32": 46 | s_ascii = base64.b32encode(s_bytes).decode('ascii').rstrip("=").lower() 47 | elif encoding in ("base16", "hex"): 48 | s_ascii = base64.b16encode(s_bytes).decode('ascii').lower() 49 | else: 50 | raise NotImplementedError 51 | return prefix+s_ascii.encode('ascii') 52 | 53 | def from_ascii(s_ascii, prefix="", encoding="base64"): 54 | """This is the opposite of to_ascii. It will throw BadPrefixError if 55 | the prefix is not found. 56 | """ 57 | if isinstance(s_ascii, bytes): 58 | s_ascii = s_ascii.decode('ascii') 59 | if isinstance(prefix, bytes): 60 | prefix = prefix.decode('ascii') 61 | s_ascii = remove_prefix(s_ascii.strip(), prefix) 62 | if encoding == "base64": 63 | s_ascii += "="*((4 - len(s_ascii)%4)%4) 64 | s_bytes = base64.b64decode(s_ascii) 65 | elif encoding == "base32": 66 | s_ascii += "="*((8 - len(s_ascii)%8)%8) 67 | s_bytes = base64.b32decode(s_ascii.upper()) 68 | elif encoding in ("base16", "hex"): 69 | s_bytes = base64.b16decode(s_ascii.upper()) 70 | else: 71 | raise NotImplementedError 72 | return s_bytes 73 | 74 | class SigningKey(object): 75 | # this can only be used to reconstruct a key created by create_keypair(). 76 | def __init__(self, sk_s, prefix="", encoding=None): 77 | assert isinstance(sk_s, bytes) 78 | if not isinstance(prefix, bytes): 79 | prefix = prefix.encode('ascii') 80 | sk_s = remove_prefix(sk_s, prefix) 81 | if encoding is not None: 82 | sk_s = from_ascii(sk_s, encoding=encoding) 83 | if len(sk_s) == 32: 84 | # create from seed 85 | vk_s, sk_s = _ed25519.publickey(sk_s) 86 | else: 87 | if len(sk_s) != 32+32: 88 | raise ValueError("SigningKey takes 32-byte seed or 64-byte string") 89 | self.sk_s = sk_s # seed+pubkey 90 | self.vk_s = sk_s[32:] # just pubkey 91 | 92 | def to_bytes(self, prefix=""): 93 | if not isinstance(prefix, bytes): 94 | prefix = prefix.encode('ascii') 95 | return prefix+self.sk_s 96 | 97 | def to_ascii(self, prefix="", encoding=None): 98 | assert encoding 99 | if not isinstance(prefix, bytes): 100 | prefix = prefix.encode('ascii') 101 | return to_ascii(self.to_seed(), prefix, encoding) 102 | 103 | def to_seed(self, prefix=""): 104 | if not isinstance(prefix, bytes): 105 | prefix = prefix.encode('ascii') 106 | return prefix+self.sk_s[:32] 107 | 108 | def __eq__(self, them): 109 | if not isinstance(them, object): return False 110 | return (them.__class__ == self.__class__ 111 | and them.sk_s == self.sk_s) 112 | 113 | def get_verifying_key(self): 114 | return VerifyingKey(self.vk_s) 115 | 116 | def sign(self, msg, prefix="", encoding=None): 117 | assert isinstance(msg, bytes) 118 | if not isinstance(prefix, bytes): 119 | prefix = prefix.encode('ascii') 120 | sig_and_msg = _ed25519.sign(msg, self.sk_s) 121 | # the response is R+S+msg 122 | sig_R = sig_and_msg[0:32] 123 | sig_S = sig_and_msg[32:64] 124 | msg_out = sig_and_msg[64:] 125 | sig_out = sig_R + sig_S 126 | assert msg_out == msg 127 | if encoding: 128 | return to_ascii(sig_out, prefix, encoding) 129 | return prefix+sig_out 130 | 131 | class VerifyingKey(object): 132 | def __init__(self, vk_s, prefix="", encoding=None): 133 | if not isinstance(prefix, bytes): 134 | prefix = prefix.encode('ascii') 135 | if not isinstance(vk_s, bytes): 136 | vk_s = vk_s.encode('ascii') 137 | assert isinstance(vk_s, bytes) 138 | vk_s = remove_prefix(vk_s, prefix) 139 | if encoding is not None: 140 | vk_s = from_ascii(vk_s, encoding=encoding) 141 | 142 | assert len(vk_s) == 32 143 | self.vk_s = vk_s 144 | 145 | def to_bytes(self, prefix=""): 146 | if not isinstance(prefix, bytes): 147 | prefix = prefix.encode('ascii') 148 | return prefix+self.vk_s 149 | 150 | def to_ascii(self, prefix="", encoding=None): 151 | assert encoding 152 | if not isinstance(prefix, bytes): 153 | prefix = prefix.encode('ascii') 154 | return to_ascii(self.vk_s, prefix, encoding) 155 | 156 | def __eq__(self, them): 157 | if not isinstance(them, object): return False 158 | return (them.__class__ == self.__class__ 159 | and them.vk_s == self.vk_s) 160 | 161 | def verify(self, sig, msg, prefix="", encoding=None): 162 | if not isinstance(sig, bytes): 163 | sig = sig.encode('ascii') 164 | if not isinstance(prefix, bytes): 165 | prefix = prefix.encode('ascii') 166 | assert isinstance(sig, bytes) 167 | assert isinstance(msg, bytes) 168 | if encoding: 169 | sig = from_ascii(sig, prefix, encoding) 170 | else: 171 | sig = remove_prefix(sig, prefix) 172 | assert len(sig) == 64 173 | sig_R = sig[:32] 174 | sig_S = sig[32:] 175 | sig_and_msg = sig_R + sig_S + msg 176 | # this might raise BadSignatureError 177 | msg2 = _ed25519.open(sig_and_msg, self.vk_s) 178 | assert msg2 == msg 179 | 180 | def selftest(): 181 | message = b"crypto libraries should always test themselves at powerup" 182 | sk = SigningKey(b"priv0-VIsfn5OFGa09Un2MR6Hm7BQ5++xhcQskU2OGXG8jSJl4cWLZrRrVcSN2gVYMGtZT+3354J5jfmqAcuRSD9KIyg", 183 | prefix="priv0-", encoding="base64") 184 | vk = VerifyingKey(b"pub0-eHFi2a0a1XEjdoFWDBrWU/t9+eCeY35qgHLkUg/SiMo", 185 | prefix="pub0-", encoding="base64") 186 | assert sk.get_verifying_key() == vk 187 | sig = sk.sign(message, prefix="sig0-", encoding="base64") 188 | assert sig == b"sig0-E/QrwtSF52x8+q0l4ahA7eJbRKc777ClKNg217Q0z4fiYMCdmAOI+rTLVkiFhX6k3D+wQQfKdJYMxaTUFfv1DQ", sig 189 | vk.verify(sig, message, prefix="sig0-", encoding="base64") 190 | 191 | selftest() 192 | -------------------------------------------------------------------------------- /misc/djbec.py: -------------------------------------------------------------------------------- 1 | # Ed25519 digital signatures 2 | # Based on http://ed25519.cr.yp.to/python/ed25519.py 3 | # See also http://ed25519.cr.yp.to/software.html 4 | # Adapted by Ron Garret 5 | # Sped up considerably using coordinate transforms found on: 6 | # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html 7 | # Specifically add-2008-hwcd-4 and dbl-2008-hwcd 8 | 9 | try: # pragma nocover 10 | unicode 11 | PY3 = False 12 | def asbytes(b): 13 | """Convert array of integers to byte string""" 14 | return ''.join(chr(x) for x in b) 15 | def joinbytes(b): 16 | """Convert array of bytes to byte string""" 17 | return ''.join(b) 18 | def bit(h, i): 19 | """Return i'th bit of bytestring h""" 20 | return (ord(h[i//8]) >> (i%8)) & 1 21 | 22 | except NameError: # pragma nocover 23 | PY3 = True 24 | asbytes = bytes 25 | joinbytes = bytes 26 | def bit(h, i): 27 | return (h[i//8] >> (i%8)) & 1 28 | 29 | import hashlib 30 | 31 | b = 256 32 | q = 2**255 - 19 33 | l = 2**252 + 27742317777372353535851937790883648493 34 | 35 | def H(m): 36 | return hashlib.sha512(m).digest() 37 | 38 | def expmod(b, e, m): 39 | if e == 0: return 1 40 | t = expmod(b, e // 2, m) ** 2 % m 41 | if e & 1: t = (t * b) % m 42 | return t 43 | 44 | # Can probably get some extra speedup here by replacing this with 45 | # an extended-euclidean, but performance seems OK without that 46 | def inv(x): 47 | return expmod(x, q-2, q) 48 | 49 | d = -121665 * inv(121666) 50 | I = expmod(2,(q-1)//4,q) 51 | 52 | def xrecover(y): 53 | xx = (y*y-1) * inv(d*y*y+1) 54 | x = expmod(xx,(q+3)//8,q) 55 | if (x*x - xx) % q != 0: x = (x*I) % q 56 | if x % 2 != 0: x = q-x 57 | return x 58 | 59 | By = 4 * inv(5) 60 | Bx = xrecover(By) 61 | B = [Bx % q,By % q] 62 | 63 | #def edwards(P,Q): 64 | # x1 = P[0] 65 | # y1 = P[1] 66 | # x2 = Q[0] 67 | # y2 = Q[1] 68 | # x3 = (x1*y2+x2*y1) * inv(1+d*x1*x2*y1*y2) 69 | # y3 = (y1*y2+x1*x2) * inv(1-d*x1*x2*y1*y2) 70 | # return (x3 % q,y3 % q) 71 | 72 | #def scalarmult(P,e): 73 | # if e == 0: return [0,1] 74 | # Q = scalarmult(P,e/2) 75 | # Q = edwards(Q,Q) 76 | # if e & 1: Q = edwards(Q,P) 77 | # return Q 78 | 79 | # Faster (!) version based on: 80 | # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html 81 | 82 | def xpt_add(pt1, pt2): 83 | (X1, Y1, Z1, T1) = pt1 84 | (X2, Y2, Z2, T2) = pt2 85 | A = ((Y1-X1)*(Y2+X2)) % q 86 | B = ((Y1+X1)*(Y2-X2)) % q 87 | C = (Z1*2*T2) % q 88 | D = (T1*2*Z2) % q 89 | E = (D+C) % q 90 | F = (B-A) % q 91 | G = (B+A) % q 92 | H = (D-C) % q 93 | X3 = (E*F) % q 94 | Y3 = (G*H) % q 95 | Z3 = (F*G) % q 96 | T3 = (E*H) % q 97 | return (X3, Y3, Z3, T3) 98 | 99 | def xpt_double (pt): 100 | (X1, Y1, Z1, _) = pt 101 | A = (X1*X1) 102 | B = (Y1*Y1) 103 | C = (2*Z1*Z1) 104 | D = (-A) % q 105 | J = (X1+Y1) % q 106 | E = (J*J-A-B) % q 107 | G = (D+B) % q 108 | F = (G-C) % q 109 | H = (D-B) % q 110 | X3 = (E*F) % q 111 | Y3 = (G*H) % q 112 | Z3 = (F*G) % q 113 | T3 = (E*H) % q 114 | return (X3, Y3, Z3, T3) 115 | 116 | def pt_xform (pt): 117 | (x, y) = pt 118 | return (x, y, 1, (x*y)%q) 119 | 120 | def pt_unxform (pt): 121 | (x, y, z, _) = pt 122 | return ((x*inv(z))%q, (y*inv(z))%q) 123 | 124 | def xpt_mult (pt, n): 125 | if n==0: return pt_xform((0,1)) 126 | _ = xpt_double(xpt_mult(pt, n>>1)) 127 | return xpt_add(_, pt) if n&1 else _ 128 | 129 | def scalarmult(pt, e): 130 | return pt_unxform(xpt_mult(pt_xform(pt), e)) 131 | 132 | def encodeint(y): 133 | bits = [(y >> i) & 1 for i in range(b)] 134 | e = [(sum([bits[i * 8 + j] << j for j in range(8)])) 135 | for i in range(b//8)] 136 | return asbytes(e) 137 | 138 | def encodepoint(P): 139 | x = P[0] 140 | y = P[1] 141 | bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] 142 | e = [(sum([bits[i * 8 + j] << j for j in range(8)])) 143 | for i in range(b//8)] 144 | return asbytes(e) 145 | 146 | def publickey(sk): 147 | h = H(sk) 148 | a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) 149 | A = scalarmult(B,a) 150 | return encodepoint(A) 151 | 152 | def Hint(m): 153 | h = H(m) 154 | return sum(2**i * bit(h,i) for i in range(2*b)) 155 | 156 | def signature(m,sk,pk): 157 | h = H(sk) 158 | a = 2**(b-2) + sum(2**i * bit(h,i) for i in range(3,b-2)) 159 | inter = joinbytes([h[i] for i in range(b//8,b//4)]) 160 | r = Hint(inter + m) 161 | R = scalarmult(B,r) 162 | S = (r + Hint(encodepoint(R) + pk + m) * a) % l 163 | return encodepoint(R) + encodeint(S) 164 | 165 | def isoncurve(P): 166 | x = P[0] 167 | y = P[1] 168 | return (-x*x + y*y - 1 - d*x*x*y*y) % q == 0 169 | 170 | def decodeint(s): 171 | return sum(2**i * bit(s,i) for i in range(0,b)) 172 | 173 | def decodepoint(s): 174 | y = sum(2**i * bit(s,i) for i in range(0,b-1)) 175 | x = xrecover(y) 176 | if x & 1 != bit(s,b-1): x = q-x 177 | P = [x,y] 178 | if not isoncurve(P): raise Exception("decoding point that is not on curve") 179 | return P 180 | 181 | def checkvalid(s, m, pk): 182 | if len(s) != b//4: raise Exception("signature length is wrong") 183 | if len(pk) != b//8: raise Exception("public-key length is wrong") 184 | R = decodepoint(s[0:b//8]) 185 | A = decodepoint(pk) 186 | S = decodeint(s[b//8:b//4]) 187 | h = Hint(encodepoint(R) + pk + m) 188 | v1 = scalarmult(B,S) 189 | # v2 = edwards(R,scalarmult(A,h)) 190 | v2 = pt_unxform(xpt_add(pt_xform(R), pt_xform(scalarmult(A, h)))) 191 | return v1==v2 192 | 193 | ########################################################## 194 | # 195 | # Curve25519 reference implementation by Matthew Dempsky, from: 196 | # http://cr.yp.to/highspeed/naclcrypto-20090310.pdf 197 | 198 | # P = 2 ** 255 - 19 199 | P = q 200 | A = 486662 201 | 202 | #def expmod(b, e, m): 203 | # if e == 0: return 1 204 | # t = expmod(b, e / 2, m) ** 2 % m 205 | # if e & 1: t = (t * b) % m 206 | # return t 207 | 208 | # def inv(x): return expmod(x, P - 2, P) 209 | 210 | def add(n, m, d): 211 | (xn, zn) = n 212 | (xm, zm) = m 213 | (xd, zd) = d 214 | x = 4 * (xm * xn - zm * zn) ** 2 * zd 215 | z = 4 * (xm * zn - zm * xn) ** 2 * xd 216 | return (x % P, z % P) 217 | 218 | def double(n): 219 | (xn, zn) = n 220 | x = (xn ** 2 - zn ** 2) ** 2 221 | z = 4 * xn * zn * (xn ** 2 + A * xn * zn + zn ** 2) 222 | return (x % P, z % P) 223 | 224 | def curve25519(n, base=9): 225 | one = (base,1) 226 | two = double(one) 227 | # f(m) evaluates to a tuple 228 | # containing the mth multiple and the 229 | # (m+1)th multiple of base. 230 | def f(m): 231 | if m == 1: return (one, two) 232 | (pm, pm1) = f(m // 2) 233 | if (m & 1): 234 | return (add(pm, pm1, one), double(pm1)) 235 | return (double(pm), add(pm, pm1, one)) 236 | ((x,z), _) = f(n) 237 | return (x * inv(z)) % P 238 | 239 | import random 240 | 241 | def genkey(n=0): 242 | n = n or random.randint(0,P) 243 | n &= ~7 244 | n &= ~(128 << 8 * 31) 245 | n |= 64 << 8 * 31 246 | return n 247 | 248 | #def str2int(s): 249 | # return int(hexlify(s), 16) 250 | # # return sum(ord(s[i]) << (8 * i) for i in range(32)) 251 | # 252 | #def int2str(n): 253 | # return unhexlify("%x" % n) 254 | # # return ''.join([chr((n >> (8 * i)) & 255) for i in range(32)]) 255 | 256 | ################################################# 257 | 258 | def dsa_test(): 259 | import os 260 | msg = str(random.randint(q,q+q)).encode('utf-8') 261 | sk = os.urandom(32) 262 | pk = publickey(sk) 263 | sig = signature(msg, sk, pk) 264 | return checkvalid(sig, msg, pk) 265 | 266 | def dh_test(): 267 | sk1 = genkey() 268 | sk2 = genkey() 269 | return curve25519(sk1, curve25519(sk2)) == curve25519(sk2, curve25519(sk1)) 270 | 271 | -------------------------------------------------------------------------------- /pure25519/test_ed25519.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import unittest 4 | import time 5 | from binascii import hexlify, unhexlify 6 | from pure25519 import ed25519_oop as ed25519 7 | from pure25519 import _ed25519 as raw 8 | 9 | if sys.version_info[0] == 3: 10 | def int2byte(i): 11 | return bytes((i,)) 12 | else: 13 | int2byte = chr 14 | 15 | def flip_bit(s, bit=0, in_byte=-1): 16 | as_bytes = [ord(b) if isinstance(b, str) else b for b in s] 17 | as_bytes[in_byte] = as_bytes[in_byte] ^ (0x01<extended 35 | # dbl-2008-hwcd 36 | (X1, Y1, Z1, _) = pt 37 | A = (X1*X1) 38 | B = (Y1*Y1) 39 | C = (2*Z1*Z1) 40 | D = (-A) % Q 41 | J = (X1+Y1) % Q 42 | E = (J*J-A-B) % Q 43 | G = (D+B) % Q 44 | F = (G-C) % Q 45 | H = (D-B) % Q 46 | X3 = (E*F) % Q 47 | Y3 = (G*H) % Q 48 | Z3 = (F*G) % Q 49 | T3 = (E*H) % Q 50 | return (X3, Y3, Z3, T3) 51 | 52 | def add_elements(pt1, pt2): # extended->extended 53 | # add-2008-hwcd-3 . Slightly slower than add-2008-hwcd-4, but -3 is 54 | # unified, so it's safe for general-purpose addition 55 | (X1, Y1, Z1, T1) = pt1 56 | (X2, Y2, Z2, T2) = pt2 57 | A = ((Y1-X1)*(Y2-X2)) % Q 58 | B = ((Y1+X1)*(Y2+X2)) % Q 59 | C = T1*(2*d)*T2 % Q 60 | D = Z1*2*Z2 % Q 61 | E = (B-A) % Q 62 | F = (D-C) % Q 63 | G = (D+C) % Q 64 | H = (B+A) % Q 65 | X3 = (E*F) % Q 66 | Y3 = (G*H) % Q 67 | T3 = (E*H) % Q 68 | Z3 = (F*G) % Q 69 | return (X3, Y3, Z3, T3) 70 | 71 | def scalarmult_element_safe_slow(pt, n): 72 | # this form is slightly slower, but tolerates arbitrary points, including 73 | # those which are not in the main 1*L subgroup. This includes points of 74 | # order 1 (the neutral element Zero), 2, 4, and 8. 75 | assert n >= 0 76 | if n==0: 77 | return xform_affine_to_extended((0,1)) 78 | _ = double_element(scalarmult_element_safe_slow(pt, n>>1)) 79 | return add_elements(_, pt) if n&1 else _ 80 | 81 | def _add_elements_nonunfied(pt1, pt2): # extended->extended 82 | # add-2008-hwcd-4 : NOT unified, only for pt1!=pt2. About 10% faster than 83 | # the (unified) add-2008-hwcd-3, and safe to use inside scalarmult if you 84 | # aren't using points of order 1/2/4/8 85 | (X1, Y1, Z1, T1) = pt1 86 | (X2, Y2, Z2, T2) = pt2 87 | A = ((Y1-X1)*(Y2+X2)) % Q 88 | B = ((Y1+X1)*(Y2-X2)) % Q 89 | C = (Z1*2*T2) % Q 90 | D = (T1*2*Z2) % Q 91 | E = (D+C) % Q 92 | F = (B-A) % Q 93 | G = (B+A) % Q 94 | H = (D-C) % Q 95 | X3 = (E*F) % Q 96 | Y3 = (G*H) % Q 97 | Z3 = (F*G) % Q 98 | T3 = (E*H) % Q 99 | return (X3, Y3, Z3, T3) 100 | 101 | def scalarmult_element(pt, n): # extended->extended 102 | # This form only works properly when given points that are a member of 103 | # the main 1*L subgroup. It will give incorrect answers when called with 104 | # the points of order 1/2/4/8, including point Zero. (it will also work 105 | # properly when given points of order 2*L/4*L/8*L) 106 | assert n >= 0 107 | if n==0: 108 | return xform_affine_to_extended((0,1)) 109 | _ = double_element(scalarmult_element(pt, n>>1)) 110 | return _add_elements_nonunfied(_, pt) if n&1 else _ 111 | 112 | # points are encoded as 32-bytes little-endian, b255 is sign, b2b1b0 are 0 113 | 114 | def encodepoint(P): 115 | x = P[0] 116 | y = P[1] 117 | # MSB of output equals x.b0 (=x&1) 118 | # rest of output is little-endian y 119 | assert 0 <= y < (1<<255) # always < 0x7fff..ff 120 | if x & 1: 121 | y += 1<<255 122 | return binascii.unhexlify("%064x" % y)[::-1] 123 | 124 | def isoncurve(P): 125 | x = P[0] 126 | y = P[1] 127 | return (-x*x + y*y - 1 - d*x*x*y*y) % Q == 0 128 | 129 | class NotOnCurve(Exception): 130 | pass 131 | 132 | def decodepoint(s): 133 | unclamped = int(binascii.hexlify(s[:32][::-1]), 16) 134 | clamp = (1 << 255) - 1 135 | y = unclamped & clamp # clear MSB 136 | x = xrecover(y) 137 | if bool(x & 1) != bool(unclamped & (1<<255)): x = Q-x 138 | P = [x,y] 139 | if not isoncurve(P): raise NotOnCurve("decoding point that is not on curve") 140 | return P 141 | 142 | # scalars are encoded as 32-bytes little-endian 143 | 144 | def bytes_to_scalar(s): 145 | assert len(s) == 32, len(s) 146 | return int(binascii.hexlify(s[::-1]), 16) 147 | 148 | def bytes_to_clamped_scalar(s): 149 | # Ed25519 private keys clamp the scalar to ensure two things: 150 | # 1: integer value is in L/2 .. L, to avoid small-logarithm 151 | # non-wraparaound 152 | # 2: low-order 3 bits are zero, so a small-subgroup attack won't learn 153 | # any information 154 | # set the top two bits to 01, and the bottom three to 000 155 | a_unclamped = bytes_to_scalar(s) 156 | AND_CLAMP = (1<<254) - 1 - 7 157 | OR_CLAMP = (1<<254) 158 | a_clamped = (a_unclamped & AND_CLAMP) | OR_CLAMP 159 | return a_clamped 160 | 161 | def random_scalar(entropy_f): # 0..L-1 inclusive 162 | # reduce the bias to a safe level by generating 256 extra bits 163 | oversized = int(binascii.hexlify(entropy_f(32+32)), 16) 164 | return oversized % L 165 | 166 | def password_to_scalar(pw): 167 | oversized = hashlib.sha512(pw).digest() 168 | return int(binascii.hexlify(oversized), 16) % L 169 | 170 | def scalar_to_bytes(y): 171 | y = y % L 172 | assert 0 <= y < 2**256 173 | return binascii.unhexlify("%064x" % y)[::-1] 174 | 175 | # Elements, of various orders 176 | 177 | def is_extended_zero(XYTZ): 178 | # catch Zero 179 | (X, Y, Z, T) = XYTZ 180 | Y = Y % Q 181 | Z = Z % Q 182 | if X==0 and Y==Z and Y!=0: 183 | return True 184 | return False 185 | 186 | class ElementOfUnknownGroup: 187 | # This is used for points of order 2,4,8,2*L,4*L,8*L 188 | def __init__(self, XYTZ): 189 | assert isinstance(XYTZ, tuple) 190 | assert len(XYTZ) == 4 191 | self.XYTZ = XYTZ 192 | 193 | def add(self, other): 194 | if not isinstance(other, ElementOfUnknownGroup): 195 | raise TypeError("elements can only be added to other elements") 196 | sum_XYTZ = add_elements(self.XYTZ, other.XYTZ) 197 | if is_extended_zero(sum_XYTZ): 198 | return Zero 199 | return ElementOfUnknownGroup(sum_XYTZ) 200 | 201 | def scalarmult(self, s): 202 | if isinstance(s, ElementOfUnknownGroup): 203 | raise TypeError("elements cannot be multiplied together") 204 | assert s >= 0 205 | product = scalarmult_element_safe_slow(self.XYTZ, s) 206 | return ElementOfUnknownGroup(product) 207 | 208 | def to_bytes(self): 209 | return encodepoint(xform_extended_to_affine(self.XYTZ)) 210 | def __eq__(self, other): 211 | return self.to_bytes() == other.to_bytes() 212 | def __ne__(self, other): 213 | return not self == other 214 | 215 | class Element(ElementOfUnknownGroup): 216 | # this only holds elements in the main 1*L subgroup. It never holds Zero, 217 | # or elements of order 1/2/4/8, or 2*L/4*L/8*L. 218 | 219 | def add(self, other): 220 | if not isinstance(other, ElementOfUnknownGroup): 221 | raise TypeError("elements can only be added to other elements") 222 | sum_element = ElementOfUnknownGroup.add(self, other) 223 | if sum_element is Zero: 224 | return sum_element 225 | if isinstance(other, Element): 226 | # adding two subgroup elements results in another subgroup 227 | # element, or Zero, and we've already excluded Zero 228 | return Element(sum_element.XYTZ) 229 | # not necessarily a subgroup member, so assume not 230 | return sum_element 231 | 232 | def scalarmult(self, s): 233 | if isinstance(s, ElementOfUnknownGroup): 234 | raise TypeError("elements cannot be multiplied together") 235 | # scalarmult of subgroup members can be done modulo the subgroup 236 | # order, and using the faster non-unified function. 237 | s = s % L 238 | # scalarmult(s=0) gets you Zero 239 | if s == 0: 240 | return Zero 241 | # scalarmult(s=1) gets you self, which is a subgroup member 242 | # scalarmult(s