LiteHashDRBG를 Promise로 변경
- subtleCrypto를 쓰다보니.. - genNextBlock()을 최대한 지연
This commit is contained in:
+92
-59
@@ -1,9 +1,11 @@
|
||||
import type { RNG } from "./RNG";
|
||||
|
||||
import { sha512 } from 'js-sha512';
|
||||
import { sha512 } from './sha2';
|
||||
|
||||
import { convertBytesLikeToUint8Array } from "./convertBytesLikeToUint8Array";
|
||||
import type { BytesLike } from "./BytesLike";
|
||||
import { unwrap } from "./unwrap";
|
||||
import { delay } from "./delay";
|
||||
|
||||
const maxRngSupportBit = 53;
|
||||
const maxInt = 0x1f_ffff_ffff_ffff; // NOTE: b 0, 10000110011, 11...11
|
||||
@@ -84,14 +86,16 @@ export class LiteHashDRBG implements RNG {
|
||||
protected hq: DataView;
|
||||
protected hqIdxPos: number;
|
||||
|
||||
protected ready: Promise<void>;
|
||||
|
||||
public constructor(protected seed: BytesLike, protected stateIdx = 0, bufferIdx = 0) {
|
||||
if(bufferIdx < 0){
|
||||
if (bufferIdx < 0) {
|
||||
throw new Error(`bufferIdx ${bufferIdx} < 0`);
|
||||
}
|
||||
if(bufferIdx >= bufferByteSize){
|
||||
if (bufferIdx >= bufferByteSize) {
|
||||
throw new Error(`bufferidx ${bufferIdx} >= ${bufferByteSize}`);
|
||||
}
|
||||
if(stateIdx < 0){
|
||||
if (stateIdx < 0) {
|
||||
throw new Error(`stateIdx ${stateIdx} < 0`);
|
||||
}
|
||||
|
||||
@@ -103,13 +107,13 @@ export class LiteHashDRBG implements RNG {
|
||||
this.hq = new DataView(hqBuffer);
|
||||
this.hqIdxPos = seedU8.byteLength;
|
||||
|
||||
this.genNextBlock();
|
||||
this.ready = this.genNextBlock();
|
||||
this.bufferIdx = bufferIdx;
|
||||
}
|
||||
|
||||
protected genNextBlock(): void {
|
||||
protected async genNextBlock(): Promise<void> {
|
||||
this.hq.setUint32(this.hqIdxPos, this.stateIdx, true);
|
||||
const digest = sha512.arrayBuffer(this.hq.buffer);
|
||||
const digest = await sha512(this.hq.buffer);
|
||||
this.buffer = digest;
|
||||
this.bufferIdx = 0;
|
||||
this.stateIdx += 1;
|
||||
@@ -119,57 +123,86 @@ export class LiteHashDRBG implements RNG {
|
||||
return maxInt;
|
||||
}
|
||||
|
||||
public nextBytes(bytes: number, baseBytes?: number): Uint8Array {
|
||||
public async nextBytes(bytes: number, baseBytes?: number): Promise<Uint8Array> {
|
||||
bytes |= 0;
|
||||
if (bytes <= 0) {
|
||||
throw new Error(`${bytes} <= 0`);
|
||||
}
|
||||
|
||||
if (this.bufferIdx + bytes <= bufferByteSize) {
|
||||
if(baseBytes === undefined || bytes >= baseBytes){
|
||||
const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes);
|
||||
this.bufferIdx += bytes;
|
||||
if (this.bufferIdx === bufferByteSize) {
|
||||
this.genNextBlock();
|
||||
const ticket = this.ready;
|
||||
|
||||
let waiter: Promise<Uint8Array | undefined> = Promise.resolve(undefined);
|
||||
|
||||
let nextBlockWait: (() => void) | null = (() => { throw 'something wrong'; });
|
||||
|
||||
this.ready = new Promise((resolve, reject) => {
|
||||
waiter = (async () => {
|
||||
await ticket;
|
||||
nextBlockWait = resolve;
|
||||
|
||||
if (this.bufferIdx + bytes <= bufferByteSize) {
|
||||
if (baseBytes === undefined || bytes >= baseBytes) {
|
||||
const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes);
|
||||
this.bufferIdx += bytes;
|
||||
if (this.bufferIdx === bufferByteSize) {
|
||||
nextBlockWait = null;
|
||||
this.genNextBlock().then(resolve, reject);
|
||||
}
|
||||
return new Uint8Array(result);
|
||||
}
|
||||
|
||||
const resultBuffer = new ArrayBuffer(Math.max(bytes, baseBytes ?? 0));
|
||||
const result = new Uint8Array(resultBuffer);
|
||||
result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes));
|
||||
this.bufferIdx += bytes;
|
||||
if (this.bufferIdx === bufferByteSize) {
|
||||
nextBlockWait = null;
|
||||
this.genNextBlock().then(resolve, reject);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new Uint8Array(result);
|
||||
}
|
||||
|
||||
const resultBuffer = new ArrayBuffer(Math.max(bytes, baseBytes));
|
||||
const result = new Uint8Array(resultBuffer);
|
||||
result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes));
|
||||
this.bufferIdx += bytes;
|
||||
if( this.bufferIdx === bufferByteSize){
|
||||
this.genNextBlock();
|
||||
}
|
||||
return result;
|
||||
const resultBuffer = new ArrayBuffer(baseBytes ? Math.max(bytes, baseBytes) : bytes);
|
||||
const result = new Uint8Array(resultBuffer);
|
||||
|
||||
result.set(new Uint8Array(this.buffer, this.bufferIdx));
|
||||
let offset = bufferByteSize - this.bufferIdx;
|
||||
let remain = bytes - offset;
|
||||
|
||||
while (remain > bufferByteSize) {
|
||||
await this.genNextBlock();
|
||||
result.set(new Uint8Array(this.buffer), offset);
|
||||
offset += bufferByteSize;
|
||||
remain -= bufferByteSize;
|
||||
}
|
||||
|
||||
if (remain === 0) {
|
||||
nextBlockWait = null;
|
||||
this.genNextBlock().then(resolve, reject);
|
||||
return result;
|
||||
}
|
||||
|
||||
await this.genNextBlock();
|
||||
result.set(new Uint8Array(this.buffer, 0, remain), offset);
|
||||
this.bufferIdx = remain;
|
||||
return result;
|
||||
})();
|
||||
|
||||
});
|
||||
|
||||
//이 코드를 통해 Promise 내부가 실행된다
|
||||
await delay(0);
|
||||
|
||||
const nextBlock = await waiter;
|
||||
if (nextBlockWait instanceof Promise) {
|
||||
nextBlockWait();
|
||||
}
|
||||
|
||||
const resultBuffer = new ArrayBuffer(baseBytes ? Math.max(bytes, baseBytes) : bytes);
|
||||
const result = new Uint8Array(resultBuffer);
|
||||
|
||||
result.set(new Uint8Array(this.buffer, this.bufferIdx));
|
||||
let offset = bufferByteSize - this.bufferIdx;
|
||||
let remain = bytes - offset;
|
||||
|
||||
while (remain > bufferByteSize) {
|
||||
this.genNextBlock();
|
||||
result.set(new Uint8Array(this.buffer), offset);
|
||||
offset += bufferByteSize;
|
||||
remain -= bufferByteSize;
|
||||
}
|
||||
|
||||
this.genNextBlock();
|
||||
if (remain === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.set(new Uint8Array(this.buffer, 0, remain), offset);
|
||||
this.bufferIdx = remain;
|
||||
return result;
|
||||
return unwrap(nextBlock);
|
||||
}
|
||||
|
||||
public nextBits(bits: number, baseBytes?: number): Uint8Array {
|
||||
public async nextBits(bits: number, baseBytes?: number): Promise<Uint8Array> {
|
||||
await this.ready;
|
||||
|
||||
bits |= 0;
|
||||
const bytes = (bits + 7) >> 3;
|
||||
const headBits = bits & 0x7;
|
||||
@@ -183,15 +216,15 @@ export class LiteHashDRBG implements RNG {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected _nextInt(bits: number): bigint{
|
||||
const buffer = this.nextBits(bits, 8);
|
||||
protected async _nextInt(bits: number): Promise<bigint> {
|
||||
const buffer = await this.nextBits(bits, 8);
|
||||
const dataView = new DataView(buffer.buffer);
|
||||
return dataView.getBigUint64(0, true);
|
||||
}
|
||||
|
||||
public nextInt(max?: number): number {
|
||||
public async nextInt(max?: number): Promise<number> {
|
||||
if (max === undefined || max === maxInt) {
|
||||
return Number(this._nextInt(maxRngSupportBit));
|
||||
return Number(await this._nextInt(maxRngSupportBit));
|
||||
}
|
||||
if (max > maxInt) {
|
||||
throw new Error('Over max int');
|
||||
@@ -207,26 +240,26 @@ export class LiteHashDRBG implements RNG {
|
||||
const bits = intBitMapMask.get(mask) as number;
|
||||
|
||||
let n = Number(this._nextInt(bits));
|
||||
while (n > max){
|
||||
while (n > max) {
|
||||
n = Number(this._nextInt(bits));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public nextFloat1(): number {
|
||||
public async nextFloat1(): Promise<number> {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while(true){
|
||||
const nInt = this._nextInt(maxRngSupportBit + 1);
|
||||
if(nInt < maxIntMore1){
|
||||
while (true) {
|
||||
const nInt = await this._nextInt(maxRngSupportBit + 1);
|
||||
if (nInt < maxIntMore1) {
|
||||
return Number(nInt) / maxIntMore1f;
|
||||
}
|
||||
if(nInt === maxIntMore1){
|
||||
if (nInt === maxIntMore1) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static build(seed: BytesLike, stateIdx = 0): LiteHashDRBG{
|
||||
public static build(seed: BytesLike, stateIdx = 0): LiteHashDRBG {
|
||||
return new LiteHashDRBG(seed, stateIdx);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user