]> git.codecow.com Git - libnemo.git/commitdiff
Extract more point functions to static members.
authorChris Duncan <chris@zoso.dev>
Fri, 5 Dec 2025 21:29:13 +0000 (13:29 -0800)
committerChris Duncan <chris@zoso.dev>
Fri, 5 Dec 2025 21:29:13 +0000 (13:29 -0800)
src/lib/crypto/secp256k1.ts

index e379e9e04c4236852844cfbc9a42a9c2cc91ca1d..8f72788b940fe286bb94789749a2cd6a2b32ff63 100644 (file)
@@ -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<s<N */
        static secretKeyToScalar (sk: Bytes): bigint {
                const num = this.bytesToBigint(this.abytes(sk, this.L, 'secret key'))
@@ -364,13 +367,13 @@ export class Secp256k1 {
                                b = this.Add(b, p)
                                points.push(b)
                        } // i=1, bc we skip 0
-                       p = b.double()
+                       p = this.Double(b)
                }
                return points
        }
        // const-time negate
        static ctneg (cnd: boolean, p: Point): Point {
-               const n = p.negate()
+               const n = this.Negate(p)
                return cnd ? n : p
        }
        static wNAF (n: bigint): { p: Point; f: Point } {