UNPKG

@noble/curves

Version:

Audited & minimal JS implementation of elliptic curve cryptography

488 lines 21.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.edwards = edwards; exports.eddsa = eddsa; exports.twistedEdwards = twistedEdwards; /** * Twisted Edwards curve. The formula is: ax² + y² = 1 + dx²y². * For design rationale of types / exports, see weierstrass module documentation. * Untwisted Edwards curves exist, but they aren't used in real-world protocols. * @module */ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ const utils_ts_1 = require("../utils.js"); const curve_ts_1 = require("./curve.js"); const modular_ts_1 = require("./modular.js"); // Be friendly to bad ECMAScript parsers by not using bigint literals // prettier-ignore const _0n = BigInt(0), _1n = BigInt(1), _2n = BigInt(2), _8n = BigInt(8); // verification rule is either zip215 or rfc8032 / nist186-5. Consult fromHex: const VERIFY_DEFAULT = { zip215: true }; function isEdValidXY(Fp, CURVE, x, y) { const x2 = Fp.sqr(x); const y2 = Fp.sqr(y); const left = Fp.add(Fp.mul(CURVE.a, x2), y2); const right = Fp.add(Fp.ONE, Fp.mul(CURVE.d, Fp.mul(x2, y2))); return Fp.eql(left, right); } function edwards(CURVE, curveOpts = {}) { const { Fp, Fn } = (0, curve_ts_1._createCurveFields)('edwards', CURVE, curveOpts); const { h: cofactor, n: CURVE_ORDER } = CURVE; (0, utils_ts_1._validateObject)(curveOpts, {}, { uvRatio: 'function' }); // Important: // There are some places where Fp.BYTES is used instead of nByteLength. // So far, everything has been tested with curves of Fp.BYTES == nByteLength. // TODO: test and find curves which behave otherwise. const MASK = _2n << (BigInt(Fn.BYTES * 8) - _1n); const modP = (n) => Fp.create(n); // Function overrides // sqrt(u/v) const uvRatio = curveOpts.uvRatio || ((u, v) => { try { return { isValid: true, value: Fp.sqrt(Fp.div(u, v)) }; } catch (e) { return { isValid: false, value: _0n }; } }); // Validate whether the passed curve params are valid. // equation ax² + y² = 1 + dx²y² should work for generator point. if (!isEdValidXY(Fp, CURVE, CURVE.Gx, CURVE.Gy)) throw new Error('bad curve params: generator point'); /** * Asserts coordinate is valid: 0 <= n < MASK. * Coordinates >= Fp.ORDER are allowed for zip215. */ function acoord(title, n, banZero = false) { const min = banZero ? _1n : _0n; (0, utils_ts_1.aInRange)('coordinate ' + title, n, min, MASK); return n; } function aextpoint(other) { if (!(other instanceof Point)) throw new Error('ExtendedPoint expected'); } // Converts Extended point to default (x, y) coordinates. // Can accept precomputed Z^-1 - for example, from invertBatch. const toAffineMemo = (0, utils_ts_1.memoized)((p, iz) => { const { ex: x, ey: y, ez: z } = p; const is0 = p.is0(); if (iz == null) iz = is0 ? _8n : Fp.inv(z); // 8 was chosen arbitrarily const ax = modP(x * iz); const ay = modP(y * iz); const zz = modP(z * iz); if (is0) return { x: _0n, y: _1n }; if (zz !== _1n) throw new Error('invZ was invalid'); return { x: ax, y: ay }; }); const assertValidMemo = (0, utils_ts_1.memoized)((p) => { const { a, d } = CURVE; if (p.is0()) throw new Error('bad point: ZERO'); // TODO: optimize, with vars below? // Equation in affine coordinates: ax² + y² = 1 + dx²y² // Equation in projective coordinates (X/Z, Y/Z, Z): (aX² + Y²)Z² = Z⁴ + dX²Y² const { ex: X, ey: Y, ez: Z, et: T } = p; const X2 = modP(X * X); // X² const Y2 = modP(Y * Y); // Y² const Z2 = modP(Z * Z); // Z² const Z4 = modP(Z2 * Z2); // Z⁴ const aX2 = modP(X2 * a); // aX² const left = modP(Z2 * modP(aX2 + Y2)); // (aX² + Y²)Z² const right = modP(Z4 + modP(d * modP(X2 * Y2))); // Z⁴ + dX²Y² if (left !== right) throw new Error('bad point: equation left != right (1)'); // In Extended coordinates we also have T, which is x*y=T/Z: check X*Y == Z*T const XY = modP(X * Y); const ZT = modP(Z * T); if (XY !== ZT) throw new Error('bad point: equation left != right (2)'); return true; }); // Extended Point works in extended coordinates: (X, Y, Z, T) ∋ (x=X/Z, y=Y/Z, T=xy). // https://en.wikipedia.org/wiki/Twisted_Edwards_curve#Extended_coordinates class Point { constructor(ex, ey, ez, et) { this.ex = acoord('x', ex); this.ey = acoord('y', ey); this.ez = acoord('z', ez, true); this.et = acoord('t', et); Object.freeze(this); } get x() { return this.toAffine().x; } get y() { return this.toAffine().y; } static fromAffine(p) { if (p instanceof Point) throw new Error('extended point not allowed'); const { x, y } = p || {}; acoord('x', x); acoord('y', y); return new Point(x, y, _1n, modP(x * y)); } static normalizeZ(points) { return (0, curve_ts_1.normalizeZ)(Point, 'ez', points); } // Multiscalar Multiplication static msm(points, scalars) { return (0, curve_ts_1.pippenger)(Point, Fn, points, scalars); } // "Private method", don't use it directly _setWindowSize(windowSize) { this.precompute(windowSize); } precompute(windowSize = 8, isLazy = true) { wnaf.setWindowSize(this, windowSize); if (!isLazy) this.multiply(_2n); // random number return this; } // Not required for fromHex(), which always creates valid points. // Could be useful for fromAffine(). assertValidity() { assertValidMemo(this); } // Compare one point to another. equals(other) { aextpoint(other); const { ex: X1, ey: Y1, ez: Z1 } = this; const { ex: X2, ey: Y2, ez: Z2 } = other; const X1Z2 = modP(X1 * Z2); const X2Z1 = modP(X2 * Z1); const Y1Z2 = modP(Y1 * Z2); const Y2Z1 = modP(Y2 * Z1); return X1Z2 === X2Z1 && Y1Z2 === Y2Z1; } is0() { return this.equals(Point.ZERO); } negate() { // Flips point sign to a negative one (-x, y in affine coords) return new Point(modP(-this.ex), this.ey, this.ez, modP(-this.et)); } // Fast algo for doubling Extended Point. // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#doubling-dbl-2008-hwcd // Cost: 4M + 4S + 1*a + 6add + 1*2. double() { const { a } = CURVE; const { ex: X1, ey: Y1, ez: Z1 } = this; const A = modP(X1 * X1); // A = X12 const B = modP(Y1 * Y1); // B = Y12 const C = modP(_2n * modP(Z1 * Z1)); // C = 2*Z12 const D = modP(a * A); // D = a*A const x1y1 = X1 + Y1; const E = modP(modP(x1y1 * x1y1) - A - B); // E = (X1+Y1)2-A-B const G = D + B; // G = D+B const F = G - C; // F = G-C const H = D - B; // H = D-B const X3 = modP(E * F); // X3 = E*F const Y3 = modP(G * H); // Y3 = G*H const T3 = modP(E * H); // T3 = E*H const Z3 = modP(F * G); // Z3 = F*G return new Point(X3, Y3, Z3, T3); } // Fast algo for adding 2 Extended Points. // https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html#addition-add-2008-hwcd // Cost: 9M + 1*a + 1*d + 7add. add(other) { aextpoint(other); const { a, d } = CURVE; const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this; const { ex: X2, ey: Y2, ez: Z2, et: T2 } = other; const A = modP(X1 * X2); // A = X1*X2 const B = modP(Y1 * Y2); // B = Y1*Y2 const C = modP(T1 * d * T2); // C = T1*d*T2 const D = modP(Z1 * Z2); // D = Z1*Z2 const E = modP((X1 + Y1) * (X2 + Y2) - A - B); // E = (X1+Y1)*(X2+Y2)-A-B const F = D - C; // F = D-C const G = D + C; // G = D+C const H = modP(B - a * A); // H = B-a*A const X3 = modP(E * F); // X3 = E*F const Y3 = modP(G * H); // Y3 = G*H const T3 = modP(E * H); // T3 = E*H const Z3 = modP(F * G); // Z3 = F*G return new Point(X3, Y3, Z3, T3); } subtract(other) { return this.add(other.negate()); } // Constant-time multiplication. multiply(scalar) { const n = scalar; (0, utils_ts_1.aInRange)('scalar', n, _1n, CURVE_ORDER); // 1 <= scalar < L const { p, f } = wnaf.wNAFCached(this, n, Point.normalizeZ); return Point.normalizeZ([p, f])[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 private key e.g. sig verification. // Does NOT allow scalars higher than CURVE.n. // Accepts optional accumulator to merge with multiply (important for sparse scalars) multiplyUnsafe(scalar, acc = Point.ZERO) { const n = scalar; (0, utils_ts_1.aInRange)('scalar', n, _0n, CURVE_ORDER); // 0 <= scalar < L if (n === _0n) return Point.ZERO; if (this.is0() || n === _1n) return this; return wnaf.wNAFCachedUnsafe(this, n, Point.normalizeZ, acc); } // Checks if point is of small order. // If you add something to small order point, you will have "dirty" // point with torsion component. // Multiplies point by cofactor and checks if the result is 0. isSmallOrder() { return this.multiplyUnsafe(cofactor).is0(); } // Multiplies point by curve order and checks if the result is 0. // Returns `false` is the point is dirty. isTorsionFree() { return wnaf.wNAFCachedUnsafe(this, CURVE_ORDER).is0(); } // Converts Extended point to default (x, y) coordinates. // Can accept precomputed Z^-1 - for example, from invertBatch. toAffine(invertedZ) { return toAffineMemo(this, invertedZ); } clearCofactor() { if (cofactor === _1n) return this; return this.multiplyUnsafe(cofactor); } static fromBytes(bytes, zip215 = false) { (0, utils_ts_1.abytes)(bytes); return this.fromHex(bytes, zip215); } // Converts hash string or Uint8Array to Point. // Uses algo from RFC8032 5.1.3. static fromHex(hex, zip215 = false) { const { d, a } = CURVE; const len = Fp.BYTES; hex = (0, utils_ts_1.ensureBytes)('pointHex', hex, len); // copy hex to a new array (0, utils_ts_1.abool)('zip215', zip215); const normed = hex.slice(); // copy again, we'll manipulate it const lastByte = hex[len - 1]; // select last byte normed[len - 1] = lastByte & ~0x80; // clear last bit const y = (0, utils_ts_1.bytesToNumberLE)(normed); // zip215=true is good for consensus-critical apps. =false follows RFC8032 / NIST186-5. // RFC8032 prohibits >= p, but ZIP215 doesn't // zip215=true: 0 <= y < MASK (2^256 for ed25519) // zip215=false: 0 <= y < P (2^255-19 for ed25519) const max = zip215 ? MASK : Fp.ORDER; (0, utils_ts_1.aInRange)('pointHex.y', y, _0n, max); // Ed25519: x² = (y²-1)/(dy²+1) mod p. Ed448: x² = (y²-1)/(dy²-1) mod p. Generic case: // ax²+y²=1+dx²y² => y²-1=dx²y²-ax² => y²-1=x²(dy²-a) => x²=(y²-1)/(dy²-a) const y2 = modP(y * y); // denominator is always non-0 mod p. const u = modP(y2 - _1n); // u = y² - 1 const v = modP(d * y2 - a); // v = d y² + 1. let { isValid, value: x } = uvRatio(u, v); // √(u/v) if (!isValid) throw new Error('Point.fromHex: invalid y coordinate'); const isXOdd = (x & _1n) === _1n; // There are 2 square roots. Use x_0 bit to select proper const isLastByteOdd = (lastByte & 0x80) !== 0; // x_0, last bit if (!zip215 && x === _0n && isLastByteOdd) // if x=0 and x_0 = 1, fail throw new Error('Point.fromHex: x=0 and x_0=1'); if (isLastByteOdd !== isXOdd) x = modP(-x); // if x_0 != x mod 2, set x = p-x return Point.fromAffine({ x, y }); } static fromPrivateScalar(scalar) { return Point.BASE.multiply(scalar); } toBytes() { const { x, y } = this.toAffine(); const bytes = (0, utils_ts_1.numberToBytesLE)(y, Fp.BYTES); // each y has 2 x values (x, -y) bytes[bytes.length - 1] |= x & _1n ? 0x80 : 0; // when compressing, it's enough to store y return bytes; // and use the last byte to encode sign of x } /** @deprecated use `toBytes` */ toRawBytes() { return this.toBytes(); } toHex() { return (0, utils_ts_1.bytesToHex)(this.toBytes()); } toString() { return `<Point ${this.is0() ? 'ZERO' : this.toHex()}>`; } } // base / generator point Point.BASE = new Point(CURVE.Gx, CURVE.Gy, _1n, modP(CURVE.Gx * CURVE.Gy)); // zero / infinity / identity point Point.ZERO = new Point(_0n, _1n, _1n, _0n); // 0, 1, 1, 0 // fields Point.Fp = Fp; Point.Fn = Fn; const wnaf = (0, curve_ts_1.wNAF)(Point, Fn.BYTES * 8); // Fn.BITS? return Point; } /** * Initializes EdDSA signatures over given Edwards curve. */ function eddsa(Point, eddsaOpts) { (0, utils_ts_1._validateObject)(eddsaOpts, { hash: 'function', }, { adjustScalarBytes: 'function', randomBytes: 'function', domain: 'function', prehash: 'function', mapToCurve: 'function', }); const { prehash, hash: cHash } = eddsaOpts; const { BASE: G, Fp, Fn } = Point; const CURVE_ORDER = Fn.ORDER; const randomBytes_ = eddsaOpts.randomBytes || utils_ts_1.randomBytes; const adjustScalarBytes = eddsaOpts.adjustScalarBytes || ((bytes) => bytes); // NOOP const domain = eddsaOpts.domain || ((data, ctx, phflag) => { (0, utils_ts_1.abool)('phflag', phflag); if (ctx.length || phflag) throw new Error('Contexts/pre-hash are not supported'); return data; }); // NOOP function modN(a) { return Fn.create(a); } // Little-endian SHA512 with modulo n function modN_LE(hash) { // Not using Fn.fromBytes: hash can be 2*Fn.BYTES return modN((0, utils_ts_1.bytesToNumberLE)(hash)); } // Get the hashed private scalar per RFC8032 5.1.5 function getPrivateScalar(key) { const len = Fp.BYTES; key = (0, utils_ts_1.ensureBytes)('private key', key, len); // Hash private key with curve's hash function to produce uniformingly random input // Check byte lengths: ensure(64, h(ensure(32, key))) const hashed = (0, utils_ts_1.ensureBytes)('hashed private key', cHash(key), 2 * len); const head = adjustScalarBytes(hashed.slice(0, len)); // clear first half bits, produce FE const prefix = hashed.slice(len, 2 * len); // second half is called key prefix (5.1.6) const scalar = modN_LE(head); // The actual private scalar return { head, prefix, scalar }; } // Convenience method that creates public key from scalar. RFC8032 5.1.5 function getExtendedPublicKey(key) { const { head, prefix, scalar } = getPrivateScalar(key); const point = G.multiply(scalar); // Point on Edwards curve aka public key const pointBytes = point.toBytes(); return { head, prefix, scalar, point, pointBytes }; } // Calculates EdDSA pub key. RFC8032 5.1.5. Privkey is hashed. Use first half with 3 bits cleared function getPublicKey(privKey) { return getExtendedPublicKey(privKey).pointBytes; } // int('LE', SHA512(dom2(F, C) || msgs)) mod N function hashDomainToScalar(context = Uint8Array.of(), ...msgs) { const msg = (0, utils_ts_1.concatBytes)(...msgs); return modN_LE(cHash(domain(msg, (0, utils_ts_1.ensureBytes)('context', context), !!prehash))); } /** Signs message with privateKey. RFC8032 5.1.6 */ function sign(msg, privKey, options = {}) { msg = (0, utils_ts_1.ensureBytes)('message', msg); if (prehash) msg = prehash(msg); // for ed25519ph etc. const { prefix, scalar, pointBytes } = getExtendedPublicKey(privKey); const r = hashDomainToScalar(options.context, prefix, msg); // r = dom2(F, C) || prefix || PH(M) const R = G.multiply(r).toBytes(); // R = rG const k = hashDomainToScalar(options.context, R, pointBytes, msg); // R || A || PH(M) const s = modN(r + k * scalar); // S = (r + k * s) mod L (0, utils_ts_1.aInRange)('signature.s', s, _0n, CURVE_ORDER); // 0 <= s < l const L = Fp.BYTES; const res = (0, utils_ts_1.concatBytes)(R, (0, utils_ts_1.numberToBytesLE)(s, L)); return (0, utils_ts_1.ensureBytes)('result', res, L * 2); // 64-byte signature } const verifyOpts = VERIFY_DEFAULT; /** * Verifies EdDSA signature against message and public key. RFC8032 5.1.7. * An extended group equation is checked. */ function verify(sig, msg, publicKey, options = verifyOpts) { const { context, zip215 } = options; const len = Fp.BYTES; // Verifies EdDSA signature against message and public key. RFC8032 5.1.7. sig = (0, utils_ts_1.ensureBytes)('signature', sig, 2 * len); // An extended group equation is checked. msg = (0, utils_ts_1.ensureBytes)('message', msg); publicKey = (0, utils_ts_1.ensureBytes)('publicKey', publicKey, len); if (zip215 !== undefined) (0, utils_ts_1.abool)('zip215', zip215); if (prehash) msg = prehash(msg); // for ed25519ph, etc const s = (0, utils_ts_1.bytesToNumberLE)(sig.slice(len, 2 * len)); let A, R, SB; try { // zip215=true is good for consensus-critical apps. =false follows RFC8032 / NIST186-5. // zip215=true: 0 <= y < MASK (2^256 for ed25519) // zip215=false: 0 <= y < P (2^255-19 for ed25519) A = Point.fromHex(publicKey, zip215); R = Point.fromHex(sig.slice(0, len), zip215); SB = G.multiplyUnsafe(s); // 0 <= s < l is done inside } catch (error) { return false; } if (!zip215 && A.isSmallOrder()) return false; const k = hashDomainToScalar(context, R.toBytes(), A.toBytes(), msg); const RkA = R.add(A.multiplyUnsafe(k)); // Extended group equation // [8][S]B = [8]R + [8][k]A' return RkA.subtract(SB).clearCofactor().is0(); } G.precompute(8); // Enable precomputes. Slows down first publicKey computation by 20ms. const utils = { getExtendedPublicKey, /** ed25519 priv keys are uniform 32b. No need to check for modulo bias, like in secp256k1. */ randomPrivateKey: () => randomBytes_(Fp.BYTES), /** * We're doing scalar multiplication (used in getPublicKey etc) with precomputed BASE_POINT * values. This slows down first getPublicKey() by milliseconds (see Speed section), * but allows to speed-up subsequent getPublicKey() calls up to 20x. * @param windowSize 2, 4, 8, 16 */ precompute(windowSize = 8, point = Point.BASE) { return point.precompute(windowSize, false); }, }; return { getPublicKey, sign, verify, utils, Point }; } function _eddsa_legacy_opts_to_new(c) { const CURVE = { a: c.a, d: c.d, p: c.Fp.ORDER, n: c.n, h: c.h, Gx: c.Gx, Gy: c.Gy, }; const Fp = c.Fp; const Fn = (0, modular_ts_1.Field)(CURVE.n, c.nBitLength, true); const curveOpts = { Fp, Fn, uvRatio: c.uvRatio }; const eddsaOpts = { hash: c.hash, randomBytes: c.randomBytes, adjustScalarBytes: c.adjustScalarBytes, domain: c.domain, prehash: c.prehash, mapToCurve: c.mapToCurve, }; return { CURVE, curveOpts, eddsaOpts }; } function _eddsa_new_output_to_legacy(c, eddsa) { const legacy = Object.assign({}, eddsa, { ExtendedPoint: eddsa.Point, CURVE: c }); return legacy; } // TODO: remove. Use eddsa function twistedEdwards(c) { const { CURVE, curveOpts, eddsaOpts } = _eddsa_legacy_opts_to_new(c); const Point = edwards(CURVE, curveOpts); const EDDSA = eddsa(Point, eddsaOpts); return _eddsa_new_output_to_legacy(c, EDDSA); } //# sourceMappingURL=edwards.js.map