// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
// SPDX-License-Identifier: GPL-3.0-or-later
-import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, SLIP10_ED25519 } from '../constants.js'
-import { bytes, dec, hex, utf8 } from '../convert.js'
type ExtendedKey = {
privateKey: string
chainCode: string
}
-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)
+async function ckdBip44 () {
+ let BIP44_COIN_NANO: any, BIP44_PURPOSE: any, HARDENED_OFFSET: any, SLIP10_ED25519: any
+ let bytes: any, dec: any, hex: any, utf8: any
+ let addEventListener = globalThis.addEventListener
+ let postMessage = globalThis.postMessage
+ if (addEventListener == null || postMessage == null) {
+ ({ BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, SLIP10_ED25519 } = await import(new URL('dist/lib/constants.js', import.meta.url).toString()));
+ ({ bytes, dec, hex, utf8 } = await import(new URL('./dist/lib/convert.js', import.meta.url).toString()))
+ 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)
+ }
+ } else {
+ ({ BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET, SLIP10_ED25519 } = await import('../constants.js'));
+ ({ bytes, dec, hex, utf8 } = await import('../convert.js'))
}
-}
-/**
-* 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
-*/
-export async function nanoCKD (seed: string, index: number): Promise<string> {
- if (!Number.isSafeInteger(index) || index < 0 || index > 0x7fffffff) {
- throw new RangeError(`Invalid child key index 0x${index.toString(16)}`)
+ /**
+ * 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 (!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)
+ return accountKey.privateKey
}
- 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)
- return accountKey.privateKey
-}
-
-async function slip10 (curve: string, S: string): Promise<ExtendedKey> {
- const key = utf8.toBytes(curve)
- const data = hex.toBytes(S)
- const I = await hmac(key, data)
- const IL = I.slice(0, I.length / 2)
- const IR = I.slice(I.length / 2)
- return ({ privateKey: IL, chainCode: IR })
-}
-async function CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {
- const key = hex.toBytes(chainCode)
- const data = hex.toBytes(`00${bytes.toHex(ser256(privateKey))}${bytes.toHex(ser32(index))}`)
- const I = await hmac(key, data)
- const IL = I.slice(0, I.length / 2)
- const IR = I.slice(I.length / 2)
- return ({ privateKey: IL, chainCode: IR })
-}
+ async function slip10 (curve: string, S: string): Promise<ExtendedKey> {
+ const key = utf8.toBytes(curve)
+ const data = hex.toBytes(S)
+ const I = await hmac(key, data)
+ const IL = I.slice(0, I.length / 2)
+ const IR = I.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}`)
+ async function CKDpriv ({ privateKey, chainCode }: ExtendedKey, index: number): Promise<ExtendedKey> {
+ const key = hex.toBytes(chainCode)
+ const data = hex.toBytes(`00${bytes.toHex(ser256(privateKey))}${bytes.toHex(ser32(index))}`)
+ const I = await hmac(key, data)
+ const IL = I.slice(0, I.length / 2)
+ const IR = I.slice(I.length / 2)
+ return ({ privateKey: IL, chainCode: IR })
}
- const bits = dec.toBin(integer)
- if (bits.length > 32) {
- throw new RangeError(`Expected 32-bit integer, received ${bits.length}-bit value: ${integer}`)
+
+ function ser32 (integer: number): Uint8Array {
+ if (typeof integer !== 'number') {
+ throw new TypeError(`Expected a number, received ${typeof integer}`)
+ }
+ const bits = dec.toBin(integer)
+ if (bits.length > 32) {
+ throw new RangeError(`Expected 32-bit integer, received ${bits.length}-bit value: ${integer}`)
+ }
+ return dec.toBytes(integer, 4)
}
- return dec.toBytes(integer, 4)
-}
-function ser256 (integer: string): Uint8Array {
- if (typeof integer !== 'string') {
- throw new TypeError(`Expected string, received ${typeof integer}`)
+ function ser256 (integer: string): Uint8Array {
+ if (typeof integer !== 'string') {
+ throw new TypeError(`Expected string, received ${typeof integer}`)
+ }
+ const bits = hex.toBin(integer)
+ if (bits.length > 256) {
+ throw new RangeError(`Expected 256-bit integer, received ${bits.length}-bit value: ${integer}`)
+ }
+ return hex.toBytes(integer, 32)
}
- const bits = hex.toBin(integer)
- if (bits.length > 256) {
- throw new RangeError(`Expected 256-bit integer, received ${bits.length}-bit value: ${integer}`)
+
+ async function hmac (key: Uint8Array, data: Uint8Array): Promise<string> {
+ 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 bytes.toHex(new Uint8Array(signature))
}
- return hex.toBytes(integer, 32)
}
-async function hmac (key: Uint8Array, data: Uint8Array): Promise<string> {
- 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 bytes.toHex(new Uint8Array(signature))
-}
+const ckdBip44Worker = `(${ckdBip44.toString()})()`
+// const ckdBip44Worker = `data:text/javascript,(${ckdBip44.toString()})()`
+export { ckdBip44Worker }
+
// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
// SPDX-License-Identifier: GPL-3.0-or-later
-import blake2b from 'blake2b'
+async function ckdBlake2b () {
+ const { default: blake2b } = await import('blake2b')
-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)
+ 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
+ }
}
+
+const ckdBlake2bWorker = `(${ckdBlake2b.toString()})()`
+// const ckdBlake2bWorker = `data:text/javascript,(${ckdBlake2b.toString()})()`
+export { ckdBlake2bWorker }