From 846802447090445e7eb3c1098cdb7120fb61833f Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 5 Dec 2025 14:07:51 -0800 Subject: [PATCH] Promote final point functions to static class members. --- src/lib/crypto/secp256k1.ts | 47 +++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/lib/crypto/secp256k1.ts b/src/lib/crypto/secp256k1.ts index 8f72788..50c0068 100644 --- a/src/lib/crypto/secp256k1.ts +++ b/src/lib/crypto/secp256k1.ts @@ -15,8 +15,6 @@ type Point = { X: bigint Y: bigint Z: bigint - toAffine: () => AffinePoint - toBytes: (isCompressed?: boolean) => Bytes } /** Alias to Uint8Array. */ @@ -208,6 +206,19 @@ export class Secp256k1 { return this.Point(X3, Y3, Z3) } + /** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */ + static Affine (p: Point): AffinePoint { + const { X: x, Y: y, Z: z } = p + // fast-paths for ZERO point OR Z=1 + if (this.Equals(p, this.I)) return { x: 0n, y: 0n } + if (z === 1n) return { x, y } + const iz = this.invert(z) + // (Z * Z^-1) must be 1, otherwise bad math + if (this.M(z * iz) !== 1n) this.err('inverse invalid') + // x = X*Z^-1; y = Y*Z^-1 + return { x: this.M(x * iz), y: this.M(y * iz) } + } + /** Point doubling: P+P */ static Double (p: Point) { return this.Add(p, p) @@ -236,25 +247,6 @@ 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), - /** 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 (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 - if (secp256k1.M(z * iz) !== 1n) secp256k1.err('inverse invalid') - // x = X*Z^-1; y = Y*Z^-1 - return { x: secp256k1.M(x * iz), y: secp256k1.M(y * iz) } - }, - /** Converts point to 33/65-byte Uint8Array. */ - toBytes (isCompressed = true): Bytes { - 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)) - } }) } @@ -282,6 +274,14 @@ export class Secp256k1 { return this.isValidPoint(p) } + /** Converts point to 33/65-byte Uint8Array. */ + static pointToBytes (p: Point, isCompressed = true): Bytes { + const { x, y } = this.Affine(this.isValidPoint(p)) + const x32b = this.bigintTo32Bytes(x) + if (isCompressed) return this.concatBytes(this.getPrefix(y), x32b) + return this.concatBytes(Uint8Array.of(0x04), x32b, this.bigintTo32Bytes(y)) + } + /** Generator / base point */ static G: Point = this.Point(this.Gx, this.Gy, 1n) /** Identity / zero point */ @@ -310,7 +310,7 @@ export class Secp256k1 { /** 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. + const { x, y } = this.Affine(p) // 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 @@ -327,7 +327,8 @@ export class Secp256k1 { /** Creates 33/65-byte public key from 32-byte private key. */ static getPublicKey (sk: Bytes, isCompressed = true): Bytes { - return this.wNAF(this.secretKeyToScalar(sk)).p.toBytes(isCompressed) + const pk = this.pointToBytes(this.wNAF(this.secretKeyToScalar(sk)).p, isCompressed) + return this.isValidPublicKey(pk) ? pk : this.err('derived invalid public key from secret key') } static isValidPublicKey (pk: Bytes, isCompressed?: boolean): boolean { -- 2.47.3