this.#validateKey(privateKey)\r
let publicKey: string\r
try {\r
- const result = (await this.#poolNanoNaCl.assign({\r
- method: 'convert',\r
- privateKey: bytes.toArray(privateKey)\r
- }))[0]\r
- publicKey = result.publicKey\r
+ const result = await this.#poolNanoNaCl.assign({ method: 'convert' }, privateKey)\r
+ publicKey = result.publicKey[0]\r
} catch (err) {\r
throw new Error(`Failed to derive public key from private key`, { cause: err })\r
}\r
throw new Error('Failed to lock account')\r
}\r
try {\r
- const data: { id: string, privateKey: Uint8Array } = {\r
- id: this.#pub,\r
- privateKey: this.#prv\r
- }\r
- const response = (await Account.#poolSafe.assign({\r
+ const headers = {\r
method: 'set',\r
name: this.#pub,\r
- password,\r
- data\r
- }))[0]\r
- const success = response?.result\r
+ id: this.#pub\r
+ }\r
+ const response = await Account.#poolSafe.assign(headers, this.#prv, password)\r
+ const success = response?.result[0]\r
if (!success) {\r
throw null\r
}\r
throw new Error('Failed to unlock account')\r
}\r
try {\r
- const response = (await Account.#poolSafe.assign({\r
+ const headers = {\r
method: 'get',\r
- name: this.#pub,\r
- password\r
- }))[0]\r
- const { id, privateKey } = response?.result\r
+ name: this.#pub\r
+ }\r
+ const response = await Account.#poolSafe.assign(headers, password)\r
+ const { id, privateKey } = response?.result[0]\r
if (id == null || id !== this.#pub) {\r
throw null\r
}\r
*/\r
export class Bip39Mnemonic {\r
static #isInternal: boolean = false\r
- #bip44Seed: string = ''\r
- #blake2bSeed: string = ''\r
+ #bip44Seed: Uint8Array | null = null\r
+ #blake2bSeed: Uint8Array | null = null\r
#phrase: string = ''\r
get phrase (): string { return this.#phrase.normalize('NFKD') }\r
\r
return true\r
}\r
\r
+ async toBip39Seed (passphrase: string): Promise<Uint8Array>\r
/**\r
* Converts the mnemonic phrase to a BIP-39 seed.\r
*\r
* @param {string} [passphrase=''] - Used as the PBKDF2 salt. Default: ""\r
* @returns {string} Hexadecimal seed\r
*/\r
- async toBip39Seed (passphrase: string): Promise<string> {\r
- if (this.#blake2bSeed === '') {\r
+ async toBip39Seed (passphrase: string, format: 'hex'): Promise<string>\r
+ async toBip39Seed (passphrase: string, format?: 'hex'): Promise<string | Uint8Array> {\r
+ if (this.#bip44Seed == null) {\r
if (passphrase == null || typeof passphrase !== 'string') {\r
passphrase = ''\r
}\r
}\r
const seedKey = await subtle.deriveKey(algorithm, phraseKey, derivedKeyType, true, ['sign'])\r
const seedBuffer = await subtle.exportKey('raw', seedKey)\r
- const seedBytes = new Uint8Array(seedBuffer)\r
- const seed = bytes.toHex(seedBytes)\r
- this.#bip44Seed = seed\r
+ this.#bip44Seed = new Uint8Array(seedBuffer)\r
}\r
- return this.#bip44Seed\r
+ return format === 'hex'\r
+ ? bytes.toHex(this.#bip44Seed)\r
+ : this.#bip44Seed\r
}\r
\r
+ async toBlake2bSeed (): Promise<Uint8Array>\r
/**\r
* Converts the mnemonic phrase to a BLAKE2b seed.\r
*\r
* @returns {string} Hexadecimal seed\r
*/\r
- async toBlake2bSeed (): Promise<string> {\r
- if (this.#blake2bSeed === '') {\r
+ async toBlake2bSeed (format: 'hex'): Promise<string>\r
+ async toBlake2bSeed (format?: 'hex'): Promise<string | Uint8Array> {\r
+ if (this.#blake2bSeed == null) {\r
const wordArray = this.phrase.split(' ')\r
const bits = wordArray.map((w: string) => {\r
const wordIndex = Bip39Words.indexOf(w)\r
if (entropyBytes == null) {\r
throw new Error('Invalid mnemonic phrase')\r
}\r
- this.#blake2bSeed = bytes.toHex(Uint8Array.from(entropyBytes))\r
+ this.#blake2bSeed = new Uint8Array(entropyBytes)\r
}\r
- return this.#blake2bSeed\r
+ return format === 'hex'\r
+ ? bytes.toHex(this.#blake2bSeed)\r
+ : this.#blake2bSeed\r
}\r
}\r
\r
}
const account = await Account.fromPrivateKey(key)
try {
- const result = (await Block.#poolNanoNaCl.assign({
+ const headers = {
method: 'detached',
- msg: hex.toArray(this.hash),
- privateKey: hex.toArray(`${account.privateKey}`)
- }))[0]
- this.signature = result.signature
+ msg: this.hash
+ }
+ const result = await Block.#poolNanoNaCl.assign(headers, hex.toBytes(account.privateKey))
+ this.signature = result.signature[0]
} catch (err) {
throw new Error(`Failed to sign block`, { cause: err })
}
throw new Error('Provide a key for block signature verification.')
}
try {
- const result = (await Block.#poolNanoNaCl.assign({
+ const headers = {
method: 'verify',
- msg: hex.toArray(this.hash),
- signature: hex.toArray(this.signature ?? ''),
- publicKey: hex.toArray(key)
- }))[0]
- return result.isVerified
+ msg: this.hash,
+ signature: this.signature ?? '',
+ publicKey: key
+ }
+ const result = await Block.#poolNanoNaCl.assign(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
+export type Header = {
+ [key: string]: number | string
+}
+
type Job = {
id: number
reject: (value: any) => void
resolve: (value: any) => void
- data: any
+ headers: Header
+ data: (ArrayBuffer | Uint8Array)[]
results: any[]
}
return n
}
- async assign (data: any): Promise<any> {
- if (!(data instanceof ArrayBuffer || Array.isArray(data))) data = [data]
+ async assign (headers: Header, ...data: (ArrayBuffer | Uint8Array)[]): Promise<any> {
return new Promise((resolve, reject) => {
const job: Job = {
id: performance.now(),
results: [],
+ headers,
data,
resolve,
reject
#assign (thread: Thread, job: Job): void {
if (job.data instanceof ArrayBuffer && job.data.byteLength > 0) {
thread.job = job
- thread.worker.postMessage({ buffer: job.data }, [job.data])
+ 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)
if (next?.length > 0) {
const buffer = new TextEncoder().encode(JSON.stringify(next)).buffer
thread.job = job
- thread.worker.postMessage({ buffer }, [buffer])
+ thread.worker.postMessage({ headers: job.headers, data: buffer }, [buffer])
}
}
}
* `listen()` function. Finally, they must override the implementation of the
* `work()` function. See the documentation of those functions for details.
*/
-export class WorkerInterface {
+export abstract class WorkerInterface {
/**
* Processes data through a worker.
*
* function signature and providing their own processing call in the try-catch
* block.
*
- * @param {any[]} data - Array of data to process
- * @returns Promise for that data after being processed
+ * @param {Header} headers - Flat object of header data
+ * @param {any[]} data - Transferred buffer of data to process
+ * @returns Promise for processed data
*/
- static async work (data: any[]): Promise<any[]> {
+ static async work (headers: Header, body: any[]): Promise<any[]> {
return new Promise(async (resolve, reject): Promise<void> => {
- for (let d of data) {
+ const { example } = headers
+ const results = []
+ for (let buf of body) {
try {
- d = await d
+ const b = JSON.parse(new TextDecoder().decode(buf))
+ results.push(await b(example))
} catch (err) {
reject(err)
}
}
- resolve(data)
+ resolve(results)
})
}
* Extending classes must call this in a static initialization block:
* ```
* static {
- * Pow.listen()
+ * Extension.listen()
* }
* ```
*/
static listen (): void {
addEventListener('message', (message: any): void => {
- const { name, buffer } = message.data
+ const { name, headers, data } = message.data
if (name === 'STOP') {
close()
const buffer = new ArrayBuffer(0)
//@ts-expect-error
postMessage(buffer, [buffer])
} else {
- const data = JSON.parse(new TextDecoder().decode(buffer))
- this.work(data).then(this.report).catch(this.report)
+ this.work(headers, data).then(this.report).catch(this.report)
}
})
}
import { KeyPair, Wallet } from './wallet'\r
import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js'\r
import { SEED_LENGTH_BIP44 } from '#src/lib/constants.js'\r
+import { hex } from '#src/lib/convert.js'\r
import { Entropy } from '#src/lib/entropy.js'\r
import { Pool } from '#src/lib/pool.js'\r
import { Bip44CkdWorker } from '#workers'\r
throw new Error(`Bip44Wallet cannot be instantiated directly. Use 'await Bip44Wallet.create()' instead.`)\r
}\r
Bip44Wallet.#isInternal = false\r
- super(id, seed, mnemonic)\r
+ super(id, hex.toBytes(seed), mnemonic)\r
Bip44Wallet.#poolBip44Ckd ??= new Pool(Bip44CkdWorker)\r
}\r
\r
const id = await Entropy.create()\r
const e = await Entropy.import(entropy)\r
const m = await Bip39Mnemonic.fromEntropy(e.hex)\r
- const s = await m.toBip39Seed(salt)\r
+ const s = await m.toBip39Seed(salt, 'hex')\r
Bip44Wallet.#isInternal = true\r
wallet = new this(id, s, m)\r
} catch (err) {\r
try {\r
const id = await Entropy.create()\r
const m = await Bip39Mnemonic.fromPhrase(mnemonic)\r
- const s = await m.toBip39Seed(salt)\r
+ const s = await m.toBip39Seed(salt, 'hex')\r
Bip44Wallet.#isInternal = true\r
wallet = new this(id, s, m)\r
} catch (err) {\r
*/\r
async ckd (indexes: number[]): Promise<KeyPair[]> {\r
const data: any = []\r
- indexes.forEach(i => data.push({ seed: this.seed, index: i }))\r
- const privateKeys: KeyPair[] = await Bip44Wallet.#poolBip44Ckd.assign(data)\r
+ indexes.forEach(i => data.push({ index: i }))\r
+ const privateKeys: KeyPair[] = await Bip44Wallet.#poolBip44Ckd.assign(data, this.seed)\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 { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js'\r
import { Blake2b } from '#src/lib/blake2b.js'\r
import { SEED_LENGTH_BLAKE2B } from '#src/lib/constants.js'\r
-import { hex } from '#src/lib/convert.js'\r
+import { bytes, hex } from '#src/lib/convert.js'\r
import { Entropy } from '#src/lib/entropy.js'\r
\r
/**\r
export class Blake2bWallet extends Wallet {\r
static #isInternal: boolean = false\r
\r
- constructor (id: Entropy, seed: string, mnemonic?: Bip39Mnemonic) {\r
+ constructor (id: Entropy, seed?: Uint8Array, mnemonic?: Bip39Mnemonic) {\r
if (!Blake2bWallet.#isInternal) {\r
throw new Error(`Blake2bWallet cannot be instantiated directly. Use 'await Blake2bWallet.create()' instead.`)\r
}\r
if (!/^[0-9a-fA-F]+$/i.test(seed)) {\r
throw new Error('Seed contains invalid hexadecimal characters.')\r
}\r
+\r
const id = await Entropy.create()\r
- const s = seed\r
+ const s = hex.toBytes(seed)\r
const m = await Bip39Mnemonic.fromEntropy(seed)\r
Blake2bWallet.#isInternal = true\r
const wallet = new this(id, s, m)\r
try {\r
const id = await Entropy.create()\r
const m = await Bip39Mnemonic.fromPhrase(mnemonic)\r
- const s = await m.toBlake2bSeed()\r
+ const s = (await m.toBlake2bSeed())\r
Blake2bWallet.#isInternal = true\r
wallet = new this(id, s, m)\r
} catch (err) {\r
throw new TypeError('Wallet ID is required to restore')\r
}\r
Blake2bWallet.#isInternal = true\r
- return new this(await Entropy.import(id), '')\r
+ return new this(await Entropy.import(id))\r
}\r
\r
/**\r
async ckd (indexes: number[]): Promise<KeyPair[]> {\r
const results = indexes.map(index => {\r
const indexHex = index.toString(16).padStart(8, '0').toUpperCase()\r
- const inputHex = `${this.seed}${indexHex}`.padStart(72, '0')\r
+ const inputHex = `${bytes.toHex(this.seed)}${indexHex}`.padStart(72, '0')\r
const inputBytes = hex.toBytes(inputHex)\r
const privateKey: string = new Blake2b(32).update(inputBytes).digest('hex')\r
return { privateKey, index }\r
import { Account, AccountList } from '#src/lib/account.js'\r
import { Bip39Mnemonic } from '#src/lib/bip39-mnemonic.js'\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 { Rpc } from '#src/lib/rpc.js'\r
import { SafeWorker } from '#workers'\r
-import { utf8 } from '../convert'\r
\r
export type KeyPair = {\r
publicKey?: string,\r
#id: Entropy\r
#locked: boolean = true\r
#m: Bip39Mnemonic | null\r
- #s: string | null\r
+ #s: Uint8Array\r
\r
get id () { return this.#id.hex }\r
get isLocked () { return this.#locked }\r
get isUnlocked () { return !this.#locked }\r
get mnemonic () { return this.#m instanceof Bip39Mnemonic ? this.#m.phrase : '' }\r
- get seed () { return typeof this.#s === 'string' ? this.#s : '' }\r
+ get seed () { return this.#s }\r
\r
- constructor (id: Entropy, seed?: string, mnemonic?: Bip39Mnemonic) {\r
+ constructor (id: Entropy, seed?: Uint8Array, mnemonic?: Bip39Mnemonic) {\r
if (this.constructor === Wallet) {\r
throw new Error('Wallet is an abstract class and cannot be instantiated directly.')\r
}\r
this.#accounts = new AccountList()\r
this.#id = id\r
this.#m = mnemonic ?? null\r
- this.#s = seed ?? null\r
+ this.#s = seed ?? new Uint8Array(32)\r
}\r
\r
/**\r
i++\r
}\r
this.#m = null\r
- this.#s = null\r
+ this.#s.fill(0)\r
await Wallet.#poolSafe.assign({\r
method: 'destroy',\r
name: this.id\r
throw new Error('Failed to unlock wallet')\r
}\r
try {\r
- const data: { id: string, mnemonic: string | null, seed: string | null } = {\r
- id: this.id,\r
- mnemonic: null,\r
- seed: null\r
- }\r
- if (this.#m instanceof Bip39Mnemonic) {\r
- data.mnemonic = this.#m.phrase\r
- }\r
- if (typeof this.#s === 'string') {\r
- data.seed = this.#s\r
- }\r
- const response = (await Wallet.#poolSafe.assign({\r
+ const headers = {\r
method: 'set',\r
name: this.id,\r
- password,\r
- data\r
- }))[0]\r
- const success = response?.result\r
+ id: this.id,\r
+ }\r
+ const response = await Wallet.#poolSafe.assign(headers, password, utf8.toBytes(this.#m?.phrase ?? ''), this.#s)\r
+ const success = response?.result[0]\r
if (!success) {\r
throw null\r
}\r
password.fill(0)\r
}\r
this.#m = null\r
- this.#s = null\r
+ this.#s.fill(0)\r
this.#locked = true\r
return true\r
}\r
throw new Error('Failed to unlock wallet')\r
}\r
try {\r
- const response = (await Wallet.#poolSafe.assign({\r
+ const headers = {\r
method: 'get',\r
- name: this.id,\r
- password\r
- }))[0]\r
- const { id, mnemonic, seed } = response?.result\r
+ name: this.id\r
+ }\r
+ const response = await Wallet.#poolSafe.assign(headers,\r
+ password)\r
+ let { id, mnemonic, seed } = response?.result[0]\r
if (id == null || id !== this.id) {\r
throw null\r
}\r
if (mnemonic != null) {\r
this.#m = await Bip39Mnemonic.fromPhrase(mnemonic)\r
+ mnemonic = null\r
}\r
if (seed != null) {\r
- this.#s = seed\r
+ this.#s.set(hex.toBytes(seed))\r
+ seed = null\r
}\r
const promises = []\r
for (const account of this.#accounts) {\r
Bip44Ckd.listen()
}
- static async work (data: any[]): Promise<any[]> {
+ static async work (headers: { [key: string]: string | number }, data: any[]): Promise<any[]> {
for (const d of data) {
try {
if (d.coin != null && d.coin !== this.BIP44_PURPOSE) {
NanoNaCl.listen()\r
}\r
\r
- static async work (data: any[]): Promise<any[]> {\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
Safe.listen()
}
- static async work (data: any[]): Promise<any[]> {
+ static async work (headers: { [key: string]: string }, data: any[]): Promise<any[]> {
this.#storage = await this.#open(this.DB_NAME)
+ const { method, name } = headers
const results: SafeOutput[] = []
for (const d of data) {
- const { name, method, password, data } = d as SafeInput
+ const { password, data } = d as SafeInput
let result
try {
const passwordBytes = obj.toBytes(password ?? [])