/*
 * Copyright (c) 2012-2020 MIRACL UK Ltd.
 *
 * This file is part of MIRACL Core
 * (see https://github.com/miracl/core).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 *   Cryptographic strong random number generator
 *
 *   Unguessable seed -> SHA -> PRNG internal state -> SHA -> random numbers
 *   Slow - but secure
 *
 *   See ftp://ftp.rsasecurity.com/pub/pdfs/bull-1.pdf for a justification
 */
/* SU=m, m is Stack Usage */

#include "core.h"

using namespace core;

/* SU= 20 */
static unsign32 sbrand(csprng *rng)
{
    /* Marsaglia & Zaman random number generator */
    int i, k;
    unsign32 pdiff, t;
    rng->rndptr++;
    if (rng->rndptr < NK) return rng->ira[rng->rndptr];
    rng->rndptr = 0;
    for (i = 0, k = NK - NJ; i < NK; i++, k++)
    {
        /* calculate next NK values */
        if (k == NK) k = 0;
        t = rng->ira[k];
        pdiff = t - rng->ira[i] - rng->borrow;

        if (pdiff < t) rng->borrow = 0;
        if (pdiff > t) rng->borrow = 1;
        rng->ira[i] = pdiff;
    }
    return rng->ira[0];
}

/* SU= 20 */
static void sirand(csprng* rng, unsign32 seed)
{
    /* initialise random number system */
    /* modified so that a subsequent call "stirs" in another seed value */
    /* in this way as many seed bits as desired may be used */
    int i, in;
    unsign32 t, m = 1;
    rng->borrow = 0L;
    rng->rndptr = 0;
    rng->ira[0] ^= seed;
    for (i = 1; i < NK; i++)
    {
        /* fill initialisation vector */
        in = (NV * i) % NK;
        rng->ira[in] ^= m;    /* note XOR */
        t = m;
        m = seed - m;
        seed = t;
    }
    for (i = 0; i < 10000; i++) sbrand(rng ); /* "warm-up" & stir the generator */
}

/* SU= 312 */
static void fill_pool(csprng *rng)
{
    /* hash down output of RNG to re-fill the pool */
    int i;
    hash256 sh;
    HASH256_init(&sh);
    for (i = 0; i < 128; i++) HASH256_process(&sh, sbrand(rng));
    HASH256_hash(&sh, rng->pool);
    rng->pool_ptr = 0;
}

static unsign32 pack(const uchar *b)
{
    /* pack bytes into a 32-bit Word */
    return ((unsign32)b[3] << 24) | ((unsign32)b[2] << 16) | ((unsign32)b[1] << 8) | (unsign32)b[0];
}

/* SU= 360 */
/* Initialize RNG with some real entropy from some external source */
void core::RAND_seed(csprng *rng, int rawlen, char *raw)
{
    /* initialise from at least 128 byte string of raw  *
     * random (keyboard?) input, and 32-bit time-of-day */
    int i;
    char digest[32];
    uchar b[4];
    hash256 sh;
    rng->pool_ptr = 0;
    for (i = 0; i < NK; i++) rng->ira[i] = 0;
    if (rawlen > 0)
    {
        HASH256_init(&sh);
        for (i = 0; i < rawlen; i++)
            HASH256_process(&sh, raw[i]);
        HASH256_hash(&sh, digest);

        /* initialise PRNG from distilled randomness */

        for (i = 0; i < 8; i++)
        {
            b[0] = digest[4 * i];
            b[1] = digest[4 * i + 1];
            b[2] = digest[4 * i + 2];
            b[3] = digest[4 * i + 3];
            //  printf("%08x\n",pack(b));
            sirand(rng, pack(b));
        }
    }
    fill_pool(rng);
}

/* Terminate and clean up */
void core::RAND_clean(csprng *rng)
{
    /* kill internal state */
    int i;
    rng->pool_ptr = rng->rndptr = 0;
    for (i = 0; i < 32; i++) rng->pool[i] = 0;
    for (i = 0; i < NK; i++) rng->ira[i] = 0;
    rng->borrow = 0;
}

/* get random byte */
/* SU= 8 */
int core::RAND_byte(csprng *rng)
{
    int r;
    r = rng->pool[rng->pool_ptr++];
    if (rng->pool_ptr >= 32) fill_pool(rng);
    return (r & 0xff);
}

/* test main program */
/*
#include <stdio.h>
#include <string.h>

void main()
{
    int i;
    char raw[256];
    csprng rng;

    RAND_clean(&rng);


    for (i=0;i<256;i++) raw[i]=(char)i;
    RAND_seed(&rng,256,raw);

    for (i=0;i<1000;i++)
        printf("%02x ",(unsigned char)RAND_byte(&rng));
}

*/
