From: Chris Duncan Date: Tue, 2 Dec 2025 23:14:43 +0000 (-0800) Subject: Adjust more names and function signatures. Deprecate CSPRNG wrapper and use API directly. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=1db9aaf596876dbc2e4a300127544cf7994551a8;p=libnemo.git Adjust more names and function signatures. Deprecate CSPRNG wrapper and use API directly. --- diff --git a/src/lib/crypto/secp256k1.ts b/src/lib/crypto/secp256k1.ts index d468540..64c83c2 100644 --- a/src/lib/crypto/secp256k1.ts +++ b/src/lib/crypto/secp256k1.ts @@ -173,10 +173,10 @@ export class Secp256k1 { // ASCII characters static C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 } as const - static _ch (ch: number): number | undefined { - if (ch >= this.C._0 && ch <= this.C._9) return ch - this.C._0 // '2' => 50-48 - if (ch >= this.C.A && ch <= this.C.F) return ch - (this.C.A - 10) // 'B' => 66-(65-10) - if (ch >= this.C.a && ch <= this.C.f) return ch - (this.C.a - 10) // 'b' => 98-(97-10) + static _char (char: number): number | undefined { + if (char >= this.C._0 && char <= this.C._9) return char - this.C._0 // '2' => 50-48 + if (char >= this.C.A && char <= this.C.F) return char - (this.C.A - 10) // 'B' => 66-(65-10) + if (char >= this.C.a && char <= this.C.f) return char - (this.C.a - 10) // 'b' => 98-(97-10) return } @@ -189,8 +189,8 @@ export class Secp256k1 { const array = new Uint8Array(al) for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { // treat each char as ASCII - const n1 = this._ch(hex.charCodeAt(hi)) // parse first char, multiply it by 16 - const n2 = this._ch(hex.charCodeAt(hi + 1)) // parse second char + const n1 = this._char(hex.charCodeAt(hi)) // parse first char, multiply it by 16 + const n2 = this._char(hex.charCodeAt(hi + 1)) // parse second char if (n1 === undefined || n2 === undefined) return this.err(e) array[ai] = n1 * 16 + n2 // example: 'A9' => 10*16 + 9 } @@ -205,15 +205,10 @@ export class Secp256k1 { return r } - /** WebCrypto OS-level CSPRNG (random number generator). Will throw when not available. */ - static randomBytes = (len: number = this.L): Bytes => { - return crypto.getRandomValues(new Uint8Array(len)) - } - - static arange (n: bigint, min: bigint, max: bigint, msg = 'bad number: out of range'): bigint { + static bigintInRange (n: bigint, min: bigint, max: bigint, msg?: string): bigint { return typeof n === 'bigint' && min <= n && n < max ? n - : this.err(msg) + : this.err(msg ?? 'bigint out of range') } /** modular division */ @@ -249,11 +244,11 @@ export class Secp256k1 { /** secp256k1 formula. Koblitz curves are subclass of weierstrass curves with a=0, making it x³+b */ static koblitz = (x: bigint) => this.M(this.M(x * x) * x + this._b) /** assert is element of field mod P (incl. 0) */ - static FpIsValid = (n: bigint) => this.arange(n, 0n, this.P) + static FpIsValid = (n: bigint) => this.bigintInRange(n, 0n, this.P) /** assert is element of field mod P (excl. 0) */ - static FpIsValidNot0 = (n: bigint) => this.arange(n, 1n, this.P) + static FpIsValidNot0 = (n: bigint) => this.bigintInRange(n, 1n, this.P) /** assert is element of field mod N (excl. 0) */ - static FnIsValidNot0 = (n: bigint) => this.arange(n, 1n, this.N) + static FnIsValidNot0 = (n: bigint) => this.bigintInRange(n, 1n, this.N) static isEven = (y: bigint) => (y & 1n) === 0n /** create Uint8Array of byte n */ static u8of = (n: number): Bytes => Uint8Array.of(n) @@ -449,7 +444,7 @@ export class Secp256k1 { /** Number to 32b. Must be 0 <= num < 2²⁵⁶. validate, pad, to bytes. */ static bigintTo32Bytes (num: bigint): Bytes { return this.hexToBytes(this - .arange(num, 0n, 2n ** 256n) // secp256k1 is weierstrass curve. Equation is x³ + ax + b. + .bigintInRange(num, 0n, 2n ** 256n) // secp256k1 is weierstrass curve. Equation is x³ + ax + b. .toString(16) .padStart(this.L2, '0') ) @@ -457,7 +452,7 @@ export class Secp256k1 { /** Normalize private key to scalar (bigint). Verifies scalar is in range 1 { const num = this.bytesToBigint(this.abytes(secretKey, this.L, 'secret key')) - return this.arange(num, 1n, this.N, 'invalid secret key: outside of range') + return this.bigintInRange(num, 1n, this.N, 'invalid secret key: outside of range') } /** For Signature malleability, validates sig.s is bigger than N/2. */ static highS = (n: bigint): boolean => n > this.N >> 1n @@ -549,14 +544,14 @@ export class Secp256k1 { static signatureFromBytes (b: Bytes, format: ECDSASignatureFormat = this.SIG_COMPACT): Signature { this.assertSigLength(b, format) - let rec: number | undefined + let recoveryBit: number | undefined if (format === this.SIG_RECOVERED) { - rec = b[0] + recoveryBit = b[0] b = b.subarray(1) } const r = this.sliceBytesNumBE(b, 0, this.L) const s = this.sliceBytesNumBE(b, this.L, this.L2) - return this.Signature(r, s, rec) + return this.Signature(r, s, recoveryBit) } /** @@ -581,19 +576,18 @@ export class Secp256k1 { extraEntropy: false, } - static _sha = 'SHA-256' static hashes = { hmacSha256Async: async (key: Bytes, message: Bytes): Promise => { const name = 'HMAC' - const k = await crypto.subtle.importKey('raw', key, { name, hash: { name: this._sha } }, false, ['sign']) + const k = await crypto.subtle.importKey('raw', key, { name, hash: { name: 'SHA-256' } }, false, ['sign']) return new Uint8Array(await crypto.subtle.sign(name, k, message)) }, hmacSha256: undefined as undefined | ((key: Bytes, message: Bytes) => Bytes), - sha256Async: async (msg: Bytes): Promise => new Uint8Array(await crypto.subtle.digest(this._sha, msg)), + sha256Async: async (msg: Bytes): Promise => new Uint8Array(await crypto.subtle.digest('SHA-256', msg)), sha256: undefined as undefined | ((message: Bytes) => Bytes), } - static prepMsg = (msg: Bytes, opts: ECDSARecoverOpts, async_: boolean): Bytes | Promise => { + static prepMsg (msg: Bytes, opts: ECDSARecoverOpts, async_: boolean): Bytes | Promise { this.abytes(msg, undefined, 'message') if (!opts.prehash) return msg return async_ ? this.hashes.sha256Async(msg) : this.callHash('sha256')(msg) @@ -689,7 +683,7 @@ export class Secp256k1 { 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 ? this.randomBytes(this.L) : extraEntropy + const e = extraEntropy === true ? crypto.getRandomValues(new Uint8Array(this.L)) : extraEntropy seedArgs.push(this.abytes(e, undefined, 'extraEntropy')) // check for being bytes } const seed = this.concatBytes(...seedArgs) @@ -901,7 +895,8 @@ export class Secp256k1 { // 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 = this.randomBytes(this.lengths.seed)) => { + static randomSecretKey (seed?: Bytes) { + seed ??= crypto.getRandomValues(new Uint8Array(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.bytesToBigint(seed), this.N - 1n) @@ -923,7 +918,6 @@ export class Secp256k1 { numberToBytesBE: this.bigintTo32Bytes 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: this.randomBytes as (len?: number) => Bytes, secretKeyToScalar: this.secretKeyToScalar as typeof this.secretKeyToScalar, abytes: this.abytes as typeof this.abytes, } @@ -1000,7 +994,8 @@ 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 = this.randomBytes(this.L)): Bytes => { + static signSchnorr = (message: Bytes, secretKey: Bytes, auxRand?: Bytes): Bytes => { + auxRand ??= crypto.getRandomValues(new Uint8Array(this.L)) 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) @@ -1016,11 +1011,8 @@ export class Secp256k1 { return sig } - static signSchnorrAsync = async ( - message: Bytes, - secretKey: Bytes, - auxRand: Bytes = this.randomBytes(this.L) - ): Promise => { + static async signSchnorrAsync (message: Bytes, secretKey: Bytes, auxRand?: Bytes): Promise { + auxRand ??= crypto.getRandomValues(new Uint8Array(this.L)) 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) @@ -1042,12 +1034,12 @@ export class Secp256k1 { return res instanceof Promise ? res.then(later) : later(res) } - static _verifSchnorr = ( + static _verifSchnorr ( signature: Bytes, message: Bytes, publicKey: Bytes, challengeFn: (...args: Bytes[]) => bigint | Promise - ): boolean | Promise => { + ): boolean | Promise { const sig = this.abytes(signature, this.L2, 'signature') const msg = this.abytes(message, undefined, 'message') const pub = this.abytes(publicKey, this.L, 'publicKey') @@ -1063,9 +1055,9 @@ export class Secp256k1 { const px = this.bigintTo32Bytes(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. - this.arange(r, 1n, this.P) + this.bigintInRange(r, 1n, this.P) const s = this.sliceBytesNumBE(sig, this.L, this.L2) // Let s = int(sig[32:64]); fail if s ≥ n. - this.arange(s, 1n, this.N) + this.bigintInRange(s, 1n, this.N) const i = this.concatBytes(this.bigintTo32Bytes(r), px, msg) // int(challenge(bytes(r)||bytes(P)||m))%n return this.callSyncAsyncFn(challengeFn(i), (e) => { @@ -1103,7 +1095,7 @@ export class Secp256k1 { static scalarBits = 256 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 = () => { + static precompute () { const points: Point[] = [] let p = this.G let b = p @@ -1120,7 +1112,7 @@ export class Secp256k1 { } static Gpows: Point[] | undefined = undefined // precomputes for base point G // const-time negate - static ctneg = (cnd: boolean, p: Point) => { + static ctneg (cnd: boolean, p: Point) { const n = p.negate() return cnd ? n : p } @@ -1156,16 +1148,16 @@ export class Secp256k1 { wbits -= maxNum n += 1n } - const off = w * this.pwindowSize - const offF = off // offsets, evaluate both - const offP = off + Math.abs(wbits) - 1 + const offset = w * this.pwindowSize + const offsetF = offset // offsets, evaluate both + const offsetP = offset + Math.abs(wbits) - 1 const isEven = w % 2 !== 0 // conditions, evaluate both const isNeg = wbits < 0 if (wbits === 0) { // off == I: can't add it. Adding random offF instead. - f = f.add(this.ctneg(isEven, comp[offF])) // bits are 0: add garbage to fake point + f = f.add(this.ctneg(isEven, comp[offsetF])) // bits are 0: add garbage to fake point } else { - p = p.add(this.ctneg(isNeg, comp[offP])) // bits are 1: add to result point + p = p.add(this.ctneg(isNeg, comp[offsetP])) // bits are 1: add to result point } } if (n !== 0n) this.err('invalid wnaf')