]> git.codecow.com Git - libnemo.git/commitdiff
Big refactoring, need to fix Safe set and get, how the data is structured.
authorChris Duncan <chris@zoso.dev>
Mon, 14 Jul 2025 21:35:37 +0000 (14:35 -0700)
committerChris Duncan <chris@zoso.dev>
Mon, 14 Jul 2025 21:35:37 +0000 (14:35 -0700)
src/lib/account.ts
src/lib/block.ts
src/lib/pool.ts
src/lib/wallets/bip44-wallet.ts
src/lib/wallets/wallet.ts
src/lib/workers/bip44-ckd.ts
src/lib/workers/nano-nacl.ts
src/lib/workers/safe.ts

index d84ac69db483a8a06ef2681f4d86fee211abc4f2..6da954229c48b5d4cc822bd0e2ce5f209f583932 100644 (file)
@@ -4,7 +4,7 @@
 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
@@ -16,8 +16,8 @@ import { NanoNaClWorker, SafeWorker } from '#workers'
 */\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
@@ -80,7 +80,7 @@ export class Account {
        */\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
@@ -136,7 +136,13 @@ export class Account {
                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
@@ -163,7 +169,11 @@ export class Account {
                                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
@@ -227,7 +237,10 @@ export class Account {
                                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
index dec40b0afcb0bc57f4ba485553092f8e88fff4ca..213c60c222f45647665e316fb8f23b535fffd44e 100644 (file)
@@ -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 })
index d714755b3eec771679ab32e319b93b97adb3202c..7b56fb77b4a8af79d1dcd095110588c0b83dfd30 100644 (file)
 // 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()
                }
        }
 }
@@ -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<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)
                })
        }
 
@@ -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)
index fd713d0ebc972c9de2c00f0122d2d861f1e998ea..3959cf9f80fd50d04fa8a19d9f1872fb0b697ef8 100644 (file)
@@ -6,7 +6,7 @@ import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js'
 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
@@ -33,7 +33,7 @@ import { Bip44CkdWorker } from '#workers'
 */\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
@@ -41,7 +41,7 @@ export class Bip44Wallet extends Wallet {
                }\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
@@ -213,9 +213,13 @@ export class Bip44Wallet extends Wallet {
        * @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
index cd2ab03df5ef7ee428ba1b4b1e4a13f738bd0680..795fc3bf985151fb819713123bd8920395b79191 100644 (file)
@@ -6,7 +6,7 @@ import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js'
 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
@@ -26,7 +26,7 @@ export type KeyPair = {
 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
@@ -139,7 +139,7 @@ export abstract class Wallet {
                }\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
@@ -165,7 +165,12 @@ export abstract class Wallet {
                                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
@@ -231,7 +236,10 @@ export abstract class Wallet {
                                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
index 563093c2f6824e625213ee05bfa50c6928360c9f..ae2e93975b3135ae511080b79e6131d2ca5ddc52 100644 (file)
@@ -1,9 +1,9 @@
 // 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
 }
 
@@ -17,19 +17,28 @@ export class Bip44Ckd extends WorkerInterface {
                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
        }
 
        /**
@@ -40,7 +49,7 @@ export class Bip44Ckd extends WorkerInterface {
        * @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)}`)
                }
@@ -57,8 +66,8 @@ export class Bip44Ckd extends WorkerInterface {
        * @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) {
@@ -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<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))
@@ -120,7 +129,7 @@ export class Bip44Ckd extends WorkerInterface {
                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)
index 122228f7bbee85a5555ab5f4fea865d3cdc70c1f..928505fd3a08db2fd1860304f2b3f0582040af74 100644 (file)
@@ -4,7 +4,7 @@
 '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
@@ -23,33 +23,23 @@ export class NanoNaCl extends WorkerInterface {
                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
index 232fd7451100f921fca6e47295706ed0d0782d7c..afc4957487fa75ecaad56b88e624d11e5667012b 100644 (file)
@@ -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<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
        }
@@ -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<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)
@@ -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<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