Skip to content

Commit

Permalink
Merge pull request #169 from tomato42/bitwise-randrange
Browse files Browse the repository at this point in the history
Bitwise randrange
  • Loading branch information
tomato42 committed Nov 30, 2019
2 parents fed45a6 + 515fb67 commit 96566c7
Showing 1 changed file with 27 additions and 33 deletions.
60 changes: 27 additions & 33 deletions src/ecdsa/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import math
import binascii
import sys
from hashlib import sha256
from six import PY3, int2byte, b, next
from . import der
Expand All @@ -16,12 +17,22 @@
oid_ecPublicKey = (1, 2, 840, 10045, 2, 1)
encoded_oid_ecPublicKey = der.encode_oid(*oid_ecPublicKey)

if sys.version > '3':
def entropy_to_bits(ent_256):
"""Convert a bytestring to string of 0's and 1's"""
return bin(int.from_bytes(ent_256, 'big'))[2:].zfill(len(ent_256)*8)
else:
def entropy_to_bits(ent_256):
"""Convert a bytestring to string of 0's and 1's"""
return ''.join(bin(ord(x))[2:].zfill(8) for x in ent_256)

def bit_length(num):
# http://docs.python.org/dev/library/stdtypes.html#int.bit_length
s = bin(num) # binary representation: bin(-37) --> '-0b100101'
s = s.lstrip('-0b') # remove leading zeros and minus sign
return len(s) # len('100101') --> 6

if sys.version < '2.7': #Can't add a method to a built-in type so we are stuck with this
def bit_length(x):
return len(bin(x)) - 2
else:
def bit_length(x):
return x.bit_length() or 1


def orderlen(order):
Expand All @@ -30,43 +41,26 @@ def orderlen(order):

def randrange(order, entropy=None):
"""Return a random integer k such that 1 <= k < order, uniformly
distributed across that range. For simplicity, this only behaves well if
'order' is fairly close (but below) a power of 256. The try-try-again
algorithm we use takes longer and longer time (on average) to complete as
'order' falls, rising to a maximum of avg=512 loops for the worst-case
(256**k)+1 . All of the standard curves behave well. There is a cutoff at
10k loops (which raises RuntimeError) to prevent an infinite loop when
something is really broken like the entropy function not working.
distributed across that range. Worst case should be a mean of 2 loops at
(2**k)+2.
Note that this function is not declared to be forwards-compatible: we may
change the behavior in future releases. The entropy= argument (which
should get a callable that behaves like os.urandom) can be used to
achieve stability within a given release (for repeatable unit tests), but
should not be used as a long-term-compatible key generation algorithm.
"""
# we could handle arbitrary orders (even 256**k+1) better if we created
# candidates bit-wise instead of byte-wise, which would reduce the
# worst-case behavior to avg=2 loops, but that would be more complex. The
# change would be to round the order up to a power of 256, subtract one
# (to get 0xffff..), use that to get a byte-long mask for the top byte,
# generate the len-1 entropy bytes, generate one extra byte and mask off
# the top bits, then combine it with the rest. Requires jumping back and
# forth between strings and integers a lot.

assert order > 1
if entropy is None:
entropy = os.urandom
assert order > 1
bytes = orderlen(order)
dont_try_forever = 10000 # gives about 2**-60 failures for worst case
while dont_try_forever > 0:
dont_try_forever -= 1
candidate = string_to_number(entropy(bytes)) + 1
if 1 <= candidate < order:
return candidate
continue
raise RuntimeError("randrange() tried hard but gave up, either something"
" is very wrong or you got realllly unlucky. Order was"
" %x" % order)
upper_2 = bit_length(order-2)
upper_256 = upper_2//8 + 1
while True: # I don't think this needs a counter with bit-wise randrange
ent_256 = entropy(upper_256)
ent_2 = entropy_to_bits(ent_256)
rand_num = int(ent_2[:upper_2], base=2) + 1
if 0 < rand_num < order:
return rand_num


class PRNG:
Expand Down

0 comments on commit 96566c7

Please sign in to comment.