/*!
 * hmac-drbg.js - hmac-drbg implementation for bcoin
 * Copyright (c) 2017, Christopher Jeffrey (MIT License).
 * https://github.com/bcoin-org/bcoin
 * Parts of this software based on hmac-drbg.
 */

'use strict';

const assert = require('assert');
const digest = require('./digest');

/*
 * Constants
 */

const HASH_ALG = 'sha256';
const HASH_SIZE = 32;
const RESEED_INTERVAL = 0x1000000000000;
const POOL33 = Buffer.allocUnsafe(HASH_SIZE + 1);
const POOL112 = Buffer.allocUnsafe(HASH_SIZE * 2 + 48);
const POOL145 = Buffer.allocUnsafe(POOL33.length + POOL112.length);

/**
 * HmacDRBG
 * @constructor
 */

function HmacDRBG(entropy, nonce, pers) {
  if (!(this instanceof HmacDRBG))
    return new HmacDRBG(entropy, nonce, pers);

  this.K = Buffer.allocUnsafe(HASH_SIZE);
  this.V = Buffer.allocUnsafe(HASH_SIZE);
  this.rounds = 0;

  this.init(entropy, nonce, pers);
}

HmacDRBG.prototype.init = function init(entropy, nonce, pers) {
  for (let i = 0; i < this.V.length; i++) {
    this.K[i] = 0x00;
    this.V[i] = 0x01;
  }

  this.reseed(entropy, nonce, pers);
};

HmacDRBG.prototype.reseed = function reseed(entropy, nonce, pers) {
  const seed = POOL112;

  assert(Buffer.isBuffer(entropy));
  assert(Buffer.isBuffer(nonce));
  assert(Buffer.isBuffer(pers));

  assert(entropy.length === HASH_SIZE);
  assert(nonce.length === HASH_SIZE);
  assert(pers.length === 48);

  entropy.copy(seed, 0);
  nonce.copy(seed, HASH_SIZE);
  pers.copy(seed, HASH_SIZE * 2);

  this.update(seed);
  this.rounds = 1;
};

HmacDRBG.prototype.iterate = function iterate() {
  const data = POOL33;

  this.V.copy(data, 0);
  data[HASH_SIZE] = 0x00;

  this.K = digest.hmac(HASH_ALG, data, this.K);
  this.V = digest.hmac(HASH_ALG, this.V, this.K);
};

HmacDRBG.prototype.update = function update(seed) {
  const data = POOL145;

  assert(Buffer.isBuffer(seed));
  assert(seed.length === HASH_SIZE * 2 + 48);

  this.V.copy(data, 0);
  data[HASH_SIZE] = 0x00;
  seed.copy(data, HASH_SIZE + 1);

  this.K = digest.hmac(HASH_ALG, data, this.K);
  this.V = digest.hmac(HASH_ALG, this.V, this.K);

  data[HASH_SIZE] = 0x01;

  this.K = digest.hmac(HASH_ALG, data, this.K);
  this.V = digest.hmac(HASH_ALG, this.V, this.K);
};

HmacDRBG.prototype.generate = function generate(len) {
  if (this.rounds > RESEED_INTERVAL)
    throw new Error('Reseed is required.');

  const data = Buffer.allocUnsafe(len);
  let pos = 0;

  while (pos < len) {
    this.V = digest.hmac(HASH_ALG, this.V, this.K);
    this.V.copy(data, pos);
    pos += HASH_SIZE;
  }

  this.iterate();
  this.rounds++;

  return data;
};

/*
 * Expose
 */

module.exports = HmacDRBG;
