import { Blake2b } from './blake2b'\r
import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants'\r
import { base32, bytes, hex, obj, utf8 } from './convert'\r
-import { Pool } from './pool'\r
+import { Queue } from './pool'\r
import { Rpc } from './rpc'\r
import { NanoNaClWorker, SafeWorker } from '#workers'\r
\r
*/\r
export class Account {\r
static #isInternal: boolean = false\r
- static #poolSafe: Pool = new Pool(SafeWorker)\r
- static #poolNanoNaCl: Pool = new Pool(NanoNaClWorker)\r
+ static #poolSafe: Queue = new Queue(SafeWorker)\r
+ static #poolNanoNaCl: Queue = new Queue(NanoNaClWorker)\r
\r
#address: string\r
#locked: boolean\r
*/\r
async destroy (): Promise<void> {\r
this.#prv.fill(0)\r
- await Account.#poolSafe.assign({\r
+ await Account.#poolSafe.add({\r
method: 'destroy',\r
name: this.#pub\r
})\r
this.#validateKey(privateKey)\r
let publicKey: string\r
try {\r
- const result = await this.#poolNanoNaCl.assign({ method: 'convert' }, privateKey.buffer)\r
+ const headers = {\r
+ method: 'convert'\r
+ }\r
+ const data = {\r
+ privateKey: privateKey.buffer\r
+ }\r
+ const result = await this.#poolNanoNaCl.add(headers, data)\r
publicKey = result.publicKey[0]\r
} catch (err) {\r
throw new Error(`Failed to derive public key from private key`, { cause: err })\r
name: this.#pub,\r
id: this.#pub\r
}\r
- const response = await Account.#poolSafe.assign(headers, this.#prv.buffer, password.buffer)\r
+ const data = {\r
+ privateKey: this.#prv.buffer,\r
+ password: password.buffer\r
+ }\r
+ const response = await Account.#poolSafe.add(headers, data)\r
const success = response?.result[0]\r
if (!success) {\r
throw null\r
method: 'get',\r
name: this.#pub\r
}\r
- const response = await Account.#poolSafe.assign(headers, password.buffer)\r
+ const data = {\r
+ password: password.buffer\r
+ }\r
+ const response = await Account.#poolSafe.add(headers, data)\r
const { id, privateKey } = response?.result[0]\r
if (id == null || id !== this.#pub) {\r
throw null\r
import { Blake2b } from './blake2b'
import { BURN_ADDRESS, PREAMBLE, DIFFICULTY_RECEIVE, DIFFICULTY_SEND } from './constants'
import { dec, hex } from './convert'
-import { Pool } from './pool'
+import { Queue } from './pool'
import { Rpc } from './rpc'
import { NanoNaClWorker } from '#workers'
* of three derived classes: SendBlock, ReceiveBlock, ChangeBlock.
*/
abstract class Block {
- static #poolNanoNaCl: Pool = new Pool(NanoNaClWorker)
+ static #poolNanoNaCl: Queue = new Queue(NanoNaClWorker)
account: Account
type: string = 'state'
method: 'detached',
msg: this.hash
}
- const result = await Block.#poolNanoNaCl.assign(headers, hex.toBytes(account.privateKey).buffer)
+ const data = {
+ privateKey: hex.toBytes(account.privateKey).buffer
+ }
+ const result = await Block.#poolNanoNaCl.add(headers, data)
this.signature = result.signature[0]
} catch (err) {
throw new Error(`Failed to sign block`, { cause: err })
signature: this.signature ?? '',
publicKey: key
}
- const result = await Block.#poolNanoNaCl.assign(headers)
+ const result = await Block.#poolNanoNaCl.add(headers)
return result.isVerified[0]
} catch (err) {
throw new Error(`Failed to derive public key from private key`, { cause: err })
// SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
// SPDX-License-Identifier: GPL-3.0-or-later
-import { isTypedArray } from "util/types"
+export type Headers = {
+ [key: string]: any
+}
-export type Header = {
- [key: string]: number | string
+export type Data = {
+ [key: string]: ArrayBuffer
}
-type Job = {
+type Task = {
id: number
+ headers: Headers | null
+ data?: Data
reject: (value: any) => void
resolve: (value: any) => void
- headers: Header
- data: ArrayBuffer[]
- results: any[]
-}
-
-type Thread = {
- worker: Worker
- job: Job | null
}
/**
-* Processes an array of tasks using Web Workers.
+* Processes a queue of tasks using Web Workers.
*/
-export class Pool {
- static #cores: number = Math.max(1, navigator.hardwareConcurrency - 1)
- #queue: Job[] = []
- #threads: Thread[] = []
+export class Queue {
+ static #decoder: TextDecoder = new TextDecoder()
+ static #encoder: TextEncoder = new TextEncoder()
+ static #instances: Queue[] = []
+ static get instances (): Queue[] { return this.#instances }
+
+ #job?: Task
+ #isIdle: boolean
+ #queue: Task[] = []
#url: string
+ #worker: Worker
- get threadsBusy (): number {
- let n = 0
- for (const thread of this.#threads) {
- n += +(thread.job != null)
- }
- return n
+ /**
+ * Creates a Web Worker from a stringified script.
+ *
+ * @param {string} worker - Stringified worker class body
+ * @param {number} [count=1] - Integer between 1 and CPU thread count shared among all Pools
+ */
+ constructor (worker: string) {
+ this.#isIdle = true
+ this.#queue = []
+ this.#url = URL.createObjectURL(new Blob([worker], { type: 'text/javascript' }))
+ this.#worker = new Worker(this.#url, { type: 'module' })
+ this.#worker.addEventListener('message', message => {
+ let result = JSON.parse(Queue.#decoder.decode(message.data) || '[]')
+ if (!Array.isArray(result)) result = [result]
+ debugger
+ this.#report(result)
+ })
+ Queue.#instances.push(this)
}
- get threadsIdle (): number {
- let n = 0
- for (const thread of this.#threads) {
- n += +(thread.job == null)
- }
- return n
+
+ async add (headers: Headers | null, data?: Data): Promise<any> {
+ return await this.#assign(task => this.#queue.push(task), headers, data)
+ }
+
+ async prioritize (headers: Headers | null, data?: Data): Promise<any> {
+ return await this.#assign(task => this.#queue.unshift(task), headers, data)
+ }
+
+ terminate (): void {
+ this.#job = undefined
+ this.#worker.terminate()
}
- async assign (headers: Header, ...data: ArrayBuffer[]): Promise<any> {
- return new Promise((resolve, reject) => {
- const job: Job = {
+ async #assign (enqueue: (task: Task) => number, headers: Headers | null, data?: Data) {
+ return new Promise(async (resolve, reject): Promise<void> => {
+ const task: Task = {
id: performance.now(),
- results: [],
headers,
data,
resolve,
reject
}
- if (this.#queue.length > 0) {
- this.#queue.push(job)
- } else {
- for (const thread of this.#threads) {
- this.#assign(thread, job)
- }
- }
+ await enqueue(task)
+ debugger
+ if (this.#isIdle) this.#process()
})
}
- terminate (): void {
- for (const thread of this.#threads) {
- thread.job = null
- thread.worker.terminate()
- }
- }
-
- /**
- * Creates a Web Worker from a stringified script.
- *
- * @param {string} worker - Stringified worker class body
- * @param {number} [count=1] - Integer between 1 and CPU thread count shared among all Pools
- */
- constructor (worker: string, count: number = 1) {
- count = Math.min(Pool.#cores, Math.max(1, Math.floor(Math.abs(count))))
- this.#url = URL.createObjectURL(new Blob([worker], { type: 'text/javascript' }))
- for (let i = 0; i < count; i++) {
- const thread = {
- worker: new Worker(this.#url, { type: 'module' }),
- job: null
- }
- thread.worker.addEventListener('message', message => {
- let result = JSON.parse(new TextDecoder().decode(message.data) || '[]')
- if (!Array.isArray(result)) result = [result]
- this.#report(thread, result)
- })
- this.#threads.push(thread)
- Pool.#cores = Math.max(1, Pool.#cores - this.#threads.length)
+ #process = (): void => {
+ debugger
+ this.#job = this.#queue.shift()
+ if (this.#job == null) {
+ throw new Error('Failed to get job from empty task queue.')
}
- }
-
- #assign (thread: Thread, job: Job): void {
- if (job.data instanceof ArrayBuffer && job.data.byteLength > 0) {
- thread.job = job
- thread.worker.postMessage({ headers: job.headers, data: job.data }, [job.data])
- } else {
- const chunk: number = 1 + (job.data.length / this.threadsIdle)
- const next = job.data.slice(0, chunk)
- job.data = job.data.slice(chunk)
- if (job.data.length === 0) this.#queue.shift()
- if (next?.length > 0) {
- const buffer = new TextEncoder().encode(JSON.stringify(next)).buffer
- thread.job = job
- thread.worker.postMessage({ headers: job.headers, data: buffer }, [buffer])
+ const { id, headers, data, reject } = this.#job
+ this.#isIdle = !id
+ try {
+ const buffers: ArrayBuffer[] = []
+ if (data != null) {
+ for (let d of Object.keys(data)) {
+ buffers.push(data[d])
+ }
}
+ this.#worker.postMessage({ headers, data }, buffers)
+ } catch (err) {
+ reject(err)
}
}
- #isJobDone (jobId: number): boolean {
- for (const thread of this.#threads) {
- if (thread.job?.id === jobId) return false
- }
- return true
- }
-
- #report (thread: Thread, results: any[]): void {
- if (thread.job == null) {
- throw new Error('Thread returned results but had nowhere to report it.')
+ #report (results: any[]): void {
+ if (this.#job == null) {
+ throw new Error('Worker returned results but had nowhere to report it.')
}
- const job = thread.job
- if (this.#queue.length > 0) {
- this.#assign(thread, this.#queue[0])
- } else {
- thread.job = null
- }
- if (results.length > 0) {
- job.results.push(...results)
- }
- if (this.#isJobDone(job.id)) {
- job.resolve(job.results)
+ const { resolve, reject } = this.#job
+ debugger
+ try {
+ resolve(results)
+ } catch (err) {
+ reject(err)
+ } finally {
+ this.#process()
}
}
}
* @param {any[]} data - Transferred buffer of data to process
* @returns Promise for processed data
*/
- static async work (headers: Header, body: any[]): Promise<any[]> {
+ static async work (headers: Headers | null, data?: Data): Promise<any> {
return new Promise(async (resolve, reject): Promise<void> => {
- const { example } = headers
- const results = []
- for (let buf of body) {
- try {
- const b = JSON.parse(new TextDecoder().decode(buf))
- results.push(await b(example))
- } catch (err) {
- reject(err)
+ try {
+ let x, y
+ if (headers != null) {
+ const { sample } = headers
+ x = sample
}
+ if (data != null) {
+ const { buf } = data
+ y = buf
+ }
+ resolve({ x, y })
+ } catch (err) {
+ reject(err)
}
- resolve(results)
})
}
*/
static listen (): void {
addEventListener('message', (message: any): void => {
- const { name, headers, data } = message.data
+ const { name, headers, data } = message
if (name === 'STOP') {
close()
const buffer = new ArrayBuffer(0)
import { SEED_LENGTH_BIP44 } from '#src/lib/constants.js'\r
import { hex, utf8 } from '#src/lib/convert.js'\r
import { Entropy } from '#src/lib/entropy.js'\r
-import { Pool } from '#src/lib/pool.js'\r
+import { Queue } from '#src/lib/pool.js'\r
import { Bip44CkdWorker } from '#workers'\r
\r
/**\r
*/\r
export class Bip44Wallet extends Wallet {\r
static #isInternal: boolean = false\r
- static #poolBip44Ckd: Pool\r
+ static #poolBip44Ckd: Queue\r
\r
constructor (id: Entropy, seed: string, mnemonic?: Bip39Mnemonic) {\r
if (!Bip44Wallet.#isInternal) {\r
}\r
Bip44Wallet.#isInternal = false\r
super(id, hex.toBytes(seed), mnemonic)\r
- Bip44Wallet.#poolBip44Ckd ??= new Pool(Bip44CkdWorker)\r
+ Bip44Wallet.#poolBip44Ckd ??= new Queue(Bip44CkdWorker)\r
}\r
\r
/**\r
* @returns {Promise<Account>}\r
*/\r
async ckd (indexes: number[]): Promise<KeyPair[]> {\r
- const data: any = []\r
- indexes.forEach(i => data.push({ index: i }))\r
- const privateKeys: KeyPair[] = await Bip44Wallet.#poolBip44Ckd.assign(data, this.seed.buffer)\r
+ const headers = {\r
+ indexes\r
+ }\r
+ const data = {\r
+ seed: this.seed.buffer\r
+ }\r
+ const privateKeys: KeyPair[] = await Bip44Wallet.#poolBip44Ckd.add(headers, data)\r
for (let i = 0; i < privateKeys.length; i++) {\r
if (privateKeys[i].privateKey == null) {\r
throw new Error('Failed to derive private keys')\r
import { ADDRESS_GAP } from '#src/lib/constants.js'\r
import { bytes, hex, utf8 } from '#src/lib/convert.js'\r
import { Entropy } from '#src/lib/entropy.js'\r
-import { Pool } from '#src/lib/pool.js'\r
+import { Queue } from '#src/lib/pool.js'\r
import { Rpc } from '#src/lib/rpc.js'\r
import { SafeWorker } from '#workers'\r
\r
export abstract class Wallet {\r
abstract ckd (index: number[]): Promise<KeyPair[]>\r
\r
- static #poolSafe: Pool = new Pool(SafeWorker)\r
+ static #poolSafe: Queue = new Queue(SafeWorker)\r
\r
#accounts: AccountList\r
#id: Entropy\r
}\r
this.#m = null\r
this.#s.fill(0)\r
- await Wallet.#poolSafe.assign({\r
+ await Wallet.#poolSafe.add({\r
method: 'destroy',\r
name: this.id\r
})\r
name: this.id,\r
id: this.id,\r
}\r
- const response = await Wallet.#poolSafe.assign(headers, password.buffer, utf8.toBytes(this.#m?.phrase ?? '').buffer, this.#s.buffer)\r
+ const data = {\r
+ password: password.buffer,\r
+ phrase: utf8.toBytes(this.#m?.phrase ?? '').buffer,\r
+ seed: this.#s.buffer\r
+ }\r
+ const response = await Wallet.#poolSafe.add(headers, data)\r
const success = response?.result[0]\r
if (!success) {\r
throw null\r
method: 'get',\r
name: this.id\r
}\r
- const response = await Wallet.#poolSafe.assign(headers, password.buffer)\r
+ const data = {\r
+ password: password.buffer\r
+ }\r
+ const response = await Wallet.#poolSafe.add(headers, data)\r
let { id, mnemonic, seed } = response?.result[0]\r
if (id == null || id !== this.id) {\r
throw null\r
// SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
// SPDX-License-Identifier: GPL-3.0-or-later
-import { WorkerInterface } from '#src/lib/pool.js'
+import { Data, Headers, WorkerInterface } from '#src/lib/pool.js'
type ExtendedKey = {
- privateKey: DataView
+ privateKey: DataView<ArrayBuffer>
chainCode: DataView
}
Bip44Ckd.listen()
}
- static async work (headers: { [key: string]: string | number }, data: any[]): Promise<any[]> {
- for (const d of data) {
+ static async work (headers: Headers, data: Data): Promise<any[]> {
+ let { coin, indexes } = headers
+ let { seed } = data
+ if (typeof coin !== 'number' || !Number.isInteger(coin)) {
+ throw new TypeError('BIP-44 coin derivation level must be an integer')
+ }
+ if (!Array.isArray(indexes)) indexes = [indexes]
+ const privateKeys: ArrayBuffer[] = []
+ for (const i of indexes) {
+ if (typeof i !== 'number' || !Number.isInteger(i)) {
+ throw new TypeError('BIP-44 account derivation level must be an integer')
+ }
try {
- if (d.coin != null && d.coin !== this.BIP44_PURPOSE) {
- d.privateKey = await this.ckd(d.seed, d.coin, d.index)
- } else {
- d.privateKey = await this.nanoCKD(d.seed, d.index)
- }
+ const pk = (coin !== this.BIP44_PURPOSE)
+ ? await this.ckd(seed, coin, i)
+ : await this.nanoCKD(seed, i)
+ privateKeys.push(pk)
} catch (err) {
- d.privateKey = null
+ console.log(err)
}
}
- return data
+ return privateKeys
}
/**
* @param {number} index - Account number between 0 and 2^31-1
* @returns {Promise<string>} Private child key for the account
*/
- static async nanoCKD (seed: string, index: number): Promise<string> {
+ static async nanoCKD (seed: ArrayBuffer, index: number): Promise<ArrayBuffer> {
if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {
throw new RangeError(`Invalid child key index 0x${index.toString(16)}`)
}
* @param {number} index - Account number between 0 and 2^31-1
* @returns {Promise<string>} Private child key for the account
*/
- static async ckd (seed: string, coin: number, index: number): Promise<string> {
- if (seed.length < 32 || seed.length > 128) {
+ static async ckd (seed: ArrayBuffer, coin: number, index: number): Promise<ArrayBuffer> {
+ if (seed.byteLength < 16 || seed.byteLength > 64) {
throw new RangeError(`Invalid seed length`)
}
if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {
const purposeKey = await this.CKDpriv(masterKey, this.BIP44_PURPOSE + this.HARDENED_OFFSET)
const coinKey = await this.CKDpriv(purposeKey, coin + this.HARDENED_OFFSET)
const accountKey = await this.CKDpriv(coinKey, index + this.HARDENED_OFFSET)
- const privateKey = new Uint8Array(accountKey.privateKey.buffer)
- let hex = ''
- for (let i = 0; i < privateKey.length; i++) {
- hex += privateKey[i].toString(16).padStart(2, '0')
- }
- return hex
+ return accountKey.privateKey.buffer
+ // const privateKey = new Uint8Array(accountKey.privateKey.buffer)
+ // let hex = ''
+ // for (let i = 0; i < privateKey.length; i++) {
+ // hex += privateKey[i].toString(16).padStart(2, '0')
+ // }
+ // return hex
}
- static async slip10 (curve: string, S: string): Promise<ExtendedKey> {
+ static async slip10 (curve: string, S: ArrayBuffer): Promise<ExtendedKey> {
const key = new TextEncoder().encode(curve)
- const data = new Uint8Array(64)
- data.set(S.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16)))
+ const data = new Uint8Array(S)
const I = await this.hmac(key, data)
const IL = new DataView(I.buffer.slice(0, I.length / 2))
const IR = new DataView(I.buffer.slice(I.length / 2))
return new Uint8Array(integer.buffer)
}
- static async hmac (key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
+ static async hmac (key: Uint8Array, data: Uint8Array): Promise<Uint8Array<ArrayBuffer>> {
const { subtle } = globalThis.crypto
const pk = await subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-512' }, false, ['sign'])
const signature = await subtle.sign('HMAC', pk, data)
'use strict'\r
\r
import { Blake2b } from '#src/lib/blake2b.js'\r
-import { WorkerInterface } from '#src/lib/pool.js'\r
+import { Data, Headers, WorkerInterface } from '#src/lib/pool.js'\r
\r
// Ported in 2014 by Dmitry Chestnykh and Devi Mandiri.\r
// Public domain.\r
NanoNaCl.listen()\r
}\r
\r
- static async work (headers: { [key: string]: string | number }, data: any[]): Promise<any[]> {\r
- return new Promise(async (resolve, reject): Promise<void> => {\r
- for (let d of data) {\r
- try {\r
- switch (d.method) {\r
- case 'convert': {\r
- d.publicKey = await this.convert(Uint8Array.from(d.privateKey))\r
- break\r
- }\r
- case 'detached': {\r
- d.signature = await this.detached(Uint8Array.from(d.msg), Uint8Array.from(d.privateKey))\r
- break\r
- }\r
- case 'verify': {\r
- d.isVerified = await this.verify(Uint8Array.from(d.msg), Uint8Array.from(d.signature), Uint8Array.from(d.publicKey))\r
- break\r
- }\r
- default: {\r
- throw new TypeError(`unknown NanoNaCl method ${d.method}`)\r
- }\r
- }\r
- } catch (err) {\r
- reject(err)\r
- }\r
+ static async work (headers: Headers, data: Data): Promise<any> {\r
+ const { method, msg, signature, publicKey } = headers\r
+ const privateKey = new Uint8Array(data.privateKey)\r
+ switch (method) {\r
+ case 'convert': {\r
+ return await this.convert(privateKey)\r
}\r
- resolve(data)\r
- })\r
+ case 'detached': {\r
+ return await this.detached(msg, privateKey)\r
+ }\r
+ case 'verify': {\r
+ return await this.verify(msg, signature, publicKey)\r
+ }\r
+ default: {\r
+ throw new TypeError(`unknown NanoNaCl method ${method}`)\r
+ }\r
+ }\r
}\r
\r
static gf = function (init?: number[]): Float64Array {\r
'use strict'
-import { bytes, hex, obj, utf8, default as Convert } from '#src/lib/convert.js'
+import { bytes, hex, utf8, default as Convert } from '#src/lib/convert.js'
import { Entropy } from '#src/lib/entropy.js'
-import { WorkerInterface } from '#src/lib/pool.js'
+import { Data, Headers, WorkerInterface } from '#src/lib/pool.js'
type SafeRecord = {
encrypted: string,
iv: string
}
-type SafeInput = {
- method: string
- name: string
- password?: { [key: number]: number }
- data?: any
-}
-
-type SafeOutput = {
- method: string
- name: string
- result: any
-}
-
/**
* Encrypts and stores data in the browser using IndexedDB.
*/
Safe.listen()
}
- static async work (headers: { [key: string]: string }, data: any[]): Promise<any[]> {
+ static async work (headers: Headers, data: Data): Promise<any> {
this.#storage = await this.#open(this.DB_NAME)
- const { method, name } = headers
- const results: SafeOutput[] = []
- for (const d of data) {
- const { password, data } = d as SafeInput
- let result
- try {
- const passwordBytes = obj.toBytes(password ?? [])
- switch (method) {
- case 'set': {
- result = await this.set(name, passwordBytes, data)
- break
- }
- case 'get': {
- result = await this.get(name, passwordBytes)
- break
- }
- case 'destroy': {
- result = await this.destroy(name)
- break
- }
- default: {
- result = `unknown Safe method ${method}`
- }
+ const { method, name, id } = headers
+ const { password, phrase, seed } = data
+ const results = []
+ let result
+ try {
+ switch (method) {
+ case 'set': {
+ result = await this.set(name, password, { id, phrase, seed })
+ break
+ }
+ case 'get': {
+ result = await this.get(name, password)
+ break
+ }
+ case 'destroy': {
+ result = await this.destroy(name)
+ break
+ }
+ default: {
+ result = `unknown Safe method ${method}`
}
- results.push({ name, method, result })
- } catch (err) {
- result = false
}
+ results.push({ name, method, result })
+ } catch (err) {
+ result = false
}
return results
}
/**
* Encrypts data with a password byte array and stores it in the Safe.
*/
- static async set (name: string, password: Uint8Array, data: any): Promise<boolean> {
- if (await this.#exists(name)) {
- password.fill(0)
- throw new Error('Record is already locked')
- }
+ static async set (name: string, password: ArrayBuffer, data: any): Promise<boolean> {
let passkey: CryptoKey
try {
+ if (await this.#exists(name)) throw new Error('Record is already locked')
passkey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
} catch {
throw new Error(this.ERR_MSG)
} finally {
- password.fill(0)
+ new Uint8Array(password).fill(0)
}
if (this.#isInvalid(name, passkey, data)) {
throw new Error(this.ERR_MSG)
/**
* Retrieves data from the Safe and decrypts it with a password byte array.
*/
- static async get (name: string, password: Uint8Array): Promise<any> {
+ static async get (name: string, password: ArrayBuffer): Promise<any> {
let passkey: CryptoKey
try {
passkey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
} catch {
return null
} finally {
- password.fill(0)
+ new Uint8Array(password).fill(0)
}
if (this.#isInvalid(name, passkey)) {
return null