From: Chris Duncan Date: Mon, 9 Dec 2024 13:20:27 +0000 (-0800) Subject: Convert synchronously-created Entropy to async factory class. Use bytes as underlying... X-Git-Url: https://zoso.dev/?a=commitdiff_plain;h=14e05bdddc929efbcaee9a6f3211d0859e276ba6;p=libnemo.git Convert synchronously-created Entropy to async factory class. Use bytes as underlying data type, and only convert to other values when calling getters. --- diff --git a/src/lib/entropy.ts b/src/lib/entropy.ts index 8f5cc26..295c8d3 100644 --- a/src/lib/entropy.ts +++ b/src/lib/entropy.ts @@ -18,103 +18,103 @@ const MOD = 4 * brand new source of entropy will be generated at the maximum size of 256 bits. */ export class Entropy { - #bits: string - #buffer: ArrayBuffer + static #isInternal: boolean = false #bytes: Uint8Array - #hex: string - get bits (): string { return this.#bits } - get buffer (): ArrayBuffer { return this.#buffer } + get bits (): string { return bytes.toBin(this.#bytes) } + get buffer (): ArrayBuffer { return this.#bytes.buffer } get bytes (): Uint8Array { return this.#bytes } - get hex (): string { return this.#hex } + get hex (): string { return bytes.toHex(this.#bytes) } + + constructor (bytes: Uint8Array) { + if (!Entropy.#isInternal) { + throw new Error(`Entropy cannot be instantiated directly. Use 'await Entropy.create()' instead.`) + } + Entropy.#isInternal = false + this.#bytes = bytes + } /** * Generate 256 bits of entropy. */ - constructor () + static async create (): Promise /** * Generate between 16-32 bytes of entropy. * @param {number} size - Number of bytes to generate in multiples of 4 */ - constructor (size: number) + static async create (size: number): Promise + static async create (size?: number): Promise { + return new Promise(resolve => { + if (size != null) { + if (typeof size !== 'number') { + throw new TypeError(`Entropy cannot use ${typeof size} as a size`) + } + if (size < MIN || size > MAX) { + throw new RangeError(`Entropy must be ${MIN}-${MAX} bytes`) + } + if (size % MOD !== 0) { + throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`) + } + } + Entropy.#isInternal = true + resolve(new this(crypto.getRandomValues(new Uint8Array(size ?? MAX)))) + }) + } + /** * Import existing entropy and validate it. * @param {string} hex - Hexadecimal string */ - constructor (hex: string) + static async import (hex: string): Promise /** * Import existing entropy and validate it. * @param {ArrayBuffer} buffer - Byte buffer */ - constructor (buffer: ArrayBuffer) + static async import (buffer: ArrayBuffer): Promise /** * Import existing entropy and validate it. * @param {Uint8Array} bytes - Byte array */ - constructor (bytes: Uint8Array) - constructor (input?: number | string | ArrayBuffer | Uint8Array) { - if (typeof input === 'number' && input > 0) { - if (input < MIN || input > MAX) { - throw new RangeError(`Entropy must be ${MIN}-${MAX} bytes`) - } - if (input % MOD !== 0) { - throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`) + static async import (bytes: Uint8Array): Promise + static async import (input: string | ArrayBuffer | Uint8Array): Promise { + return new Promise((resolve, reject) => { + if (typeof input === 'string') { + if (input.length < MIN * 2 || input.length > MAX * 2) { + throw new RangeError(`Entropy must be ${MIN * 2}-${MAX * 2} characters`) + } + if (input.length % MOD * 2 !== 0) { + throw new RangeError(`Entropy must be a multiple of ${MOD * 2} characters`) + } + if (!/^[0-9a-fA-F]+$/i.test(input)) { + throw new RangeError('Entropy contains invalid hexadecimal characters') + } + Entropy.#isInternal = true + resolve(new this(hex.toBytes(input))) } - this.#bytes = crypto.getRandomValues(new Uint8Array(input)) - this.#hex = bytes.toHex(this.#bytes) - this.#bits = hex.toBin(this.#hex) - this.#buffer = this.#bytes.buffer - return - } - if (typeof input === 'string' && input.length > 0) { - if (input.length < MIN * 2 || input.length > MAX * 2) { - throw new RangeError(`Entropy must be ${MIN * 2}-${MAX * 2} characters`) - } - if (input.length % MOD * 2 !== 0) { - throw new RangeError(`Entropy must be a multiple of ${MOD * 2} characters`) + if (input instanceof ArrayBuffer) { + if (input.byteLength < MIN || input.byteLength > MAX) { + throw new Error(`Entropy must be ${MIN}-${MAX} bytes`) + } + if (input.byteLength % MOD !== 0) { + throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`) + } + Entropy.#isInternal = true + resolve(new this(new Uint8Array(input))) } - this.#hex = input - if (!/^[0-9a-fA-F]+$/i.test(this.#hex)) { - throw new RangeError('Entropy contains invalid hexadecimal characters') - } - this.#bytes = hex.toBytes(this.#hex) - this.#bits = hex.toBin(this.#hex) - this.#buffer = this.#bytes.buffer - return - } - - if (input instanceof ArrayBuffer && input.byteLength > 0) { - if (input.byteLength < MIN || input.byteLength > MAX) { - throw new Error(`Entropy must be ${MIN}-${MAX} bytes`) - } - if (input.byteLength % MOD !== 0) { - throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`) - } - this.#buffer = input - this.#bytes = new Uint8Array(this.#buffer) - this.#bits = bytes.toBin(this.#bytes) - this.#hex = bytes.toHex(this.#bytes) - return - } - if (input instanceof Uint8Array && input.length > 0) { - if (input.length < MIN || input.length > MAX) { - throw new Error(`Entropy must be ${MIN}-${MAX} bytes`) + if (input instanceof Uint8Array) { + if (input.length < MIN || input.length > MAX) { + throw new Error(`Entropy must be ${MIN}-${MAX} bytes`) + } + if (input.length % MOD !== 0) { + throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`) + } + Entropy.#isInternal = true + resolve(new this(input)) } - if (input.length % MOD !== 0) { - throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`) - } - this.#bytes = input - this.#bits = bytes.toBin(this.#bytes) - this.#buffer = this.#bytes.buffer - this.#hex = bytes.toHex(this.#bytes) - return - } - this.#bytes = crypto.getRandomValues(new Uint8Array(MAX)) - this.#hex = bytes.toHex(this.#bytes) - this.#bits = hex.toBin(this.#hex) - this.#buffer = this.#bytes.buffer + reject(new TypeError(`Entropy cannot import ${typeof input}`)) + }) } }