"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DER = exports.DERErr = void 0; exports._splitEndoScalar = _splitEndoScalar; exports._normFnElement = _normFnElement; exports.weierstrassN = weierstrassN; exports.SWUFpSqrtRatio = SWUFpSqrtRatio; exports.mapToCurveSimpleSWU = mapToCurveSimpleSWU; exports.ecdh = ecdh; exports.ecdsa = ecdsa; exports.weierstrassPoints = weierstrassPoints; exports._legacyHelperEquat = _legacyHelperEquat; exports.weierstrass = weierstrass; /** * Short Weierstrass curve methods. The formula is: y² = x³ + ax + b. * * ### Design rationale for types * * * Interaction between classes from different curves should fail: * `k256.Point.BASE.add(p256.Point.BASE)` * * For this purpose we want to use `instanceof` operator, which is fast and works during runtime * * Different calls of `curve()` would return different classes - * `curve(params) !== curve(params)`: if somebody decided to monkey-patch their curve, * it won't affect others * * TypeScript can't infer types for classes created inside a function. Classes is one instance * of nominative types in TypeScript and interfaces only check for shape, so it's hard to create * unique type for every function call. * * We can use generic types via some param, like curve opts, but that would: * 1. Enable interaction between `curve(params)` and `curve(params)` (curves of same params) * which is hard to debug. * 2. Params can be generic and we can't enforce them to be constant value: * if somebody creates curve from non-constant params, * it would be allowed to interact with other curves with non-constant params * * @todo https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#unique-symbol * @module */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ const hmac_js_1 = require("@noble/hashes/hmac.js"); const utils_1 = require("@noble/hashes/utils"); const utils_ts_1 = require("../utils.js"); const curve_ts_1 = require("./curve.js"); const modular_ts_1 = require("./modular.js"); // We construct basis in such way that den is always positive and equals n, but num sign depends on basis (not on secret value) const divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n) / den; /** * Splits scalar for GLV endomorphism. */ function _splitEndoScalar(k, basis, n) { // Split scalar into two such that part is ~half bits: `abs(part) < sqrt(N)` // Since part can be negative, we need to do this on point. // TODO: verifyScalar function which consumes lambda const [[a1, b1], [a2, b2]] = basis; const c1 = divNearest(b2 * k, n); const c2 = divNearest(-b1 * k, n); // |k1|/|k2| is < sqrt(N), but can be negative. // If we do `k1 mod N`, we'll get big scalar (`> sqrt(N)`): so, we do cheaper negation instead. let k1 = k - c1 * a1 - c2 * a2; let k2 = -c1 * b1 - c2 * b2; const k1neg = k1 < _0n; const k2neg = k2 < _0n; if (k1neg) k1 = -k1; if (k2neg) k2 = -k2; // Double check that resulting scalar less than half bits of N: otherwise wNAF will fail. // This should only happen on wrong basises. Also, math inside is too complex and I don't trust it. const MAX_NUM = (0, utils_ts_1.bitMask)(Math.ceil((0, utils_ts_1.bitLen)(n) / 2)) + _1n; // Half bits of N if (k1 < _0n || k1 >= MAX_NUM || k2 < _0n || k2 >= MAX_NUM) { throw new Error('splitScalar (endomorphism): failed, k=' + k); } return { k1neg, k1, k2neg, k2 }; } function validateSigFormat(format) { if (!['compact', 'recovered', 'der'].includes(format)) throw new Error('Signature format must be "compact", "recovered", or "der"'); return format; } function validateSigOpts(opts, def) { const optsn = {}; for (let optName of Object.keys(def)) { // @ts-ignore optsn[optName] = opts[optName] === undefined ? def[optName] : opts[optName]; } (0, utils_ts_1._abool2)(optsn.lowS, 'lowS'); (0, utils_ts_1._abool2)(optsn.prehash, 'prehash'); if (optsn.format !== undefined) validateSigFormat(optsn.format); return optsn; } class DERErr extends Error { constructor(m = '') { super(m); } } exports.DERErr = DERErr; /** * ASN.1 DER encoding utilities. ASN is very complex & fragile. Format: * * [0x30 (SEQUENCE), bytelength, 0x02 (INTEGER), intLength, R, 0x02 (INTEGER), intLength, S] * * Docs: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/, https://luca.ntop.org/Teaching/Appunti/asn1.html */ exports.DER = { // asn.1 DER encoding utils Err: DERErr, // Basic building block is TLV (Tag-Length-Value) _tlv: { encode: (tag, data) => { const { Err: E } = exports.DER; if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag'); if (data.length & 1) throw new E('tlv.encode: unpadded data'); const dataLen = data.length / 2; const len = (0, utils_ts_1.numberToHexUnpadded)(dataLen); if ((len.length / 2) & 128) throw new E('tlv.encode: long form length too big'); // length of length with long form flag const lenLen = dataLen > 127 ? (0, utils_ts_1.numberToHexUnpadded)((len.length / 2) | 128) : ''; const t = (0, utils_ts_1.numberToHexUnpadded)(tag); return t + lenLen + len + data; }, // v - value, l - left bytes (unparsed) decode(tag, data) { const { Err: E } = exports.DER; let pos = 0; if (tag < 0 || tag > 256) throw new E('tlv.encode: wrong tag'); if (data.length < 2 || data[pos++] !== tag) throw new E('tlv.decode: wrong tlv'); const first = data[pos++]; const isLong = !!(first & 128); // First bit of first length byte is flag for short/long form let length = 0; if (!isLong) length = first; else { // Long form: [longFlag(1bit), lengthLength(7bit), length (BE)] const lenLen = first & 127; if (!lenLen) throw new E('tlv.decode(long): indefinite length not supported'); if (lenLen > 4) throw new E('tlv.decode(long): byte length is too big'); // this will overflow u32 in js const lengthBytes = data.subarray(pos, pos + lenLen); if (lengthBytes.length !== lenLen) throw new E('tlv.decode: length bytes not complete'); if (lengthBytes[0] === 0) throw new E('tlv.decode(long): zero leftmost byte'); for (const b of lengthBytes) length = (length << 8) | b; pos += lenLen; if (length < 128) throw new E('tlv.decode(long): not minimal encoding'); } const v = data.subarray(pos, pos + length); if (v.length !== length) throw new E('tlv.decode: wrong value length'); return { v, l: data.subarray(pos + length) }; }, }, // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag, // since we always use positive integers here. It must always be empty: // - add zero byte if exists // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding) _int: { encode(num) { const { Err: E } = exports.DER; if (num < _0n) throw new E('integer: negative integers are not allowed'); let hex = (0, utils_ts_1.numberToHexUnpadded)(num); // Pad with zero byte if negative flag is present if (Number.parseInt(hex[0], 16) & 0b1000) hex = '00' + hex; if (hex.length & 1) throw new E('unexpected DER parsing assertion: unpadded hex'); return hex; }, decode(data) { const { Err: E } = exports.DER; if (data[0] & 128) throw new E('invalid signature integer: negative'); if (data[0] === 0x00 && !(data[1] & 128)) throw new E('invalid signature integer: unnecessary leading zero'); return (0, utils_ts_1.bytesToNumberBE)(data); }, }, toSig(hex) { // parse DER signature const { Err: E, _int: int, _tlv: tlv } = exports.DER; const data = (0, utils_ts_1.ensureBytes)('signature', hex); const { v: seqBytes, l: seqLeftBytes } = tlv.decode(0x30, data); if (seqLeftBytes.length) throw new E('invalid signature: left bytes after parsing'); const { v: rBytes, l: rLeftBytes } = tlv.decode(0x02, seqBytes); const { v: sBytes, l: sLeftBytes } = tlv.decode(0x02, rLeftBytes); if (sLeftBytes.length) throw new E('invalid signature: left bytes after parsing'); return { r: int.decode(rBytes), s: int.decode(sBytes) }; }, hexFromSig(sig) { const { _tlv: tlv, _int: int } = exports.DER; const rs = tlv.encode(0x02, int.encode(sig.r)); const ss = tlv.encode(0x02, int.encode(sig.s)); const seq = rs + ss; return tlv.encode(0x30, seq); }, }; // Be friendly to bad ECMAScript parsers by not using bigint literals // prettier-ignore const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _3n = BigInt(3), _4n = BigInt(4); function _normFnElement(Fn, key) { const { BYTES: expected } = Fn; let num; if (typeof key === 'bigint') { num = key; } else { let bytes = (0, utils_ts_1.ensureBytes)('private key', key); try { num = Fn.fromBytes(bytes); } catch (error) { throw new Error(`invalid private key: expected ui8a of size ${expected}, got ${typeof key}`); } } if (!Fn.isValidNot0(num)) throw new Error('invalid private key: out of range [1..N-1]'); return num; } /** * Creates weierstrass Point constructor, based on specified curve options. * * @example ```js const opts = { p: BigInt('0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff'), n: BigInt('0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551'), h: BigInt(1), a: BigInt('0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc'), b: BigInt('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b'), Gx: BigInt('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296'), Gy: BigInt('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'), }; const p256_Point = weierstrass(opts); ``` */ function weierstrassN(params, extraOpts = {}) { const validated = (0, curve_ts_1._createCurveFields)('weierstrass', params, extraOpts); const { Fp, Fn } = validated; let CURVE = validated.CURVE; const { h: cofactor, n: CURVE_ORDER } = CURVE; (0, utils_ts_1._validateObject)(extraOpts, {}, { allowInfinityPoint: 'boolean', clearCofactor: 'function', isTorsionFree: 'function', fromBytes: 'function', toBytes: 'function', endo: 'object', wrapPrivateKey: 'boolean', }); const { endo } = extraOpts; if (endo) { // validateObject(endo, { beta: 'bigint', splitScalar: 'function' }); if (!Fp.is0(CURVE.a) || typeof endo.beta !== 'bigint' || !Array.isArray(endo.basises)) { throw new Error('invalid endo: expected "beta": bigint and "basises": array'); } } const lengths = getWLengths(Fp, Fn); function assertCompressionIsSupported() { if (!Fp.isOdd) throw new Error('compression is not supported: Field does not have .isOdd()'); } // Implements IEEE P1363 point encoding function pointToBytes(_c, point, isCompressed) { const { x, y } = point.toAffine(); const bx = Fp.toBytes(x); (0, utils_ts_1._abool2)(isCompressed, 'isCompressed'); if (isCompressed) { assertCompressionIsSupported(); const hasEvenY = !Fp.isOdd(y); return (0, utils_ts_1.concatBytes)(pprefix(hasEvenY), bx); } else { return (0, utils_ts_1.concatBytes)(Uint8Array.of(0x04), bx, Fp.toBytes(y)); } } function pointFromBytes(bytes) { (0, utils_ts_1._abytes2)(bytes, undefined, 'Point'); const { publicKey: comp, publicKeyUncompressed: uncomp } = lengths; // e.g. for 32-byte: 33, 65 const length = bytes.length; const head = bytes[0]; const tail = bytes.subarray(1); // No actual validation is done here: use .assertValidity() if (length === comp && (head === 0x02 || head === 0x03)) { const x = Fp.fromBytes(tail); if (!Fp.isValid(x)) throw new Error('bad point: is not on curve, wrong x'); const y2 = weierstrassEquation(x); // y² = x³ + ax + b let y; try { y = Fp.sqrt(y2); // y = y² ^ (p+1)/4 } catch (sqrtError) { const err = sqrtError instanceof Error ? ': ' + sqrtError.message : ''; throw new Error('bad point: is not on curve, sqrt error' + err); } assertCompressionIsSupported(); const isYOdd = Fp.isOdd(y); // (y & _1n) === _1n; const isHeadOdd = (head & 1) === 1; // ECDSA-specific if (isHeadOdd !== isYOdd) y = Fp.neg(y); return { x, y }; } else if (length === uncomp && head === 0x04) { // TODO: more checks const L = Fp.BYTES; const x = Fp.fromBytes(tail.subarray(0, L)); const y = Fp.fromBytes(tail.subarray(L, L * 2)); if (!isValidXY(x, y)) throw new Error('bad point: is not on curve'); return { x, y }; } else { throw new Error(`bad point: got length ${length}, expected compressed=${comp} or uncompressed=${uncomp}`); } } const encodePoint = extraOpts.toBytes || pointToBytes; const decodePoint = extraOpts.fromBytes || pointFromBytes; function weierstrassEquation(x) { const x2 = Fp.sqr(x); // x * x const x3 = Fp.mul(x2, x); // x² * x return Fp.add(Fp.add(x3, Fp.mul(x, CURVE.a)), CURVE.b); // x³ + a * x + b } // TODO: move top-level /** Checks whether equation holds for given x, y: y² == x³ + ax + b */ function isValidXY(x, y) { const left = Fp.sqr(y); // y² const right = weierstrassEquation(x); // x³ + ax + b return Fp.eql(left, right); } // Validate whether the passed curve params are valid. // Test 1: equation y² = x³ + ax + b should work for generator point. if (!isValidXY(CURVE.Gx, CURVE.Gy)) throw new Error('bad curve params: generator point'); // Test 2: discriminant Δ part should be non-zero: 4a³ + 27b² != 0. // Guarantees curve is genus-1, smooth (non-singular). const _4a3 = Fp.mul(Fp.pow(CURVE.a, _3n), _4n); const _27b2 = Fp.mul(Fp.sqr(CURVE.b), BigInt(27)); if (Fp.is0(Fp.add(_4a3, _27b2))) throw new Error('bad curve params: a or b'); /** Asserts coordinate is valid: 0 <= n < Fp.ORDER. */ function acoord(title, n, banZero = false) { if (!Fp.isValid(n) || (banZero && Fp.is0(n))) throw new Error(`bad point coordinate ${title}`); return n; } function aprjpoint(other) { if (!(other instanceof Point)) throw new Error('ProjectivePoint expected'); } function splitEndoScalarN(k) { if (!endo || !endo.basises) throw new Error('no endo'); return _splitEndoScalar(k, endo.basises, Fn.ORDER); } // Memoized toAffine / validity check. They are heavy. Points are immutable. // Converts Projective point to affine (x, y) coordinates. // Can accept precomputed Z^-1 - for example, from invertBatch. // (X, Y, Z) ∋ (x=X/Z, y=Y/Z) const toAffineMemo = (0, utils_ts_1.memoized)((p, iz) => { const { X, Y, Z } = p; // Fast-path for normalized points if (Fp.eql(Z, Fp.ONE)) return { x: X, y: Y }; const is0 = p.is0(); // If invZ was 0, we return zero point. However we still want to execute // all operations, so we replace invZ with a random number, 1. if (iz == null) iz = is0 ? Fp.ONE : Fp.inv(Z); const x = Fp.mul(X, iz); const y = Fp.mul(Y, iz); const zz = Fp.mul(Z, iz); if (is0) return { x: Fp.ZERO, y: Fp.ZERO }; if (!Fp.eql(zz, Fp.ONE)) throw new Error('invZ was invalid'); return { x, y }; }); // NOTE: on exception this will crash 'cached' and no value will be set. // Otherwise true will be return const assertValidMemo = (0, utils_ts_1.memoized)((p) => { if (p.is0()) { // (0, 1, 0) aka ZERO is invalid in most contexts. // In BLS, ZERO can be serialized, so we allow it. // (0, 0, 0) is invalid representation of ZERO. if (extraOpts.allowInfinityPoint && !Fp.is0(p.Y)) return; throw new Error('bad point: ZERO'); } // Some 3rd-party test vectors require different wording between here & `fromCompressedHex` const { x, y } = p.toAffine(); if (!Fp.isValid(x) || !Fp.isValid(y)) throw new Error('bad point: x or y not field elements'); if (!isValidXY(x, y)) throw new Error('bad point: equation left != right'); if (!p.isTorsionFree()) throw new Error('bad point: not in prime-order subgroup'); return true; }); function finishEndo(endoBeta, k1p, k2p, k1neg, k2neg) { k2p = new Point(Fp.mul(k2p.X, endoBeta), k2p.Y, k2p.Z); k1p = (0, curve_ts_1.negateCt)(k1neg, k1p); k2p = (0, curve_ts_1.negateCt)(k2neg, k2p); return k1p.add(k2p); } /** * Projective Point works in 3d / projective (homogeneous) coordinates:(X, Y, Z) ∋ (x=X/Z, y=Y/Z). * Default Point works in 2d / affine coordinates: (x, y). * We're doing calculations in projective, because its operations don't require costly inversion. */ class Point { /** Does NOT validate if the point is valid. Use `.assertValidity()`. */ constructor(X, Y, Z) { this.X = acoord('x', X); this.Y = acoord('y', Y, true); this.Z = acoord('z', Z); Object.freeze(this); } static CURVE() { return CURVE; } /** Does NOT validate if the point is valid. Use `.assertValidity()`. */ static fromAffine(p) { const { x, y } = p || {}; if (!p || !Fp.isValid(x) || !Fp.isValid(y)) throw new Error('invalid affine point'); if (p instanceof Point) throw new Error('projective point not allowed'); // (0, 0) would've produced (0, 0, 1) - instead, we need (0, 1, 0) if (Fp.is0(x) && Fp.is0(y)) return Point.ZERO; return new Point(x, y, Fp.ONE); } static fromBytes(bytes) { const P = Point.fromAffine(decodePoint((0, utils_ts_1._abytes2)(bytes, undefined, 'point'))); P.assertValidity(); return P; } static fromHex(hex) { return Point.fromBytes((0, utils_ts_1.ensureBytes)('pointHex', hex)); } get x() { return this.toAffine().x; } get y() { return this.toAffine().y; } /** * * @param windowSize * @param isLazy true will defer table computation until the first multiplication * @returns */ precompute(windowSize = 8, isLazy = true) { wnaf.createCache(this, windowSize); if (!isLazy) this.multiply(_3n); // random number return this; } // TODO: return `this` /** A point on curve is valid if it conforms to equation. */ assertValidity() { assertValidMemo(this); } hasEvenY() { const { y } = this.toAffine(); if (!Fp.isOdd) throw new Error("Field doesn't support isOdd"); return !Fp.isOdd(y); } /** Compare one point to another. */ equals(other) { aprjpoint(other); const { X: X1, Y: Y1, Z: Z1 } = this; const { X: X2, Y: Y2, Z: Z2 } = other; const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1)); const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1)); return U1 && U2; } /** Flips point to one corresponding to (x, -y) in Affine coordinates. */ negate() { return new Point(this.X, Fp.neg(this.Y), this.Z); } // Renes-Costello-Batina exception-free doubling formula. // There is 30% faster Jacobian formula, but it is not complete. // https://eprint.iacr.org/2015/1060, algorithm 3 // Cost: 8M + 3S + 3*a + 2*b3 + 15add. double() { const { a, b } = CURVE; const b3 = Fp.mul(b, _3n); const { X: X1, Y: Y1, Z: Z1 } = this; let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore let t0 = Fp.mul(X1, X1); // step 1 let t1 = Fp.mul(Y1, Y1); let t2 = Fp.mul(Z1, Z1); let t3 = Fp.mul(X1, Y1); t3 = Fp.add(t3, t3); // step 5 Z3 = Fp.mul(X1, Z1); Z3 = Fp.add(Z3, Z3); X3 = Fp.mul(a, Z3); Y3 = Fp.mul(b3, t2); Y3 = Fp.add(X3, Y3); // step 10 X3 = Fp.sub(t1, Y3); Y3 = Fp.add(t1, Y3); Y3 = Fp.mul(X3, Y3); X3 = Fp.mul(t3, X3); Z3 = Fp.mul(b3, Z3); // step 15 t2 = Fp.mul(a, t2); t3 = Fp.sub(t0, t2); t3 = Fp.mul(a, t3); t3 = Fp.add(t3, Z3); Z3 = Fp.add(t0, t0); // step 20 t0 = Fp.add(Z3, t0); t0 = Fp.add(t0, t2); t0 = Fp.mul(t0, t3); Y3 = Fp.add(Y3, t0); t2 = Fp.mul(Y1, Z1); // step 25 t2 = Fp.add(t2, t2); t0 = Fp.mul(t2, t3); X3 = Fp.sub(X3, t0); Z3 = Fp.mul(t2, t1); Z3 = Fp.add(Z3, Z3); // step 30 Z3 = Fp.add(Z3, Z3); return new Point(X3, Y3, Z3); } // Renes-Costello-Batina exception-free addition formula. // There is 30% faster Jacobian formula, but it is not complete. // https://eprint.iacr.org/2015/1060, algorithm 1 // Cost: 12M + 0S + 3*a + 3*b3 + 23add. add(other) { aprjpoint(other); const { X: X1, Y: Y1, Z: Z1 } = this; const { X: X2, Y: Y2, Z: Z2 } = other; let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore const a = CURVE.a; const b3 = Fp.mul(CURVE.b, _3n); let t0 = Fp.mul(X1, X2); // step 1 let t1 = Fp.mul(Y1, Y2); let t2 = Fp.mul(Z1, Z2); let t3 = Fp.add(X1, Y1); let t4 = Fp.add(X2, Y2); // step 5 t3 = Fp.mul(t3, t4); t4 = Fp.add(t0, t1); t3 = Fp.sub(t3, t4); t4 = Fp.add(X1, Z1); let t5 = Fp.add(X2, Z2); // step 10 t4 = Fp.mul(t4, t5); t5 = Fp.add(t0, t2); t4 = Fp.sub(t4, t5); t5 = Fp.add(Y1, Z1); X3 = Fp.add(Y2, Z2); // step 15 t5 = Fp.mul(t5, X3); X3 = Fp.add(t1, t2); t5 = Fp.sub(t5, X3); Z3 = Fp.mul(a, t4); X3 = Fp.mul(b3, t2); // step 20 Z3 = Fp.add(X3, Z3); X3 = Fp.sub(t1, Z3); Z3 = Fp.add(t1, Z3); Y3 = Fp.mul(X3, Z3); t1 = Fp.add(t0, t0); // step 25 t1 = Fp.add(t1, t0); t2 = Fp.mul(a, t2); t4 = Fp.mul(b3, t4); t1 = Fp.add(t1, t2); t2 = Fp.sub(t0, t2); // step 30 t2 = Fp.mul(a, t2); t4 = Fp.add(t4, t2); t0 = Fp.mul(t1, t4); Y3 = Fp.add(Y3, t0); t0 = Fp.mul(t5, t4); // step 35 X3 = Fp.mul(t3, X3); X3 = Fp.sub(X3, t0); t0 = Fp.mul(t3, t1); Z3 = Fp.mul(t5, Z3); Z3 = Fp.add(Z3, t0); // step 40 return new Point(X3, Y3, Z3); } subtract(other) { return this.add(other.negate()); } is0() { return this.equals(Point.ZERO); } /** * Constant time multiplication. * Uses wNAF method. Windowed method may be 10% faster, * but takes 2x longer to generate and consumes 2x memory. * Uses precomputes when available. * Uses endomorphism for Koblitz curves. * @param scalar by which the point would be multiplied * @returns New point */ multiply(scalar) { const { endo } = extraOpts; if (!Fn.isValidNot0(scalar)) throw new Error('invalid scalar: out of range'); // 0 is invalid let point, fake; // Fake point is used to const-time mult const mul = (n) => wnaf.cached(this, n, (p) => (0, curve_ts_1.normalizeZ)(Point, p)); /** See docs for {@link EndomorphismOpts} */ if (endo) { const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar); const { p: k1p, f: k1f } = mul(k1); const { p: k2p, f: k2f } = mul(k2); fake = k1f.add(k2f); point = finishEndo(endo.beta, k1p, k2p, k1neg, k2neg); } else { const { p, f } = mul(scalar); point = p; fake = f; } // Normalize `z` for both points, but return only real one return (0, curve_ts_1.normalizeZ)(Point, [point, fake])[0]; } /** * Non-constant-time multiplication. Uses double-and-add algorithm. * It's faster, but should only be used when you don't care about * an exposed secret key e.g. sig verification, which works over *public* keys. */ multiplyUnsafe(sc) { const { endo } = extraOpts; const p = this; if (!Fn.isValid(sc)) throw new Error('invalid scalar: out of range'); // 0 is valid if (sc === _0n || p.is0()) return Point.ZERO; if (sc === _1n) return p; // fast-path if (wnaf.hasCache(this)) return this.multiply(sc); if (endo) { const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(sc); const { p1, p2 } = (0, curve_ts_1.mulEndoUnsafe)(Point, p, k1, k2); // 30% faster vs wnaf.unsafe return finishEndo(endo.beta, p1, p2, k1neg, k2neg); } else { return wnaf.unsafe(p, sc); } } multiplyAndAddUnsafe(Q, a, b) { const sum = this.multiplyUnsafe(a).add(Q.multiplyUnsafe(b)); return sum.is0() ? undefined : sum; } /** * Converts Projective point to affine (x, y) coordinates. * @param invertedZ Z^-1 (inverted zero) - optional, precomputation is useful for invertBatch */ toAffine(invertedZ) { return toAffineMemo(this, invertedZ); } /** * Checks whether Point is free of torsion elements (is in prime subgroup). * Always torsion-free for cofactor=1 curves. */ isTorsionFree() { const { isTorsionFree } = extraOpts; if (cofactor === _1n) return true; if (isTorsionFree) return isTorsionFree(Point, this); return wnaf.unsafe(this, CURVE_ORDER).is0(); } clearCofactor() { const { clearCofactor } = extraOpts; if (cofactor === _1n) return this; // Fast-path if (clearCofactor) return clearCofactor(Point, this); return this.multiplyUnsafe(cofactor); } isSmallOrder() { // can we use this.clearCofactor()? return this.multiplyUnsafe(cofactor).is0(); } toBytes(isCompressed = true) { (0, utils_ts_1._abool2)(isCompressed, 'isCompressed'); this.assertValidity(); return encodePoint(Point, this, isCompressed); } toHex(isCompressed = true) { return (0, utils_ts_1.bytesToHex)(this.toBytes(isCompressed)); } toString() { return ``; } // TODO: remove get px() { return this.X; } get py() { return this.X; } get pz() { return this.Z; } toRawBytes(isCompressed = true) { return this.toBytes(isCompressed); } _setWindowSize(windowSize) { this.precompute(windowSize); } static normalizeZ(points) { return (0, curve_ts_1.normalizeZ)(Point, points); } static msm(points, scalars) { return (0, curve_ts_1.pippenger)(Point, Fn, points, scalars); } static fromPrivateKey(privateKey) { return Point.BASE.multiply(_normFnElement(Fn, privateKey)); } } // base / generator point Point.BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE); // zero / infinity / identity point Point.ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO); // 0, 1, 0 // math field Point.Fp = Fp; // scalar field Point.Fn = Fn; const bits = Fn.BITS; const wnaf = new curve_ts_1.wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits); Point.BASE.precompute(8); // Enable precomputes. Slows down first publicKey computation by 20ms. return Point; } // Points start with byte 0x02 when y is even; otherwise 0x03 function pprefix(hasEvenY) { return Uint8Array.of(hasEvenY ? 0x02 : 0x03); } /** * Implementation of the Shallue and van de Woestijne method for any weierstrass curve. * TODO: check if there is a way to merge this with uvRatio in Edwards; move to modular. * b = True and y = sqrt(u / v) if (u / v) is square in F, and * b = False and y = sqrt(Z * (u / v)) otherwise. * @param Fp * @param Z * @returns */ function SWUFpSqrtRatio(Fp, Z) { // Generic implementation const q = Fp.ORDER; let l = _0n; for (let o = q - _1n; o % _2n === _0n; o /= _2n) l += _1n; const c1 = l; // 1. c1, the largest integer such that 2^c1 divides q - 1. // We need 2n ** c1 and 2n ** (c1-1). We can't use **; but we can use <<. // 2n ** c1 == 2n << (c1-1) const _2n_pow_c1_1 = _2n << (c1 - _1n - _1n); const _2n_pow_c1 = _2n_pow_c1_1 * _2n; const c2 = (q - _1n) / _2n_pow_c1; // 2. c2 = (q - 1) / (2^c1) # Integer arithmetic const c3 = (c2 - _1n) / _2n; // 3. c3 = (c2 - 1) / 2 # Integer arithmetic const c4 = _2n_pow_c1 - _1n; // 4. c4 = 2^c1 - 1 # Integer arithmetic const c5 = _2n_pow_c1_1; // 5. c5 = 2^(c1 - 1) # Integer arithmetic const c6 = Fp.pow(Z, c2); // 6. c6 = Z^c2 const c7 = Fp.pow(Z, (c2 + _1n) / _2n); // 7. c7 = Z^((c2 + 1) / 2) let sqrtRatio = (u, v) => { let tv1 = c6; // 1. tv1 = c6 let tv2 = Fp.pow(v, c4); // 2. tv2 = v^c4 let tv3 = Fp.sqr(tv2); // 3. tv3 = tv2^2 tv3 = Fp.mul(tv3, v); // 4. tv3 = tv3 * v let tv5 = Fp.mul(u, tv3); // 5. tv5 = u * tv3 tv5 = Fp.pow(tv5, c3); // 6. tv5 = tv5^c3 tv5 = Fp.mul(tv5, tv2); // 7. tv5 = tv5 * tv2 tv2 = Fp.mul(tv5, v); // 8. tv2 = tv5 * v tv3 = Fp.mul(tv5, u); // 9. tv3 = tv5 * u let tv4 = Fp.mul(tv3, tv2); // 10. tv4 = tv3 * tv2 tv5 = Fp.pow(tv4, c5); // 11. tv5 = tv4^c5 let isQR = Fp.eql(tv5, Fp.ONE); // 12. isQR = tv5 == 1 tv2 = Fp.mul(tv3, c7); // 13. tv2 = tv3 * c7 tv5 = Fp.mul(tv4, tv1); // 14. tv5 = tv4 * tv1 tv3 = Fp.cmov(tv2, tv3, isQR); // 15. tv3 = CMOV(tv2, tv3, isQR) tv4 = Fp.cmov(tv5, tv4, isQR); // 16. tv4 = CMOV(tv5, tv4, isQR) // 17. for i in (c1, c1 - 1, ..., 2): for (let i = c1; i > _1n; i--) { let tv5 = i - _2n; // 18. tv5 = i - 2 tv5 = _2n << (tv5 - _1n); // 19. tv5 = 2^tv5 let tvv5 = Fp.pow(tv4, tv5); // 20. tv5 = tv4^tv5 const e1 = Fp.eql(tvv5, Fp.ONE); // 21. e1 = tv5 == 1 tv2 = Fp.mul(tv3, tv1); // 22. tv2 = tv3 * tv1 tv1 = Fp.mul(tv1, tv1); // 23. tv1 = tv1 * tv1 tvv5 = Fp.mul(tv4, tv1); // 24. tv5 = tv4 * tv1 tv3 = Fp.cmov(tv2, tv3, e1); // 25. tv3 = CMOV(tv2, tv3, e1) tv4 = Fp.cmov(tvv5, tv4, e1); // 26. tv4 = CMOV(tv5, tv4, e1) } return { isValid: isQR, value: tv3 }; }; if (Fp.ORDER % _4n === _3n) { // sqrt_ratio_3mod4(u, v) const c1 = (Fp.ORDER - _3n) / _4n; // 1. c1 = (q - 3) / 4 # Integer arithmetic const c2 = Fp.sqrt(Fp.neg(Z)); // 2. c2 = sqrt(-Z) sqrtRatio = (u, v) => { let tv1 = Fp.sqr(v); // 1. tv1 = v^2 const tv2 = Fp.mul(u, v); // 2. tv2 = u * v tv1 = Fp.mul(tv1, tv2); // 3. tv1 = tv1 * tv2 let y1 = Fp.pow(tv1, c1); // 4. y1 = tv1^c1 y1 = Fp.mul(y1, tv2); // 5. y1 = y1 * tv2 const y2 = Fp.mul(y1, c2); // 6. y2 = y1 * c2 const tv3 = Fp.mul(Fp.sqr(y1), v); // 7. tv3 = y1^2; 8. tv3 = tv3 * v const isQR = Fp.eql(tv3, u); // 9. isQR = tv3 == u let y = Fp.cmov(y2, y1, isQR); // 10. y = CMOV(y2, y1, isQR) return { isValid: isQR, value: y }; // 11. return (isQR, y) isQR ? y : y*c2 }; } // No curves uses that // if (Fp.ORDER % _8n === _5n) // sqrt_ratio_5mod8 return sqrtRatio; } /** * Simplified Shallue-van de Woestijne-Ulas Method * https://www.rfc-editor.org/rfc/rfc9380#section-6.6.2 */ function mapToCurveSimpleSWU(Fp, opts) { (0, modular_ts_1.validateField)(Fp); const { A, B, Z } = opts; if (!Fp.isValid(A) || !Fp.isValid(B) || !Fp.isValid(Z)) throw new Error('mapToCurveSimpleSWU: invalid opts'); const sqrtRatio = SWUFpSqrtRatio(Fp, Z); if (!Fp.isOdd) throw new Error('Field does not have .isOdd()'); // Input: u, an element of F. // Output: (x, y), a point on E. return (u) => { // prettier-ignore let tv1, tv2, tv3, tv4, tv5, tv6, x, y; tv1 = Fp.sqr(u); // 1. tv1 = u^2 tv1 = Fp.mul(tv1, Z); // 2. tv1 = Z * tv1 tv2 = Fp.sqr(tv1); // 3. tv2 = tv1^2 tv2 = Fp.add(tv2, tv1); // 4. tv2 = tv2 + tv1 tv3 = Fp.add(tv2, Fp.ONE); // 5. tv3 = tv2 + 1 tv3 = Fp.mul(tv3, B); // 6. tv3 = B * tv3 tv4 = Fp.cmov(Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); // 7. tv4 = CMOV(Z, -tv2, tv2 != 0) tv4 = Fp.mul(tv4, A); // 8. tv4 = A * tv4 tv2 = Fp.sqr(tv3); // 9. tv2 = tv3^2 tv6 = Fp.sqr(tv4); // 10. tv6 = tv4^2 tv5 = Fp.mul(tv6, A); // 11. tv5 = A * tv6 tv2 = Fp.add(tv2, tv5); // 12. tv2 = tv2 + tv5 tv2 = Fp.mul(tv2, tv3); // 13. tv2 = tv2 * tv3 tv6 = Fp.mul(tv6, tv4); // 14. tv6 = tv6 * tv4 tv5 = Fp.mul(tv6, B); // 15. tv5 = B * tv6 tv2 = Fp.add(tv2, tv5); // 16. tv2 = tv2 + tv5 x = Fp.mul(tv1, tv3); // 17. x = tv1 * tv3 const { isValid, value } = sqrtRatio(tv2, tv6); // 18. (is_gx1_square, y1) = sqrt_ratio(tv2, tv6) y = Fp.mul(tv1, u); // 19. y = tv1 * u -> Z * u^3 * y1 y = Fp.mul(y, value); // 20. y = y * y1 x = Fp.cmov(x, tv3, isValid); // 21. x = CMOV(x, tv3, is_gx1_square) y = Fp.cmov(y, value, isValid); // 22. y = CMOV(y, y1, is_gx1_square) const e1 = Fp.isOdd(u) === Fp.isOdd(y); // 23. e1 = sgn0(u) == sgn0(y) y = Fp.cmov(Fp.neg(y), y, e1); // 24. y = CMOV(-y, y, e1) const tv4_inv = (0, modular_ts_1.FpInvertBatch)(Fp, [tv4], true)[0]; x = Fp.mul(x, tv4_inv); // 25. x = x / tv4 return { x, y }; }; } function getWLengths(Fp, Fn) { return { secretKey: Fn.BYTES, publicKey: 1 + Fp.BYTES, publicKeyUncompressed: 1 + 2 * Fp.BYTES, publicKeyHasPrefix: true, signature: 2 * Fn.BYTES, }; } /** * Sometimes users only need getPublicKey, getSharedSecret, and secret key handling. * This helper ensures no signature functionality is present. Less code, smaller bundle size. */ function ecdh(Point, ecdhOpts = {}) { const { Fn } = Point; const randomBytes_ = ecdhOpts.randomBytes || utils_ts_1.randomBytes; const lengths = Object.assign(getWLengths(Point.Fp, Fn), { seed: (0, modular_ts_1.getMinHashLength)(Fn.ORDER) }); function isValidSecretKey(secretKey) { try { return !!_normFnElement(Fn, secretKey); } catch (error) { return false; } } function isValidPublicKey(publicKey, isCompressed) { const { publicKey: comp, publicKeyUncompressed } = lengths; try { const l = publicKey.length; if (isCompressed === true && l !== comp) return false; if (isCompressed === false && l !== publicKeyUncompressed) return false; return !!Point.fromBytes(publicKey); } catch (error) { return false; } } /** * Produces cryptographically secure secret key from random of size * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible. */ function randomSecretKey(seed = randomBytes_(lengths.seed)) { return (0, modular_ts_1.mapHashToField)((0, utils_ts_1._abytes2)(seed, lengths.seed, 'seed'), Fn.ORDER); } /** * Computes public key for a secret key. Checks for validity of the secret key. * @param isCompressed whether to return compact (default), or full key * @returns Public key, full when isCompressed=false; short when isCompressed=true */ function getPublicKey(secretKey, isCompressed = true) { return Point.BASE.multiply(_normFnElement(Fn, secretKey)).toBytes(isCompressed); } function keygen(seed) { const secretKey = randomSecretKey(seed); return { secretKey, publicKey: getPublicKey(secretKey) }; } /** * Quick and dirty check for item being public key. Does not validate hex, or being on-curve. */ function isProbPub(item) { if (typeof item === 'bigint') return false; if (item instanceof Point) return true; const { secretKey, publicKey, publicKeyUncompressed } = lengths; if (Fn.allowedLengths || secretKey === publicKey) return undefined; const l = (0, utils_ts_1.ensureBytes)('key', item).length; return l === publicKey || l === publicKeyUncompressed; } /** * ECDH (Elliptic Curve Diffie Hellman). * Computes shared public key from secret key A and public key B. * Checks: 1) secret key validity 2) shared key is on-curve. * Does NOT hash the result. * @param isCompressed whether to return compact (default), or full key * @returns shared public key */ function getSharedSecret(secretKeyA, publicKeyB, isCompressed = true) { if (isProbPub(secretKeyA) === true) throw new Error('first arg must be private key'); if (isProbPub(publicKeyB) === false) throw new Error('second arg must be public key'); const s = _normFnElement(Fn, secretKeyA); const b = Point.fromHex(publicKeyB); // checks for being on-curve return b.multiply(s).toBytes(isCompressed); } const utils = { isValidSecretKey, isValidPublicKey, randomSecretKey, // TODO: remove isValidPrivateKey: isValidSecretKey, randomPrivateKey: randomSecretKey, normPrivateKeyToScalar: (key) => _normFnElement(Fn, key), precompute(windowSize = 8, point = Point.BASE) { return point.precompute(windowSize, false); }, }; return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths }); } /** * Creates ECDSA signing interface for given elliptic curve `Point` and `hash` function. * We need `hash` for 2 features: * 1. Message prehash-ing. NOT used if `sign` / `verify` are called with `prehash: false` * 2. k generation in `sign`, using HMAC-drbg(hash) * * ECDSAOpts are only rarely needed. * * @example * ```js * const p256_Point = weierstrass(...); * const p256_sha256 = ecdsa(p256_Point, sha256); * const p256_sha224 = ecdsa(p256_Point, sha224); * const p256_sha224_r = ecdsa(p256_Point, sha224, { randomBytes: (length) => { ... } }); * ``` */ function ecdsa(Point, hash, ecdsaOpts = {}) { (0, utils_1.ahash)(hash); (0, utils_ts_1._validateObject)(ecdsaOpts, {}, { hmac: 'function', lowS: 'boolean', randomBytes: 'function', bits2int: 'function', bits2int_modN: 'function', }); const randomBytes = ecdsaOpts.randomBytes || utils_ts_1.randomBytes; const hmac = ecdsaOpts.hmac || ((key, ...msgs) => (0, hmac_js_1.hmac)(hash, key, (0, utils_ts_1.concatBytes)(...msgs))); const { Fp, Fn } = Point; const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn; const { keygen, getPublicKey, getSharedSecret, utils, lengths } = ecdh(Point, ecdsaOpts); const defaultSigOpts = { prehash: false, lowS: typeof ecdsaOpts.lowS === 'boolean' ? ecdsaOpts.lowS : false, format: undefined, //'compact' as ECDSASigFormat, extraEntropy: false, }; const defaultSigOpts_format = 'compact'; function isBiggerThanHalfOrder(number) { const HALF = CURVE_ORDER >> _1n; return number > HALF; } function validateRS(title, num) { if (!Fn.isValidNot0(num)) throw new Error(`invalid signature ${title}: out of range 1..Point.Fn.ORDER`); return num; } function validateSigLength(bytes, format) { validateSigFormat(format); const size = lengths.signature; const sizer = format === 'compact' ? size : format === 'recovered' ? size + 1 : undefined; return (0, utils_ts_1._abytes2)(bytes, sizer, `${format} signature`); } /** * ECDSA signature with its (r, s) properties. Supports compact, recovered & DER representations. */ class Signature { constructor(r, s, recovery) { this.r = validateRS('r', r); // r in [1..N-1]; this.s = validateRS('s', s); // s in [1..N-1]; if (recovery != null) this.recovery = recovery; Object.freeze(this); } static fromBytes(bytes, format = defaultSigOpts_format) { validateSigLength(bytes, format); let recid; if (format === 'der') { const { r, s } = exports.DER.toSig((0, utils_ts_1._abytes2)(bytes)); return new Signature(r, s); } if (format === 'recovered') { recid = bytes[0]; format = 'compact'; bytes = bytes.subarray(1); } const L = Fn.BYTES; const r = bytes.subarray(0, L); const s = bytes.subarray(L, L * 2); return new Signature(Fn.fromBytes(r), Fn.fromBytes(s), recid); } static fromHex(hex, format) { return this.fromBytes((0, utils_ts_1.hexToBytes)(hex), format); } addRecoveryBit(recovery) { return new Signature(this.r, this.s, recovery); } recoverPublicKey(messageHash) { const FIELD_ORDER = Fp.ORDER; const { r, s, recovery: rec } = this; if (rec == null || ![0, 1, 2, 3].includes(rec)) throw new Error('recovery id invalid'); // ECDSA recovery is hard for cofactor > 1 curves. // In sign, `r = q.x mod n`, and here we recover q.x from r. // While recovering q.x >= n, we need to add r+n for cofactor=1 curves. // However, for cofactor>1, r+n may not get q.x: // r+n*i would need to be done instead where i is unknown. // To easily get i, we either need to: // a. increase amount of valid recid values (4, 5...); OR // b. prohibit non-prime-order signatures (recid > 1). const hasCofactor = CURVE_ORDER * _2n < FIELD_ORDER; if (hasCofactor && rec > 1) throw new Error('recovery id is ambiguous for h>1 curve'); const radj = rec === 2 || rec === 3 ? r + CURVE_ORDER : r; if (!Fp.isValid(radj)) throw new Error('recovery id 2 or 3 invalid'); const x = Fp.toBytes(radj); const R = Point.fromBytes((0, utils_ts_1.concatBytes)(pprefix((rec & 1) === 0), x)); const ir = Fn.inv(radj); // r^-1 const h = bits2int_modN((0, utils_ts_1.ensureBytes)('msgHash', messageHash)); // Truncate hash const u1 = Fn.create(-h * ir); // -hr^-1 const u2 = Fn.create(s * ir); // sr^-1 // (sr^-1)R-(hr^-1)G = -(hr^-1)G + (sr^-1). unsafe is fine: there is no private data. const Q = Point.BASE.multiplyUnsafe(u1).add(R.multiplyUnsafe(u2)); if (Q.is0()) throw new Error('point at infinify'); Q.assertValidity(); return Q; } // Signatures should be low-s, to prevent malleability. hasHighS() { return isBiggerThanHalfOrder(this.s); } toBytes(format = defaultSigOpts_format) { validateSigFormat(format); if (format === 'der') return (0, utils_ts_1.hexToBytes)(exports.DER.hexFromSig(this)); const r = Fn.toBytes(this.r); const s = Fn.toBytes(this.s); if (format === 'recovered') { if (this.recovery == null) throw new Error('recovery bit must be present'); return (0, utils_ts_1.concatBytes)(Uint8Array.of(this.recovery), r, s); } return (0, utils_ts_1.concatBytes)(r, s); } toHex(format) { return (0, utils_ts_1.bytesToHex)(this.toBytes(format)); } // TODO: remove assertValidity() { } static fromCompact(hex) { return Signature.fromBytes((0, utils_ts_1.ensureBytes)('sig', hex), 'compact'); } static fromDER(hex) { return Signature.fromBytes((0, utils_ts_1.ensureBytes)('sig', hex), 'der'); } normalizeS() { return this.hasHighS() ? new Signature(this.r, Fn.neg(this.s), this.recovery) : this; } toDERRawBytes() { return this.toBytes('der'); } toDERHex() { return (0, utils_ts_1.bytesToHex)(this.toBytes('der')); } toCompactRawBytes() { return this.toBytes('compact'); } toCompactHex() { return (0, utils_ts_1.bytesToHex)(this.toBytes('compact')); } } // RFC6979: ensure ECDSA msg is X bytes and < N. RFC suggests optional truncating via bits2octets. // FIPS 186-4 4.6 suggests the leftmost min(nBitLen, outLen) bits, which matches bits2int. // bits2int can produce res>N, we can do mod(res, N) since the bitLen is the same. // int2octets can't be used; pads small msgs with 0: unacceptatble for trunc as per RFC vectors const bits2int = ecdsaOpts.bits2int || function bits2int_def(bytes) { // Our custom check "just in case", for protection against DoS if (bytes.length > 8192) throw new Error('input is too large'); // For curves with nBitLength % 8 !== 0: bits2octets(bits2octets(m)) !== bits2octets(m) // for some cases, since bytes.length * 8 is not actual bitLength. const num = (0, utils_ts_1.bytesToNumberBE)(bytes); // check for == u8 done here const delta = bytes.length * 8 - fnBits; // truncate to nBitLength leftmost bits return delta > 0 ? num >> BigInt(delta) : num; }; const bits2int_modN = ecdsaOpts.bits2int_modN || function bits2int_modN_def(bytes) { return Fn.create(bits2int(bytes)); // can't use bytesToNumberBE here }; // Pads output with zero as per spec const ORDER_MASK = (0, utils_ts_1.bitMask)(fnBits); /** Converts to bytes. Checks if num in `[0..ORDER_MASK-1]` e.g.: `[0..2^256-1]`. */ function int2octets(num) { // IMPORTANT: the check ensures working for case `Fn.BYTES != Fn.BITS * 8` (0, utils_ts_1.aInRange)('num < 2^' + fnBits, num, _0n, ORDER_MASK); return Fn.toBytes(num); } function validateMsgAndHash(message, prehash) { (0, utils_ts_1._abytes2)(message, undefined, 'message'); return prehash ? (0, utils_ts_1._abytes2)(hash(message), undefined, 'prehashed message') : message; } /** * Steps A, D of RFC6979 3.2. * Creates RFC6979 seed; converts msg/privKey to numbers. * Used only in sign, not in verify. * * Warning: we cannot assume here that message has same amount of bytes as curve order, * this will be invalid at least for P521. Also it can be bigger for P224 + SHA256. */ function prepSig(message, privateKey, opts) { if (['recovered', 'canonical'].some((k) => k in opts)) throw new Error('sign() legacy options not supported'); const { lowS, prehash, extraEntropy } = validateSigOpts(opts, defaultSigOpts); message = validateMsgAndHash(message, prehash); // RFC6979 3.2 A: h1 = H(m) // We can't later call bits2octets, since nested bits2int is broken for curves // with fnBits % 8 !== 0. Because of that, we unwrap it here as int2octets call. // const bits2octets = (bits) => int2octets(bits2int_modN(bits)) const h1int = bits2int_modN(message); const d = _normFnElement(Fn, privateKey); // validate secret key, convert to bigint const seedArgs = [int2octets(d), int2octets(h1int)]; // extraEntropy. RFC6979 3.6: additional k' (optional). 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(lengths.secretKey) : extraEntropy; seedArgs.push((0, utils_ts_1.ensureBytes)('extraEntropy', e)); // check for being bytes } const seed = (0, utils_ts_1.concatBytes)(...seedArgs); // Step D of RFC6979 3.2 const m = h1int; // NOTE: no need to call bits2int second time here, it is inside truncateHash! // Converts signature params into point w r/s, checks result for validity. // To transform k => Signature: // q = k⋅G // r = q.x mod n // s = k^-1(m + rd) mod n // Can use scalar blinding b^-1(bm + bdr) where b ∈ [1,q−1] according to // https://tches.iacr.org/index.php/TCHES/article/view/7337/6509. We've decided against it: // a) dependency on CSPRNG b) 15% slowdown c) doesn't really help since bigints are not CT function k2sig(kBytes) { // RFC 6979 Section 3.2, step 3: k = bits2int(T) // Important: all mod() calls here must be done over N const k = bits2int(kBytes); // mod n, not mod p if (!Fn.isValidNot0(k)) return; // Valid scalars (including k) must be in 1..N-1 const ik = Fn.inv(k); // k^-1 mod n const q = Point.BASE.multiply(k).toAffine(); // q = k⋅G const r = Fn.create(q.x); // r = q.x mod n if (r === _0n) return; const s = Fn.create(ik * Fn.create(m + r * d)); // Not using blinding here, see comment above 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; if (lowS && isBiggerThanHalfOrder(s)) { normS = Fn.neg(s); // if lowS was passed, ensure s is always recovery ^= 1; // // in the bottom half of N } return new Signature(r, normS, recovery); // use normS, not s } return { seed, k2sig }; } /** * Signs message hash with a secret key. * * ``` * sign(m, d) where * k = rfc6979_hmac_drbg(m, d) * (x, y) = G × k * r = x mod n * s = (m + dr) / k mod n * ``` */ function sign(message, secretKey, opts = {}) { message = (0, utils_ts_1.ensureBytes)('message', message); const { seed, k2sig } = prepSig(message, secretKey, opts); // Steps A, D of RFC6979 3.2. const drbg = (0, utils_ts_1.createHmacDrbg)(hash.outputLen, Fn.BYTES, hmac); const sig = drbg(seed, k2sig); // Steps B, C, D, E, F, G return sig; } function tryParsingSig(sg) { // Try to deduce format let sig = undefined; const isHex = typeof sg === 'string' || (0, utils_ts_1.isBytes)(sg); const isObj = !isHex && sg !== null && typeof sg === 'object' && typeof sg.r === 'bigint' && typeof sg.s === 'bigint'; if (!isHex && !isObj) throw new Error('invalid signature, expected Uint8Array, hex string or Signature instance'); if (isObj) { sig = new Signature(sg.r, sg.s); } else if (isHex) { try { sig = Signature.fromBytes((0, utils_ts_1.ensureBytes)('sig', sg), 'der'); } catch (derError) { if (!(derError instanceof exports.DER.Err)) throw derError; } if (!sig) { try { sig = Signature.fromBytes((0, utils_ts_1.ensureBytes)('sig', sg), 'compact'); } catch (error) { return false; } } } if (!sig) return false; return sig; } /** * Verifies a signature against message and public key. * Rejects lowS signatures by default: see {@link ECDSAVerifyOpts}. * Implements section 4.1.4 from https://www.secg.org/sec1-v2.pdf: * * ``` * verify(r, s, h, P) where * u1 = hs^-1 mod n * u2 = rs^-1 mod n * R = u1⋅G + u2⋅P * mod(R.x, n) == r * ``` */ function verify(signature, message, publicKey, opts = {}) { const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts); publicKey = (0, utils_ts_1.ensureBytes)('publicKey', publicKey); message = validateMsgAndHash((0, utils_ts_1.ensureBytes)('message', message), prehash); if ('strict' in opts) throw new Error('options.strict was renamed to lowS'); const sig = format === undefined ? tryParsingSig(signature) : Signature.fromBytes((0, utils_ts_1.ensureBytes)('sig', signature), format); if (sig === false) return false; try { const P = Point.fromBytes(publicKey); if (lowS && sig.hasHighS()) return false; const { r, s } = sig; const h = bits2int_modN(message); // mod n, not mod p const is = Fn.inv(s); // s^-1 mod n const u1 = Fn.create(h * is); // u1 = hs^-1 mod n const u2 = Fn.create(r * is); // u2 = rs^-1 mod n const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2)); // u1⋅G + u2⋅P if (R.is0()) return false; const v = Fn.create(R.x); // v = r.x mod n return v === r; } catch (e) { return false; } } function recoverPublicKey(signature, message, opts = {}) { const { prehash } = validateSigOpts(opts, defaultSigOpts); message = validateMsgAndHash(message, prehash); return Signature.fromBytes(signature, 'recovered').recoverPublicKey(message).toBytes(); } return Object.freeze({ keygen, getPublicKey, getSharedSecret, utils, lengths, Point, sign, verify, recoverPublicKey, Signature, hash, }); } /** @deprecated use `weierstrass` in newer releases */ function weierstrassPoints(c) { const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c); const Point = weierstrassN(CURVE, curveOpts); return _weierstrass_new_output_to_legacy(c, Point); } function _weierstrass_legacy_opts_to_new(c) { const CURVE = { a: c.a, b: c.b, p: c.Fp.ORDER, n: c.n, h: c.h, Gx: c.Gx, Gy: c.Gy, }; const Fp = c.Fp; let allowedLengths = c.allowedPrivateKeyLengths ? Array.from(new Set(c.allowedPrivateKeyLengths.map((l) => Math.ceil(l / 2)))) : undefined; const Fn = (0, modular_ts_1.Field)(CURVE.n, { BITS: c.nBitLength, allowedLengths: allowedLengths, modFromBytes: c.wrapPrivateKey, }); const curveOpts = { Fp, Fn, allowInfinityPoint: c.allowInfinityPoint, endo: c.endo, isTorsionFree: c.isTorsionFree, clearCofactor: c.clearCofactor, fromBytes: c.fromBytes, toBytes: c.toBytes, }; return { CURVE, curveOpts }; } function _ecdsa_legacy_opts_to_new(c) { const { CURVE, curveOpts } = _weierstrass_legacy_opts_to_new(c); const ecdsaOpts = { hmac: c.hmac, randomBytes: c.randomBytes, lowS: c.lowS, bits2int: c.bits2int, bits2int_modN: c.bits2int_modN, }; return { CURVE, curveOpts, hash: c.hash, ecdsaOpts }; } function _legacyHelperEquat(Fp, a, b) { /** * y² = x³ + ax + b: Short weierstrass curve formula. Takes x, returns y². * @returns y² */ function weierstrassEquation(x) { const x2 = Fp.sqr(x); // x * x const x3 = Fp.mul(x2, x); // x² * x return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); // x³ + a * x + b } return weierstrassEquation; } function _weierstrass_new_output_to_legacy(c, Point) { const { Fp, Fn } = Point; function isWithinCurveOrder(num) { return (0, utils_ts_1.inRange)(num, _1n, Fn.ORDER); } const weierstrassEquation = _legacyHelperEquat(Fp, c.a, c.b); return Object.assign({}, { CURVE: c, Point: Point, ProjectivePoint: Point, normPrivateKeyToScalar: (key) => _normFnElement(Fn, key), weierstrassEquation, isWithinCurveOrder, }); } function _ecdsa_new_output_to_legacy(c, _ecdsa) { const Point = _ecdsa.Point; return Object.assign({}, _ecdsa, { ProjectivePoint: Point, CURVE: Object.assign({}, c, (0, modular_ts_1.nLength)(Point.Fn.ORDER, Point.Fn.BITS)), }); } // _ecdsa_legacy function weierstrass(c) { const { CURVE, curveOpts, hash, ecdsaOpts } = _ecdsa_legacy_opts_to_new(c); const Point = weierstrassN(CURVE, curveOpts); const signs = ecdsa(Point, hash, ecdsaOpts); return _ecdsa_new_output_to_legacy(c, signs); } //# sourceMappingURL=weierstrass.js.map