]> git.codecow.com Git - libnemo.git/commitdiff
Refactor unused functions and combine where possible.
authorChris Duncan <chris@zoso.dev>
Fri, 5 Dec 2025 22:43:19 +0000 (14:43 -0800)
committerChris Duncan <chris@zoso.dev>
Fri, 5 Dec 2025 22:43:19 +0000 (14:43 -0800)
src/lib/crypto/secp256k1.ts

index f5d8ad8a30ad4e97b55d8f4aa0d780d3e88fe2ad..2daef4898a1cff16fc9541322381b67ece575f05 100644 (file)
@@ -48,6 +48,11 @@ export class Secp256k1 {
        static L: 32 = 32 // field / group byte length
        static C: 33 = 33 // byte length of SEC1 compressed form of coordinate pair
 
+       /** Generator / base point */
+       static G: Point = this.Point(this.Gx, this.Gy, 1n)
+       /** Identity / zero point */
+       static I: Point = this.Point(0n, 1n, 0n)
+
        // ## Helpers
        // ----------
        static err (message = ''): never {
@@ -72,21 +77,6 @@ export class Secp256k1 {
                return value
        }
 
-       static hexToBytes (hex: string): Bytes {
-               if (!/[0-9A-Fa-f]+/.test(hex ?? '')) return this.err('hex invalid')
-               if (hex.length % 2) hex = `0${hex}`
-               const intArray = hex.match(/.{2}/g)?.map(v => parseInt(v, 16))
-               return new Uint8Array(intArray ?? [])
-       }
-
-       // prettier-ignore
-       static concatBytes (...arrs: Bytes[]): Bytes {
-               const r = new Uint8Array(arrs.reduce((sum, a) => sum + this.abytes(a).length, 0)) // create u8a of summed length
-               let pad = 0 // walk through each array,
-               arrs.forEach(a => { r.set(a, pad); pad += a.length }) // ensure they have proper type
-               return r
-       }
-
        static bigintInRange (n: bigint, min: bigint, max: bigint, msg?: string): bigint {
                return typeof n === 'bigint' && min <= n && n < max
                        ? n
@@ -95,8 +85,8 @@ export class Secp256k1 {
 
        /** modular division */
        static M (a: bigint): bigint {
-               const r = a % this.P
-               return r >= 0n ? r : this.P + r
+               const p = this.P, r = a % p
+               return r >= 0n ? r : p + r
        }
 
        /** Modular inversion using eucledian GCD (non-CT). No negative exponent for now. */
@@ -118,7 +108,6 @@ export class Secp256k1 {
        /** secp256k1 formula. Koblitz curves are subclass of weierstrass curves with a=0, making it x³+b */
        static koblitz = (x: bigint): bigint => this.M(this.M(x * x) * x + this.b)
        static isEven = (y: bigint): boolean => (y & 1n) === 0n
-       static getPrefix = (y: bigint) => Uint8Array.of(this.isEven(y) ? 0x02 : 0x03)
        /** lift_x from BIP340 calculates square root. Validates x, then validates root*root. */
        static lift_x (x: bigint): bigint {
                // Let c = x³ + 7 mod p. Fail if x ≥ p. (also fail if x < 1)
@@ -144,7 +133,6 @@ export class Secp256k1 {
        static Add (p: Point, q: Point): Point {
                const { X: X1, Y: Y1, Z: Z1 } = p
                const { X: X2, Y: Y2, Z: Z2 } = q
-               const P = this.P
                const a = this.a
                const b3 = this.M(this.b * 3n)
                let X3 = 0n, Y3 = 0n, Z3 = 0n
@@ -227,7 +215,6 @@ export class Secp256k1 {
 
        /** Point in 3d xyz projective coordinates. 3d takes less inversions than 2d. */
        static Point (X: bigint, Y: bigint, Z: bigint): Point {
-               const secp256k1 = this
                return Object.freeze({
                        X: this.bigintInRange(X, 0n, this.P),
                        Y: this.bigintInRange(Y, 1n, this.P), // Y can't be 0 in Projective
@@ -235,40 +222,6 @@ export class Secp256k1 {
                })
        }
 
-       /** Convert Uint8Array or hex string to Point. */
-       static pointFromBytes (bytes: Bytes): Point {
-               this.abytes(bytes)
-               let p: Point | undefined = undefined
-               const length = bytes.length
-               const head = bytes[0]
-               const tail = bytes.subarray(1)
-               const x = this.bytesToBigint(tail.subarray(0, this.L))
-               // No actual validation is done here: use .assertValidity()
-               if (length === this.C && (head === 0x02 || head === 0x03)) {
-                       // Equation is y² == x³ + ax + b. We calculate y from x.
-                       // y = √y²; there are two solutions: y, -y. Determine proper solution based on prefix
-                       let y = this.lift_x(x)
-                       const evenY = this.isEven(y)
-                       const evenH = this.isEven(BigInt(head))
-                       if (evenH !== evenY) y = this.M(-y)
-                       p = this.Point(x, y, 1n)
-               }
-               // Validate point
-               return this.isValidPoint(p)
-       }
-
-       /** Converts point to 33/65-byte Uint8Array. */
-       static pointToBytes (p: Point): Bytes {
-               const { x, y } = this.Affine(this.isValidPoint(p))
-               const x32b = this.bigintTo32Bytes(x)
-               return this.concatBytes(this.getPrefix(y), x32b)
-       }
-
-       /** Generator / base point */
-       static G: Point = this.Point(this.Gx, this.Gy, 1n)
-       /** Identity / zero point */
-       static I: Point = this.Point(0n, 1n, 0n)
-
        static bytesToBigint (b: Bytes): bigint {
                let int = BigInt(b[0]), len = b.length
                for (let i = 1; i < len; i++) {
@@ -307,17 +260,42 @@ export class Secp256k1 {
                return this.bigintInRange(num, 1n, this.N, 'invalid secret key: outside of range')
        }
 
-       /** Creates 33/65-byte public key from 32-byte private key. */
+       /** Derives a compressed 33-byte public key from a 32-byte private key. */
        static getPublicKey (sk: Bytes): Bytes {
-               const pk = this.pointToBytes(this.wNAF(this.secretKeyToScalar(sk)).p)
+               const p = this.wNAF(this.secretKeyToScalar(sk)).p
+               let { x, y } = this.Affine(this.isValidPoint(p))
+               const len = this.C
+               const pk = new Uint8Array(len)
+               for (let i = len - 1; i >= 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
                try {
-                       const l = pk.length
-                       if (l !== this.C) return false
-                       return !!this.pointFromBytes(pk)
+                       this.abytes(pk)
+                       let p: Point | undefined = undefined
+                       const length = pk.length
+                       const head = pk[0]
+                       const tail = pk.subarray(1)
+                       const x = this.bytesToBigint(tail.subarray(0, this.L))
+                       // No actual validation is done here until calling isValidPoint()
+                       if (length === this.C && (head === 0x02 || head === 0x03)) {
+                               // Equation is y² == x³ + ax + b. We calculate y from x.
+                               // y = √y²; there are two solutions: y, -y. Determine proper solution based on prefix
+                               let y = this.lift_x(x)
+                               const evenY = this.isEven(y)
+                               const evenH = this.isEven(BigInt(head))
+                               if (evenH !== evenY) y = this.M(-y)
+                               p = this.Point(x, y, 1n)
+                       }
+                       // Validate point
+                       return !!this.isValidPoint(p)
                } catch (error) {
                        return false
                }