From: Chris Duncan Date: Fri, 5 Dec 2025 21:29:13 +0000 (-0800) Subject: Extract more point functions to static members. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=a3a9857db0c0f0bc86a3abba53eca1d2157e9706;p=libnemo.git Extract more point functions to static members. --- diff --git a/src/lib/crypto/secp256k1.ts b/src/lib/crypto/secp256k1.ts index e379e9e..8f72788 100644 --- a/src/lib/crypto/secp256k1.ts +++ b/src/lib/crypto/secp256k1.ts @@ -15,11 +15,7 @@ type Point = { X: bigint Y: bigint Z: bigint - equals: (other: Point) => boolean - negate: () => Point - double: () => Point toAffine: () => AffinePoint - assertValidity: () => Point toBytes: (isCompressed?: boolean) => Bytes } @@ -212,6 +208,27 @@ export class Secp256k1 { return this.Point(X3, Y3, Z3) } + /** Point doubling: P+P */ + static Double (p: Point) { + return this.Add(p, p) + } + + /** Equality check: compare points P&Q. */ + static Equals (p: Point, q: Point): boolean { + const { X: X1, Y: Y1, Z: Z1 } = p + const { X: X2, Y: Y2, Z: Z2 } = q + const X1Z2 = this.M(X1 * Z2) + const X2Z1 = this.M(X2 * Z1) + const Y1Z2 = this.M(Y1 * Z2) + const Y2Z1 = this.M(Y2 * Z1) + return X1Z2 === X2Z1 && Y1Z2 === Y2Z1 + } + + /** Flip point over y coordinate. */ + static Negate (p: Point) { + return this.Point(p.X, this.M(-p.Y), p.Z) + } + /** Point in 3d xyz projective coordinates. 3d takes less inversions than 2d. */ static Point (X: bigint, Y: bigint, Z: bigint): Point { const secp256k1 = this @@ -219,29 +236,11 @@ export class Secp256k1 { X: this.bigintInRange(X, 0n, this.P), Y: this.bigintInRange(Y, 1n, this.P), // Y can't be 0 in Projective Z: this.bigintInRange(Z, 0n, this.P), - /** Equality check: compare points P&Q. */ - equals (other: Point): boolean { - const { X: X1, Y: Y1, Z: Z1 } = this - const { X: X2, Y: Y2, Z: Z2 } = other - const X1Z2 = secp256k1.M(X1 * Z2) - const X2Z1 = secp256k1.M(X2 * Z1) - const Y1Z2 = secp256k1.M(Y1 * Z2) - const Y2Z1 = secp256k1.M(Y2 * Z1) - return X1Z2 === X2Z1 && Y1Z2 === Y2Z1 - }, - /** Flip point over y coordinate. */ - negate (): Point { - return secp256k1.Point(X, secp256k1.M(-Y), Z) - }, - /** Point doubling: P+P, complete formula. */ - double (): Point { - return secp256k1.Add(this, this) - }, /** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */ toAffine (): AffinePoint { const { X: x, Y: y, Z: z } = this // fast-paths for ZERO point OR Z=1 - if (this.equals(secp256k1.I)) return { x: 0n, y: 0n } + if (secp256k1.Equals(this, secp256k1.I)) return { x: 0n, y: 0n } if (z === 1n) return { x, y } const iz = secp256k1.invert(z) // (Z * Z^-1) must be 1, otherwise bad math @@ -249,17 +248,9 @@ export class Secp256k1 { // x = X*Z^-1; y = Y*Z^-1 return { x: secp256k1.M(x * iz), y: secp256k1.M(y * iz) } }, - /** Checks if the point is valid and on-curve. */ - assertValidity (): Point { - const { x, y } = this.toAffine() // convert to 2d xy affine point. - secp256k1.bigintInRange(x, 1n, secp256k1.P) // must be in range 1 <= x,y < P - secp256k1.bigintInRange(y, 1n, secp256k1.P) - // y² == x³ + ax + b, equation sides must be equal - return secp256k1.M(y * y) === secp256k1.koblitz(x) ? this : secp256k1.err('bad point: not on curve') - }, /** Converts point to 33/65-byte Uint8Array. */ toBytes (isCompressed = true): Bytes { - const { x, y } = this.assertValidity().toAffine() + const { x, y } = secp256k1.isValidPoint(this).toAffine() const x32b = secp256k1.bigintTo32Bytes(x) if (isCompressed) return secp256k1.concatBytes(secp256k1.getPrefix(y), x32b) return secp256k1.concatBytes(Uint8Array.of(0x04), x32b, secp256k1.bigintTo32Bytes(y)) @@ -288,7 +279,7 @@ export class Secp256k1 { // Uncompressed 65-byte point, 0x04 prefix if (length === this.pkLength.uncompressed && head === 0x04) p = this.Point(x, this.bytesToBigint(tail.subarray(this.L, this.L2)), 1n) // Validate point - return p ? p.assertValidity() : this.err('bad point: not on curve') + return this.isValidPoint(p) } /** Generator / base point */ @@ -316,6 +307,18 @@ export class Secp256k1 { return bytes } + /** Checks if the point is valid and on-curve. */ + static isValidPoint (p?: Point): Point { + if (p) { + const { x, y } = p.toAffine() // convert to 2d xy affine point. + this.bigintInRange(x, 1n, this.P) // must be in range 1 <= x,y < P + this.bigintInRange(y, 1n, this.P) + // y² == x³ + ax + b, equation sides must be equal + return this.M(y * y) === this.koblitz(x) ? p : this.err('bad point: not on curve') + } + this.err('bad point: undefined') + } + /** Normalize private key to scalar (bigint). Verifies scalar is in range 1