await SafeWorker.request<boolean>({\r
method: 'destroy',\r
store: 'Account',\r
- [this.publicKey]: this.publicKey\r
+ name: this.publicKey\r
})\r
} catch (err) {\r
console.error(err)\r
const { result } = await SafeWorker.request<boolean>({
method: 'destroy',
store: 'Rolodex',
- [address]: address
+ name: address
})
return result
}
static async deleteName (name: string): Promise<boolean> {
const data: NamedData = {
method: 'destroy',
- store: 'Rolodex',
- [name]: name
+ store: 'Rolodex'
}
+ const names: string[] = [name]
const addresses = await this.getAddresses(name)
for (const address of addresses) {
- data[address] = address
+ names.push(address)
}
+ data.name = JSON.stringify(names)
const { result } = await SafeWorker.request<boolean>(data)
return result
}
+++ /dev/null
-//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
-//! SPDX-License-Identifier: GPL-3.0-or-later
-
-'use strict'
-
-import { WorkerInterface } from './worker-interface'
-import { NamedData } from '#types'
-
-/**
-* Converts a user password to a `CryptoKey`.
-*/
-export class Passkey extends WorkerInterface {
- static {
- this.listen()
- }
-
- static async work (data: NamedData | unknown): Promise<NamedData<ArrayBuffer | CryptoKey>> {
- const { purpose, password, salt } = this.#extractData(data)
- try {
- const key = await this.#createAesKey(purpose, password, salt)
- return { salt, key }
- } catch (err) {
- throw new Error('Failed to derive key from password', { cause: err })
- } finally {
- password.transfer()
- }
- }
-
- static #extractData (data: unknown) {
- if (data == null) {
- throw new TypeError('Worker received no data')
- }
- if (typeof data !== 'object') {
- throw new Error('Invalid data')
- }
- const dataObject = data as { [key: string]: unknown }
- if (!('password' in dataObject) || !(dataObject.password instanceof ArrayBuffer)) {
- throw new TypeError('Password must be ArrayBuffer')
- }
- const password: ArrayBuffer = dataObject.password
- if (!('purpose' in dataObject)) {
- throw new TypeError('Key purpose is required')
- }
- if (dataObject.purpose !== 'encrypt' && dataObject.purpose !== 'decrypt') {
- throw new TypeError('Invalid key purpose')
- }
- const purpose: 'encrypt' | 'decrypt' = dataObject.purpose
- if (purpose === 'decrypt' && !('salt' in dataObject)) {
- throw new TypeError('Salt required for decryption key')
- }
- if (dataObject.salt != null && !(dataObject.salt instanceof ArrayBuffer)) {
- throw new TypeError('Salt must be ArrayBuffer')
- }
- const salt: ArrayBuffer = dataObject.salt ?? globalThis.crypto.getRandomValues(new Uint8Array(32)).buffer
- return { purpose, password, salt }
- }
-
- static async #createAesKey (purpose: 'encrypt' | 'decrypt', password: ArrayBuffer, salt: ArrayBuffer): Promise<CryptoKey> {
- const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey'])
- const derivationAlgorithm: Pbkdf2Params = {
- name: 'PBKDF2',
- hash: 'SHA-512',
- iterations: 210000,
- salt
- }
- const derivedKeyType: AesKeyGenParams = {
- name: 'AES-GCM',
- length: 256
- }
- return await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose])
- }
-}
-
-let importWorkerThreads = ''
-NODE: importWorkerThreads = `import { parentPort } from 'node:worker_threads'`
-export default `
- ${importWorkerThreads}
- const WorkerInterface = ${WorkerInterface}
- const Passkey = ${Passkey}
-`
this.listen()
}
- static async work (data: NamedData): Promise<NamedData<boolean | ArrayBuffer>> {
- const { method, name, store } = data
+ static async work (data: NamedData | unknown): Promise<NamedData<boolean | ArrayBuffer>> {
+ if (data == null) {
+ throw new TypeError('Worker received no data')
+ }
+ if (typeof data !== 'object') {
+ throw new TypeError('Invalid data')
+ }
+ const { method, names, store, password } = data as { [key: string]: unknown }
if (typeof method !== 'string') {
throw new TypeError('Invalid method')
}
- delete data.method
- if (name != null && typeof name !== 'string') {
- throw new TypeError('Invalid name')
+ function validateNames (names: unknown): asserts names is string[] {
+ if (!Array.isArray(names) || names.some(n => typeof n !== 'string')) {
+ throw new TypeError('Invalid name')
+ }
}
- delete data.name
+ validateNames(names)
if (typeof store !== 'string') {
throw new TypeError('Invalid store')
}
- delete data.store
- const password = data.password
- delete data.password
+ if (password != null && !(password instanceof ArrayBuffer)) {
+ throw new TypeError('Invalid password')
+ }
this.#storage = await this.#open(this.DB_NAME)
try {
switch (method) {
return { result: await this.store(data, store, password) }
}
case 'fetch': {
- return await this.fetch(name, store, password)
+ return await this.fetch(names, store, password)
}
case 'export': {
return await this.export(store, password)
}
case 'destroy': {
- return { result: await this.destroy(data, store) }
+ return { result: await this.destroy(names, store) }
}
default: {
throw new Error(`unknown Safe method ${method}`)
/**
* Removes data from the Safe without decrypting.
*/
- static async destroy (data: NamedData, store: string): Promise<boolean> {
+ static async destroy (names: string[], store: string): Promise<boolean> {
try {
- return await this.#delete(Object.keys(data), store)
+ return await this.#delete(names, store)
} catch (err) {
console.error(err)
throw new Error(this.ERR_MSG)
/**
* Retrieves data from the Safe and decrypts it with a password byte array.
*/
- static async fetch (name: string | string[] | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<NamedData<ArrayBuffer>> {
- const names = Array.isArray(name) ? name : [name]
- if (names.some(v => typeof v !== 'string')) {
- throw new Error('Invalid fields')
+ static async fetch (names: string[], store: string, password: ArrayBuffer | unknown): Promise<NamedData<ArrayBuffer>> {
+ if (password == null || !(password instanceof ArrayBuffer)) {
+ throw new TypeError('Invalid password')
}
- const fields: string[] = names
- if (typeof store !== 'string' || store === '') {
- throw new Error('Invalid database store name')
- }
- if (!(password instanceof ArrayBuffer)) {
- throw new Error('Invalid password')
- }
-
const results: NamedData<ArrayBuffer> = {}
try {
- const records: SafeRecord[] = await this.#get(fields, store)
+ const records: SafeRecord[] = await this.#get(names, store)
if (records == null) {
throw new Error('')
}
constructor (account: Account | string, balance: string, representative: Account | string, frontier: string, work?: string)
}
-export type Data = boolean | number[] | string | ArrayBuffer | CryptoKey
+export type Data = boolean | number[] | string | string[] | ArrayBuffer | CryptoKey
/**
* Represents a cryptographically strong source of entropy suitable for use in