From: Chris Duncan Date: Mon, 14 Jul 2025 21:35:37 +0000 (-0700) Subject: Big refactoring, need to fix Safe set and get, how the data is structured. X-Git-Tag: v0.10.5~57^2~20 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=be53921c0a94ed8968d4f188584b66d10578cae9;p=libnemo.git Big refactoring, need to fix Safe set and get, how the data is structured. --- diff --git a/src/lib/account.ts b/src/lib/account.ts index d84ac69..6da9542 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -4,7 +4,7 @@ import { Blake2b } from './blake2b' import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants' import { base32, bytes, hex, obj, utf8 } from './convert' -import { Pool } from './pool' +import { Queue } from './pool' import { Rpc } from './rpc' import { NanoNaClWorker, SafeWorker } from '#workers' @@ -16,8 +16,8 @@ import { NanoNaClWorker, SafeWorker } from '#workers' */ export class Account { static #isInternal: boolean = false - static #poolSafe: Pool = new Pool(SafeWorker) - static #poolNanoNaCl: Pool = new Pool(NanoNaClWorker) + static #poolSafe: Queue = new Queue(SafeWorker) + static #poolNanoNaCl: Queue = new Queue(NanoNaClWorker) #address: string #locked: boolean @@ -80,7 +80,7 @@ export class Account { */ async destroy (): Promise { this.#prv.fill(0) - await Account.#poolSafe.assign({ + await Account.#poolSafe.add({ method: 'destroy', name: this.#pub }) @@ -136,7 +136,13 @@ export class Account { this.#validateKey(privateKey) let publicKey: string try { - const result = await this.#poolNanoNaCl.assign({ method: 'convert' }, privateKey.buffer) + const headers = { + method: 'convert' + } + const data = { + privateKey: privateKey.buffer + } + const result = await this.#poolNanoNaCl.add(headers, data) publicKey = result.publicKey[0] } catch (err) { throw new Error(`Failed to derive public key from private key`, { cause: err }) @@ -163,7 +169,11 @@ export class Account { name: this.#pub, id: this.#pub } - const response = await Account.#poolSafe.assign(headers, this.#prv.buffer, password.buffer) + const data = { + privateKey: this.#prv.buffer, + password: password.buffer + } + const response = await Account.#poolSafe.add(headers, data) const success = response?.result[0] if (!success) { throw null @@ -227,7 +237,10 @@ export class Account { method: 'get', name: this.#pub } - const response = await Account.#poolSafe.assign(headers, password.buffer) + const data = { + password: password.buffer + } + const response = await Account.#poolSafe.add(headers, data) const { id, privateKey } = response?.result[0] if (id == null || id !== this.#pub) { throw null diff --git a/src/lib/block.ts b/src/lib/block.ts index dec40b0..213c60c 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -6,7 +6,7 @@ import { Account } from './account' 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' @@ -16,7 +16,7 @@ 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' @@ -152,7 +152,10 @@ abstract class Block { 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 }) @@ -207,7 +210,7 @@ abstract class Block { 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 }) diff --git a/src/lib/pool.ts b/src/lib/pool.ts index d714755..7b56fb7 100644 --- a/src/lib/pool.ts +++ b/src/lib/pool.ts @@ -1,140 +1,118 @@ // SPDX-FileCopyrightText: 2025 Chris Duncan // 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 { + return await this.#assign(task => this.#queue.push(task), headers, data) + } + + async prioritize (headers: Headers | null, data?: Data): Promise { + 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 { - 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 => { + 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() } } } @@ -167,19 +145,22 @@ export abstract class WorkerInterface { * @param {any[]} data - Transferred buffer of data to process * @returns Promise for processed data */ - static async work (headers: Header, body: any[]): Promise { + static async work (headers: Headers | null, data?: Data): Promise { return new Promise(async (resolve, reject): Promise => { - 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) }) } @@ -207,7 +188,7 @@ export abstract class WorkerInterface { */ 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) diff --git a/src/lib/wallets/bip44-wallet.ts b/src/lib/wallets/bip44-wallet.ts index fd713d0..3959cf9 100644 --- a/src/lib/wallets/bip44-wallet.ts +++ b/src/lib/wallets/bip44-wallet.ts @@ -6,7 +6,7 @@ import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js' import { SEED_LENGTH_BIP44 } from '#src/lib/constants.js' import { hex, utf8 } from '#src/lib/convert.js' import { Entropy } from '#src/lib/entropy.js' -import { Pool } from '#src/lib/pool.js' +import { Queue } from '#src/lib/pool.js' import { Bip44CkdWorker } from '#workers' /** @@ -33,7 +33,7 @@ import { Bip44CkdWorker } from '#workers' */ export class Bip44Wallet extends Wallet { static #isInternal: boolean = false - static #poolBip44Ckd: Pool + static #poolBip44Ckd: Queue constructor (id: Entropy, seed: string, mnemonic?: Bip39Mnemonic) { if (!Bip44Wallet.#isInternal) { @@ -41,7 +41,7 @@ export class Bip44Wallet extends Wallet { } Bip44Wallet.#isInternal = false super(id, hex.toBytes(seed), mnemonic) - Bip44Wallet.#poolBip44Ckd ??= new Pool(Bip44CkdWorker) + Bip44Wallet.#poolBip44Ckd ??= new Queue(Bip44CkdWorker) } /** @@ -213,9 +213,13 @@ export class Bip44Wallet extends Wallet { * @returns {Promise} */ async ckd (indexes: number[]): Promise { - const data: any = [] - indexes.forEach(i => data.push({ index: i })) - const privateKeys: KeyPair[] = await Bip44Wallet.#poolBip44Ckd.assign(data, this.seed.buffer) + const headers = { + indexes + } + const data = { + seed: this.seed.buffer + } + const privateKeys: KeyPair[] = await Bip44Wallet.#poolBip44Ckd.add(headers, data) for (let i = 0; i < privateKeys.length; i++) { if (privateKeys[i].privateKey == null) { throw new Error('Failed to derive private keys') diff --git a/src/lib/wallets/wallet.ts b/src/lib/wallets/wallet.ts index cd2ab03..795fc3b 100644 --- a/src/lib/wallets/wallet.ts +++ b/src/lib/wallets/wallet.ts @@ -6,7 +6,7 @@ import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js' import { ADDRESS_GAP } from '#src/lib/constants.js' import { bytes, hex, utf8 } from '#src/lib/convert.js' import { Entropy } from '#src/lib/entropy.js' -import { Pool } from '#src/lib/pool.js' +import { Queue } from '#src/lib/pool.js' import { Rpc } from '#src/lib/rpc.js' import { SafeWorker } from '#workers' @@ -26,7 +26,7 @@ export type KeyPair = { export abstract class Wallet { abstract ckd (index: number[]): Promise - static #poolSafe: Pool = new Pool(SafeWorker) + static #poolSafe: Queue = new Queue(SafeWorker) #accounts: AccountList #id: Entropy @@ -139,7 +139,7 @@ export abstract class Wallet { } this.#m = null this.#s.fill(0) - await Wallet.#poolSafe.assign({ + await Wallet.#poolSafe.add({ method: 'destroy', name: this.id }) @@ -165,7 +165,12 @@ export abstract class Wallet { name: this.id, id: this.id, } - const response = await Wallet.#poolSafe.assign(headers, password.buffer, utf8.toBytes(this.#m?.phrase ?? '').buffer, this.#s.buffer) + const data = { + password: password.buffer, + phrase: utf8.toBytes(this.#m?.phrase ?? '').buffer, + seed: this.#s.buffer + } + const response = await Wallet.#poolSafe.add(headers, data) const success = response?.result[0] if (!success) { throw null @@ -231,7 +236,10 @@ export abstract class Wallet { method: 'get', name: this.id } - const response = await Wallet.#poolSafe.assign(headers, password.buffer) + const data = { + password: password.buffer + } + const response = await Wallet.#poolSafe.add(headers, data) let { id, mnemonic, seed } = response?.result[0] if (id == null || id !== this.id) { throw null diff --git a/src/lib/workers/bip44-ckd.ts b/src/lib/workers/bip44-ckd.ts index 563093c..ae2e939 100644 --- a/src/lib/workers/bip44-ckd.ts +++ b/src/lib/workers/bip44-ckd.ts @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2025 Chris Duncan // 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 chainCode: DataView } @@ -17,19 +17,28 @@ export class Bip44Ckd extends WorkerInterface { Bip44Ckd.listen() } - static async work (headers: { [key: string]: string | number }, data: any[]): Promise { - for (const d of data) { + static async work (headers: Headers, data: Data): Promise { + 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 } /** @@ -40,7 +49,7 @@ export class Bip44Ckd extends WorkerInterface { * @param {number} index - Account number between 0 and 2^31-1 * @returns {Promise} Private child key for the account */ - static async nanoCKD (seed: string, index: number): Promise { + static async nanoCKD (seed: ArrayBuffer, index: number): Promise { if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) { throw new RangeError(`Invalid child key index 0x${index.toString(16)}`) } @@ -57,8 +66,8 @@ export class Bip44Ckd extends WorkerInterface { * @param {number} index - Account number between 0 and 2^31-1 * @returns {Promise} Private child key for the account */ - static async ckd (seed: string, coin: number, index: number): Promise { - if (seed.length < 32 || seed.length > 128) { + static async ckd (seed: ArrayBuffer, coin: number, index: number): Promise { + if (seed.byteLength < 16 || seed.byteLength > 64) { throw new RangeError(`Invalid seed length`) } if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) { @@ -68,18 +77,18 @@ export class Bip44Ckd extends WorkerInterface { 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 { + static async slip10 (curve: string, S: ArrayBuffer): Promise { 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)) @@ -120,7 +129,7 @@ export class Bip44Ckd extends WorkerInterface { return new Uint8Array(integer.buffer) } - static async hmac (key: Uint8Array, data: Uint8Array): Promise { + static async hmac (key: Uint8Array, data: Uint8Array): Promise> { 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) diff --git a/src/lib/workers/nano-nacl.ts b/src/lib/workers/nano-nacl.ts index 122228f..928505f 100644 --- a/src/lib/workers/nano-nacl.ts +++ b/src/lib/workers/nano-nacl.ts @@ -4,7 +4,7 @@ 'use strict' import { Blake2b } from '#src/lib/blake2b.js' -import { WorkerInterface } from '#src/lib/pool.js' +import { Data, Headers, WorkerInterface } from '#src/lib/pool.js' // Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. // Public domain. @@ -23,33 +23,23 @@ export class NanoNaCl extends WorkerInterface { NanoNaCl.listen() } - static async work (headers: { [key: string]: string | number }, data: any[]): Promise { - return new Promise(async (resolve, reject): Promise => { - for (let d of data) { - try { - switch (d.method) { - case 'convert': { - d.publicKey = await this.convert(Uint8Array.from(d.privateKey)) - break - } - case 'detached': { - d.signature = await this.detached(Uint8Array.from(d.msg), Uint8Array.from(d.privateKey)) - break - } - case 'verify': { - d.isVerified = await this.verify(Uint8Array.from(d.msg), Uint8Array.from(d.signature), Uint8Array.from(d.publicKey)) - break - } - default: { - throw new TypeError(`unknown NanoNaCl method ${d.method}`) - } - } - } catch (err) { - reject(err) - } + static async work (headers: Headers, data: Data): Promise { + const { method, msg, signature, publicKey } = headers + const privateKey = new Uint8Array(data.privateKey) + switch (method) { + case 'convert': { + return await this.convert(privateKey) } - resolve(data) - }) + case 'detached': { + return await this.detached(msg, privateKey) + } + case 'verify': { + return await this.verify(msg, signature, publicKey) + } + default: { + throw new TypeError(`unknown NanoNaCl method ${method}`) + } + } } static gf = function (init?: number[]): Float64Array { diff --git a/src/lib/workers/safe.ts b/src/lib/workers/safe.ts index 232fd74..afc4957 100644 --- a/src/lib/workers/safe.ts +++ b/src/lib/workers/safe.ts @@ -3,28 +3,15 @@ '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. */ @@ -38,36 +25,33 @@ export class Safe extends WorkerInterface { Safe.listen() } - static async work (headers: { [key: string]: string }, data: any[]): Promise { + static async work (headers: Headers, data: Data): Promise { 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 } @@ -86,18 +70,15 @@ export class Safe extends WorkerInterface { /** * Encrypts data with a password byte array and stores it in the Safe. */ - static async set (name: string, password: Uint8Array, data: any): Promise { - 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 { 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) @@ -131,14 +112,14 @@ export class Safe extends WorkerInterface { /** * Retrieves data from the Safe and decrypts it with a password byte array. */ - static async get (name: string, password: Uint8Array): Promise { + static async get (name: string, password: ArrayBuffer): Promise { 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