From: Chris Duncan Date: Fri, 22 Aug 2025 07:02:28 +0000 (-0700) Subject: Deprecate unnecessary Key type alias. Inline public and private key validation. X-Git-Tag: v0.10.5~41^2~9 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=91d2d1397c7495f3c6589cc3bffef697db1b1469;p=libnemo.git Deprecate unnecessary Key type alias. Inline public and private key validation. --- diff --git a/src/lib/account/index.ts b/src/lib/account/index.ts index 5954e02..b957c11 100644 --- a/src/lib/account/index.ts +++ b/src/lib/account/index.ts @@ -1,7 +1,7 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Key, KeyPair } from '#types' +import { KeyPair } from '#types' import { Block } from '../block' import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from '../constants' import { base32, bytes, hex } from '../convert' @@ -97,7 +97,7 @@ export class Account { set representative_block (v: string | undefined) { this.#representative_block = v } set weight (v: bigint | number | string) { this.#weight = BigInt(v) } - private constructor (address: string, publicKey: Key, index?: number) { + private constructor (address: string, publicKey: string | Uint8Array, index?: number) { if (!Account.#isInternal) { throw new Error('Account cannot be instantiated directly. Use `load()` instead.') } @@ -155,18 +155,18 @@ export class Account { * Instantiates an Account object from its public key. It is unable to sign * blocks or messages since it has no private key. * - * @param {Key} publicKey - Public key of the account + * @param {string | Uint8Array} publicKey - Public key of the account * @returns {Account} A new Account object */ - static load (publicKey: Key): Account + static load (publicKey: string | Uint8Array): Account /** * Instantiates Account objects from their public keys. They are unable to sign * blocks or messages since they have no private key. * - * @param {Key[]} publicKeys - Public keys of the accounts + * @param {string | Uint8Array[]} publicKeys - Public keys of the accounts * @returns {Account[]} Array of new Account objects */ - static load (publicKeys: Key[]): Account[] + static load (publicKeys: string | Uint8Array[]): Account[] /** * Instantiates an Account object from its public key. It is unable to sign * blocks or messages since it has no private key. @@ -201,7 +201,7 @@ export class Account { * @returns {Promise} Promise for array of new Account objects */ static async load (keypairs: KeyPair[], type: 'private'): Promise - static load (input: Key | KeyPair | (Key | KeyPair)[], type?: 'private'): Account | Account[] | Promise { + static load (input: string | Uint8Array | KeyPair | (string | Uint8Array | KeyPair)[], type?: 'private'): Account | Account[] | Promise { const isInputArray = Array.isArray(input) const inputs = isInputArray ? input : [input] if (this.#isKeyPairs(inputs) && type === 'private') { @@ -269,9 +269,15 @@ export class Account { if (index == null) { throw new RangeError('Index missing for Account') } - this.#validateKey(privateKey) - if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey) - + if (typeof privateKey === 'string' && RegExp(`^[A-F0-9]{${ACCOUNT_KEY_HEX_LENGTH}}$`, 'i').test(privateKey)) { + privateKey = hex.toBytes(privateKey) + } + if (!(privateKey instanceof Uint8Array) || privateKey.every(v => v === 0)) { + throw new TypeError('Invalid private key') + } + if (privateKey.byteLength !== ACCOUNT_KEY_BYTE_LENGTH) { + throw new TypeError(`Private key must be ${ACCOUNT_KEY_BYTE_LENGTH} bytes`) + } const publicKey = await NanoNaCl.convert(privateKey) const address = this.#keyToAddress(publicKey) this.#isInternal = true @@ -287,59 +293,62 @@ export class Account { * Instantiates Account objects from public data, each specifying either its * public key or its Nano address. * - * @param {Key[]} input - Public keys or addresses of the accounts + * @param {(string | Uint8Array|KeyPair)[]} input - Public keys or addresses of the accounts * @returns {Account[]} The instantiated Account objects */ - static #fromPublic (input: (Key | KeyPair)[] | unknown): Account[] { - const keypairs = this.#isKeyPairs(input) - ? input - : this.#isKeys(input) - ? input.map(i => { return { publicKey: i } as KeyPair }) - : [] - if (keypairs.length === 0) { - throw new TypeError('Invalid public input for Account') - } + static #fromPublic (input: (string | Uint8Array | KeyPair)[] | unknown): Account[] { + try { + const keypairs = this.#isKeyPairs(input) + ? input + : this.#isKeys(input) + ? input.map(i => { return { publicKey: i } as KeyPair }) + : [] + if (keypairs.length === 0) { + throw new TypeError('Invalid public input for Account') + } - const accounts: Account[] = [] - let address: string - let publicKey: Uint8Array - let index - for (let keypair of keypairs) { - let keyError, addressError - const key = keypair.publicKey instanceof ArrayBuffer - ? new Uint8Array(keypair.publicKey) - : typeof keypair.publicKey === 'string' && [PREFIX, PREFIX_LEGACY].includes(keypair.publicKey.slice(0, 5)) - ? this.#addressToKey(keypair.publicKey) - : keypair.publicKey - try { - this.#validateKey(key) - publicKey = (typeof key === 'string') - ? hex.toBytes(key) - : key - address = this.#keyToAddress(publicKey) - } catch (err) { - keyError = err - try { - this.validate(key) - address = key - publicKey = this.#addressToKey(address) - } catch (err) { - addressError = err - throw new TypeError('Failed to import Account from public data', { cause: { keyError, addressError } }) + const accounts: Account[] = [] + for (let keypair of keypairs) { + const { index } = keypair + if (typeof keypair.publicKey === 'string') { + if (RegExp(`^[A-F0-9]{${ACCOUNT_KEY_HEX_LENGTH}}$`, 'i').test(keypair.publicKey)) { + const publicKey = hex.toBytes(keypair.publicKey) + const address = this.#keyToAddress(publicKey) + this.#isInternal = true + accounts.push(new this(address, publicKey, index)) + } else if (RegExp(`(${PREFIX}|${PREFIX_LEGACY})`).test(keypair.publicKey)) { + const address = keypair.publicKey + const publicKey = this.#addressToKey(address) + this.#isInternal = true + accounts.push(new this(address, publicKey, index)) + } else { + throw new TypeError('Invalid string', { cause: keypair.publicKey }) + } + } else if (keypair.publicKey instanceof ArrayBuffer) { + if (keypair.publicKey.byteLength === ACCOUNT_KEY_BYTE_LENGTH) { + const publicKey = new Uint8Array(keypair.publicKey) + const address = this.#keyToAddress(publicKey) + this.#isInternal = true + accounts.push(new this(address, publicKey, index)) + } else { + throw new TypeError('Invalid buffer', { cause: keypair.publicKey }) + } + } else { + throw new TypeError('Invalid Account input', { cause: keypair.publicKey }) } } - index = keypair.index - this.#isInternal = true - accounts.push(new this(address, publicKey, index)) + return accounts + } catch (err) { + console.error(err) + throw new TypeError('Failed to import Account from public data', { cause: { err } }) } - return accounts } - static #isKey (input: unknown): input is Key { + static #isKey (input: unknown): input is string | Uint8Array { return typeof input === 'string' || (input instanceof Uint8Array && 'buffer' in input) } - static #isKeys (input: unknown): input is Key[] { + static #isKeys (input: unknown): input is (string | Uint8Array)[] { if (Array.isArray(input)) { for (const i of input) { if (!this.#isKey(i)) { @@ -389,40 +398,6 @@ export class Account { const encodedChecksum = bytes.toBase32(checksum) return `${PREFIX}${encodedPublicKey}${encodedChecksum}` } - - /** - * Validates a public or private key is 32-byte array or a 64-char hex string. - * - * @param {unknown} key - Key bytes as Uint8Array or hexadecimal string - * @throws If key is invalid - */ - static #validateKey (key: unknown): asserts key is (Key) { - if (key === undefined) { - throw new TypeError(`Key is undefined`) - } - if (key instanceof ArrayBuffer) { - key = new Uint8Array(key) - } - if (typeof key !== 'string' && !(key instanceof Uint8Array)) { - throw new TypeError(`Key must be a string or Uint8Array`) - } - if (typeof key === 'string') { - if (key.length !== ACCOUNT_KEY_HEX_LENGTH) { - throw new TypeError(`Key must be ${ACCOUNT_KEY_HEX_LENGTH} characters`) - } - if (!/^[A-Fa-f0-9]{64}$/i.test(key)) { - throw new RangeError(`Key is not a valid hexadecimal value`) - } - } - if (key instanceof Uint8Array) { - if (key.byteLength !== ACCOUNT_KEY_BYTE_LENGTH) { - throw new TypeError(`Key must be ${ACCOUNT_KEY_BYTE_LENGTH} BYTES`) - } - if (key.every(v => v === 0)) { - throw new TypeError(`Key is not a valid byte array`) - } - } - } } export class AccountList extends Object { diff --git a/src/lib/tools.ts b/src/lib/tools.ts index 6e753be..e279c57 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -1,7 +1,7 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Key, SweepResult } from '#types' +import { SweepResult } from '#types' import { Account } from './account' import { Block } from './block' import { MAX_SUPPLY, UNITS } from './constants' @@ -96,11 +96,11 @@ function hash (data: string | string[], encoding?: 'hex', format?: 'hex'): strin /** * Signs arbitrary strings with a private key using the Ed25519 signature scheme. * -* @param {Key} key - Hexadecimal-formatted private key to use for signing +* @param {string | Uint8Array} key - Hexadecimal-formatted private key to use for signing * @param {...string} input - Data to be signed * @returns {Promise} Hexadecimal-formatted signature */ -export async function sign (key: Key, ...input: string[]): Promise { +export async function sign (key: string | Uint8Array, ...input: string[]): Promise { if (typeof key === 'string') key = hex.toBytes(key) try { const signature = await NanoNaCl.detached(hash(input), key) @@ -173,12 +173,12 @@ export async function sweep ( /** * Verifies the signature of arbitrary strings using a public key. * -* @param {Key} key - Hexadecimal-formatted public key to use for verification +* @param {string | Uint8Array} key - Hexadecimal-formatted public key to use for verification * @param {string} signature - Hexadcimal-formatted signature * @param {...string} input - Data to be verified * @returns {Promise} True if the data was signed by the public key's matching private key */ -export async function verify (key: Key, signature: string, ...input: string[]): Promise { +export async function verify (key: string | Uint8Array, signature: string, ...input: string[]): Promise { if (typeof key === 'string') key = hex.toBytes(key) try { return await NanoNaCl.verify(hash(input), hex.toBytes(signature), key) diff --git a/src/types.d.ts b/src/types.d.ts index 8cf9870..6062b46 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -70,18 +70,18 @@ export declare class Account { * Instantiates an Account object from its public key. It is unable to sign * blocks or messages since it has no private key. * - * @param {Key} publicKey - Public key of the account + * @param {string | Uint8Array} publicKey - Public key of the account * @returns {Account} A new Account object */ - static load (publicKey: Key): Account + static load (publicKey: string | Uint8Array): Account /** * Instantiates Account objects from their public keys. They are unable to sign * blocks or messages since they have no private key. * - * @param {Key[]} publicKeys - Public keys of the accounts + * @param {string | Uint8Array[]} publicKeys - Public keys of the accounts * @returns {Account[]} Array of new Account objects */ - static load (publicKeys: Key[]): Account[] + static load (publicKeys: string | Uint8Array[]): Account[] /** * Instantiates an Account object from its public key. It is unable to sign * blocks or messages since it has no private key. @@ -389,12 +389,10 @@ export type NamedData = { [key: string]: T } -export type Key = string | Uint8Array - export type KeyPair = { index?: number - privateKey?: Key - publicKey?: Key + privateKey?: string | Uint8Array + publicKey?: string | Uint8Array } /** @@ -501,11 +499,11 @@ declare function hash (data: string | string[], encoding?: 'hex'): Uint8Array} key - Hexadecimal-formatted private key to use for signing * @param {...string} input - Data to be signed * @returns {Promise} Hexadecimal-formatted signature */ -export declare function sign (key: Key, ...input: string[]): Promise +export declare function sign (key: string | Uint8Array, ...input: string[]): Promise /** * Collects the funds from a specified range of accounts in a wallet and sends * them all to a single recipient address. Hardware wallets are unsupported. @@ -521,12 +519,12 @@ export declare function sweep (rpc: Rpc | string | URL, wallet: Wallet, recipien /** * Verifies the signature of arbitrary strings using a public key. * -* @param {Key} key - Hexadecimal-formatted public key to use for verification +* @param {string | Uint8Array} key - Hexadecimal-formatted public key to use for verification * @param {string} signature - Hexadcimal-formatted signature * @param {...string} input - Data to be verified * @returns {Promise} True if the data was signed by the public key's matching private key */ -export declare function verify (key: Key, signature: string, ...input: string[]): Promise +export declare function verify (key: string | Uint8Array, signature: string, ...input: string[]): Promise export declare const Tools: { convert: typeof convert hash: typeof hash