* 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
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()
}
}
}