feat: rng 테스트 추가

This commit is contained in:
2022-03-12 23:47:49 +09:00
parent 801b846d69
commit d2dcaaf93c
4 changed files with 410 additions and 25 deletions
+200 -24
View File
@@ -1,4 +1,4 @@
import chai, { assert, expect } from 'chai';
import chai, { assert } from 'chai';
import chaiBytes from 'chai-bytes';
import { bufferByteSize, LiteHashDRBG } from '../ts/util/LiteHashDRBG';
import { RandUtil } from '../ts/util/RandUtil';
@@ -36,6 +36,7 @@ function fillBlock(body: MaybeBytes, filler: MaybeBytes = '\0', length = bufferB
return buffer;
}
class DummyBlockRNG extends LiteHashDRBG {
private repeatBlockCnt: number;
private repeatBlock: ArrayBuffer[];
@@ -133,37 +134,117 @@ describe('RNGtestDummy', () => {
});
});
describe('RNGexpectedError', ()=>{
const rng = new LiteHashDRBG(fixedKey);
it('nextBits0', ()=>{
assert.throw(()=>rng.nextBits(0));
});
it('nextBits-1', ()=>{
assert.throw(()=>rng.nextBits(-1));
});
describe('RandUtilDummy', () => {
it('shuffle', () => {
const rng = new DummyBlockRNG([fillBlock('', '\x17\x16\x15\x14\x13\x12\x11\x10')]);
const randUtil = new RandUtil(rng);
/**
* 7, [7,1,2,3,4,5,6,0]
* 6, [7,0,2,3,4,5,6,1]
* 5, [7,0,1,3,4,5,6,2]
* 4, [7,0,1,2,4,5,6,3]
* 3, [7,0,1,2,3,5,6,4]
* 2, [7,0,1,2,3,4,6,5]
* 1, [7,0,1,2,3,4,5,6]
*/
assert.deepEqual(
randUtil.shuffle(Array.from(_.range(8))),
[7, 0, 1, 2, 3, 4, 5, 6]
);
it('nextBytes0', ()=>{
assert.throw(()=>rng.nextBytes(0));
});
it('nextBytes-1', ()=>{
assert.throw(()=>rng.nextBytes(-1));
/**
* 0, [0,1,2,3,4,5,6,7,8,9]
* 7, [0,8,2,3,4,5,6,7,1,9]
* 6, [0,8,1,3,4,5,6,7,2,9]
* 5, [0,8,1,2,4,5,6,7,3,9]
* 4, [0,8,1,2,3,5,6,7,4,9]
* 3, [0,8,1,2,3,4,6,7,5,9]
* 2, [0,8,1,2,3,4,5,7,6,9]
* 1, [0,8,1,2,3,4,5,6,7,9]
* 0, [0,8,1,2,3,4,5,6,7,9]
*/
assert.deepEqual(
randUtil.shuffle(Array.from(_.range(10))),
[0, 8, 1, 2, 3, 4, 5, 6, 7, 9]
);
});
const rng = new DummyBlockRNG([fillBlock('', '\x17\x16\x15\x14\x13\x12\x11\x10')]);
const randUtil = new RandUtil(rng);
it('utilEmptyChoice', ()=>{
assert.throw(()=>randUtil.choice([]));
it('choice', () => {
//0x17(7), 0x16(6)
assert.equal(randUtil.choice([0, 1, 2, 3, 4, 5]), 5);
//0x15(5), Set 순서 유지
assert.equal(randUtil.choice(new Set([5, 3, 1, 2, 8, 0])), 8);
//0x14(4), 정렬 순서상 숫자(소-대) > 문자열(삽입순) > 심볼 순서
assert.equal(randUtil.choice({ c: 'c', a: 'a', b: 'b', 4: 'x', 2: 't', '3': 'q' }), 'c');
});
it('utilEmptyChoiceUsingWeight', ()=>{
assert.throw(()=>randUtil.choiceUsingWeight({}));
});
it('utilEmptyChoiceUsingWeightPair', ()=>{
assert.throw(()=>randUtil.choiceUsingWeightPair([]));
it('choiceUsingWeight', () => {
//0.6275740099377194 * 38.1 = 23.91
assert.equal(randUtil.choiceUsingWeight({
a: 0.1,
b: 10,
tt: 2,
x: -1,
c: 20,
d: 0,
e: 6
}), 'c');
//0.658946544056166
assert.equal(randUtil.choiceUsingWeightPair([
['xx', 10],
]), 'xx');
//0.6903152783785083 * 27.3 = 18.84560709973328
assert.equal(randUtil.choiceUsingWeightPair([
['e', 10],
['d', 4],
['c', 0.1],
['baba', 0.2],
['q', 9],
['xt', 4]
]), 'q');
});
});
describe('RNGAcceptable', ()=>{
describe('RNGexpectedError', () => {
const rng = new LiteHashDRBG(fixedKey);
it('RNG', ()=>{
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);
@@ -171,7 +252,7 @@ describe('RNGAcceptable', ()=>{
});
const randUtil = new RandUtil(rng);
it('RandUtil', ()=>{
it('RandUtil', () => {
randUtil.choice([0, 0, 0]);
randUtil.choiceUsingWeight({
0: 0,
@@ -191,4 +272,99 @@ describe('RNGAcceptable', ()=>{
randUtil.nextRange(1, -1);
randUtil.nextRangeInt(1, -1);
})
it('RNGLong', () => {
const longKey = fixedKey;
for(const _a of _.range(8)){
longKey.concat(longKey);
}
const rngLong = new LiteHashDRBG(longKey);
for(const _a of _.range(10)){
rngLong.nextBytes(16);
}
})
});
/* Python TestVector
import hashlib
import struct
fixedKey = 'HelloWorld'.encode('utf-8')
def hash(key, idx):
idxV = struct.pack("<I", idx)
return hashlib.sha512(key + idxV).digest()
for idx in range(5):
print(hash(fixedKey, idx).hex())
*/
describe('RNG', () => {
//JS - PHP 일치 확인 정도로.
const testVector = Buffer.from([
'24d9ccd648556255fd0ee9f5b29918de90617341958b3b354d572167e4dee02b757816a2bbe0b502c52413ffd384381a9d7b4e193df6f4345d6a95e111d661c4',
'2e9264512f6f4b080cf1376b74fab6878ecf4a6e185942d2e5b22cf923885b9952d40601a414225d6901417fd4ce9368ac77e4a63d3fc9b58ab952bb8c33f165',
'8e2ebf5af6283a1b18f4c044c86c20d02be3890613c4cc8b7c6b7b35581263b972a82630df69a9289988422d7c3a9be5edf78d5de16fabd01e5dd4e458068d8a',
'398596047ba547bfe371ec863a3e019ab0dbc4bb3b27e9077685aae4283ff6bbccfd981d92f9358f7efffbb72a940414802d98466d132e2ad0a16a12946d5f47',
'b3606fe9b18c4aa7315e78bb9e47cb51cc4e203fcc2e631f0405c1b872c8e1cb5b6415ea74bbb77fffaaadb002b47cb4f4628dc0709634365b187667f5c708cb',
].join(''), 'hex');
it('bytes', ()=>{
const rng = new LiteHashDRBG(fixedKey);
let offset = 0;
assert.equalBytes(rng.nextBytes(10), testVector.slice(offset, offset + 10), '1');
offset += 10;
assert.equalBytes(rng.nextBytes(32), testVector.slice(offset, offset + 32), '2');
offset += 32;
assert.equalBytes(rng.nextBytes(1), testVector.slice(offset, offset + 1), '3');
offset += 1;
assert.equalBytes(rng.nextBytes(64), testVector.slice(offset, offset + 64), '4');
offset += 64;
assert.equalBytes(rng.nextBytes(5), testVector.slice(offset, offset + 5), '5');
offset += 5;
const lastA = rng.nextBytes(16, 18);
const lastB = new Uint8Array(18);
lastB.set(testVector.slice(offset, offset + 16));
assert.equalBytes(lastA, lastB);
});
it('bits', ()=>{
const rng = new LiteHashDRBG(fixedKey);
let offset = 0;
const testBits = [10, 4, 15, 32, 7, 99, 512, 1, 2, 3];
for(const bits of testBits){
const bytes = Math.ceil(bits / 8)
const A = rng.nextBits(bits);
const B = new Uint8Array(testVector.slice(offset, offset + bytes));
offset += bytes;
if(bits % 8 != 0){
const bitMask = 0xff >> (8 - (bits % 8));
B[bytes - 1] &= bitMask;
}
assert.equalBytes(A, B);
}
});
it('float', ()=>{
const rng = new LiteHashDRBG(fixedKey);
const rng2 = new DummyBlockRNG([
new Uint8Array(testVector.slice(bufferByteSize * 0, bufferByteSize * 1)),
new Uint8Array(testVector.slice(bufferByteSize * 1, bufferByteSize * 2)),
new Uint8Array(testVector.slice(bufferByteSize * 2, bufferByteSize * 3)),
new Uint8Array(testVector.slice(bufferByteSize * 3, bufferByteSize * 4)),
new Uint8Array(testVector.slice(bufferByteSize * 4, bufferByteSize * 5)),
]);
for(const idx of _.range(18)){
assert.equal(rng.nextFloat1(), rng2.nextFloat1(), `float${idx}`);
}
});
});
+3
View File
@@ -47,6 +47,9 @@ export class RandUtil {
const result: T[] = Array.from(srcArray);
for (let srcIdx = 0; srcIdx < cnt; srcIdx += 1) {
const destIdx = this.rng.nextInt(cnt - srcIdx - 1) + srcIdx;
if(srcIdx === destIdx){
continue;
}
[result[srcIdx], result[destIdx]] = [result[destIdx], result[srcIdx]];
}
+3
View File
@@ -62,6 +62,9 @@ class RandUtil
//PHP의 range는 max 포함
foreach (range(0, $cnt - 1) as $srcIdx) {
$destIdx = $this->rng->nextInt($cnt - $srcIdx - 1) + $srcIdx;
if($srcIdx === $destIdx){
continue;
}
$tmpVal = $result[$srcIdx];
$result[$srcIdx] = $result[$destIdx];
$result[$destIdx] = $tmpVal;
+204 -1
View File
@@ -28,7 +28,6 @@ function fillBlock(string $src, string $filler = '\0', int $length = BLOCK_SIZE)
return $result . substr($filler, 0, $moreLen);
}
class DummyBlockRNG extends LiteHashDRBG
{
private int $repeatBlockCnt;
@@ -58,10 +57,31 @@ class DummyBlockRNG extends LiteHashDRBG
}
}
/* Python TestVector
import hashlib
import struct
fixedKey = 'HelloWorld'.encode('utf-8')
def hash(key, idx):
idxV = struct.pack("<I", idx)
return hashlib.sha512(key + idxV).digest()
for idx in range(5):
print(hash(fixedKey, idx).hex())
*/
$rngTestVector = hex2bin(join('', [
'24d9ccd648556255fd0ee9f5b29918de90617341958b3b354d572167e4dee02b757816a2bbe0b502c52413ffd384381a9d7b4e193df6f4345d6a95e111d661c4',
'2e9264512f6f4b080cf1376b74fab6878ecf4a6e185942d2e5b22cf923885b9952d40601a414225d6901417fd4ce9368ac77e4a63d3fc9b58ab952bb8c33f165',
'8e2ebf5af6283a1b18f4c044c86c20d02be3890613c4cc8b7c6b7b35581263b972a82630df69a9289988422d7c3a9be5edf78d5de16fabd01e5dd4e458068d8a',
'398596047ba547bfe371ec863a3e019ab0dbc4bb3b27e9077685aae4283ff6bbccfd981d92f9358f7efffbb72a940414802d98466d132e2ad0a16a12946d5f47',
'b3606fe9b18c4aa7315e78bb9e47cb51cc4e203fcc2e631f0405c1b872c8e1cb5b6415ea74bbb77fffaaadb002b47cb4f4628dc0709634365b187667f5c708cb',
]));
class RNGTest extends PHPUnit\Framework\TestCase
{
const FIXED_KEY = 'HelloWorld';
public function testDummyRNG()
{
//Block의 값을 고정하고 입력값을 확인
@@ -120,6 +140,114 @@ class RNGTest extends PHPUnit\Framework\TestCase
$this->assertEquals(0x08776655443322 / $floatMax, $fb, 'float1-2');
}
public function testRandUtilDummy_shuffle(){
$rng = new DummyBlockRNG([
fillBlock('', "\x17\x16\x15\x14\x13\x12\x11\x10")
]);
$randUtil = new RandUtil($rng);
/**
* 7, [7,1,2,3,4,5,6,0]
* 6, [7,0,2,3,4,5,6,1]
* 5, [7,0,1,3,4,5,6,2]
* 4, [7,0,1,2,4,5,6,3]
* 3, [7,0,1,2,3,5,6,4]
* 2, [7,0,1,2,3,4,6,5]
* 1, [7,0,1,2,3,4,5,6]
*/
$this->assertEquals(
$randUtil->shuffle(range(0, 7)),
[7, 0, 1, 2, 3, 4, 5, 6]
);
/**
* 0, [0,1,2,3,4,5,6,7,8,9]
* 7, [0,8,2,3,4,5,6,7,1,9]
* 6, [0,8,1,3,4,5,6,7,2,9]
* 5, [0,8,1,2,4,5,6,7,3,9]
* 4, [0,8,1,2,3,5,6,7,4,9]
* 3, [0,8,1,2,3,4,6,7,5,9]
* 2, [0,8,1,2,3,4,5,7,6,9]
* 1, [0,8,1,2,3,4,5,6,7,9]
* 0, [0,8,1,2,3,4,5,6,7,9]
*/
$this->assertEquals(
$randUtil->shuffle(range(0, 9)),
[0, 8, 1, 2, 3, 4, 5, 6, 7, 9]
);
$rng = new DummyBlockRNG([
fillBlock('', "\x17\x16\x15\x14\x13\x12\x11\x10")
]);
$randUtil = new RandUtil($rng);
//Same as first, but assoc.
$this->assertEquals($randUtil->shuffleAssoc([
'a' => 0,
'b' => 1,
'c' => 2,
'd' => 3,
'e' => 4,
'f' => 5,
'g' => 6,
'h' => 7,
]), [
'h' => 7,
'a' => 0,
'b' => 1,
'c' => 2,
'd' => 3,
'e' => 4,
'f' => 5,
'g' => 6,
]);
}
public function testRandUtilDummy_choiceSeries(){
$rng = new DummyBlockRNG([
fillBlock('', "\x17\x16\x15\x14\x13\x12\x11\x10")
]);
$randUtil = new RandUtil($rng);
//0x17(7), 0x16(6)
$this->assertEquals($randUtil->choice([0, 1, 2, 3, 4, 5]), 5);
//0x15(5), Set 순서 유지
$this->assertEquals($randUtil->choice([5, 3, 1, 2, 8, 0]), 8);
//0x14(4), Js의 Object와 순서 다름!
$this->assertEquals($randUtil->choice([
2=>'t', 3=>'q', 4=>'x',
'c'=> 'c', 'a'=> 'a', 'b'=> 'b'
]), 'c');
//0.6275740099377194 * 38.1 = 23.91
$this->assertEquals($randUtil->choiceUsingWeight([
"a"=> 0.1,
"b"=> 10,
"tt"=> 2,
"x"=> -1,
"c"=> 20,
"d"=> 0,
"e"=> 6
]), 'c');
//0.658946544056166
$this->assertEquals($randUtil->choiceUsingWeightPair([
['xx', 10],
]), 'xx');
//0.6903152783785083 * 27.3 = 18.84560709973328
$this->assertEquals($randUtil->choiceUsingWeightPair([
['e', 10],
['d', 4],
['c', 0.1],
['baba', 0.2],
['q', 9],
['xt', 4]
]), 'q');
}
public function testRNGInvalidNextBits0()
{
$rng = new LiteHashDRBG(self::FIXED_KEY);
@@ -208,5 +336,80 @@ class RNGTest extends PHPUnit\Framework\TestCase
$randUtil->nextRangeInt(0, 0);
$randUtil->nextRange(1, -1);
$randUtil->nextRangeInt(1, -1);
$longKey = self::FIXED_KEY;
foreach(range(0, 7) as $_){
$longKey.=$longKey;
}
$rngLong = new LiteHashDRBG($longKey);
foreach(range(0, 9) as $_){
$rngLong->nextBytes(16);
}
}
public function testRNGBytes()
{
global $rngTestVector;
$testVector = $rngTestVector;
$rng = new LiteHashDRBG(static::FIXED_KEY);
$offset = 0;
$this->assertEquals($rng->nextBytes(10), substr($testVector, $offset, 10));
$offset += 10;
$this->assertEquals($rng->nextBytes(32), substr($testVector, $offset, 32));
$offset += 32;
$this->assertEquals($rng->nextBytes(1), substr($testVector, $offset, 1));
$offset += 1;
$this->assertEquals($rng->nextBytes(64), substr($testVector, $offset, 64));
$offset += 64;
$this->assertEquals($rng->nextBytes(5), substr($testVector, $offset, 5));
$offset += 5;
$this->assertEquals($rng->nextBytes(16), substr($testVector, $offset, 16));
$offset += 16;
}
public function testRNGBits()
{
global $rngTestVector;
$testVector = $rngTestVector;
$rng = new LiteHashDRBG(static::FIXED_KEY);
$offset = 0;
$testBits = [10, 4, 15, 32, 7, 99, 512, 1, 2, 3];
foreach($testBits as $bits){
$bytes = intdiv($bits + 7, 8);
$A = $rng->nextBits($bits);
$B = substr($testVector, $offset, $bytes);
$offset += $bytes;
if($bits % 8 != 0){
$bitMask = 0xff >> (8 - ($bits % 8));
$B[$bytes - 1] = chr(ord($B[$bytes - 1]) & $bitMask);
}
$this->assertEquals($A, $B);
}
}
public function testRNGFloat()
{
global $rngTestVector;
$testVector = $rngTestVector;
$rng = new LiteHashDRBG(static::FIXED_KEY);
$rng2 = new DummyBlockRNG([
substr($testVector, BLOCK_SIZE * 0, BLOCK_SIZE),
substr($testVector, BLOCK_SIZE * 1, BLOCK_SIZE),
substr($testVector, BLOCK_SIZE * 2, BLOCK_SIZE),
substr($testVector, BLOCK_SIZE * 3, BLOCK_SIZE),
substr($testVector, BLOCK_SIZE * 4, BLOCK_SIZE),
]);
foreach(range(0, 17) as $idx){
$this->assertEquals($rng->nextFloat1(), $rng2->nextFloat1(), "float{$idx}");
}
}
}