From: Chris Duncan Date: Sun, 30 Nov 2025 07:53:58 +0000 (-0800) Subject: Progress on classification. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=a2d0173659606b57146c44e0a2552da05b0682cd;p=libnemo.git Progress on classification. --- diff --git a/src/lib/crypto/secp256k1.ts b/src/lib/crypto/secp256k1.ts index 254ab81..d928ce8 100644 --- a/src/lib/crypto/secp256k1.ts +++ b/src/lib/crypto/secp256k1.ts @@ -1,9 +1,5 @@ /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ -import { Point, Signature, getPublicKey, hashes } from "@noble/secp256k1" -import { randomBytes } from "crypto" -import { _verify } from "../wallet/verify" - /** * 5KB JS implementation of secp256k1 ECDSA / Schnorr signatures & ECDH. * Compliant with RFC6979 & BIP340. @@ -277,15 +273,15 @@ export class Secp256k1 { return Secp256k1.secp256k1_CURVE } /** Create 3d xyz point from 2d xy. (0, 0) => (0, 1, 0), not (0, 0, 1) */ - static fromAffine (ap: AffinePoint): Point { + static fromAffine (ap: AffinePoint): Secp256k1['Point'] { const { x, y } = ap return x === 0n && y === 0n ? Secp256k1.I : new Secp256k1.Point(x, y, 1n) } /** Convert Uint8Array or hex string to Point. */ - static fromBytes (bytes: Bytes): Point { + static fromBytes (bytes: Bytes): Secp256k1['Point'] { Secp256k1.abytes(bytes) const { publicKey: comp, publicKeyUncompressed: uncomp } = Secp256k1.lengths // e.g. for 32-byte: 33, 65 - let p: Point | undefined = undefined + let p: Secp256k1['Point'] | undefined = undefined const length = bytes.length const head = bytes[0] const tail = bytes.subarray(1) @@ -298,14 +294,14 @@ export class Secp256k1 { const evenY = Secp256k1.isEven(y) const evenH = Secp256k1.isEven(Secp256k1.big(head)) if (evenH !== evenY) y = Secp256k1.M(-y) - p = new Point(x, y, 1n) + p = new this(x, y, 1n) } // Uncompressed 65-byte point, 0x04 prefix - if (length === uncomp && head === 0x04) p = new Point(x, Secp256k1.sliceBytesNumBE(tail, Secp256k1.L, Secp256k1.L2), 1n) + if (length === uncomp && head === 0x04) p = new this(x, Secp256k1.sliceBytesNumBE(tail, Secp256k1.L, Secp256k1.L2), 1n) // Validate point return p ? p.assertValidity() : Secp256k1.err('bad point: not on curve') } - static fromHex (hex: string): Point { + static fromHex (hex: string): Secp256k1['Point'] { return Secp256k1.Point.fromBytes(Secp256k1.hexToBytes(hex)) } get x (): bigint { @@ -315,7 +311,7 @@ export class Secp256k1 { return this.toAffine().y } /** Equality check: compare points P&Q. */ - equals (other: Point): boolean { + equals (other: Secp256k1['Point']): boolean { const { X: X1, Y: Y1, Z: Z1 } = this const { X: X2, Y: Y2, Z: Z2 } = Secp256k1.apoint(other) // checks class equality const X1Z2 = Secp256k1.M(X1 * Z2) @@ -328,11 +324,11 @@ export class Secp256k1 { return this.equals(Secp256k1.I) } /** Flip point over y coordinate. */ - negate (): Point { - return new Point(this.X, Secp256k1.M(-this.Y), this.Z) + negate (): Secp256k1['Point'] { + return new Secp256k1.Point(this.X, Secp256k1.M(-this.Y), this.Z) } /** Point doubling: P+P, complete formula. */ - double (): Point { + double (): Secp256k1['Point'] { return this.add(this) } /** @@ -341,7 +337,7 @@ export class Secp256k1 { * Cost: `12M + 0S + 3*a + 3*b3 + 23add`. */ // prettier-ignore - add (other: Point): Point { + add (other: Secp256k1['Point']): Secp256k1['Point'] { const { X: X1, Y: Y1, Z: Z1 } = this const { X: X2, Y: Y2, Z: Z2 } = Secp256k1.apoint(other) const a = 0n @@ -364,9 +360,9 @@ export class Secp256k1 { t0 = Secp256k1.M(t5 * t4) // step 35 X3 = Secp256k1.M(t3 * X3); X3 = Secp256k1.M(X3 - t0); t0 = Secp256k1.M(t3 * t1); Z3 = Secp256k1.M(t5 * Z3) Z3 = Secp256k1.M(Z3 + t0) // step 40 - return new Point(X3, Y3, Z3) + return new Secp256k1.Point(X3, Y3, Z3) } - subtract (other: Point): Point { + subtract (other: Secp256k1['Point']): Secp256k1['Point'] { return this.add(Secp256k1.apoint(other).negate()) } /** @@ -376,15 +372,15 @@ export class Secp256k1 { * @param n scalar by which point is multiplied * @param safe safe mode guards against timing attacks; unsafe mode is faster */ - multiply (n: bigint, safe = true): Point { + multiply (n: bigint, safe = true): Secp256k1['Point'] { if (!safe && n === 0n) return Secp256k1.I Secp256k1.FnIsValidNot0(n) if (n === 1n) return this - if (this.equals(Secp256k1.G)) return wNAF(n).p + if (this.equals(Secp256k1.G)) return Secp256k1.wNAF(n).p // init result point & fake point let p = Secp256k1.I let f = Secp256k1.G - for (let d: Point = this; n > 0n; d = d.double(), n >>= 1n) { + for (let d: Secp256k1['Point'] = this; n > 0n; d = d.double(), n >>= 1n) { // if bit is present, add to point // if not present, add to fake, for timing safety if (n & 1n) p = p.add(d) @@ -392,7 +388,7 @@ export class Secp256k1 { } return p } - multiplyUnsafe (scalar: bigint): Point { + multiplyUnsafe (scalar: bigint): Secp256k1['Point'] { return this.multiply(scalar, false) } /** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */ @@ -408,7 +404,7 @@ export class Secp256k1 { return { x: Secp256k1.M(x * iz), y: Secp256k1.M(y * iz) } } /** Checks if the point is valid and on-curve. */ - assertValidity (): Point { + assertValidity (): Secp256k1['Point'] { const { x, y } = this.toAffine() // convert to 2d xy affine point. Secp256k1.FpIsValidNot0(x) // must be in range 1 <= x,y < P Secp256k1.FpIsValidNot0(y) @@ -427,17 +423,18 @@ export class Secp256k1 { return Secp256k1.bytesToHex(this.toBytes(isCompressed)) } } + /** Generator / base point */ - static G: Point = new Point(this.Gx, this.Gy, 1n) + static G: Secp256k1['Point'] = new this.Point(this.Gx, this.Gy, 1n) /** Identity / zero point */ - static I: Point = new Point(0n, 1n, 0n) + static I: Secp256k1['Point'] = new this.Point(0n, 1n, 0n) // Static aliases static { this.Point.BASE = this.G this.Point.ZERO = this.I } /** `Q = u1⋅G + u2⋅R`. Verifies Q is not ZERO. Unsafe: non-CT. */ - static doubleScalarMulUns = (R: Point, u1: bigint, u2: bigint): Point => { + static doubleScalarMulUns = (R: Secp256k1['Point'], u1: bigint, u2: bigint): Secp256k1['Point'] => { return this.G.multiply(u1, false).add(R.multiply(u2, false)).assertValidity() } static bytesToNumBE = (b: Bytes): bigint => this.big('0x' + (this.bytesToHex(b) || '0')) @@ -535,10 +532,10 @@ export class Secp256k1 { } const r = Secp256k1.sliceBytesNumBE(b, 0, Secp256k1.L) const s = Secp256k1.sliceBytesNumBE(b, Secp256k1.L, Secp256k1.L2) - return new Signature(r, s, rec) + return new this(r, s, rec) } addRecoveryBit (bit: number): RecoveredSignature { - return new Signature(this.r, this.s, bit) as RecoveredSignature + return new Secp256k1.Signature(this.r, this.s, bit) as RecoveredSignature } hasHighS (): boolean { return Secp256k1.highS(this.s) @@ -643,7 +640,7 @@ export class Secp256k1 { k.fill(0) } // h = hmac(K || V || ...) - const h = (...b: Bytes[]) => hashes.hmacSha256Async(k, this.concatBytes(v, ...b)) + const h = (...b: Bytes[]) => this.hashes.hmacSha256Async(k, this.concatBytes(v, ...b)) const reseed = async (seed = this.NULL) => { // HMAC-DRBG reseed() function. Steps D-G k = await h(this.byte0, seed) // k = hmac(K || V || 0x00 || seed) @@ -676,19 +673,19 @@ export class Secp256k1 { ): T => { let { lowS, extraEntropy } = opts // generates low-s sigs by default // RFC6979 3.2: we skip step A - const int2octets = numTo32b // int to octets - const h1i = bits2int_modN(messageHash) // msg bigint + const int2octets = this.numTo32b // int to octets + const h1i = this.bits2int_modN(messageHash) // msg bigint const h1o = int2octets(h1i) // msg octets - const d = secretKeyToScalar(secretKey) // validate private key, convert to bigint + const d = this.secretKeyToScalar(secretKey) // validate private key, convert to bigint const seedArgs = [int2octets(d), h1o] // Step D of RFC6979 3.2 /** RFC6979 3.6: additional k' (optional). See {@link ECDSAExtraEntropy}. */ if (extraEntropy != null && extraEntropy !== false) { // K = HMAC_K(V || 0x00 || int2octets(x) || bits2octets(h1) || k') // gen random bytes OR pass as-is - const e = extraEntropy === true ? randomBytes(L) : extraEntropy - seedArgs.push(abytes(e, undefined, 'extraEntropy')) // check for being bytes + const e = extraEntropy === true ? this.randomBytes(this.L) : extraEntropy + seedArgs.push(this.abytes(e, undefined, 'extraEntropy')) // check for being bytes } - const seed = concatBytes(...seedArgs) + const seed = this.concatBytes(...seedArgs) const m = h1i // convert msg to bigint // Converts signature params into point w r/s, checks result for validity. // To transform k => Signature: @@ -701,22 +698,22 @@ export class Secp256k1 { const k2sig = (kBytes: Bytes): Bytes | undefined => { // RFC 6979 Section 3.2, step 3: k = bits2int(T) // Important: all mod() calls here must be done over N - const k = bits2int(kBytes) - if (!(1n <= k && k < N)) return // Valid scalars (including k) must be in 1..N-1 - const ik = invert(k, N) // k^-1 mod n - const q = G.multiply(k).toAffine() // q = k⋅G - const r = modN(q.x) // r = q.x mod n + const k = this.bits2int(kBytes) + if (!(1n <= k && k < this.N)) return // Valid scalars (including k) must be in 1..N-1 + const ik = this.invert(k, this.N) // k^-1 mod n + const q = this.G.multiply(k).toAffine() // q = k⋅G + const r = this.modN(q.x) // r = q.x mod n if (r === 0n) return - const s = modN(ik * modN(m + r * d)) // s = k^-1(m + rd) mod n + const s = this.modN(ik * this.modN(m + r * d)) // s = k^-1(m + rd) mod n if (s === 0n) return let recovery = (q.x === r ? 0 : 2) | Number(q.y & 1n) // recovery bit (2 or 3, when q.x > n) let normS = s // normalized S - if (lowS && highS(s)) { + if (lowS && this.highS(s)) { // if lowS was passed, ensure s is always - normS = modN(-s) // in the bottom half of CURVE.n + normS = this.modN(-s) // in the bottom half of CURVE.n recovery ^= 1 } - const sig = new Signature(r, normS, recovery) as RecoveredSignature // use normS, not s + const sig = new this.Signature(r, normS, recovery) as RecoveredSignature // use normS, not s return sig.toBytes(opts.format) } return hmacDrbg(seed, k2sig) @@ -725,20 +722,20 @@ export class Secp256k1 { // Follows [SEC1](https://secg.org/sec1-v2.pdf) 4.1.4. static _verify = (sig: Bytes, messageHash: Bytes, publicKey: Bytes, opts: ECDSAVerifyOpts = {}) => { const { lowS, format } = opts - if (sig instanceof Signature) err('Signature must be in Uint8Array, use .toBytes()') - assertSigLength(sig, format) - abytes(publicKey, undefined, 'publicKey') + if (sig instanceof this.Signature) this.err('Signature must be in Uint8Array, use .toBytes()') + this.assertSigLength(sig, format) + this.abytes(publicKey, undefined, 'publicKey') try { - const { r, s } = Signature.fromBytes(sig, format) - const h = bits2int_modN(messageHash) // Truncate hash - const P = Point.fromBytes(publicKey) // Validate public key - if (lowS && highS(s)) return false // lowS bans sig.s >= CURVE.n/2 - const is = invert(s, N) // s^-1 - const u1 = modN(h * is) // u1 = hs^-1 mod n - const u2 = modN(r * is) // u2 = rs^-1 mod n - const R = doubleScalarMulUns(P, u1, u2).toAffine() // R = u1⋅G + u2⋅P + const { r, s } = this.Signature.fromBytes(sig, format) + const h = this.bits2int_modN(messageHash) // Truncate hash + const P = this.Point.fromBytes(publicKey) // Validate public key + if (lowS && this.highS(s)) return false // lowS bans sig.s >= CURVE.n/2 + const is = this.invert(s, this.N) // s^-1 + const u1 = this.modN(h * is) // u1 = hs^-1 mod n + const u2 = this.modN(r * is) // u2 = rs^-1 mod n + const R = this.doubleScalarMulUns(P, u1, u2).toAffine() // R = u1⋅G + u2⋅P // Stop if R is identity / zero point. Check is done inside `doubleScalarMulUns` - const v = modN(R.x) // R.x must be in N's field, not P's + const v = this.modN(R.x) // R.x must be in N's field, not P's return v === r // mod(R.x, n) == r } catch (error) { return false @@ -747,7 +744,7 @@ export class Secp256k1 { static setDefaults = (opts: ECDSASignOpts): Required => { const res: ECDSASignOpts = {} - Object.keys(defaultSignOpts).forEach((k: string) => { + Object.keys(this.defaultSignOpts).forEach((k: string) => { // @ts-ignore res[k] = opts[k] ?? defaultSignOpts[k] }) @@ -768,9 +765,9 @@ export class Secp256k1 { * ``` */ static sign = (message: Bytes, secretKey: Bytes, opts: ECDSASignOpts = {}): Bytes => { - opts = setDefaults(opts) - message = prepMsg(message, opts, false) as Bytes - return _sign(message, secretKey, opts, hmacDrbg) + opts = this.setDefaults(opts) + message = this.prepMsg(message, opts, false) as Bytes + return this._sign(message, secretKey, opts, this.hmacDrbg) } /** @@ -791,9 +788,9 @@ export class Secp256k1 { secretKey: Bytes, opts: ECDSASignOpts = {} ): Promise => { - opts = setDefaults(opts) - message = await prepMsg(message, opts, true) - return _sign(message, secretKey, opts, hmacDrbgAsync) + opts = this.setDefaults(opts) + message = await this.prepMsg(message, opts, true) + return this._sign(message, secretKey, opts, this.hmacDrbgAsync) } /** @@ -817,9 +814,9 @@ export class Secp256k1 { publicKey: Bytes, opts: ECDSAVerifyOpts = {} ): boolean => { - opts = setDefaults(opts) - message = prepMsg(message, opts, false) as Bytes - return _verify(signature, message, publicKey, opts) + opts = this.setDefaults(opts) + message = this.prepMsg(message, opts, false) as Bytes + return this._verify(signature, message, publicKey, opts) } /** @@ -843,27 +840,27 @@ export class Secp256k1 { publicKey: Bytes, opts: ECDSAVerifyOpts = {} ): Promise => { - opts = setDefaults(opts) - message = await prepMsg(message, opts, true) - return _verify(sig, message, publicKey, opts) + opts = this.setDefaults(opts) + message = await this.prepMsg(message, opts, true) + return this._verify(sig, message, publicKey, opts) } static _recover = (signature: Bytes, messageHash: Bytes) => { - const sig = Signature.fromBytes(signature, 'recovered') + const sig = this.Signature.fromBytes(signature, 'recovered') const { r, s, recovery } = sig // 0 or 1 recovery id determines sign of "y" coordinate. // 2 or 3 means q.x was >N. - assertRecoveryBit(recovery) - const h = bits2int_modN(abytes(messageHash, L)) // Truncate hash - const radj = recovery === 2 || recovery === 3 ? r + N : r - FpIsValidNot0(radj) // ensure q.x is still a field element - const head = getPrefix(big(recovery!)) // head is 0x02 or 0x03 - const Rb = concatBytes(head, numTo32b(radj)) // concat head + r - const R = Point.fromBytes(Rb) - const ir = invert(radj, N) // r^-1 - const u1 = modN(-h * ir) // -hr^-1 - const u2 = modN(s * ir) // sr^-1 - const point = doubleScalarMulUns(R, u1, u2) // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1) + this.assertRecoveryBit(recovery) + const h = this.bits2int_modN(this.abytes(messageHash, this.L)) // Truncate hash + const radj = recovery === 2 || recovery === 3 ? r + this.N : r + this.FpIsValidNot0(radj) // ensure q.x is still a field element + const head = this.getPrefix(this.big(recovery!)) // head is 0x02 or 0x03 + const Rb = this.concatBytes(head, this.numTo32b(radj)) // concat head + r + const R = this.Point.fromBytes(Rb) + const ir = this.invert(radj, this.N) // r^-1 + const u1 = this.modN(-h * ir) // -hr^-1 + const u2 = this.modN(s * ir) // sr^-1 + const point = this.doubleScalarMulUns(R, u1, u2) // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1) return point.toBytes() } @@ -872,8 +869,8 @@ export class Secp256k1 { * Follows [SEC1](https://secg.org/sec1-v2.pdf) 4.1.6. */ static recoverPublicKey = (signature: Bytes, message: Bytes, opts: ECDSARecoverOpts = {}): Bytes => { - message = prepMsg(message, setDefaults(opts), false) as Bytes - return _recover(signature, message) + message = this.prepMsg(message, this.setDefaults(opts), false) as Bytes + return this._recover(signature, message) } static recoverPublicKeyAsync = async ( @@ -881,8 +878,8 @@ export class Secp256k1 { message: Bytes, opts: ECDSARecoverOpts = {} ): Promise => { - message = await prepMsg(message, setDefaults(opts), true) - return _recover(signature, message) + message = await this.prepMsg(message, this.setDefaults(opts), true) + return this._recover(signature, message) } /** @@ -892,23 +889,23 @@ export class Secp256k1 { * @returns public key C */ static getSharedSecret = (secretKeyA: Bytes, publicKeyB: Bytes, isCompressed = true): Bytes => { - return Point.fromBytes(publicKeyB).multiply(secretKeyToScalar(secretKeyA)).toBytes(isCompressed) + return this.Point.fromBytes(publicKeyB).multiply(this.secretKeyToScalar(secretKeyA)).toBytes(isCompressed) } // FIPS 186 B.4.1 compliant key generation produces private keys // with modulo bias being neglible. takes >N+16 bytes, returns (hash mod n-1)+1 - static randomSecretKey = (seed = randomBytes(lengths.seed)) => { - abytes(seed) - if (seed.length < lengths.seed || seed.length > 1024) err('expected 40-1024b') - const num = M(bytesToNumBE(seed), N - 1n) - return numTo32b(num + 1n) + static randomSecretKey = (seed = this.randomBytes(this.lengths.seed)) => { + this.abytes(seed) + if (seed.length < this.lengths.seed || seed.length > 1024) this.err('expected 40-1024b') + const num = this.M(this.bytesToNumBE(seed), this.N - 1n) + return this.numTo32b(num + 1n) } static createKeygen = (getPublicKey: (secretKey: Bytes) => Bytes) => (seed?: Bytes): KeysSecPub => { const secretKey = this.randomSecretKey(seed) return { secretKey, publicKey: getPublicKey(secretKey) } } - static keygen: KeygenFn = this.createKeygen(getPublicKey) + static keygen: KeygenFn = this.createKeygen(this.getPublicKey) /** Math, hex, byte helpers. Not in `utils` because utils share API with noble-curves. */ static etc = { @@ -919,7 +916,7 @@ export class Secp256k1 { numberToBytesBE: this.numTo32b as (n: bigint) => Bytes, mod: this.M as (a: bigint, md?: bigint) => bigint, invert: this.invert as (num: bigint, md?: bigint) => bigint, // math utilities - randomBytes: randomBytes as (len?: number) => Bytes, + randomBytes: this.randomBytes as (len?: number) => Bytes, secretKeyToScalar: this.secretKeyToScalar as typeof this.secretKeyToScalar, abytes: this.abytes as typeof this.abytes, } @@ -938,57 +935,57 @@ export class Secp256k1 { static T_NONCE = 'nonce' static T_CHALLENGE = 'challenge' static taggedHash = (tag: string, ...messages: Bytes[]): Bytes => { - const fn = callHash('sha256') - const tagH = fn(getTag(tag)) - return fn(concatBytes(tagH, tagH, ...messages)) + const fn = this.callHash('sha256') + const tagH = fn(this.getTag(tag)) + return fn(this.concatBytes(tagH, tagH, ...messages)) } static taggedHashAsync = async (tag: string, ...messages: Bytes[]): Promise => { - const fn = hashes.sha256Async - const tagH = await fn(getTag(tag)) - return await fn(concatBytes(tagH, tagH, ...messages)) + const fn = this.hashes.sha256Async + const tagH = await fn(this.getTag(tag)) + return await fn(this.concatBytes(tagH, tagH, ...messages)) } // ECDSA compact points are 33-byte. Schnorr is 32: we strip first byte 0x02 or 0x03 // Calculate point, scalar and bytes static extpubSchnorr = (priv: Bytes) => { - const d_ = secretKeyToScalar(priv) - const p = G.multiply(d_) // P = d'⋅G; 0 < d' < n check is done inside + const d_ = this.secretKeyToScalar(priv) + const p = this.G.multiply(d_) // P = d'⋅G; 0 < d' < n check is done inside const { x, y } = p.assertValidity().toAffine() // validate Point is not at infinity - const d = isEven(y) ? d_ : modN(-d_) - const px = numTo32b(x) + const d = this.isEven(y) ? d_ : this.modN(-d_) + const px = this.numTo32b(x) return { d, px } } - static bytesModN = (bytes: Bytes) => modN(bytesToNumBE(bytes)) - static challenge = (...args: Bytes[]): bigint => bytesModN(taggedHash(T_CHALLENGE, ...args)) + static bytesModN = (bytes: Bytes) => this.modN(this.bytesToNumBE(bytes)) + static challenge = (...args: Bytes[]): bigint => this.bytesModN(this.taggedHash(this.T_CHALLENGE, ...args)) static challengeAsync = async (...args: Bytes[]): Promise => - bytesModN(await taggedHashAsync(T_CHALLENGE, ...args)) + this.bytesModN(await this.taggedHashAsync(this.T_CHALLENGE, ...args)) /** * Schnorr public key is just `x` coordinate of Point as per BIP340. */ static pubSchnorr = (secretKey: Bytes): Bytes => { - return extpubSchnorr(secretKey).px // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G) + return this.extpubSchnorr(secretKey).px // d'=int(sk). Fail if d'=0 or d'≥n. Ret bytes(d'⋅G) } - static keygenSchnorr: KeygenFn = createKeygen(pubSchnorr) + static keygenSchnorr: KeygenFn = this.createKeygen(this.pubSchnorr) // Common preparation fn for both sync and async signing static prepSigSchnorr = (message: Bytes, secretKey: Bytes, auxRand: Bytes) => { - const { px, d } = extpubSchnorr(secretKey) - return { m: abytes(message), px, d, a: abytes(auxRand, L) } + const { px, d } = this.extpubSchnorr(secretKey) + return { m: this.abytes(message), px, d, a: this.abytes(auxRand, this.L) } } static extractK = (rand: Bytes) => { - const k_ = bytesModN(rand) // Let k' = int(rand) mod n - if (k_ === 0n) err('sign failed: k is zero') // Fail if k' = 0. - const { px, d } = extpubSchnorr(numTo32b(k_)) // Let R = k'⋅G. + const k_ = this.bytesModN(rand) // Let k' = int(rand) mod n + if (k_ === 0n) this.err('sign failed: k is zero') // Fail if k' = 0. + const { px, d } = this.extpubSchnorr(this.numTo32b(k_)) // Let R = k'⋅G. return { rx: px, k: d } } // Common signature creation helper static createSigSchnorr = (k: bigint, px: Bytes, e: bigint, d: bigint): Bytes => { - return concatBytes(px, numTo32b(modN(k + e * d))) + return this.concatBytes(px, this.numTo32b(this.modN(k + e * d))) } static E_INVSIG = 'invalid signature produced' @@ -996,39 +993,39 @@ export class Secp256k1 { * Creates Schnorr signature as per BIP340. Verifies itself before returning anything. * auxRand is optional and is not the sole source of k generation: bad CSPRNG won't be dangerous. */ - static signSchnorr = (message: Bytes, secretKey: Bytes, auxRand: Bytes = randomBytes(L)): Bytes => { - const { m, px, d, a } = prepSigSchnorr(message, secretKey, auxRand) - const aux = taggedHash(T_AUX, a) + static signSchnorr = (message: Bytes, secretKey: Bytes, auxRand: Bytes = this.randomBytes(this.L)): Bytes => { + const { m, px, d, a } = this.prepSigSchnorr(message, secretKey, auxRand) + const aux = this.taggedHash(this.T_AUX, a) // Let t be the byte-wise xor of bytes(d) and hash/aux(a) - const t = numTo32b(d ^ bytesToNumBE(aux)) + const t = this.numTo32b(d ^ this.bytesToNumBE(aux)) // Let rand = hash/nonce(t || bytes(P) || m) - const rand = taggedHash(T_NONCE, t, px, m) - const { rx, k } = extractK(rand) + const rand = this.taggedHash(this.T_NONCE, t, px, m) + const { rx, k } = this.extractK(rand) // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n. - const e = challenge(rx, px, m) - const sig = createSigSchnorr(k, rx, e, d) + const e = this.challenge(rx, px, m) + const sig = this.createSigSchnorr(k, rx, e, d) // If Verify(bytes(P), m, sig) (see below) returns failure, abort - if (!verifySchnorr(sig, m, px)) err(E_INVSIG) + if (!this.verifySchnorr(sig, m, px)) this.err(this.E_INVSIG) return sig } static signSchnorrAsync = async ( message: Bytes, secretKey: Bytes, - auxRand: Bytes = randomBytes(L) + auxRand: Bytes = this.randomBytes(this.L) ): Promise => { - const { m, px, d, a } = prepSigSchnorr(message, secretKey, auxRand) - const aux = await taggedHashAsync(T_AUX, a) + const { m, px, d, a } = this.prepSigSchnorr(message, secretKey, auxRand) + const aux = await this.taggedHashAsync(this.T_AUX, a) // Let t be the byte-wise xor of bytes(d) and hash/aux(a) - const t = numTo32b(d ^ bytesToNumBE(aux)) + const t = this.numTo32b(d ^ this.bytesToNumBE(aux)) // Let rand = hash/nonce(t || bytes(P) || m) - const rand = await taggedHashAsync(T_NONCE, t, px, m) - const { rx, k } = extractK(rand) + const rand = await this.taggedHashAsync(this.T_NONCE, t, px, m) + const { rx, k } = this.extractK(rand) // Let e = int(hash/challenge(bytes(R) || bytes(P) || m)) mod n. - const e = await challengeAsync(rx, px, m) - const sig = createSigSchnorr(k, rx, e, d) + const e = await this.challengeAsync(rx, px, m) + const sig = this.createSigSchnorr(k, rx, e, d) // If Verify(bytes(P), m, sig) (see below) returns failure, abort - if (!(await verifySchnorrAsync(sig, m, px))) err(E_INVSIG) + if (!(await this.verifySchnorrAsync(sig, m, px))) this.err(this.E_INVSIG) return sig } @@ -1055,7 +1052,7 @@ export class Secp256k1 { const y_ = this.isEven(y) ? y : this.M(-y) // Return the unique point P such that x(P) = x and // y(P) = y if y mod 2 = 0 or y(P) = p-y otherwise. - const P_ = new Point(x, y_, 1n).assertValidity() + const P_ = new this.Point(x, y_, 1n).assertValidity() const px = this.numTo32b(P_.toAffine().x) // P = lift_x(int(pk)); fail if that fails const r = this.sliceBytesNumBE(sig, 0, this.L) // Let r = int(sig[0:32]); fail if r ≥ p. @@ -1107,7 +1104,7 @@ export class Secp256k1 { static pwindows = Math.ceil(this.scalarBits / this.W) + 1 // 33 for W=8, NOT 32 - see wNAF loop static pwindowSize = 2 ** (this.W - 1) // 128 for W=8 static precompute = () => { - const points: Point[] = [] + const points: Secp256k1['Point'][] = [] let p = this.G let b = p for (let w = 0; w < this.pwindows; w++) { @@ -1121,9 +1118,9 @@ export class Secp256k1 { } return points } - static Gpows: Point[] | undefined = undefined // precomputes for base point G + static Gpows: Secp256k1['Point'][] | undefined = undefined // precomputes for base point G // const-time negate - static ctneg = (cnd: boolean, p: Point) => { + static ctneg = (cnd: boolean, p: Secp256k1['Point']) => { const n = p.negate() return cnd ? n : p } @@ -1139,7 +1136,7 @@ export class Secp256k1 { * * !! Precomputes can be disabled by commenting-out call of the wNAF() inside Point#multiply(). */ - static wNAF = (n: bigint): { p: Point; f: Point } => { + static wNAF = (n: bigint): { p: Secp256k1['Point']; f: Secp256k1['Point'] } => { const comp = this.Gpows || (this.Gpows = this.precompute()) let p = this.I let f = this.G // f must be G, or could become I in the end