280 строки
7.7 KiB
TypeScript
280 строки
7.7 KiB
TypeScript
///<reference path='refs.ts'/>
|
|
|
|
module TDev.Random {
|
|
|
|
var sha256_k = new Uint32Array([
|
|
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 rotr(v:number, b:number)
|
|
{
|
|
return (v >>> b) | (v << (32 - b));
|
|
}
|
|
|
|
function sha256round(w:Uint32Array)
|
|
{
|
|
Util.assert(w.length == 64);
|
|
|
|
for (var i = 16; i < 64; ++i) {
|
|
var s0 = rotr(w[i-15], 7) ^ rotr(w[i-15], 18) ^ (w[i-15] >>> 3);
|
|
var s1 = rotr(w[i-2], 17) ^ rotr(w[i-2], 19) ^ (w[i-2] >>> 10);
|
|
w[i] = (w[i-16] + s0 + w[i-7] + s1) | 0;
|
|
}
|
|
|
|
var a = w[0];
|
|
var b = w[1];
|
|
var c = w[2];
|
|
var d = w[3];
|
|
var e = w[4];
|
|
var f = w[5];
|
|
var g = w[6];
|
|
var h = w[7];
|
|
|
|
for (var i = 0; i < 64; ++i) {
|
|
var s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)
|
|
var ch = (e & f) ^ (~e & g)
|
|
var temp1 = h + s1 + ch + sha256_k[i] + w[i]
|
|
var s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)
|
|
var maj = (a & b) ^ (a & c) ^ (b & c)
|
|
var temp2 = s0 + maj
|
|
|
|
h = g
|
|
g = f
|
|
f = e
|
|
e = d + temp1
|
|
d = c
|
|
c = b
|
|
b = a
|
|
a = temp1 + temp2
|
|
}
|
|
|
|
w[0] += a
|
|
w[1] += b
|
|
w[2] += c
|
|
w[3] += d
|
|
w[4] += e
|
|
w[5] += f
|
|
w[6] += g
|
|
w[7] += h
|
|
}
|
|
|
|
export class RC4 {
|
|
private rc4_buf:Uint8Array;
|
|
private rc4_i = 0;
|
|
private rc4_j = 0;
|
|
|
|
constructor()
|
|
{
|
|
this.rc4_buf = new Uint8Array(256);
|
|
for (var i = 0; i < 256; ++i)
|
|
this.rc4_buf[i] = i;
|
|
}
|
|
|
|
public getBytes(arr:Uint8Array)
|
|
{
|
|
var rc4_i = this.rc4_i
|
|
var rc4_j = this.rc4_j
|
|
var rc4_buf = this.rc4_buf
|
|
|
|
for (var i = 0; i < arr.length; ++i) {
|
|
rc4_i = (rc4_i + 1) & 0xff;
|
|
rc4_j = (rc4_j + rc4_buf[rc4_i]) & 0xff;
|
|
|
|
var tmp = rc4_buf[rc4_i]
|
|
var tmp2 = rc4_buf[rc4_j]
|
|
rc4_buf[rc4_i] = tmp2
|
|
rc4_buf[rc4_j] = tmp
|
|
|
|
arr[i] = rc4_buf[(tmp + tmp2) & 0xff];
|
|
}
|
|
|
|
this.rc4_i = rc4_i
|
|
this.rc4_j = rc4_j
|
|
}
|
|
|
|
public addEntropy(key:Uint8Array)
|
|
{
|
|
var rc4_buf = this.rc4_buf
|
|
var j = 0;
|
|
for (var i = 0; i < 256; ++i) {
|
|
j = (j + rc4_buf[i] + key[i % key.length]) & 0xff;
|
|
var tmp = rc4_buf[i]
|
|
rc4_buf[i] = rc4_buf[j]
|
|
rc4_buf[j] = tmp
|
|
}
|
|
|
|
// drop 4k of output
|
|
bytes(new Uint8Array(4096))
|
|
}
|
|
|
|
// '4' and '2' are repeated, so we have only alpha-numeric characters in ids
|
|
static idChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345678942"
|
|
public uniqueId(len = 24)
|
|
{
|
|
// 12 is about 64 bits; this gives 1/1M chance of collision on 1M entries
|
|
// 24 is about 128 bits; this gives 1/1000B chance of collision on 10000B entries
|
|
var buf = new Uint8Array(len)
|
|
this.getBytes(buf)
|
|
var r = ""
|
|
for (var i = 0; i < buf.length; ++i)
|
|
r += RC4.idChars.charAt(buf[i] & 63)
|
|
return r.replace(/^[0-9]/, "x"); // it should better start with a letter
|
|
}
|
|
}
|
|
|
|
var rc4:RC4;
|
|
var saveScheduled = false;
|
|
var u32_8:Uint8Array;
|
|
var u32_32:Uint32Array;
|
|
|
|
export var strongEntropySource : (buf:Uint8Array)=>void = null;
|
|
|
|
function addEntropy(key:Uint8Array)
|
|
{
|
|
rc4.addEntropy(key)
|
|
}
|
|
|
|
function scheduleSave()
|
|
{
|
|
if (saveScheduled || strongEntropySource) return;
|
|
saveScheduled = true;
|
|
Util.setTimeout(1000, () => {
|
|
saveScheduled = false;
|
|
saveState();
|
|
})
|
|
}
|
|
|
|
function setup()
|
|
{
|
|
if (rc4) return;
|
|
|
|
rc4 = new RC4()
|
|
|
|
u32_8 = new Uint8Array(4);
|
|
u32_32 = new Uint32Array(u32_8.buffer);
|
|
|
|
var key = new Uint8Array(64);
|
|
for (var i = 0; i < key.length; ++i)
|
|
key[i] = (Math.random() * 0x100) & 0xff;
|
|
addEntropy(key);
|
|
|
|
var needsMoreEntropy = true;
|
|
var wc = (<any>window).crypto
|
|
try {
|
|
if (wc && wc.getRandomValues) {
|
|
wc.getRandomValues(key);
|
|
addEntropy(key);
|
|
needsMoreEntropy = false;
|
|
}
|
|
} catch (exn) {
|
|
// this tends to fail on some versions of Firefox with Permission denied exception
|
|
}
|
|
|
|
if (needsMoreEntropy) {
|
|
if (strongEntropySource) {
|
|
strongEntropySource(key);
|
|
addEntropy(key);
|
|
} else {
|
|
addEntropy64(window.localStorage["entropy"]);
|
|
|
|
if (!window.localStorage["gotCloudEntropy"])
|
|
// most likely we get entropy from InstalledHeaders; no need to ask
|
|
Util.setTimeout(1000, () => {
|
|
if (!window.localStorage["gotCloudEntropy"])
|
|
Cloud.getRandomAsync().done(addCloudEntropy, err => { });
|
|
})
|
|
}
|
|
}
|
|
|
|
scheduleSave();
|
|
}
|
|
|
|
export function bytes(arr:Uint8Array)
|
|
{
|
|
setup();
|
|
bytes_internal(arr);
|
|
scheduleSave();
|
|
}
|
|
|
|
function bytes_internal(arr:Uint8Array)
|
|
{
|
|
rc4.getBytes(arr)
|
|
}
|
|
|
|
export function uint32():number
|
|
{
|
|
setup();
|
|
bytes_internal(u32_8);
|
|
scheduleSave();
|
|
return u32_32[0];
|
|
}
|
|
|
|
var m32 = 1 / 0x100000000;
|
|
var m64 = 1 / 0x10000000000000000;
|
|
|
|
export function normalized()
|
|
{
|
|
return uint32() * m32 + uint32() * m64;
|
|
}
|
|
|
|
function saveState()
|
|
{
|
|
setup();
|
|
if (strongEntropySource) return;
|
|
var state = new Uint8Array(8*4);
|
|
bytes_internal(state);
|
|
window.localStorage["entropy"] = Util.base64EncodeBytes(<any>state);
|
|
}
|
|
|
|
export function addCloudEntropy(buf:string)
|
|
{
|
|
if (buf) {
|
|
addEntropy64(buf);
|
|
saveState();
|
|
window.localStorage["gotCloudEntropy"] = "yes";
|
|
}
|
|
}
|
|
|
|
function addEntropy64(buf:string)
|
|
{
|
|
if (!buf) return;
|
|
var strbuf = Util.base64Decode(buf);
|
|
if (!strbuf) return;
|
|
setup();
|
|
addEntropy(Util.stringToUint8Array(strbuf))
|
|
scheduleSave();
|
|
}
|
|
|
|
export function uniqueId(len = 24)
|
|
{
|
|
setup()
|
|
var r = rc4.uniqueId(len)
|
|
scheduleSave()
|
|
return r
|
|
}
|
|
|
|
export function permute<T>(arr:T[])
|
|
{
|
|
for (var i = 0; i < arr.length; ++i) {
|
|
var j = uint32() % arr.length
|
|
var tmp = arr[i]
|
|
arr[i] = arr[j]
|
|
arr[j] = tmp
|
|
}
|
|
}
|
|
|
|
export function pick<T>(arr:T[]):T
|
|
{
|
|
if (arr.length == 0) return null;
|
|
return arr[uint32() % arr.length];
|
|
}
|
|
|
|
}
|