From: Chris Duncan Date: Sun, 7 Dec 2025 00:42:16 +0000 (-0800) Subject: Refactor error handling. Refactor byte array type assertions. Reorganize method order... X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=a49044e4a521a7884b8943c1c98148a5def3dbc0;p=libnemo.git Refactor error handling. Refactor byte array type assertions. Reorganize method ordering. Allow getting public key from bigint secret key. --- diff --git a/src/lib/crypto/secp256k1.ts b/src/lib/crypto/secp256k1.ts index 1bb198d..cb78163 100644 --- a/src/lib/crypto/secp256k1.ts +++ b/src/lib/crypto/secp256k1.ts @@ -55,32 +55,22 @@ export class Secp256k1 { // ## Helpers // ---------- - static err (message = ''): never { - const e = new Error(message) + static err (message: string, expected?: string, actual?: string): never { + const e = new Error(message, { cause: { expected, actual } }) Error.captureStackTrace?.(e, this.err) throw e } - /** Asserts something is Uint8Array. */ - static abytes (value: unknown, length?: number, title: string = ''): Bytes { - function isUint8Array (a: unknown): a is Bytes { - return (a instanceof Uint8Array && a.buffer instanceof ArrayBuffer) || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array') - } - const isBytes = isUint8Array(value) - const needsLen = length !== undefined - if (!isBytes || (needsLen && value.length !== length)) { - const prefix = title && `"${title}" ` - const ofLength = needsLen ? ` of length ${length}` : '' - const actual = isBytes ? `length=${value.length}` : `type=${typeof value}` - this.err(prefix + 'expected Uint8Array' + ofLength + ', got ' + actual) - } - return value + /** Typechecks if a value is a Uint8Array. */ + static isBytes (value: unknown): value is Bytes { + return (value instanceof Uint8Array && value.buffer instanceof ArrayBuffer) + || (ArrayBuffer.isView(value) && value.constructor.name === 'Uint8Array') } static bigintInRange (n: bigint, min: bigint, max: bigint, msg?: string): bigint { - return typeof n === 'bigint' && min <= n && n < max - ? n - : this.err(msg ?? 'bigint out of range') + return n < min || max <= n + ? this.err(`${msg ?? 'bigint'} out of range`, `between ${min} and ${max - 1n}`, `${n}`) + : n } /** modular division */ @@ -222,6 +212,15 @@ export class Secp256k1 { }) } + static bigintToLBytes (b: bigint): Bytes { + const bytes = new Uint8Array(this.L) + for (let i = bytes.byteLength - 1; i >= 0; i--) { + bytes[i] = Number(b & 0xffn) + b >>= 8n + } + return bytes + } + static bytesToBigint (b: Bytes): bigint { let int = BigInt(b[0]), len = b.length for (let i = 1; i < len; i++) { @@ -240,48 +239,6 @@ export class Secp256k1 { return this.M(y * y) === this.koblitz(x) ? p : this.err('bad point: not on curve') } - /** Normalize private key to scalar (bigint). Verifies scalar is in range 1= 1; i--) { - pk[i] = Number(x & 0xffn) - x >>= 8n - } - pk[0] = this.isEven(y) ? 0x02 : 0x03 - return this.isValidPublicKey(pk) ? pk : this.err('derived invalid public key from secret key') - } - - /** Converts point to 33-byte Uint8Array and checks validity. */ - static isValidPublicKey (pk: Bytes): boolean { - if (pk.length !== this.C) return false - const prefix = pk[0] - if (prefix !== 0x02 && prefix !== 0x03) return false - const evenH = prefix === 0x02 - try { - this.abytes(pk) - const x = this.bytesToBigint(pk.subarray(1)) - // Equation is y² == x³ + ax + b. We calculate y from x. - // y = √y²; there are two solutions: y, -y. Determine proper solution based on SEC1 prefix - let y = this.lift_x(x) - const evenY = this.isEven(y) - if (evenH !== evenY) y = this.M(-y) - const p = this.Point(x, y, 1n) - // Validate point - return !!this.isValidPoint(p) - } catch (error) { - return false - } - } - /** * Precomputes give 12x faster getPublicKey(), 10x sign(), 2x verify() by * caching multiples of G (base point). Cache is stored in 32MB of RAM. @@ -352,4 +309,68 @@ export class Secp256k1 { if (n !== 0n) this.err('invalid wnaf') return { p, f } // return both real and fake points for JIT } + + /** + * Normalize private key to scalar (bigint). + * Verifies scalar is in range 0