From 0c6eae3da59c90c5499337a9120cc5d152693f17 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Tue, 19 Nov 2024 17:09:16 -0800 Subject: [PATCH] Tabs. --- src/lib/entropy.ts | 186 +++++++++++++++++++++---------------------- src/lib/rpc.ts | 102 ++++++++++++------------ test/TEST_VECTORS.js | 32 ++++---- 3 files changed, 160 insertions(+), 160 deletions(-) diff --git a/src/lib/entropy.ts b/src/lib/entropy.ts index 3437fbd..da51f0f 100644 --- a/src/lib/entropy.ts +++ b/src/lib/entropy.ts @@ -18,103 +18,103 @@ const MOD = 4 * 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 + } } diff --git a/src/lib/rpc.ts b/src/lib/rpc.ts index 752ce1c..cf88263 100644 --- a/src/lib/rpc.ts +++ b/src/lib/rpc.ts @@ -9,60 +9,60 @@ * 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 { - 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 { + 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') + } + } } diff --git a/test/TEST_VECTORS.js b/test/TEST_VECTORS.js index c94e08b..27b3dff 100644 --- a/test/TEST_VECTORS.js +++ b/test/TEST_VECTORS.js @@ -131,22 +131,22 @@ export const BIP32_TEST_VECTORS = Object.freeze({ /** * 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({ -- 2.34.1