]> git.codecow.com Git - libnemo.git/commitdiff
Refactor entropy to be simpler and just use overloaded constructor to create object...
authorChris Duncan <chris@zoso.dev>
Mon, 4 Aug 2025 17:29:31 +0000 (10:29 -0700)
committerChris Duncan <chris@zoso.dev>
Mon, 4 Aug 2025 17:29:31 +0000 (10:29 -0700)
src/lib/entropy.ts

index 614b1c1f6cc01cbc0852fbb5c4f87a24764710db..bbc8d19d200e7c4e31a185e48bfc37654d3c597e 100644 (file)
@@ -13,7 +13,6 @@ import { bytes, hex } from './convert'
 * brand new source of entropy will be generated at the maximum size of 256 bits.
 */
 export class Entropy {
-       static #isInternal: boolean = false
        static MIN: 16 = 16
        static MAX: 32 = 32
        static MOD: 4 = 4
@@ -24,109 +23,76 @@ export class Entropy {
        get bytes (): Uint8Array<ArrayBuffer> { return this.#bytes }
        get hex (): string { return bytes.toHex(this.#bytes) }
 
-       private constructor (bytes: Uint8Array<ArrayBuffer>) {
-               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.
        */
-       static async create (): Promise<Entropy>
+       constructor ()
        /**
        * Generate between 16-32 bytes of entropy.
        * @param {number} size - Number of bytes to generate in multiples of 4
        */
-       static async create (size: number): Promise<Entropy>
-       static async create (size?: number): Promise<Entropy> {
-               return new Promise(resolve => {
-                       if (size != null) {
-                               if (typeof size !== 'number') {
-                                       throw new TypeError(`Entropy cannot use ${typeof size} as a size`)
-                               }
-                               if (size < this.MIN || size > this.MAX) {
-                                       throw new RangeError(`Entropy must be ${this.MIN}-${this.MAX} bytes`)
-                               }
-                               if (size % this.MOD !== 0) {
-                                       throw new RangeError(`Entropy must be a multiple of ${this.MOD} bytes`)
-                               }
-                       }
-                       Entropy.#isInternal = true
-                       resolve(new this(crypto.getRandomValues(new Uint8Array(size ?? this.MAX))))
-               })
-       }
-
+       constructor (size: number)
        /**
        * Import existing entropy and validate it.
-       * @param {string} hex - Hexadecimal string
+       * @param {ArrayBuffer} buffer - Byte buffer
        */
-       static async import (hex: string): Promise<Entropy>
+       constructor (buffer: ArrayBuffer)
        /**
        * Import existing entropy and validate it.
-       * @param {ArrayBuffer} buffer - Byte buffer
+       * @param {Uint8Array} bytes - Byte array
        */
-       static async import (buffer: ArrayBuffer): Promise<Entropy>
+       constructor (bytes: Uint8Array<ArrayBuffer>)
        /**
        * Import existing entropy and validate it.
-       * @param {Uint8Array} bytes - Byte array
+       * @param {string} hex - Hexadecimal string
        */
-       static async import (bytes: Uint8Array<ArrayBuffer>): Promise<Entropy>
-       static async import (input: unknown): Promise<Entropy> {
-               return new Promise((resolve, reject) => {
+       constructor (hex: string)
+       constructor (input?: unknown) {
 
-                       if (input instanceof ArrayBuffer) {
-                               if (input.byteLength < this.MIN || input.byteLength > this.MAX) {
-                                       throw new Error(`Entropy must be ${this.MIN}-${this.MAX} bytes`)
-                               }
-                               if (input.byteLength % this.MOD !== 0) {
-                                       throw new RangeError(`Entropy must be a multiple of ${this.MOD} bytes`)
-                               }
-                               Entropy.#isInternal = true
-                               resolve(new this(new Uint8Array(input)))
-                               return
+               if (input == null) {
+                       this.#bytes = crypto.getRandomValues(new Uint8Array(Entropy.MAX))
+
+               } else if (typeof input === 'number') {
+                       if (input < Entropy.MIN || input > Entropy.MAX) {
+                               throw new RangeError(`Entropy must be ${Entropy.MIN}-${Entropy.MAX} bytes`)
+                       }
+                       if (input % Entropy.MOD !== 0) {
+                               throw new RangeError(`Entropy must be a multiple of ${Entropy.MOD} bytes`)
                        }
+                       this.#bytes = crypto.getRandomValues(new Uint8Array(input))
 
-                       if (input instanceof Uint8Array) {
-                               const { buffer } = input
-                               if (!(buffer instanceof ArrayBuffer)) {
-                                       throw new TypeError(`Entropy imported bytes must be backed by an ArrayBuffer.`)
-                               }
-                               this.import(buffer).then(resolve).catch(reject)
-                               return
+               } else if (input instanceof ArrayBuffer || input instanceof Uint8Array) {
+                       if (input.byteLength < Entropy.MIN || input.byteLength > Entropy.MAX) {
+                               throw new Error(`Entropy must be ${Entropy.MIN}-${Entropy.MAX} bytes`)
                        }
+                       if (input.byteLength % Entropy.MOD !== 0) {
+                               throw new RangeError(`Entropy must be a multiple of ${Entropy.MOD} bytes`)
+                       }
+                       this.#bytes = new Uint8Array(input)
 
-                       if (typeof input === 'string') {
-                               if (input.length < this.MIN * 2 || input.length > this.MAX * 2) {
-                                       throw new RangeError(`Entropy must be ${this.MIN * 2}-${this.MAX * 2} characters`)
-                               }
-                               if (input.length % this.MOD * 2 !== 0) {
-                                       throw new RangeError(`Entropy must be a multiple of ${this.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)))
-                               return
+               } else if (typeof input === 'string') {
+                       if (input.length < Entropy.MIN * 2 || input.length > Entropy.MAX * 2) {
+                               throw new RangeError(`Entropy must be ${Entropy.MIN * 2}-${Entropy.MAX * 2} characters`)
+                       }
+                       if (input.length % Entropy.MOD * 2 !== 0) {
+                               throw new RangeError(`Entropy must be a multiple of ${Entropy.MOD * 2} characters`)
                        }
+                       if (!/^[0-9a-fA-F]+$/i.test(input)) {
+                               throw new RangeError('Entropy contains invalid hexadecimal characters')
+                       }
+                       this.#bytes = hex.toBytes(input)
 
-                       reject(new TypeError(`Entropy cannot import ${typeof input}`))
-                       return
-               })
+               } else {
+                       throw new TypeError(`Entropy cannot import ${typeof input}`)
+               }
        }
 
        /**
        * Randomizes the bytes, rendering the original values generally inaccessible.
        */
-       destroy (): boolean {
-               try {
-                       crypto.getRandomValues(this.#bytes)
-                       return true
-               } catch (err) {
-                       return false
+       destroy (): void {
+               if (!this.#bytes.buffer.detached) {
+                       this.#bytes.fill(0).buffer.transfer()
                }
        }
 }