import chai, { assert, expect } from 'chai'; import chaiBytes from 'chai-bytes'; import { bufferByteSize, LiteHashDRBG } from '../ts/util/LiteHashDRBG'; import { RandUtil } from '../ts/util/RandUtil'; import { convertBytesLikeToArrayBuffer } from '../ts/util/convertBytesLikeToArrayBuffer'; import { convertBytesLikeToUint8Array as toBytes } from '../ts/util/convertBytesLikeToUint8Array'; import _ from 'lodash'; chai.use(chaiBytes); type Bytes = ArrayBuffer | DataView | Uint8Array; type MaybeBytes = Bytes | string; function fillBlock(body: MaybeBytes, filler: MaybeBytes = '\0', length = bufferByteSize): Uint8Array { const u8Body = toBytes(body); const u8Filler = toBytes(filler, false); if (u8Filler.byteLength < 1) { throw new Error('filler must have length'); } const buffer = new Uint8Array(length); buffer.set(u8Body, 0); let bufferIdx = u8Body.byteLength; while (bufferIdx + u8Filler.byteLength < length) { buffer.set(u8Filler, bufferIdx); bufferIdx += u8Filler.byteLength; } if (bufferIdx < length) { const slice = new Uint8Array(u8Filler.buffer, u8Filler.byteOffset, length - bufferIdx); buffer.set(slice, bufferIdx); } return buffer; } class DummyBlockRNG extends LiteHashDRBG { private repeatBlockCnt: number; private repeatBlock: ArrayBuffer[]; public constructor(repeatBlock: MaybeBytes[], stateIdx = 0) { super('x'); this.repeatBlock = []; for (const rawBlock of repeatBlock) { const block = convertBytesLikeToArrayBuffer(rawBlock); if (block.byteLength !== bufferByteSize) { throw new Error; } this.repeatBlock.push(block); } this.repeatBlockCnt = this.repeatBlock.length; this.stateIdx = stateIdx; this.bufferIdx = 0; this.genNextBlock(); } protected genNextBlock() { if (!this.repeatBlock) { return; } this.buffer = this.repeatBlock[this.stateIdx]; this.bufferIdx = 0; this.stateIdx = (this.stateIdx + 1) % this.repeatBlockCnt; } } const fixedKey = 'HelloWorld'; describe('RNGtestDummy', () => { const rng = new DummyBlockRNG([ fillBlock('', "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff") ]); it('BasicConvert', () => { assert.equal(toBytes("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", false).length, 16); }); it('SimpleByte', () => { assert.equalBytes(toBytes("\x00", false), rng.nextBytes(1), 'b1'); assert.equalBytes(toBytes("\x11\x22", false), rng.nextBytes(2), 'b2'); assert.equalBytes(toBytes("\x33\x44\x55", false), rng.nextBytes(3), 'b3'); assert.equalBytes(toBytes("\x66\x77\x88\x99", false), rng.nextBytes(4), 'b4'); }); it('OverflowBlock', () => { for (const idx in _.range(16)) { assert.equalBytes( toBytes("\xaa\xbb\xcc\xdd\xee\xff\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99", false), rng.nextBytes(16) ); } }); it('MultiBlock', () => { assert.equalBytes( fillBlock('', "\xaa\xbb\xcc\xdd\xee\xff\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99", bufferByteSize * 2), rng.nextBytes(bufferByteSize * 2) ); }); it('bitTest', () => { assert.equalBytes(toBytes("\x00", false), rng.nextBits(1)); //aa assert.equalBytes(toBytes("\x01", false), rng.nextBits(1)); //bb assert.equalBytes(toBytes("\xcc", false), rng.nextBits(8)); //cc assert.equalBytes(toBytes("\xdd\x02", false), rng.nextBits(10)); //ddee assert.equalBytes(toBytes("\x7f", false), rng.nextBits(7)); //ff assert.equalBytes(toBytes("\x00\x11\x22\x33\x44\x55\x06", false), rng.nextBits(53)); }); it('int', () => { assert.equal(0x77, rng.nextInt(0xff)); assert.equal(0x9988, rng.nextInt((1 << 16) - 1)); assert.equal(0xddccbbaa, rng.nextInt(0xffffffff)); assert.equal(0x0433221100ffee, rng.nextInt()); assert.equal(0x05, rng.nextInt(0x0f)); //55 assert.equal(0x06, rng.nextInt(0x12)); //66 assert.equal(0x08, rng.nextInt(99)); //77(119 -> 7bit) -> 88(136 -> 8bit -> 8) assert.equal(0x99, rng.nextInt(0x99)); //99 assert.equal(0xaa, rng.nextInt(0xaa)); //aa (fit Max) }); it('float', () => { const floatMax = 2 ** 53; const fa = rng.nextFloat1(); assert.equal(0x1100ffeeddccbb / floatMax, fa); assert.isTrue(0.5313720384 > fa); assert.isTrue(0.5313720383 < fa); const fb = rng.nextFloat1(); assert.equal(0x08776655443322 / floatMax, fb); }); }); describe('RNGexpectedError', ()=>{ const rng = new LiteHashDRBG(fixedKey); it('nextBits0', ()=>{ assert.throw(()=>rng.nextBits(0)); }); it('nextBits-1', ()=>{ assert.throw(()=>rng.nextBits(-1)); }); it('nextBytes0', ()=>{ assert.throw(()=>rng.nextBytes(0)); }); it('nextBytes-1', ()=>{ assert.throw(()=>rng.nextBytes(-1)); }); const randUtil = new RandUtil(rng); it('utilEmptyChoice', ()=>{ assert.throw(()=>randUtil.choice([])); }); it('utilEmptyChoiceUsingWeight', ()=>{ assert.throw(()=>randUtil.choiceUsingWeight({})); }); it('utilEmptyChoiceUsingWeightPair', ()=>{ assert.throw(()=>randUtil.choiceUsingWeightPair([])); }); }); describe('RNGAcceptable', ()=>{ const rng = new LiteHashDRBG(fixedKey); it('RNG', ()=>{ rng.nextInt(0); rng.nextInt(2 ** 53 - 1); rng.nextBytes(65); rng.nextBits(512); }); const randUtil = new RandUtil(rng); it('RandUtil', ()=>{ randUtil.choice([0, 0, 0]); randUtil.choiceUsingWeight({ 0: 0, 1: -1 }); randUtil.choiceUsingWeightPair([ [0, 0], [1, 0], [2, -2] ]); randUtil.nextBool(1.1); randUtil.nextBool(-0.1); randUtil.shuffle([]); randUtil.shuffle([1]); randUtil.nextRange(0, 0); randUtil.nextRangeInt(0, 0); randUtil.nextRange(1, -1); randUtil.nextRangeInt(1, -1); }) });