From 0777c24d28814f1a4c9cb171a729528cb56cb791 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sun, 17 Aug 2025 15:17:12 -0700 Subject: [PATCH] Refactor input checking. --- src/lib/crypto/nano-nacl.ts | 125 ++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 56 deletions(-) diff --git a/src/lib/crypto/nano-nacl.ts b/src/lib/crypto/nano-nacl.ts index 104e5af..a942801 100644 --- a/src/lib/crypto/nano-nacl.ts +++ b/src/lib/crypto/nano-nacl.ts @@ -443,25 +443,6 @@ export class NanoNaCl { return n } - /** - * Type-checks arbitrary number of arguments to verify whether they are all - * Uint8Array objects. - * - * @param {object} args - Key-value pairs of arguments to be checked - * @throws {TypeError} If any argument is not a Uint8Array - */ - static #checkArrayTypes (args: { [i: string]: unknown }): asserts args is { [i: string]: Uint8Array } { - for (const arg of Object.keys(args)) { - if (typeof args[arg] !== 'object') { - throw new TypeError(`Invalid input, expected Uint8Array, actual ${typeof arg}`) - } - const obj = args[arg] as { [key: string]: unknown } - if (!(obj instanceof Uint8Array)) { - throw new TypeError(`Invalid input, expected Uint8Array, actual ${obj.constructor?.name ?? typeof arg}`) - } - } - } - /** * Derives a public key from a private key "seed". * @@ -471,12 +452,14 @@ export class NanoNaCl { static convert (seed: Uint8Array): Uint8Array static convert (seed: unknown): Uint8Array { try { - const args: { [i: string]: unknown } = { s: seed } - this.#checkArrayTypes(args) - const { s } = args - if (s.length !== this.crypto_sign_SEEDBYTES) { - throw new Error('Invalid seed size to convert to public key') + if (!(seed instanceof Uint8Array)) { + throw new TypeError('Seed must be Uint8Array') + } + if (seed.byteLength !== this.crypto_sign_SEEDBYTES) { + throw new Error(`Seed must be ${this.crypto_sign_SEEDBYTES} bytes`) } + const s = new Uint8Array(seed) + seed = undefined const pk = new Uint8Array(this.crypto_sign_PUBLICKEYBYTES) const p: Float64Array[] = [new Float64Array(16), new Float64Array(16), new Float64Array(16), new Float64Array(16)] @@ -504,15 +487,21 @@ export class NanoNaCl { static detached (message: Uint8Array, privateKey: Uint8Array): Uint8Array static detached (message: unknown, privateKey: unknown): Uint8Array { try { - const args: { [i: string]: unknown } = { msg: message, prv: privateKey } - this.#checkArrayTypes(args) - const { msg, prv } = args - const signed = this.sign(msg, prv) - const sig = new Uint8Array(this.crypto_sign_BYTES) - for (let i = 0; i < sig.length; i++) { - sig[i] = signed[i] + if (!(message instanceof Uint8Array)) { + throw new TypeError('Message must be Uint8Array') } - return sig + if (!(privateKey instanceof Uint8Array)) { + throw new TypeError('Private key must be Uint8Array') + } + const m = new Uint8Array(message) + const mLen = m.length + const prv = new Uint8Array(privateKey) + message = undefined + privateKey = undefined + const sm = new Uint8Array(this.crypto_sign_BYTES + mLen) + const pub = this.convert(prv) + this.crypto_sign(sm, m, mLen, prv, pub) + return new Uint8Array(sm.buffer.slice(0, this.crypto_sign_BYTES)) } catch (err) { throw new Error('Failed to sign and return signature', { cause: err }) } @@ -528,19 +517,24 @@ export class NanoNaCl { static open (signedMessage: Uint8Array, publicKey: Uint8Array): Uint8Array static open (signedMessage: unknown, publicKey: unknown): Uint8Array { try { - const args: { [i: string]: unknown } = { signed: signedMessage, pub: publicKey } - this.#checkArrayTypes(args) - const { signed, pub } = args - if (pub.byteLength !== this.crypto_sign_PUBLICKEYBYTES) { - throw new Error(`Invalid public key size to open message, expected ${this.crypto_sign_PUBLICKEYBYTES}, actual ${pub.byteLength}`) + if (!(signedMessage instanceof Uint8Array)) { + throw new TypeError('Signed message must be Uint8Array') } - const tmp = new Uint8Array(signed.length) - const mlen = this.crypto_sign_open(tmp, signed, signed.length, pub) - + if (!(publicKey instanceof Uint8Array)) { + throw new TypeError('Public key must be Uint8Array') + } + if (publicKey.byteLength !== this.crypto_sign_PUBLICKEYBYTES) { + throw new Error(`Public key must be ${this.crypto_sign_PUBLICKEYBYTES} bytes`) + } + const sm = new Uint8Array(signedMessage) + const pub = new Uint8Array(publicKey) + signedMessage = undefined + publicKey = undefined + const tmp = new Uint8Array(sm.length) + const mlen = this.crypto_sign_open(tmp, sm, sm.length, pub) if (mlen < 0) { throw new Error('Signature verification failed') } - const m = new Uint8Array(mlen) m.set(tmp.subarray(0, mlen), 0) return m @@ -559,12 +553,19 @@ export class NanoNaCl { static sign (message: Uint8Array, privateKey: Uint8Array): Uint8Array static sign (message: unknown, privateKey: unknown): Uint8Array { try { - const args: { [i: string]: unknown } = { msg: message, prv: privateKey } - this.#checkArrayTypes(args) - const { msg, prv } = args - if (prv.byteLength !== this.crypto_sign_PRIVATEKEYBYTES) { - throw new Error(`Invalid key byte length to sign message, expected ${this.crypto_sign_PRIVATEKEYBYTES}, actual ${prv.byteLength}`) + if (!(message instanceof Uint8Array)) { + throw new TypeError('Message must be Uint8Array') + } + if (!(privateKey instanceof Uint8Array)) { + throw new TypeError('Private key must be Uint8Array') } + if (privateKey.byteLength !== this.crypto_sign_SEEDBYTES) { + throw new Error(`Private key must be ${this.crypto_sign_PRIVATEKEYBYTES} bytes`) + } + const msg = new Uint8Array(message) + const prv = new Uint8Array(privateKey) + message = undefined + privateKey = undefined const signed = new Uint8Array(this.crypto_sign_BYTES + msg.length) const pub = this.convert(prv) this.crypto_sign(signed, msg, msg.length, prv, pub) @@ -577,23 +578,35 @@ export class NanoNaCl { /** * Verifies a detached signature for a message. * - * @param {Uint8Array} message - Signed message + * @param {Uint8Array} signedMessage - Signed message * @param {Uint8Array} signature - 64-byte signature * @param {Uint8Array} publicKey - 32-byte key used for signing * @returns {boolean} - True if `publicKey` was used to sign `msg` and generate `sig`, else false */ - static verify (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean - static verify (message: unknown, signature: unknown, publicKey: unknown): boolean { + static verify (signedMessage: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean + static verify (signedMessage: unknown, signature: unknown, publicKey: unknown): boolean { try { - const args: { [i: string]: unknown } = { msg: message, sig: signature, pub: publicKey } - this.#checkArrayTypes(args) - const { msg, sig, pub } = args - if (sig.length !== this.crypto_sign_BYTES) { - throw new Error('Invalid signature size to verify signature') + if (!(signedMessage instanceof Uint8Array)) { + throw new TypeError('Message must be Uint8Array') + } + if (!(signature instanceof Uint8Array)) { + throw new TypeError('Signature must be Uint8Array') + } + if (!(publicKey instanceof Uint8Array)) { + throw new TypeError('Public key must be Uint8Array') + } + if (signature.byteLength !== this.crypto_sign_BYTES) { + throw new Error(`Signature must be ${this.crypto_sign_BYTES} bytes`) } - if (pub.length !== this.crypto_sign_PUBLICKEYBYTES) { - throw new Error('Invalid public key size to verify signature') + if (publicKey.byteLength !== this.crypto_sign_PUBLICKEYBYTES) { + throw new Error(`Public key must be ${this.crypto_sign_PUBLICKEYBYTES} bytes`) } + const msg = new Uint8Array(signedMessage) + const sig = new Uint8Array(signature) + const pub = new Uint8Array(publicKey) + signedMessage = undefined + signature = undefined + publicKey = undefined const sm = new Uint8Array(this.crypto_sign_BYTES + msg.length) const m = new Uint8Array(this.crypto_sign_BYTES + msg.length) sm.set(sig, 0) -- 2.47.3