#  Copyright (c) 2002-2012 Zooko Wilcox-O'Hearn
#  This file is part of pyutil; see README.rst for licensing terms.

import warnings

"""
Cryptographically strong pseudo-random number generator based on SHA256.
"""

class SHA256Expander:
    """
    Provide a cryptographically strong pseudo-random number generator based on
    SHA256.  Hopefully this means that no attacker will be able to predict any
    bit 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 bits of output as he desires
    except for the bit 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.
    The safety of this technique has not been verified by a Real Cryptographer.
    ... but it is similar to the PRNG in FIPS-186...

    The seed and counter are encoded in DJB's netstring format so that I
    don't have to think about the possibility of ambiguity.
    
    Note: I've since learned more about the theory of secure hash functions
    and the above is a strong assumption about a secure hash function.  Use
    of this class should be considered deprecated and you should use a more
    well-analyzed KDF (such as the nascent standard HKDF) or stream cipher or
    whatever it is that you need.
    """
    def __init__(self, seed=None):
        warnings.warn("deprecated", DeprecationWarning)
        if seed is not None:
            self.seed(seed)

    def seed(self, seed):
        import hashlib
        self.starth = hashlib.sha256('24:pyutil hash expansion v2,10:algorithm:,6:SHA256,6:value:,')
        seedlen = len(seed)
        seedlenstr = str(seedlen)
        self.starth.update(seedlenstr)
        self.starth.update(':')
        self.starth.update(seed)
        self.starth.update(',')

        self.avail = ""
        self.counter = 0

    def get(self, bytes):
        bytesleft = bytes

        res = []

        while bytesleft > 0:
            if len(self.avail) == 0:
                h = self.starth.copy()
                counterstr = str(self.counter)
                counterstrlen = len(counterstr)
                counterstrlenstr = str(counterstrlen)
                h.update(counterstrlenstr)
                h.update(':')
                h.update(counterstr)
                h.update(',')
                self.avail = h.digest()
                self.counter += 1

            numb = min(len(self.avail), bytesleft)

            (chunk, self.avail,) = (self.avail[:numb], self.avail[numb:],)

            res.append(chunk)
            
            bytesleft = bytesleft - numb

        resstr = ''.join(res)
        assert len(resstr) == bytes

        return resstr

def sha256expand(inpstr, expbytes):
    return SHA256Expander(inpstr).get(expbytes)
