#!/usr/bin/env python import random import base64 import os SEED = os.environ.get('REPEATABLE_RANDOMNESS_SEED', None) if SEED is None: # Generate a seed which is fairly short (to ease cut-and-paste, writing it # down, etc.). Note that Python's random module's seed() function is going # to take the hash() of this seed, which is a 32-bit value (currently) so # there is no point in making this seed larger than 32 bits. Make it 30 # bits, which conveniently fits into six base-32 chars. Include a separator # because chunking facilitates memory (including working and short-term # memory) in humans. chars = "ybndrfg8ejkmcpqxot1uwisza345h769" # Zooko's choice, rationale in "DESIGN" doc in z-base-32 project SEED = ''.join([random.choice(chars) for x in range(3)] + ['-'] + [random.choice(chars) for x in range(3)]) import logging logging.info("REPEATABLE_RANDOMNESS_SEED: %s\n" % SEED) logging.info("In order to reproduce this run of the code, set the environment variable \"REPEATABLE_RANDOMNESS_SEED\" to %s before executing.\n" % SEED) random.seed(SEED) def seed_which_refuses(a): logging.warn("I refuse to reseed to %s -- I already seeded with %s.\n" % (a, SEED,)) return random.seed = seed_which_refuses from random import randrange import unittest from pycryptopp.publickey import ecdsa def randstr(n, rr=randrange): return ''.join([chr(rr(0, 256)) for x in xrange(n)]) from base64 import b32encode def ab(x): # debuggery if len(x) >= 3: return "%s:%s" % (len(x), b32encode(x[-3:]),) elif len(x) == 2: return "%s:%s" % (len(x), b32encode(x[-2:]),) elif len(x) == 1: return "%s:%s" % (len(x), b32encode(x[-1:]),) elif len(x) == 0: return "%s:%s" % (len(x), "--empty--",) def div_ceil(n, d): """ The smallest integer k such that k*d >= n. """ return (n/d) + (n%d != 0) KEYBITS=192 # The number of bytes required for a seed to have the same security level as a # key in this elliptic curve: 2 bits of public key per bit of security. SEEDBITS=div_ceil(192, 2) SEEDBYTES=div_ceil(SEEDBITS, 8) # The number of bytes required to encode a public key in this elliptic curve. PUBKEYBYTES=div_ceil(KEYBITS, 8)+1 # 1 byte for the sign of the y component # The number of bytes requires to encode a signature in this elliptic curve. SIGBITS=KEYBITS*2 SIGBYTES=div_ceil(SIGBITS, 8) class Signer(unittest.TestCase): def test_construct(self): seed = randstr(SEEDBYTES) ecdsa.SigningKey(seed) def test_sign(self): seed = randstr(SEEDBYTES) signer = ecdsa.SigningKey(seed) sig = signer.sign("message") self.failUnlessEqual(len(sig), SIGBYTES) def test_sign_and_verify(self): seed = randstr(SEEDBYTES) signer = ecdsa.SigningKey(seed) sig = signer.sign("message") v = signer.get_verifying_key() self.failUnless(v.verify("message", sig)) def test_sign_and_verify_emptymsg(self): seed = randstr(SEEDBYTES) signer = ecdsa.SigningKey(seed) sig = signer.sign("") v = signer.get_verifying_key() self.failUnless(v.verify("", sig)) def test_construct_from_same_seed_is_reproducible(self): seed = randstr(SEEDBYTES) signer1 = ecdsa.SigningKey(seed) signer2 = ecdsa.SigningKey(seed) self.failUnlessEqual(signer1.get_verifying_key().serialize(), signer2.get_verifying_key().serialize()) # ... and using different seeds constructs a different private key. seed3 = randstr(SEEDBYTES) assert seed3 != seed, "Internal error in Python random module's PRNG (or in pycryptopp's hacks to it to facilitate testing) -- got two identical strings from randstr(%s)" % SEEDBYTES signer3 = ecdsa.SigningKey(seed3) self.failIfEqual(signer1.get_verifying_key().serialize(), signer3.get_verifying_key().serialize()) # Also try the all-zeroes string just because bugs sometimes are # data-dependent on zero or cause bogus zeroes. seed4 = '\x00'*SEEDBYTES assert seed4 != seed, "Internal error in Python random module's PRNG (or in pycryptopp's hacks to it to facilitate testing) -- got the all-zeroes string from randstr(%s)" % SEEDBYTES signer4 = ecdsa.SigningKey(seed4) self.failIfEqual(signer4.get_verifying_key().serialize(), signer1.get_verifying_key().serialize()) signer5 = ecdsa.SigningKey(seed4) self.failUnlessEqual(signer5.get_verifying_key().serialize(), signer4.get_verifying_key().serialize()) def test_construct_short_seed(self): try: ecdsa.SigningKey("\x00\x00\x00") except ecdsa.Error, le: self.failUnless("seed is required to be of length " in str(le), le) else: self.fail("Should have raised error from seed being too short.") def test_construct_bad_arg_type(self): try: ecdsa.SigningKey(1) except TypeError, le: self.failUnless("must be string" in str(le), le) else: self.fail("Should have raised error from seed being of the wrong type.") class Verifier(unittest.TestCase): def test_from_signer_and_serialize_and_deserialize(self): seed = randstr(SEEDBYTES) signer = ecdsa.SigningKey(seed) verifier = signer.get_verifying_key() s1 = verifier.serialize() self.failUnlessEqual(len(s1), PUBKEYBYTES) ecdsa.VerifyingKey(s1) s2 = verifier.serialize() self.failUnlessEqual(s1, s2) def flip_one_bit(s): assert s i = randrange(0, len(s)) result = s[:i] + chr(ord(s[i])^(0x01<