]> zoso.dev Git - libnemo.git/commitdiff
Convert synchronously-created Entropy to async factory class. Use bytes as underlying...
authorChris Duncan <chris@zoso.dev>
Mon, 9 Dec 2024 13:20:27 +0000 (05:20 -0800)
committerChris Duncan <chris@zoso.dev>
Mon, 9 Dec 2024 13:20:27 +0000 (05:20 -0800)
src/lib/entropy.ts

index 8f5cc2613d93d2866495b2bb4cb013fb8f93e908..295c8d32a1d6344dd4a67750159ebc3f9c851248 100644 (file)
@@ -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<Entropy>
        /**
        * 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<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 < 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<Entropy>
        /**
        * Import existing entropy and validate it.
        * @param {ArrayBuffer} buffer - Byte buffer
        */
-       constructor (buffer: ArrayBuffer)
+       static async import (buffer: ArrayBuffer): Promise<Entropy>
        /**
        * 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<Entropy>
+       static async import (input: string | ArrayBuffer | Uint8Array): Promise<Entropy> {
+               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}`))
+               })
        }
 }