#  Copyright (c) 2000 Autonomous Zone Industries
#  This file is licensed under the
#    GNU Lesser General Public License v2.1.
#    See the file COPYING or visit http://www.gnu.org/ for details.

__revision__ = "$Id: hashrandom.py,v 1.1 2002/12/02 21:20:46 myers_carpenter Exp $"

# python standard modules
import sha

# egtp modules
from egtp import mencode, mojosixbit, std

def shafunc(inpstr):
    return sha.new(inpstr).digest()


class HashRandom:
    """
    Provides a cryptographically strong pseudo-random number generator based
    on a cryptographically strong hash function.  Hopefully this means that
    as long as the hash function is collision free and irreversible then no
    attacker will be able to predict any byte of output that he hasn't seen,
    given that he doesn't know anything about the seed and given that he can
    see as many bytes of output as he desires except for the byte that he is
    trying to predict.

    Further it is hoped that an attacker will not even be able to determine
    whether a given stream of random bytes was generated by this PRNG or by
    flipping a coin repeatedly.

    #### !!!! XXXX TODO Zooko: investigate this.  --Zooko 2000/07/03
    """
    def __init__(self, hashfunc, seed):
        """ 
        The caller must make sure that an attacker cannot guess `seed' with
        a non-negligible chance of success.  Ensuring this may be harder
        than it at first appears.  The preconditions below do _not_ assert
        that an attacker cannot guess `seed' with a non-negligible chance of
        success, and satisfying the preconditions does not mean that you
        have succeeded at that goal.  The preconditions are just to help you
        notice if you pass the wrong variable by mistake.

        Note: I suggest that you collect 160 bits of entropy for your seed
        even though 80 bits should be more than enough to prevent anyone
        from guessing your seed.  Why?  Because for one thing you might
        always over-estimate your entropy, but moreover I think that it is
        good to be in the habit of using random numbers large enough to
        prevent birthday attacks.  This will help you avoid a
        chosen-protocol attack (or a badly designed protocol that you might
        use) in which someone convinces you that a given thing must have
        some singular property since it was generated using a random seed
        (that they chose) of size 80 bits, when in fact they used a birthday
        technique to find a seed that violates this assumption of yours.

        Of course, some people would say that you should scour your
        protocols more closely to find birthday weaknesses, avoid trusting
        other people's random numbers, and so forth.  I certainly can't
        argue with that, but I also think it won't hurt to use 160 bit
        random seeds when you can afford them.

        Now if you are getting this random seed by asking the user to type
        randomly or to wiggle the mouse, then I personally don't think the
        added 80 bits is worth the user interface costs (of having to type
        or wiggle for twice as long), so in that case I would just use an
        80-bit seed.  (But estimate your entropy carefully.)

        --Zooko 2000/07/03

        @precondition: `seed' is long enough.: len(seed) >= 10: "seed: %s" % std.hr(seed)
        """
        assert len(seed) >= 10, "`seed' is long enough." + " -- " + "seed: %s" % std.hr(seed)

        self._hashfunc = hashfunc
        self._seed = seed
        self._avail = ""
        self._counter = 0

    def get(self, bytes):
        bytesleft = bytes

        res = "" ## !! XX Note: this could be faster...  -Zooko 2000/07/03

        while bytesleft > 0:
            if len(self._avail) == 0:
                hinputdict = {'input values for generating pseudo-random output': {'seed': mojosixbit.b2a(self._seed), 'diversifier': `self._counter`}}
                self._counter = self._counter + 1L
                self._avail = self._hashfunc(mencode.mencode(hinputdict))

            numb = min(len(self._avail), bytesleft)
        
            chunk = self._avail[:numb]
            self._avail = self._avail[numb:]
            #### !!!! XXXX TODO Zooko: check this.  --Zooko 2000/07/03

            res = res + chunk
            assert len(chunk) == numb
            
            bytesleft = bytesleft - numb

        assert len(res) == bytes

        return res


class SHARandom(HashRandom):
    def __init__(self, seed):
        HashRandom.__init__(self, shafunc, seed)
    

def hashexpand(inpstr, expbytes, HRClass=SHARandom):
    return HRClass(inpstr).get(expbytes)
