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 { 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);
}
}