LiteHashDRBG를 Promise로 변경

- subtleCrypto를 쓰다보니..
- genNextBlock()을 최대한 지연
This commit is contained in:
2023-08-05 15:35:34 +00:00
parent 8130c731e0
commit 92a67315f3
+50 -17
View File
@@ -1,9 +1,11 @@
import type { RNG } from "./RNG"; import type { RNG } from "./RNG";
import { sha512 } from 'js-sha512'; import { sha512 } from './sha2';
import { convertBytesLikeToUint8Array } from "./convertBytesLikeToUint8Array"; import { convertBytesLikeToUint8Array } from "./convertBytesLikeToUint8Array";
import type { BytesLike } from "./BytesLike"; import type { BytesLike } from "./BytesLike";
import { unwrap } from "./unwrap";
import { delay } from "./delay";
const maxRngSupportBit = 53; const maxRngSupportBit = 53;
const maxInt = 0x1f_ffff_ffff_ffff; // NOTE: b 0, 10000110011, 11...11 const maxInt = 0x1f_ffff_ffff_ffff; // NOTE: b 0, 10000110011, 11...11
@@ -84,6 +86,8 @@ export class LiteHashDRBG implements RNG {
protected hq: DataView; protected hq: DataView;
protected hqIdxPos: number; protected hqIdxPos: number;
protected ready: Promise<void>;
public constructor(protected seed: BytesLike, protected stateIdx = 0, bufferIdx = 0) { public constructor(protected seed: BytesLike, protected stateIdx = 0, bufferIdx = 0) {
if (bufferIdx < 0) { if (bufferIdx < 0) {
throw new Error(`bufferIdx ${bufferIdx} < 0`); throw new Error(`bufferIdx ${bufferIdx} < 0`);
@@ -103,13 +107,13 @@ export class LiteHashDRBG implements RNG {
this.hq = new DataView(hqBuffer); this.hq = new DataView(hqBuffer);
this.hqIdxPos = seedU8.byteLength; this.hqIdxPos = seedU8.byteLength;
this.genNextBlock(); this.ready = this.genNextBlock();
this.bufferIdx = bufferIdx; this.bufferIdx = bufferIdx;
} }
protected genNextBlock(): void { protected async genNextBlock(): Promise<void> {
this.hq.setUint32(this.hqIdxPos, this.stateIdx, true); 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.buffer = digest;
this.bufferIdx = 0; this.bufferIdx = 0;
this.stateIdx += 1; this.stateIdx += 1;
@@ -119,28 +123,41 @@ export class LiteHashDRBG implements RNG {
return maxInt; return maxInt;
} }
public nextBytes(bytes: number, baseBytes?: number): Uint8Array { public async nextBytes(bytes: number, baseBytes?: number): Promise<Uint8Array> {
bytes |= 0; bytes |= 0;
if (bytes <= 0) { if (bytes <= 0) {
throw new Error(`${bytes} <= 0`); throw new Error(`${bytes} <= 0`);
} }
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 (this.bufferIdx + bytes <= bufferByteSize) {
if (baseBytes === undefined || bytes >= baseBytes) { if (baseBytes === undefined || bytes >= baseBytes) {
const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes); const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes);
this.bufferIdx += bytes; this.bufferIdx += bytes;
if (this.bufferIdx === bufferByteSize) { if (this.bufferIdx === bufferByteSize) {
this.genNextBlock(); nextBlockWait = null;
this.genNextBlock().then(resolve, reject);
} }
return new Uint8Array(result); return new Uint8Array(result);
} }
const resultBuffer = new ArrayBuffer(Math.max(bytes, baseBytes)); const resultBuffer = new ArrayBuffer(Math.max(bytes, baseBytes ?? 0));
const result = new Uint8Array(resultBuffer); const result = new Uint8Array(resultBuffer);
result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes)); result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes));
this.bufferIdx += bytes; this.bufferIdx += bytes;
if (this.bufferIdx === bufferByteSize) { if (this.bufferIdx === bufferByteSize) {
this.genNextBlock(); nextBlockWait = null;
this.genNextBlock().then(resolve, reject);
} }
return result; return result;
} }
@@ -153,23 +170,39 @@ export class LiteHashDRBG implements RNG {
let remain = bytes - offset; let remain = bytes - offset;
while (remain > bufferByteSize) { while (remain > bufferByteSize) {
this.genNextBlock(); await this.genNextBlock();
result.set(new Uint8Array(this.buffer), offset); result.set(new Uint8Array(this.buffer), offset);
offset += bufferByteSize; offset += bufferByteSize;
remain -= bufferByteSize; remain -= bufferByteSize;
} }
this.genNextBlock();
if (remain === 0) { if (remain === 0) {
nextBlockWait = null;
this.genNextBlock().then(resolve, reject);
return result; return result;
} }
await this.genNextBlock();
result.set(new Uint8Array(this.buffer, 0, remain), offset); result.set(new Uint8Array(this.buffer, 0, remain), offset);
this.bufferIdx = remain; this.bufferIdx = remain;
return result; return result;
})();
});
//이 코드를 통해 Promise 내부가 실행된다
await delay(0);
const nextBlock = await waiter;
if (nextBlockWait instanceof Promise) {
nextBlockWait();
}
return unwrap(nextBlock);
} }
public nextBits(bits: number, baseBytes?: number): Uint8Array { public async nextBits(bits: number, baseBytes?: number): Promise<Uint8Array> {
await this.ready;
bits |= 0; bits |= 0;
const bytes = (bits + 7) >> 3; const bytes = (bits + 7) >> 3;
const headBits = bits & 0x7; const headBits = bits & 0x7;
@@ -183,15 +216,15 @@ export class LiteHashDRBG implements RNG {
return result; return result;
} }
protected _nextInt(bits: number): bigint{ protected async _nextInt(bits: number): Promise<bigint> {
const buffer = this.nextBits(bits, 8); const buffer = await this.nextBits(bits, 8);
const dataView = new DataView(buffer.buffer); const dataView = new DataView(buffer.buffer);
return dataView.getBigUint64(0, true); return dataView.getBigUint64(0, true);
} }
public nextInt(max?: number): number { public async nextInt(max?: number): Promise<number> {
if (max === undefined || max === maxInt) { if (max === undefined || max === maxInt) {
return Number(this._nextInt(maxRngSupportBit)); return Number(await this._nextInt(maxRngSupportBit));
} }
if (max > maxInt) { if (max > maxInt) {
throw new Error('Over max int'); throw new Error('Over max int');
@@ -213,10 +246,10 @@ export class LiteHashDRBG implements RNG {
return n; return n;
} }
public nextFloat1(): number { public async nextFloat1(): Promise<number> {
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
while (true) { while (true) {
const nInt = this._nextInt(maxRngSupportBit + 1); const nInt = await this._nextInt(maxRngSupportBit + 1);
if (nInt < maxIntMore1) { if (nInt < maxIntMore1) {
return Number(nInt) / maxIntMore1f; return Number(nInt) / maxIntMore1f;
} }