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 { isMainThread, parentPort } = await import('node:worker_threads')
+ if (!isMainThread && parentPort) {
+ addEventListener = Object.getPrototypeOf(parentPort).addListener.bind(parentPort)
+ postMessage = Object.getPrototypeOf(parentPort).postMessage.bind(parentPort)
+ }
}
/**
* @returns {Promise<Account>}
*/
addEventListener('message', (message) => {
- const { type, seed, index } = message.data ?? message
- switch (type) {
- case 'bip44': {
- ckdBip44(seed, index).then(postMessage)
- break
- }
- case 'blake2b': {
- ckdBlake2b(seed, index).then(postMessage)
- break
- }
- }
+ const { type, seed, index } = message.data ?? message
+ switch (type) {
+ case 'bip44': {
+ ckdBip44(seed, index).then(postMessage)
+ break
+ }
+ case 'blake2b': {
+ ckdBlake2b(seed, index).then(postMessage)
+ break
+ }
+ }
})
/**
* @returns {Promise<Account>}
*/
async function ckdBip44 (seed: string, index: number): Promise<string> {
- return nanoCKD(seed, index)
+ return nanoCKD(seed, index)
}
/**
* @returns {Promise<Account>}
*/
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 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
}
* brand new source of entropy will be generated at the maximum size of 256 bits.
*/
export class Entropy {
- #bits: string
- #buffer: ArrayBuffer
- #bytes: Uint8Array
- #hex: string
+ #bits: string
+ #buffer: ArrayBuffer
+ #bytes: Uint8Array
+ #hex: string
- get bits () { return this.#bits }
- get buffer () { return this.#buffer }
- get bytes () { return this.#bytes }
- get hex () { return this.#hex }
+ get bits () { return this.#bits }
+ get buffer () { return this.#buffer }
+ get bytes () { return this.#bytes }
+ get hex () { return this.#hex }
- /**
- * Generate 256 bits of entropy.
- */
- constructor ()
- /**
- * Generate between 16-32 bytes of entropy.
- * @param {number} size - Number of bytes to generate
- */
- constructor (size: number)
- /**
- * Import existing entropy and validate it.
- * @param {string} hex - Hexadecimal string
- */
- constructor (hex: string)
- /**
- * Import existing entropy and validate it.
- * @param {ArrayBuffer} buffer - Byte buffer
- */
- constructor (buffer: ArrayBuffer)
- /**
- * Import existing entropy and validate it.
- * @param {Uint8Array} bytes - Byte array
- */
- constructor (bytes: Uint8Array)
- constructor (input?: number | string | ArrayBuffer | Uint8Array) {
- if (typeof input === 'number' && input > 0) {
- if (input < MIN || input > MAX) {
- throw new RangeError(`Entropy must be ${MIN}-${MAX} bytes`)
- }
- if (input % MOD !== 0) {
- throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`)
- }
- this.#bytes = crypto.getRandomValues(new Uint8Array(input))
- this.#hex = bytes.toHex(this.#bytes)
- this.#bits = hex.toBin(this.#hex)
- this.#buffer = this.#bytes.buffer
- return
- }
+ /**
+ * Generate 256 bits of entropy.
+ */
+ constructor ()
+ /**
+ * Generate between 16-32 bytes of entropy.
+ * @param {number} size - Number of bytes to generate
+ */
+ constructor (size: number)
+ /**
+ * Import existing entropy and validate it.
+ * @param {string} hex - Hexadecimal string
+ */
+ constructor (hex: string)
+ /**
+ * Import existing entropy and validate it.
+ * @param {ArrayBuffer} buffer - Byte buffer
+ */
+ constructor (buffer: ArrayBuffer)
+ /**
+ * Import existing entropy and validate it.
+ * @param {Uint8Array} bytes - Byte array
+ */
+ constructor (bytes: Uint8Array)
+ constructor (input?: number | string | ArrayBuffer | Uint8Array) {
+ if (typeof input === 'number' && input > 0) {
+ if (input < MIN || input > MAX) {
+ throw new RangeError(`Entropy must be ${MIN}-${MAX} bytes`)
+ }
+ if (input % MOD !== 0) {
+ throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`)
+ }
+ this.#bytes = crypto.getRandomValues(new Uint8Array(input))
+ this.#hex = bytes.toHex(this.#bytes)
+ this.#bits = hex.toBin(this.#hex)
+ this.#buffer = this.#bytes.buffer
+ return
+ }
- if (typeof input === 'string' && input.length > 0) {
- if (input.length < MIN * 2 || input.length > MAX * 2) {
- throw new RangeError(`Entropy must be ${MIN * 2}-${MAX * 2} characters`)
- }
- if (input.length % MOD * 2 !== 0) {
- throw new RangeError(`Entropy must be a multiple of ${MOD * 2} characters`)
- }
- this.#hex = input
- if (!/^[0-9a-fA-F]+$/i.test(this.#hex)) {
- throw new RangeError('Entropy contains invalid hexadecimal characters')
- }
- this.#bytes = hex.toBytes(this.#hex)
- this.#bits = hex.toBin(this.#hex)
- this.#buffer = this.#bytes.buffer
- return
- }
+ if (typeof input === 'string' && input.length > 0) {
+ if (input.length < MIN * 2 || input.length > MAX * 2) {
+ throw new RangeError(`Entropy must be ${MIN * 2}-${MAX * 2} characters`)
+ }
+ if (input.length % MOD * 2 !== 0) {
+ throw new RangeError(`Entropy must be a multiple of ${MOD * 2} characters`)
+ }
+ this.#hex = input
+ if (!/^[0-9a-fA-F]+$/i.test(this.#hex)) {
+ throw new RangeError('Entropy contains invalid hexadecimal characters')
+ }
+ this.#bytes = hex.toBytes(this.#hex)
+ this.#bits = hex.toBin(this.#hex)
+ this.#buffer = this.#bytes.buffer
+ return
+ }
- if (input instanceof ArrayBuffer && input.byteLength > 0) {
- if (input.byteLength < MIN || input.byteLength > MAX) {
- throw new Error(`Entropy must be ${MIN}-${MAX} bytes`)
- }
- if (input.byteLength % MOD !== 0) {
- throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`)
- }
- this.#buffer = input
- this.#bytes = new Uint8Array(this.#buffer)
- this.#bits = bytes.toBin(this.#bytes)
- this.#hex = bytes.toHex(this.#bytes)
- return
- }
+ if (input instanceof ArrayBuffer && input.byteLength > 0) {
+ if (input.byteLength < MIN || input.byteLength > MAX) {
+ throw new Error(`Entropy must be ${MIN}-${MAX} bytes`)
+ }
+ if (input.byteLength % MOD !== 0) {
+ throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`)
+ }
+ this.#buffer = input
+ this.#bytes = new Uint8Array(this.#buffer)
+ this.#bits = bytes.toBin(this.#bytes)
+ this.#hex = bytes.toHex(this.#bytes)
+ return
+ }
- if (input instanceof Uint8Array && input.length > 0) {
- if (input.length < MIN || input.length > MAX) {
- throw new Error(`Entropy must be ${MIN}-${MAX} bytes`)
- }
- if (input.length % MOD !== 0) {
- throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`)
- }
- this.#bytes = input
- this.#bits = bytes.toBin(this.#bytes)
- this.#buffer = this.#bytes.buffer
- this.#hex = bytes.toHex(this.#bytes)
- return
- }
+ if (input instanceof Uint8Array && input.length > 0) {
+ if (input.length < MIN || input.length > MAX) {
+ throw new Error(`Entropy must be ${MIN}-${MAX} bytes`)
+ }
+ if (input.length % MOD !== 0) {
+ throw new RangeError(`Entropy must be a multiple of ${MOD} bytes`)
+ }
+ this.#bytes = input
+ this.#bits = bytes.toBin(this.#bytes)
+ this.#buffer = this.#bytes.buffer
+ this.#hex = bytes.toHex(this.#bytes)
+ return
+ }
- this.#bytes = crypto.getRandomValues(new Uint8Array(MAX))
- this.#hex = bytes.toHex(this.#bytes)
- this.#bits = hex.toBin(this.#hex)
- this.#buffer = this.#bytes.buffer
- }
+ this.#bytes = crypto.getRandomValues(new Uint8Array(MAX))
+ this.#hex = bytes.toHex(this.#bytes)
+ this.#bits = hex.toBin(this.#hex)
+ this.#buffer = this.#bytes.buffer
+ }
}
* other value will be changed automatically.
*/
export class Rpc {
- #u: URL
- #n?: string
+ #u: URL
+ #n?: string
- constructor (url: string | URL, apiKeyName?: string) {
- this.#u = new URL(url)
- this.#u.protocol = 'https:'
- this.#n = apiKeyName
- }
+ constructor (url: string | URL, apiKeyName?: string) {
+ this.#u = new URL(url)
+ this.#u.protocol = 'https:'
+ this.#n = apiKeyName
+ }
- /**
- *
- * @param {string} action - Nano protocol RPC call to execute
- * @param {object} [data] - JSON to send to the node as defined by the action
- * @returns JSON-formatted RPC results from the node
- */
- async call (action: string, data?: { [key: string]: any }): Promise<any> {
- this.#validate(action)
- const headers: { [key: string]: string } = {}
- headers['Content-Type'] = 'application/json'
- if (this.#n && process.env.LIBNEMO_RPC_API_KEY) {
- headers[this.#n] = process.env.LIBNEMO_RPC_API_KEY
- }
+ /**
+ *
+ * @param {string} action - Nano protocol RPC call to execute
+ * @param {object} [data] - JSON to send to the node as defined by the action
+ * @returns JSON-formatted RPC results from the node
+ */
+ async call (action: string, data?: { [key: string]: any }): Promise<any> {
+ this.#validate(action)
+ const headers: { [key: string]: string } = {}
+ headers['Content-Type'] = 'application/json'
+ if (this.#n && process.env.LIBNEMO_RPC_API_KEY) {
+ headers[this.#n] = process.env.LIBNEMO_RPC_API_KEY
+ }
- data ??= {}
- data.action = action.toLowerCase()
- const body = JSON.stringify(data)
- .replaceAll('/', '\\u002f')
- .replaceAll('<', '\\u003c')
- .replaceAll('>', '\\u003d')
- .replaceAll('\\', '\\u005c')
+ data ??= {}
+ data.action = action.toLowerCase()
+ const body = JSON.stringify(data)
+ .replaceAll('/', '\\u002f')
+ .replaceAll('<', '\\u003c')
+ .replaceAll('>', '\\u003d')
+ .replaceAll('\\', '\\u005c')
- const req = new Request(this.#u, {
- method: 'POST',
- headers,
- body
- })
- try {
- const res = await fetch(req)
- return await res.json()
- } catch (err) {
- console.error(err)
- return JSON.stringify(err)
- }
- }
+ const req = new Request(this.#u, {
+ method: 'POST',
+ headers,
+ body
+ })
+ try {
+ const res = await fetch(req)
+ return await res.json()
+ } catch (err) {
+ console.error(err)
+ return JSON.stringify(err)
+ }
+ }
- #validate (action: string): void {
- if (!action) {
- throw new ReferenceError('Action is required for RPCs')
- }
- if (typeof action !== 'string') {
- throw new TypeError('RPC action must be a string')
- }
- if (!/^[A-Za-z]+(_[A-Za-z]+)*$/.test(action)) {
- throw new TypeError('RPC action contains invalid characters')
- }
- }
+ #validate (action: string): void {
+ if (!action) {
+ throw new ReferenceError('Action is required for RPCs')
+ }
+ if (typeof action !== 'string') {
+ throw new TypeError('RPC action must be a string')
+ }
+ if (!/^[A-Za-z]+(_[A-Za-z]+)*$/.test(action)) {
+ throw new TypeError('RPC action contains invalid characters')
+ }
+ }
}
/**
* These vectors test that invalid extended keys are recognized as invalid.
*/
- INVALID_0: 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm', // (pubkey version / prvkey mismatch)
- INVALID_1: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH', // (prvkey version / pubkey mismatch)
- INVALID_2: 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn', // (invalid pubkey prefix 04)
- INVALID_3: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ', // (invalid prvkey prefix 04)
- INVALID_4: 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4', // (invalid pubkey prefix 01)
- INVALID_5: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J', // (invalid prvkey prefix 01)
- INVALID_6: 'xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv', // (zero depth with non - zero parent fingerprint)
- INVALID_7: 'xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ', // (zero depth with non - zero parent fingerprint)
- INVALID_8: 'xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN', // (zero depth with non - zero index)
- INVALID_9: 'xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8', // (zero depth with non - zero index)
- INVALID_10: 'DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4', // (unknown extended key version)
- INVALID_11: 'DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9', // (unknown extended key version)
- INVALID_12: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx', // (private key 0 not in 1..n - 1)
- INVALID_13: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G', // (private key n not in 1..n - 1)
- INVALID_14: 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY', // (invalid pubkey 020000000000000000000000000000000000000000000000000000000000000007)
- INVALID_15: 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL' // (invalid checksum)
+ INVALID_0: 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5fTtTQBm', // (pubkey version / prvkey mismatch)
+ INVALID_1: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGTQQD3dC4H2D5GBj7vWvSQaaBv5cxi9gafk7NF3pnBju6dwKvH', // (prvkey version / pubkey mismatch)
+ INVALID_2: 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Txnt3siSujt9RCVYsx4qHZGc62TG4McvMGcAUjeuwZdduYEvFn', // (invalid pubkey prefix 04)
+ INVALID_3: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFGpWnsj83BHtEy5Zt8CcDr1UiRXuWCmTQLxEK9vbz5gPstX92JQ', // (invalid prvkey prefix 04)
+ INVALID_4: 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6N8ZMMXctdiCjxTNq964yKkwrkBJJwpzZS4HS2fxvyYUA4q2Xe4', // (invalid pubkey prefix 01)
+ INVALID_5: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fEQ3Qen6J', // (invalid prvkey prefix 01)
+ INVALID_6: 'xprv9s2SPatNQ9Vc6GTbVMFPFo7jsaZySyzk7L8n2uqKXJen3KUmvQNTuLh3fhZMBoG3G4ZW1N2kZuHEPY53qmbZzCHshoQnNf4GvELZfqTUrcv', // (zero depth with non - zero parent fingerprint)
+ INVALID_7: 'xpub661no6RGEX3uJkY4bNnPcw4URcQTrSibUZ4NqJEw5eBkv7ovTwgiT91XX27VbEXGENhYRCf7hyEbWrR3FewATdCEebj6znwMfQkhRYHRLpJ', // (zero depth with non - zero parent fingerprint)
+ INVALID_8: 'xprv9s21ZrQH4r4TsiLvyLXqM9P7k1K3EYhA1kkD6xuquB5i39AU8KF42acDyL3qsDbU9NmZn6MsGSUYZEsuoePmjzsB3eFKSUEh3Gu1N3cqVUN', // (zero depth with non - zero index)
+ INVALID_9: 'xpub661MyMwAuDcm6CRQ5N4qiHKrJ39Xe1R1NyfouMKTTWcguwVcfrZJaNvhpebzGerh7gucBvzEQWRugZDuDXjNDRmXzSZe4c7mnTK97pTvGS8', // (zero depth with non - zero index)
+ INVALID_10: 'DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHGMQzT7ayAmfo4z3gY5KfbrZWZ6St24UVf2Qgo6oujFktLHdHY4', // (unknown extended key version)
+ INVALID_11: 'DMwo58pR1QLEFihHiXPVykYB6fJmsTeHvyTp7hRThAtCX8CvYzgPcn8XnmdfHPmHJiEDXkTiJTVV9rHEBUem2mwVbbNfvT2MTcAqj3nesx8uBf9', // (unknown extended key version)
+ INVALID_12: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzF93Y5wvzdUayhgkkFoicQZcP3y52uPPxFnfoLZB21Teqt1VvEHx', // (private key 0 not in 1..n - 1)
+ INVALID_13: 'xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD5SDKr24z3aiUvKr9bJpdrcLg1y3G', // (private key n not in 1..n - 1)
+ INVALID_14: 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6Q5JXayek4PRsn35jii4veMimro1xefsM58PgBMrvdYre8QyULY', // (invalid pubkey 020000000000000000000000000000000000000000000000000000000000000007)
+ INVALID_15: 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHL' // (invalid checksum)
})
export const CUSTOM_TEST_VECTORS = Object.freeze({
})\r
\r
describe('child key derivation performance', { skip: true }, async () => {\r
- it('performance test of BIP-44 ckd', async function () {\r
+ it('performance test of BIP-44 ckd', async () => {\r
const wallet = await Bip44Wallet.create(NANO_TEST_VECTORS.PASSWORD)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
\r
assert.equal(accounts.length, 0x8000)\r
})\r
\r
- it('performance test of BLAKE2b ckd', async function () {\r
+ it('performance test of BLAKE2b ckd', async () => {\r
const wallet = await Blake2bWallet.create(NANO_TEST_VECTORS.PASSWORD)\r
await wallet.unlock(NANO_TEST_VECTORS.PASSWORD)\r
\r