// Secure hash sha256 function.
// Look in FIPS "Secure hash standard"

var BLOCK_SIZE = 64;
var PADDING_CONST = 56;
var HASH_SIZE = 32;
var WORD_SIZE = 32;
var BITS_PER_BYTE = 8;

var HEX_DIGITS = "0123456789abcdef";

var K = new Array(0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
		  0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
		  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
		  0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
		  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
		  0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
		  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
		  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
		 );

function shr(x, n)
{
    return (x >>> n);
}

function rotr(x, n)
{
    return ((x << (WORD_SIZE - n)) | shr(x, n)) >>> 0;
}

function ch(x, y, z)
{
    return ((x & y) ^ (~x & z)) >>> 0;
}

function maj(x, y, z)
{
    return ((x & y) ^ (x & z) ^ (y & z)) >>> 0;
}

function s0(x)
{
    return (rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3)) >>> 0;
}

function s1(x)
{
    return (rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10)) >>> 0;
}

function s2(x)
{
    return (rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22)) >>> 0;
}

function s3(x)
{
    return (rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25)) >>> 0;
}

function make_32bit(data, i)
{
    return ((data[i + 3]) | (data[i + 2] << 8) | (data[i + 1] << 16) | (data[i] << 24)) >>> 0;
}

function expand(W, j)
{
    return (W[j & 0x0f] += s1(W[(j + 14) & 0x0f]) + W[(j + 9) & 0x0f] + s0(W[(j + 1) & 0x0f]));
}

function add(x, y)
{
    return (x + y) >>> 0;
}

function SHA256()
{
    this.hash = new Array(8);
    this.buffer = new Array(64);
    this.count = new Array(2);

    this.count[0] = 0;
    this.count[1] = 0;

    this.transform = SHA256_transform;
    this.digest = SHA256_digest;
    this.update = SHA256_update;
    this.finalize = SHA256_finalize;
    this.byte_array = SHA256_to_byte_array;
    this.hex_string = SHA256_to_hex_string;
    this.init = SHA256_init;

    this.block_size = function() { return BLOCK_SIZE; };
    this.hash_size = function() { return HASH_SIZE; };
    this.hash_size_bits = function() { return HASH_SIZE * BITS_PER_BYTE; };

    this.init();
}

function SHA256_init()
{
    this.hash[0] = 0x6A09E667;
    this.hash[1] = 0xBB67AE85;
    this.hash[2] = 0x3C6EF372;
    this.hash[3] = 0xA54FF53A;
    this.hash[4] = 0x510E527F;
    this.hash[5] = 0x9B05688C;
    this.hash[6] = 0x1F83D9AB;
    this.hash[7] = 0x5BE0CD19;

    this.count[0] = 0;
    this.count[1] = 0;
}

function SHA256_transform()
{
    var a, b, c, d, e, f, g, h, T1, T2;
    var W = new Array(16);

    a = this.hash[0];
    b = this.hash[1];
    c = this.hash[2];
    d = this.hash[3];
    e = this.hash[4];
    f = this.hash[5];
    g = this.hash[6];
    h = this.hash[7];

    for (var i = 0; i < 16; i++) {
	W[i] = make_32bit(this.buffer, i << 2);
    }

    for (var j = 0; j < 64; j++) {
	T1 = h + s3(e) + ch(e, f, g) + K[j];

	if (j < 16) {
	    T1 += W[j];
	}
	else {
	    T1 += expand(W, j);
	}

	T2 = s2(a) + maj(a, b, c);

	h = g;
	g = f;
	f = e;
	e = add(d, T1);
	d = c;
	c = b;
	b = a;
	a = add(T1, T2);
    }

    this.hash[0] = add(this.hash[0], a);
    this.hash[1] = add(this.hash[1], b);
    this.hash[2] = add(this.hash[2], c);
    this.hash[3] = add(this.hash[3], d);
    this.hash[4] = add(this.hash[4], e);
    this.hash[5] = add(this.hash[5], f);
    this.hash[6] = add(this.hash[6], g);
    this.hash[7] = add(this.hash[7], h);
}

function SHA256_update(data, length)
{
    var index;
    var curpos = 0;
    var remainder = (length & 0x3f);

    index = ((this.count[0] >> 3) & 0x3f);

    if ((this.count[0] += (length << 3)) < (length << 3)) {
	this.count[1]++;
    }

    this.count[1] += (length >> 29);

    for (var i = 0; i < length - 63; i += 64) {
	for (var j = index; j < 64; j++) {
//	    this.buffer[j] = data.charCodeAt(curpos++);
	    this.buffer[j] = data[curpos++];
	}

	this.transform();

	index = 0;
    }

    for (var j = 0; j < remainder; j++) {
//	this.buffer[j] = data.charCodeAt(curpos++);
	this.buffer[j] = data[curpos++];
    }
}

function SHA256_finalize()
{
    var index = ((this.count[0] >> 3) & 0x3f);
    this.buffer[index++] = 0x80;

    if (index <= PADDING_CONST) {
	for (var i = index; i < PADDING_CONST; i++) {
	    this.buffer[i] = 0;
	}
    }
    else {
	for (var i = index; i < BLOCK_SIZE; i++) {
	    this.buffer[i] = 0;
	}

	this.transform();

	for (var i = 0; i < PADDING_CONST; i++) {
	    this.buffer[i] = 0;
	}
    }

    this.buffer[PADDING_CONST    ] = (this.count[1] >>> 24) & 0xff;
    this.buffer[PADDING_CONST + 1] = (this.count[1] >>> 16) & 0xff;
    this.buffer[PADDING_CONST + 2] = (this.count[1] >>>  8) & 0xff;
    this.buffer[PADDING_CONST + 3] = (this.count[1]       ) & 0xff;
    this.buffer[PADDING_CONST + 4] = (this.count[0] >>> 24) & 0xff;
    this.buffer[PADDING_CONST + 5] = (this.count[0] >>> 16) & 0xff;
    this.buffer[PADDING_CONST + 6] = (this.count[0] >>>  8) & 0xff;
    this.buffer[PADDING_CONST + 7] = (this.count[0]       ) & 0xff;

    this.transform();
}

function SHA256_to_byte_array()
{
    var j = 0;
    var result = new Array(32);

    for (var i = 0; i < 8; i++) {
	result[j++] = ((this.hash[i] >>> 24) & 0xff);
	result[j++] = ((this.hash[i] >>> 16) & 0xff);
	result[j++] = ((this.hash[i] >>>  8) & 0xff);
	result[j++] = ((this.hash[i]       ) & 0xff);
    }

    return result;
}

function SHA256_to_hex_string() {
    var result = new String();

    for(var i = 0; i < 8; i++) {
	for(var j = 28; j >= 0; j -= 4)
	    result += HEX_DIGITS.charAt((this.hash[i] >>> j) & 0x0f);
    }

    return result;
}

function SHA256_digest(data)
{
    this.init();
    this.update(data, data.length);
    this.finalize();

    return this.byte_array();
}

