From 0e16ea6eacc609034a2dc3d5dfa1e53d42c41158 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 8 Sep 2021 22:18:08 +0200 Subject: [PATCH] Perform exactly the same operations for every speed.py run To make comparisons between different algorithms meaningful, we need to benchmark the exact same operations over and over. Use 100 deterministically random keys to even out key-to-key or sig-to-sig (for verification) differences --- speed.py | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/speed.py b/speed.py index 7e5eb02b..c44dcba6 100644 --- a/speed.py +++ b/speed.py @@ -1,8 +1,10 @@ -import six import timeit from ecdsa.curves import curves +UNIQUE_KEYS = 100 + + def do(setup_statements, statement): # extracted from timeit.py t = timeit.Timer(stmt=statement, setup="\n".join(setup_statements)) @@ -12,7 +14,7 @@ def do(setup_statements, statement): x = t.timeit(number) if x >= 0.2: break - return x / number + return x / number / UNIQUE_KEYS prnt_form = ( @@ -43,13 +45,21 @@ def do(setup_statements, statement): ) for curve in [i.name for i in curves]: - S1 = "import six; from ecdsa import SigningKey, %s" % curve - S2 = "sk = SigningKey.generate(%s)" % curve - S3 = "msg = six.b('msg')" - S4 = "sig = sk.sign(msg)" - S5 = "vk = sk.get_verifying_key()" - S6 = "vk.precompute()" - S7 = "vk.verify(sig, msg)" + S1 = "from ecdsa import SigningKey, %s" % curve + S1 += "; from ecdsa.util import PRNG" + # as the code is very much data dependant, use the same 100 keys + # to sign the same 100 messages over and over to make the + # benchmarking stable + S2 = ( + "sk = [" + "SigningKey.generate({0}, entropy=PRNG(i)) " + "for i in range({1})]" + ).format(curve, UNIQUE_KEYS) + S3 = "msg = b'msg'" + S4 = "sig = [k.sign(msg, entropy=PRNG(b'A')) for k in sk]" + S5 = "vk = [k.get_verifying_key() for k in sk]" + S6 = "[k.precompute() for k in vk]" + S7 = "[k.verify(s, msg) for k, s in zip(vk, sig)]" # We happen to know that .generate() also calculates the # verifying key, which is the time-consuming part. If the code # were changed to lazily calculate vk, we'd need to change this @@ -61,7 +71,7 @@ def do(setup_statements, statement): import ecdsa c = getattr(ecdsa, curve) - sig = ecdsa.SigningKey.generate(c).sign(six.b("msg")) + sig = ecdsa.SigningKey.generate(c).sign(b"msg") print( prnt_form.format( name=curve, @@ -101,10 +111,25 @@ def do(setup_statements, statement): if curve == "Ed25519" or curve == "Ed448": continue S1 = "from ecdsa import SigningKey, ECDH, {0}".format(curve) - S2 = "our = SigningKey.generate({0})".format(curve) - S3 = "remote = SigningKey.generate({0}).verifying_key".format(curve) - S4 = "ecdh = ECDH(private_key=our, public_key=remote)" - S5 = "ecdh.generate_sharedsecret_bytes()" + S1 += "; from ecdsa.util import PRNG" + # as with signatures, calculate shared secrets for the same + # set of keys over and over + S2 = ( + "our = [" + "SigningKey.generate({0}, entropy=PRNG(i)) " + "for i in range({1})]" + ).format(curve, UNIQUE_KEYS) + S3 = ( + "remote = [" + "SigningKey.generate({0}, entropy=PRNG(i+10)).verifying_key " + "for i in range({1})]" + ).format(curve, UNIQUE_KEYS) + S4 = ( + "ecdh = [" + "ECDH(private_key=o, public_key=r) " + "for o, r in zip(our, remote)]" + ) + S5 = "[e.generate_sharedsecret_bytes() for e in ecdh]" ecdh = do([S1, S2, S3, S4], S5) print( ecdh_form.format(