LiteHashDRBG를 Promise로 변경

- subtleCrypto를 쓰다보니..
- genNextBlock()을 최대한 지연
This commit is contained in:
2023-08-05 15:35:34 +00:00
parent 8130c731e0
commit 92a67315f3
+92 -59
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,14 +86,16 @@ 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`);
} }
if(bufferIdx >= bufferByteSize){ if (bufferIdx >= bufferByteSize) {
throw new Error(`bufferidx ${bufferIdx} >= ${bufferByteSize}`); throw new Error(`bufferidx ${bufferIdx} >= ${bufferByteSize}`);
} }
if(stateIdx < 0){ if (stateIdx < 0) {
throw new Error(`stateIdx ${stateIdx} < 0`); throw new Error(`stateIdx ${stateIdx} < 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,57 +123,86 @@ 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`);
} }
if (this.bufferIdx + bytes <= bufferByteSize) { const ticket = this.ready;
if(baseBytes === undefined || bytes >= baseBytes){
const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes); let waiter: Promise<Uint8Array | undefined> = Promise.resolve(undefined);
this.bufferIdx += bytes;
if (this.bufferIdx === bufferByteSize) { let nextBlockWait: (() => void) | null = (() => { throw 'something wrong'; });
this.genNextBlock();
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 resultBuffer = new ArrayBuffer(baseBytes ? Math.max(bytes, baseBytes) : bytes);
const result = new Uint8Array(resultBuffer); const result = new Uint8Array(resultBuffer);
result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes));
this.bufferIdx += bytes; result.set(new Uint8Array(this.buffer, this.bufferIdx));
if( this.bufferIdx === bufferByteSize){ let offset = bufferByteSize - this.bufferIdx;
this.genNextBlock(); let remain = bytes - offset;
}
return result; 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();
} }
return unwrap(nextBlock);
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;
} }
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');
@@ -207,26 +240,26 @@ export class LiteHashDRBG implements RNG {
const bits = intBitMapMask.get(mask) as number; const bits = intBitMapMask.get(mask) as number;
let n = Number(this._nextInt(bits)); let n = Number(this._nextInt(bits));
while (n > max){ while (n > max) {
n = Number(this._nextInt(bits)); n = Number(this._nextInt(bits));
} }
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;
} }
if(nInt === maxIntMore1){ if (nInt === maxIntMore1) {
return 1; return 1;
} }
} }
} }
public static build(seed: BytesLike, stateIdx = 0): LiteHashDRBG{ public static build(seed: BytesLike, stateIdx = 0): LiteHashDRBG {
return new LiteHashDRBG(seed, stateIdx); return new LiteHashDRBG(seed, stateIdx);
} }
} }