// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>\r
// SPDX-License-Identifier: GPL-3.0-or-later\r
\r
-import blake2b from 'blake2b'\r
+import { ckdBip44, ckdBlake2b } from './workers.js'\r
import { Account } from './account.js'\r
-import { nanoCKD } from './bip32-key-derivation.js'\r
import { Bip39Mnemonic } from './bip39-mnemonic.js'\r
-import { ckdBip44Worker } from './workers/ckdBip44.js'\r
-import { ckdBlake2bWorker } from './workers/ckdBlake2b.js'\r
import { ADDRESS_GAP, SEED_LENGTH_BIP44, SEED_LENGTH_BLAKE2B } from './constants.js'\r
-import { dec, hex } from './convert.js'\r
import { Entropy } from './entropy.js'\r
import { Pool } from './pool.js'\r
import { Rpc } from './rpc.js'\r
throw new Error(`Bip44Wallet cannot be instantiated directly. Use 'await Bip44Wallet.create()' instead.`)\r
}\r
super(seed, mnemonic, id)\r
- if (globalThis.Window) {\r
- const ckdBlob = new Blob([ckdBip44Worker], { type: 'application/javascript' })\r
- const ckdUrl = URL.createObjectURL(ckdBlob)\r
- this.#pool = new Pool(ckdUrl)\r
- } else {\r
- this.#pool = new Pool(ckdBip44Worker)\r
- }\r
+ this.#pool = new Pool(ckdBip44)\r
Bip44Wallet.#isInternal = false\r
}\r
\r
throw new Error(`Blake2bWallet cannot be instantiated directly. Use 'await Blake2bWallet.create()' instead.`)\r
}\r
super(seed, mnemonic, id)\r
- if (globalThis.Window) {\r
- const ckdBlob = new Blob([ckdBlake2bWorker], { type: 'application/javascript' })\r
- const ckdUrl = URL.createObjectURL(ckdBlob)\r
- this.#pool = new Pool(ckdUrl)\r
- } else {\r
- this.#pool = new Pool(ckdBlake2bWorker)\r
- }\r
+ this.#pool = new Pool(ckdBlake2b)\r
Blake2bWallet.#isInternal = false\r
}\r
\r
chainCode: DataView
}
-async function ckdBip44 () {
- const BIP44_COIN_NANO = 165
- const BIP44_PURPOSE = 44
- const HARDENED_OFFSET = 0x80000000
- const SLIP10_ED25519 = 'ed25519 seed'
- let addEventListener = globalThis.addEventListener
- let postMessage = globalThis.postMessage
- if (addEventListener == null || postMessage == null) {
- const { isMainThread, parentPort } = await import('node:worker_threads')
- if (!isMainThread && parentPort) {
- addEventListener = Object.getPrototypeOf(parentPort).addListener.bind(parentPort)
- postMessage = Object.getPrototypeOf(parentPort).postMessage.bind(parentPort)
- }
+const BIP44_COIN_NANO = 165
+const BIP44_PURPOSE = 44
+const HARDENED_OFFSET = 0x80000000
+const SLIP10_ED25519 = 'ed25519 seed'
+let addEventListener = globalThis.addEventListener
+let postMessage = globalThis.postMessage
+if (addEventListener == null || postMessage == null) {
+ const { isMainThread, parentPort } = await import('node:worker_threads')
+ if (!isMainThread && parentPort) {
+ addEventListener = Object.getPrototypeOf(parentPort).addListener.bind(parentPort)
+ postMessage = Object.getPrototypeOf(parentPort).postMessage.bind(parentPort)
}
+}
- /**
- * Listens for messages from a calling function.
- */
- addEventListener('message', (message) => {
- const { seed, index } = message.data ?? message
- nanoCKD(seed, index).then(key => postMessage({ index, key }))
- })
+/**
+* Listens for messages from a calling function.
+*/
+addEventListener('message', (message) => {
+ const { seed, index } = message.data ?? message
+ nanoCKD(seed, index).then(key => postMessage({ index, key }))
+})
- /**
- * Derives a private child key following the BIP-32 and BIP-44 derivation path
- * registered to the Nano block lattice. Only hardened child keys are defined.
- *
- * @param {string} seed - Hexadecimal seed derived from mnemonic phrase
- * @param {number} index - Account number between 0 and 2^31-1
- * @returns Private child key for the account
- */
- async function nanoCKD (seed: string, index: number): Promise<string> {
- if (seed.length < 32 || seed.length > 128) {
- throw new RangeError(`Invalid seed length`)
- }
- if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {
- throw new RangeError(`Invalid child key index 0x${index.toString(16)}`)
- }
- const masterKey = await slip10(SLIP10_ED25519, seed)
- const purposeKey = await CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET)
- const coinKey = await CKDpriv(purposeKey, BIP44_COIN_NANO + HARDENED_OFFSET)
- const accountKey = await CKDpriv(coinKey, index + HARDENED_OFFSET)
- const privateKey = new Uint8Array(accountKey.privateKey.buffer)
- return privateKey.reduce((key, byte) => key.concat(byte.toString(16).padStart(2, '0')), '')
+/**
+* Derives a private child key following the BIP-32 and BIP-44 derivation path
+* registered to the Nano block lattice. Only hardened child keys are defined.
+*
+* @param {string} seed - Hexadecimal seed derived from mnemonic phrase
+* @param {number} index - Account number between 0 and 2^31-1
+* @returns Private child key for the account
+*/
+async function nanoCKD (seed: string, index: number): Promise<string> {
+ if (seed.length < 32 || seed.length > 128) {
+ throw new RangeError(`Invalid seed length`)
}
-
- async function slip10 (curve: string, S: string): 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 I = await 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 ({ privateKey: IL, chainCode: IR })
+ if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {
+ throw new RangeError(`Invalid child key index 0x${index.toString(16)}`)
}
+ const masterKey = await slip10(SLIP10_ED25519, seed)
+ const purposeKey = await CKDpriv(masterKey, BIP44_PURPOSE + HARDENED_OFFSET)
+ const coinKey = await CKDpriv(purposeKey, BIP44_COIN_NANO + HARDENED_OFFSET)
+ const accountKey = await CKDpriv(coinKey, index + HARDENED_OFFSET)
+ const privateKey = new Uint8Array(accountKey.privateKey.buffer)
+ return privateKey.reduce((key, byte) => key.concat(byte.toString(16).padStart(2, '0')), '')
+}
- async function CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {
- const key = new Uint8Array(chainCode.buffer)
- const data = new Uint8Array(37)
- data.set([0])
- data.set(ser256(privateKey), 1)
- data.set(ser32(index), 33)
- const I = await 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 ({ privateKey: IL, chainCode: IR })
- }
+async function slip10 (curve: string, S: string): 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 I = await 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 ({ privateKey: IL, chainCode: IR })
+}
- function ser32 (integer: number): Uint8Array {
- if (typeof integer !== 'number') {
- throw new TypeError(`Expected a number, received ${typeof integer}`)
- }
- if (integer > 0xffffffff) {
- throw new RangeError(`Expected 32-bit integer, received ${integer.toString(2).length}-bit value: ${integer}`)
- }
- const view = new DataView(new ArrayBuffer(4))
- view.setUint32(0, integer, false)
- return new Uint8Array(view.buffer)
- }
+async function CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {
+ const key = new Uint8Array(chainCode.buffer)
+ const data = new Uint8Array(37)
+ data.set([0])
+ data.set(ser256(privateKey), 1)
+ data.set(ser32(index), 33)
+ const I = await 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 ({ privateKey: IL, chainCode: IR })
+}
- function ser256 (integer: DataView): Uint8Array {
- if (integer.constructor !== DataView) {
- throw new TypeError(`Expected DataView, received ${typeof integer}`)
- }
- if (integer.byteLength > 32) {
- throw new RangeError(`Expected 32-byte integer, received ${integer.byteLength}-byte value: ${integer}`)
- }
- return new Uint8Array(integer.buffer)
+function ser32 (integer: number): Uint8Array {
+ if (typeof integer !== 'number') {
+ throw new TypeError(`Expected a number, received ${typeof integer}`)
+ }
+ if (integer > 0xffffffff) {
+ throw new RangeError(`Expected 32-bit integer, received ${integer.toString(2).length}-bit value: ${integer}`)
}
+ const view = new DataView(new ArrayBuffer(4))
+ view.setUint32(0, integer, false)
+ return new Uint8Array(view.buffer)
+}
- async function hmac (key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
- 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)
- return new Uint8Array(signature)
+function ser256 (integer: DataView): Uint8Array {
+ if (integer.constructor !== DataView) {
+ throw new TypeError(`Expected DataView, received ${typeof integer}`)
}
+ if (integer.byteLength > 32) {
+ throw new RangeError(`Expected 32-byte integer, received ${integer.byteLength}-byte value: ${integer}`)
+ }
+ return new Uint8Array(integer.buffer)
+}
+
+async function hmac (key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
+ 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)
+ return new Uint8Array(signature)
}
-export const ckdBip44Worker = `(${ckdBip44.toString()})()`
+export default import.meta.url
import blake2b from 'blake2b'
-async function ckdBlake2b () {
- let blake2b: any
-
- let addEventListener = globalThis.addEventListener
- let postMessage = globalThis.postMessage
- if (addEventListener == null || postMessage == null) {
- blake2b = (await import('blake2b')).default
- const { isMainThread, parentPort } = await import('node:worker_threads')
- if (!isMainThread && parentPort) {
- addEventListener = Object.getPrototypeOf(parentPort).addListener.bind(parentPort)
- postMessage = Object.getPrototypeOf(parentPort).postMessage.bind(parentPort)
- }
+let addEventListener = globalThis.addEventListener
+let postMessage = globalThis.postMessage
+if (addEventListener == null || postMessage == null) {
+ const { isMainThread, parentPort } = await import('node:worker_threads')
+ if (!isMainThread && parentPort) {
+ addEventListener = Object.getPrototypeOf(parentPort).addListener.bind(parentPort)
+ postMessage = Object.getPrototypeOf(parentPort).postMessage.bind(parentPort)
}
+}
- /**
- * Listens for messages from a calling function.
- */
- addEventListener('message', (message) => {
- const { seed, index } = message.data ?? message
- ckdBlake2b(seed, index).then(key => postMessage({ index, key }))
- })
+/**
+* Listens for messages from a calling function.
+*/
+addEventListener('message', (message) => {
+ const { seed, index } = message.data ?? message
+ ckdBlake2b(seed, index).then(key => postMessage({ index, key }))
+})
- /**
- * Derives BLAKE2b account private keys.
- *
- * @param {number} index - Index of the account
- * @returns {Promise<string>}
- */
- async function ckdBlake2b (seed: string, index: number): Promise<string> {
- const indexHex = index.toString(16).padStart(8, '0').toUpperCase()
- const inputHex = `${seed}${indexHex}`.padStart(72, '0')
- const inputArray = (inputHex.match(/.{1,2}/g) ?? []).map(h => parseInt(h, 16))
- const inputBytes = Uint8Array.from(inputArray)
- const hash = blake2b(32).update(inputBytes).digest('hex')
- return hash
- }
+/**
+* Derives BLAKE2b account private keys.
+*
+* @param {number} index - Index of the account
+* @returns {Promise<string>}
+*/
+async function ckdBlake2b (seed: string, index: number): Promise<string> {
+ const indexHex = index.toString(16).padStart(8, '0').toUpperCase()
+ const inputHex = `${seed}${indexHex}`.padStart(72, '0')
+ const inputArray = (inputHex.match(/.{1,2}/g) ?? []).map(h => parseInt(h, 16))
+ const inputBytes = Uint8Array.from(inputArray)
+ const hash = blake2b(32).update(inputBytes).digest('hex')
+ return hash
}
-export const ckdBlake2bWorker = `(${ckdBlake2b.toString()})()`
+export default import.meta.url