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 { 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,6 +86,8 @@ 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) {
throw new Error(`bufferIdx ${bufferIdx} < 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,28 +123,41 @@ 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`);
}
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) {
this.genNextBlock();
nextBlockWait = null;
this.genNextBlock().then(resolve, reject);
}
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);
result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes));
this.bufferIdx += bytes;
if (this.bufferIdx === bufferByteSize) {
this.genNextBlock();
nextBlockWait = null;
this.genNextBlock().then(resolve, reject);
}
return result;
}
@@ -153,23 +170,39 @@ export class LiteHashDRBG implements RNG {
let remain = bytes - offset;
while (remain > bufferByteSize) {
this.genNextBlock();
await this.genNextBlock();
result.set(new Uint8Array(this.buffer), offset);
offset += bufferByteSize;
remain -= bufferByteSize;
}
this.genNextBlock();
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();
}
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');
@@ -213,10 +246,10 @@ export class LiteHashDRBG implements RNG {
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);
const nInt = await this._nextInt(maxRngSupportBit + 1);
if (nInt < maxIntMore1) {
return Number(nInt) / maxIntMore1f;
}