diff --git a/server/util/LiteHashDRBG.ts b/server/util/LiteHashDRBG.ts index 69cabd7..6303736 100644 --- a/server/util/LiteHashDRBG.ts +++ b/server/util/LiteHashDRBG.ts @@ -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; + 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 { 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 { 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 = 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 { + 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 { + 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 { 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 { // 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); } } \ No newline at end of file