From 135e69bd7b6de37774c0e98e7ae15a4383c87da8 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 27 Dec 2022 00:27:09 +0000 Subject: [PATCH] Utilize complete formulas for weierstrass curves --- README.md | 2 +- curve-definitions/package.json | 2 +- curve-definitions/src/bls12-381.ts | 6 +- curve-definitions/src/secp256k1.ts | 2 +- curve-definitions/src/stark.ts | 30 +-- curve-definitions/test/basic.test.js | 2 +- curve-definitions/test/secp256k1.test.js | 6 +- src/bls.ts | 4 +- src/weierstrass.ts | 312 ++++++++++++----------- 9 files changed, 194 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index b7c0ab8..0a38864 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,7 @@ export type CurveFn = { signature: Hex | SignatureType, msgHash: Hex, publicKey: PubKey, opts?: {lowS?: boolean;} ) => boolean; Point: PointConstructor; - JacobianPoint: JacobianPointConstructor; + ProjectivePoint: ProjectivePointConstructor; Signature: SignatureConstructor; utils: { mod: (a: bigint, b?: bigint) => bigint; diff --git a/curve-definitions/package.json b/curve-definitions/package.json index 1626d95..1bdc8b7 100644 --- a/curve-definitions/package.json +++ b/curve-definitions/package.json @@ -9,7 +9,7 @@ "module": "lib/index.js", "types": "lib/index.d.ts", "dependencies": { - "@noble/curves": "0.4.0", + "@noble/curves": "file:..", "@noble/hashes": "1.1.5" }, "devDependencies": { diff --git a/curve-definitions/src/bls12-381.ts b/curve-definitions/src/bls12-381.ts index 601b38c..642cead 100644 --- a/curve-definitions/src/bls12-381.ts +++ b/curve-definitions/src/bls12-381.ts @@ -14,7 +14,7 @@ import { bitMask, } from '@noble/curves/utils'; // Types -import { PointType, JacobianPointType, JacobianConstructor } from '@noble/curves/weierstrass'; +import { PointType, ProjectivePointType, ProjectiveConstructor } from '@noble/curves/weierstrass'; // Differences from bls12-381: // - PointG1 -> G1.Point @@ -1010,7 +1010,7 @@ function psi(x: Fp2, y: Fp2): [Fp2, Fp2] { return [x2, y2]; } // Ψ endomorphism -function G2psi(c: JacobianConstructor, P: JacobianPointType) { +function G2psi(c: ProjectiveConstructor, P: ProjectivePointType) { const affine = P.toAffine(); const p = psi(affine.x, affine.y); return new c(p[0], p[1], Fp2.ONE); @@ -1023,7 +1023,7 @@ const PSI2_C1 = function psi2(x: Fp2, y: Fp2): [Fp2, Fp2] { return [Fp2.multiply(x, PSI2_C1), Fp2.negate(y)]; } -function G2psi2(c: JacobianConstructor, P: JacobianPointType) { +function G2psi2(c: ProjectiveConstructor, P: ProjectivePointType) { const affine = P.toAffine(); const p = psi2(affine.x, affine.y); return new c(p[0], p[1], Fp2.ONE); diff --git a/curve-definitions/src/secp256k1.ts b/curve-definitions/src/secp256k1.ts index f4cf2ec..388c843 100644 --- a/curve-definitions/src/secp256k1.ts +++ b/curve-definitions/src/secp256k1.ts @@ -18,7 +18,7 @@ import { randomBytes } from '@noble/hashes/utils'; * efficiently computable Frobenius endomorphism. * Endomorphism improves efficiency: * Uses 2x less RAM, speeds up precomputation by 2x and ECDH / sign key recovery by 20%. - * Should always be used for Jacobian's double-and-add multiplication. + * Should always be used for Projective's double-and-add multiplication. * For affines cached multiplication, it trades off 1/2 init time & 1/3 ram for 20% perf hit. * https://gist.github.com/paulmillr/eb670806793e84df628a7c434a873066 */ diff --git a/curve-definitions/src/stark.ts b/curve-definitions/src/stark.ts index 31c71e7..cc86a74 100644 --- a/curve-definitions/src/stark.ts +++ b/curve-definitions/src/stark.ts @@ -3,12 +3,12 @@ import { keccak_256 } from '@noble/hashes/sha3'; import { sha256 } from '@noble/hashes/sha256'; import { hmac } from '@noble/hashes/hmac'; import { concatBytes, randomBytes } from '@noble/hashes/utils'; -import { weierstrass, JacobianPointType } from '@noble/curves/weierstrass'; +import { weierstrass, ProjectivePointType } from '@noble/curves/weierstrass'; import * as cutils from '@noble/curves/utils'; import { Fp } from '@noble/curves/modular'; import { getHash } from './_shortw_utils.js'; -type JacobianPoint = JacobianPointType; +type ProjectivePoint = ProjectivePointType; // Stark-friendly elliptic curve // https://docs.starkware.co/starkex/stark-curve.html @@ -108,13 +108,13 @@ function verify0x(signature: Hex, msgHash: Hex, pubKey: Hex) { return starkCurve.verify(sig, ensureBytes0x(msgHash), ensureBytes0x(pubKey)); } -const { CURVE, Point, JacobianPoint, Signature } = starkCurve; +const { CURVE, Point, ProjectivePoint, Signature } = starkCurve; export const utils = starkCurve.utils; export { CURVE, Point, Signature, - JacobianPoint, + ProjectivePoint, getPublicKey0x as getPublicKey, getSharedSecret0x as getSharedSecret, sign0x as sign, @@ -173,7 +173,7 @@ export function getAccountPath( } // https://docs.starkware.co/starkex/pedersen-hash-function.html -const PEDERSEN_POINTS = [ +const PEDERSEN_POINTS_AFFINE = [ new Point( 2089986280348253421170679821480865132823066470938446095505822317253594081284n, 1713931329540660377023406109199410414810705867260802078187082345529207694986n @@ -196,10 +196,10 @@ const PEDERSEN_POINTS = [ ), ]; // for (const p of PEDERSEN_POINTS) p._setWindowSize(8); -const PEDERSEN_POINTS_JACOBIAN = PEDERSEN_POINTS.map(JacobianPoint.fromAffine); +const PEDERSEN_POINTS = PEDERSEN_POINTS_AFFINE.map(ProjectivePoint.fromAffine); -function pedersenPrecompute(p1: JacobianPoint, p2: JacobianPoint): JacobianPoint[] { - const out: JacobianPoint[] = []; +function pedersenPrecompute(p1: ProjectivePoint, p2: ProjectivePoint): ProjectivePoint[] { + const out: ProjectivePoint[] = []; let p = p1; for (let i = 0; i < 248; i++) { out.push(p); @@ -212,14 +212,8 @@ function pedersenPrecompute(p1: JacobianPoint, p2: JacobianPoint): JacobianPoint } return out; } -const PEDERSEN_POINTS1 = pedersenPrecompute( - PEDERSEN_POINTS_JACOBIAN[1], - PEDERSEN_POINTS_JACOBIAN[2] -); -const PEDERSEN_POINTS2 = pedersenPrecompute( - PEDERSEN_POINTS_JACOBIAN[3], - PEDERSEN_POINTS_JACOBIAN[4] -); +const PEDERSEN_POINTS1 = pedersenPrecompute(PEDERSEN_POINTS[1], PEDERSEN_POINTS[2]); +const PEDERSEN_POINTS2 = pedersenPrecompute(PEDERSEN_POINTS[3], PEDERSEN_POINTS[4]); type PedersenArg = Hex | bigint | number; function pedersenArg(arg: PedersenArg): bigint { @@ -235,7 +229,7 @@ function pedersenArg(arg: PedersenArg): bigint { return value; } -function pedersenSingle(point: JacobianPoint, value: PedersenArg, constants: JacobianPoint[]) { +function pedersenSingle(point: ProjectivePoint, value: PedersenArg, constants: ProjectivePoint[]) { let x = pedersenArg(value); for (let j = 0; j < 252; j++) { const pt = constants[j]; @@ -248,7 +242,7 @@ function pedersenSingle(point: JacobianPoint, value: PedersenArg, constants: Jac // shift_point + x_low * P_0 + x_high * P1 + y_low * P2 + y_high * P3 export function pedersen(x: PedersenArg, y: PedersenArg) { - let point: JacobianPoint = PEDERSEN_POINTS_JACOBIAN[0]; + let point: ProjectivePoint = PEDERSEN_POINTS[0]; point = pedersenSingle(point, x, PEDERSEN_POINTS1); point = pedersenSingle(point, y, PEDERSEN_POINTS2); return bytesToHexEth(point.toAffine().toRawBytes(true).slice(1)); diff --git a/curve-definitions/test/basic.test.js b/curve-definitions/test/basic.test.js index 62441b3..6ff38c7 100644 --- a/curve-definitions/test/basic.test.js +++ b/curve-definitions/test/basic.test.js @@ -51,7 +51,7 @@ for (const name in CURVES) { const O = name === 'secp256k1' ? secp256r1 : secp256k1; const POINTS = {}; const OTHER_POINTS = {}; - for (const name of ['Point', 'JacobianPoint', 'ExtendedPoint', 'ProjectivePoint']) { + for (const name of ['Point', 'ProjectivePoint', 'ExtendedPoint', 'ProjectivePoint']) { POINTS[name] = C[name]; OTHER_POINTS[name] = O[name]; } diff --git a/curve-definitions/test/secp256k1.test.js b/curve-definitions/test/secp256k1.test.js index 7637646..5bb0d6f 100644 --- a/curve-definitions/test/secp256k1.test.js +++ b/curve-definitions/test/secp256k1.test.js @@ -164,15 +164,15 @@ should('secp256k1.Point#multiply(privateKey)', () => { }); // multiply() should equal multiplyUnsafe() -// should('JacobianPoint#multiplyUnsafe', () => { -// const p0 = new secp.JacobianPoint( +// should('ProjectivePoint#multiplyUnsafe', () => { +// const p0 = new secp.ProjectivePoint( // 55066263022277343669578718895168534326250603453777594175500187360389116729240n, // 32670510020758816978083085130507043184471273380659243275938904335757337482424n, // 1n // ); // const z = 106011723082030650010038151861333186846790370053628296836951575624442507889495n; // console.log(p0.multiply(z)); -// console.log(secp.JacobianPoint.normalizeZ([p0.multiplyUnsafe(z)])[0]) +// console.log(secp.ProjectivePoint.normalizeZ([p0.multiplyUnsafe(z)])[0]) // }); should('secp256k1.Signature.fromCompactHex() roundtrip', () => { diff --git a/src/bls.ts b/src/bls.ts index 8d2ab99..e44f39f 100644 --- a/src/bls.ts +++ b/src/bls.ts @@ -337,7 +337,7 @@ export function bls( if (!publicKeys.length) throw new Error('Expected non-empty array'); const agg = publicKeys .map(normP1) - .reduce((sum, p) => sum.add(G1.JacobianPoint.fromAffine(p)), G1.JacobianPoint.ZERO); + .reduce((sum, p) => sum.add(G1.ProjectivePoint.fromAffine(p)), G1.ProjectivePoint.ZERO); const aggAffine = agg.toAffine(); if (publicKeys[0] instanceof G1.Point) { aggAffine.assertValidity(); @@ -354,7 +354,7 @@ export function bls( if (!signatures.length) throw new Error('Expected non-empty array'); const agg = signatures .map(normP2) - .reduce((sum, s) => sum.add(G2.JacobianPoint.fromAffine(s)), G2.JacobianPoint.ZERO); + .reduce((sum, s) => sum.add(G2.ProjectivePoint.fromAffine(s)), G2.ProjectivePoint.ZERO); const aggAffine = agg.toAffine(); if (signatures[0] instanceof G2.Point) { aggAffine.assertValidity(); diff --git a/src/weierstrass.ts b/src/weierstrass.ts index f36e53f..f315e1a 100644 --- a/src/weierstrass.ts +++ b/src/weierstrass.ts @@ -41,8 +41,11 @@ export type BasicCurve = utils.BasicCurve & { // Endomorphism options for Koblitz curves endo?: EndomorphismOpts; // Torsions, can be optimized via endomorphisms - isTorsionFree?: (c: JacobianConstructor, point: JacobianPointType) => boolean; - clearCofactor?: (c: JacobianConstructor, point: JacobianPointType) => JacobianPointType; + isTorsionFree?: (c: ProjectiveConstructor, point: ProjectivePointType) => boolean; + clearCofactor?: ( + c: ProjectiveConstructor, + point: ProjectivePointType + ) => ProjectivePointType; // Hash to field opts htfDefaults?: htfOpts; mapToCurve?: (scalar: bigint[]) => { x: T; y: T }; @@ -94,9 +97,7 @@ function parseDERSignature(data: Uint8Array) { // Be friendly to bad ECMAScript parsers by not using bigint literals like 123n const _0n = BigInt(0); const _1n = BigInt(1); -const _2n = BigInt(2); const _3n = BigInt(3); -const _8n = BigInt(8); type Entropy = Hex | true; type SignOpts = { lowS?: boolean; extraEntropy?: Entropy }; @@ -124,20 +125,20 @@ type SignOpts = { lowS?: boolean; extraEntropy?: Entropy }; */ // Instance -export interface JacobianPointType extends Group> { +export interface ProjectivePointType extends Group> { readonly x: T; readonly y: T; readonly z: T; - multiply(scalar: number | bigint, affinePoint?: PointType): JacobianPointType; - multiplyUnsafe(scalar: bigint): JacobianPointType; + multiply(scalar: number | bigint, affinePoint?: PointType): ProjectivePointType; + multiplyUnsafe(scalar: bigint): ProjectivePointType; toAffine(invZ?: T): PointType; } // Static methods -export interface JacobianConstructor extends GroupConstructor> { - new (x: T, y: T, z: T): JacobianPointType; - fromAffine(p: PointType): JacobianPointType; - toAffineBatch(points: JacobianPointType[]): PointType[]; - normalizeZ(points: JacobianPointType[]): JacobianPointType[]; +export interface ProjectiveConstructor extends GroupConstructor> { + new (x: T, y: T, z: T): ProjectivePointType; + fromAffine(p: PointType): ProjectivePointType; + toAffineBatch(points: ProjectivePointType[]): PointType[]; + normalizeZ(points: ProjectivePointType[]): ProjectivePointType[]; } // Instance export interface PointType extends Group> { @@ -199,7 +200,7 @@ function validatePointOpts(curve: CurvePointsType) { export type CurvePointsRes = { Point: PointConstructor; - JacobianPoint: JacobianConstructor; + ProjectivePoint: ProjectiveConstructor; normalizePrivateKey: (key: PrivKey) => bigint; weierstrassEquation: (x: T) => T; isWithinCurveOrder: (num: bigint) => boolean; @@ -267,130 +268,160 @@ export function weierstrassPoints(opts: CurvePointsType) { } /** - * Jacobian Point works in 3d / jacobi coordinates: (x, y, z) ∋ (x=x/z², y=y/z³) + * 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 jacobi, because its operations don't require costly inversion. + * We're doing calculations in projective, because its operations don't require costly inversion. */ - class JacobianPoint implements JacobianPointType { + class ProjectivePoint implements ProjectivePointType { constructor(readonly x: T, readonly y: T, readonly z: T) {} - static readonly BASE = new JacobianPoint(CURVE.Gx, CURVE.Gy, Fp.ONE); - static readonly ZERO = new JacobianPoint(Fp.ZERO, Fp.ONE, Fp.ZERO); + static readonly BASE = new ProjectivePoint(CURVE.Gx, CURVE.Gy, Fp.ONE); + static readonly ZERO = new ProjectivePoint(Fp.ZERO, Fp.ONE, Fp.ZERO); - static fromAffine(p: Point): JacobianPoint { + static fromAffine(p: Point): ProjectivePoint { if (!(p instanceof Point)) { - throw new TypeError('JacobianPoint#fromAffine: expected Point'); + throw new TypeError('ProjectivePoint#fromAffine: expected Point'); } // fromAffine(x:0, y:0) would produce (x:0, y:0, z:1), but we need (x:0, y:1, z:0) - if (p.equals(Point.ZERO)) return JacobianPoint.ZERO; - return new JacobianPoint(p.x, p.y, Fp.ONE); + if (p.equals(Point.ZERO)) return ProjectivePoint.ZERO; + return new ProjectivePoint(p.x, p.y, Fp.ONE); } /** - * Takes a bunch of Jacobian Points but executes only one + * Takes a bunch of Projective Points but executes only one * invert on all of them. invert is very slow operation, * so this improves performance massively. */ - static toAffineBatch(points: JacobianPoint[]): Point[] { + static toAffineBatch(points: ProjectivePoint[]): Point[] { const toInv = Fp.invertBatch(points.map((p) => p.z)); return points.map((p, i) => p.toAffine(toInv[i])); } - static normalizeZ(points: JacobianPoint[]): JacobianPoint[] { - return JacobianPoint.toAffineBatch(points).map(JacobianPoint.fromAffine); + static normalizeZ(points: ProjectivePoint[]): ProjectivePoint[] { + return ProjectivePoint.toAffineBatch(points).map(ProjectivePoint.fromAffine); } /** * Compare one point to another. */ - equals(other: JacobianPoint): boolean { - if (!(other instanceof JacobianPoint)) throw new TypeError('JacobianPoint expected'); + equals(other: ProjectivePoint): boolean { + if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected'); const { x: X1, y: Y1, z: Z1 } = this; const { x: X2, y: Y2, z: Z2 } = other; - const Z1Z1 = Fp.square(Z1); // Z1 * Z1 - const Z2Z2 = Fp.square(Z2); // Z2 * Z2 - const U1 = Fp.multiply(X1, Z2Z2); // X1 * Z2Z2 - const U2 = Fp.multiply(X2, Z1Z1); // X2 * Z1Z1 - const S1 = Fp.multiply(Fp.multiply(Y1, Z2), Z2Z2); // Y1 * Z2 * Z2Z2 - const S2 = Fp.multiply(Fp.multiply(Y2, Z1), Z1Z1); // Y2 * Z1 * Z1Z1 - return Fp.equals(U1, U2) && Fp.equals(S1, S2); + const U1 = Fp.equals(Fp.multiply(X1, Z2), Fp.multiply(X2, Z1)); + const U2 = Fp.equals(Fp.multiply(Y1, Z2), Fp.multiply(Y2, Z1)); + return U1 && U2; } /** * Flips point to one corresponding to (x, -y) in Affine coordinates. */ - negate(): JacobianPoint { - return new JacobianPoint(this.x, Fp.negate(this.y), this.z); + negate(): ProjectivePoint { + return new ProjectivePoint(this.x, Fp.negate(this.y), this.z); } - // Fast algo for doubling 2 Jacobian Points. - // From: https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl - // Cost: 1M + 8S + 1*a + 10add + 2*2 + 1*3 + 1*8. - double(): JacobianPoint { + doubleAdd(): ProjectivePoint { + return this.add(this); + } + + // 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.multiply(b, 3n); const { x: X1, y: Y1, z: Z1 } = this; - const { a } = CURVE; - // Faster algorithm: when a=0 - // From: https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l - // Cost: 2M + 5S + 6add + 3*2 + 1*3 + 1*8. - if (Fp.isZero(a)) { - const A = Fp.square(X1); // X1 * X1 - const B = Fp.square(Y1); // Y1 * Y1 - const C = Fp.square(B); // B * B - const x1b = Fp.addN(X1, B); // X1 + B - const D = Fp.multiply(Fp.subtractN(Fp.subtractN(Fp.square(x1b), A), C), _2n); // ((x1b * x1b) - A - C) * 2 - const E = Fp.multiply(A, _3n); // A * 3 - const F = Fp.square(E); // E * E - const X3 = Fp.subtract(F, Fp.multiplyN(D, _2n)); // F - 2 * D - const Y3 = Fp.subtract(Fp.multiplyN(E, Fp.subtractN(D, X3)), Fp.multiplyN(C, _8n)); // E * (D - X3) - 8 * C; - const Z3 = Fp.multiply(Fp.multiplyN(Y1, _2n), Z1); // 2 * Y1 * Z1 - return new JacobianPoint(X3, Y3, Z3); - } - const XX = Fp.square(X1); // X1 * X1 - const YY = Fp.square(Y1); // Y1 * Y1 - const YYYY = Fp.square(YY); // YY * YY - const ZZ = Fp.square(Z1); // Z1 * Z1 - const tmp1 = Fp.add(X1, YY); // X1 + YY - const S = Fp.multiply(Fp.subtractN(Fp.subtractN(Fp.square(tmp1), XX), YYYY), _2n); // 2*((X1+YY)^2-XX-YYYY) - const M = Fp.add(Fp.multiplyN(XX, _3n), Fp.multiplyN(Fp.square(ZZ), a)); // 3 * XX + a * ZZ^2 - const T = Fp.subtract(Fp.square(M), Fp.multiplyN(S, _2n)); // M^2-2*S - const X3 = T; - const Y3 = Fp.subtract(Fp.multiplyN(M, Fp.subtractN(S, T)), Fp.multiplyN(YYYY, _8n)); // M*(S-T)-8*YYYY - const y1az1 = Fp.add(Y1, Z1); // (Y1+Z1) - const Z3 = Fp.subtract(Fp.subtractN(Fp.square(y1az1), YY), ZZ); // (Y1+Z1)^2-YY-ZZ - return new JacobianPoint(X3, Y3, Z3); + let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore + let t0 = Fp.multiply(X1, X1); // step 1 + let t1 = Fp.multiply(Y1, Y1); + let t2 = Fp.multiply(Z1, Z1); + let t3 = Fp.multiply(X1, Y1); + t3 = Fp.add(t3, t3); // step 5 + Z3 = Fp.multiply(X1, Z1); + Z3 = Fp.add(Z3, Z3); + X3 = Fp.multiply(a, Z3); + Y3 = Fp.multiply(b3, t2); + Y3 = Fp.add(X3, Y3); // step 10 + X3 = Fp.subtract(t1, Y3); + Y3 = Fp.add(t1, Y3); + Y3 = Fp.multiply(X3, Y3); + X3 = Fp.multiply(t3, X3); + Z3 = Fp.multiply(b3, Z3); // step 15 + t2 = Fp.multiply(a, t2); + t3 = Fp.subtract(t0, t2); + t3 = Fp.multiply(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.multiply(t0, t3); + Y3 = Fp.add(Y3, t0); + t2 = Fp.multiply(Y1, Z1); // step 25 + t2 = Fp.add(t2, t2); + t0 = Fp.multiply(t2, t3); + X3 = Fp.subtract(X3, t0); + Z3 = Fp.multiply(t2, t1); + Z3 = Fp.add(Z3, Z3); // step 30 + Z3 = Fp.add(Z3, Z3); + return new ProjectivePoint(X3, Y3, Z3); } - // Fast algo for adding 2 Jacobian Points. - // https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#addition-add-1998-cmo-2 - // Cost: 12M + 4S + 6add + 1*2 - // Note: 2007 Bernstein-Lange (11M + 5S + 9add + 4*2) is actually 10% slower. - add(other: JacobianPoint): JacobianPoint { - if (!(other instanceof JacobianPoint)) throw new TypeError('JacobianPoint expected'); + // 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: ProjectivePoint): ProjectivePoint { + if (!(other instanceof ProjectivePoint)) throw new TypeError('ProjectivePoint expected'); const { x: X1, y: Y1, z: Z1 } = this; const { x: X2, y: Y2, z: Z2 } = other; - if (Fp.isZero(X2) || Fp.isZero(Y2)) return this; - if (Fp.isZero(X1) || Fp.isZero(Y1)) return other; - // We're using same code in equals() - const Z1Z1 = Fp.square(Z1); // Z1Z1 = Z1^2 - const Z2Z2 = Fp.square(Z2); // Z2Z2 = Z2^2; - const U1 = Fp.multiply(X1, Z2Z2); // X1 * Z2Z2 - const U2 = Fp.multiply(X2, Z1Z1); // X2 * Z1Z1 - const S1 = Fp.multiply(Fp.multiply(Y1, Z2), Z2Z2); // Y1 * Z2 * Z2Z2 - const S2 = Fp.multiply(Fp.multiply(Y2, Z1), Z1Z1); // Y2 * Z1 * Z1Z1 - const H = Fp.subtractN(U2, U1); // H = U2 - U1 - const r = Fp.subtractN(S2, S1); // S2 - S1 - // H = 0 meaning it's the same point. - if (Fp.isZero(H)) return Fp.isZero(r) ? this.double() : JacobianPoint.ZERO; - const HH = Fp.square(H); // HH = H2 - const HHH = Fp.multiply(H, HH); // HHH = H * HH - const V = Fp.multiply(U1, HH); // V = U1 * HH - const X3 = Fp.subtract(Fp.subtractN(Fp.squareN(r), HHH), Fp.multiplyN(V, _2n)); // X3 = r^2 - HHH - 2 * V; - const Y3 = Fp.subtract(Fp.multiplyN(r, Fp.subtractN(V, X3)), Fp.multiplyN(S1, HHH)); // Y3 = r * (V - X3) - S1 * HHH; - const Z3 = Fp.multiply(Fp.multiply(Z1, Z2), H); // Z3 = Z1 * Z2 * H; - return new JacobianPoint(X3, Y3, Z3); + let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; // prettier-ignore + const a = CURVE.a; + const b3 = Fp.multiply(CURVE.b, 3n); + let t0 = Fp.multiply(X1, X2); // step 1 + let t1 = Fp.multiply(Y1, Y2); + let t2 = Fp.multiply(Z1, Z2); + let t3 = Fp.add(X1, Y1); + let t4 = Fp.add(X2, Y2); // step 5 + t3 = Fp.multiply(t3, t4); + t4 = Fp.add(t0, t1); + t3 = Fp.subtract(t3, t4); + t4 = Fp.add(X1, Z1); + let t5 = Fp.add(X2, Z2); // step 10 + t4 = Fp.multiply(t4, t5); + t5 = Fp.add(t0, t2); + t4 = Fp.subtract(t4, t5); + t5 = Fp.add(Y1, Z1); + X3 = Fp.add(Y2, Z2); // step 15 + t5 = Fp.multiply(t5, X3); + X3 = Fp.add(t1, t2); + t5 = Fp.subtract(t5, X3); + Z3 = Fp.multiply(a, t4); + X3 = Fp.multiply(b3, t2); // step 20 + Z3 = Fp.add(X3, Z3); + X3 = Fp.subtract(t1, Z3); + Z3 = Fp.add(t1, Z3); + Y3 = Fp.multiply(X3, Z3); + t1 = Fp.add(t0, t0); // step 25 + t1 = Fp.add(t1, t0); + t2 = Fp.multiply(a, t2); + t4 = Fp.multiply(b3, t4); + t1 = Fp.add(t1, t2); + t2 = Fp.subtract(t0, t2); // step 30 + t2 = Fp.multiply(a, t2); + t4 = Fp.add(t4, t2); + t0 = Fp.multiply(t1, t4); + Y3 = Fp.add(Y3, t0); + t0 = Fp.multiply(t5, t4); // step 35 + X3 = Fp.multiply(t3, X3); + X3 = Fp.subtract(X3, t0); + t0 = Fp.multiply(t3, t1); + Z3 = Fp.multiply(t5, Z3); + Z3 = Fp.add(Z3, t0); // step 40 + return new ProjectivePoint(X3, Y3, Z3); } - subtract(other: JacobianPoint) { + subtract(other: ProjectivePoint) { return this.add(other.negate()); } @@ -399,8 +430,8 @@ export function weierstrassPoints(opts: CurvePointsType) { * It's faster, but should only be used when you don't care about * an exposed private key e.g. sig verification, which works over *public* keys. */ - multiplyUnsafe(scalar: bigint): JacobianPoint { - const P0 = JacobianPoint.ZERO; + multiplyUnsafe(scalar: bigint): ProjectivePoint { + const P0 = ProjectivePoint.ZERO; if (typeof scalar === 'bigint' && scalar === _0n) return P0; // Will throw on 0 let n = normalizeScalar(scalar); @@ -412,7 +443,7 @@ export function weierstrassPoints(opts: CurvePointsType) { let { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n); let k1p = P0; let k2p = P0; - let d: JacobianPoint = this; + let d: ProjectivePoint = this; while (k1 > _0n || k2 > _0n) { if (k1 & _1n) k1p = k1p.add(d); if (k2 & _1n) k2p = k2p.add(d); @@ -422,22 +453,22 @@ export function weierstrassPoints(opts: CurvePointsType) { } if (k1neg) k1p = k1p.negate(); if (k2neg) k2p = k2p.negate(); - k2p = new JacobianPoint(Fp.multiply(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); + k2p = new ProjectivePoint(Fp.multiply(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); return k1p.add(k2p); } /** * Implements w-ary non-adjacent form for calculating ec multiplication. */ - private wNAF(n: bigint, affinePoint?: Point): { p: JacobianPoint; f: JacobianPoint } { - if (!affinePoint && this.equals(JacobianPoint.BASE)) affinePoint = Point.BASE; + private wNAF(n: bigint, affinePoint?: Point): { p: ProjectivePoint; f: ProjectivePoint } { + if (!affinePoint && this.equals(ProjectivePoint.BASE)) affinePoint = Point.BASE; const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1; // Calculate precomputes on a first run, reuse them after let precomputes = affinePoint && pointPrecomputes.get(affinePoint); if (!precomputes) { - precomputes = wnaf.precomputeWindow(this, W) as JacobianPoint[]; + precomputes = wnaf.precomputeWindow(this, W) as ProjectivePoint[]; if (affinePoint && W !== 1) { - precomputes = JacobianPoint.normalizeZ(precomputes); + precomputes = ProjectivePoint.normalizeZ(precomputes); pointPrecomputes.set(affinePoint, precomputes); } } @@ -452,20 +483,20 @@ export function weierstrassPoints(opts: CurvePointsType) { * @param affinePoint optional point ot save cached precompute windows on it * @returns New point */ - multiply(scalar: number | bigint, affinePoint?: Point): JacobianPoint { + multiply(scalar: number | bigint, affinePoint?: Point): ProjectivePoint { let n = normalizeScalar(scalar); // Real point. - let point: JacobianPoint; + let point: ProjectivePoint; // Fake point, we use it to achieve constant-time multiplication. - let fake: JacobianPoint; + let fake: ProjectivePoint; if (CURVE.endo) { const { k1neg, k1, k2neg, k2 } = CURVE.endo.splitScalar(n); let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint); let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint); k1p = wnaf.constTimeNegate(k1neg, k1p); k2p = wnaf.constTimeNegate(k2neg, k2p); - k2p = new JacobianPoint(Fp.multiply(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); + k2p = new ProjectivePoint(Fp.multiply(k2p.x, CURVE.endo.beta), k2p.y, k2p.z); point = k1p.add(k2p); fake = f1p.add(f2p); } else { @@ -474,46 +505,42 @@ export function weierstrassPoints(opts: CurvePointsType) { fake = f; } // Normalize `z` for both points, but return only real one - return JacobianPoint.normalizeZ([point, fake])[0]; + return ProjectivePoint.normalizeZ([point, fake])[0]; } - // Converts Jacobian point to affine (x, y) coordinates. + // 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³) - // https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#scaling-z + // (x, y, z) ∋ (x=x/z, y=y/z) toAffine(invZ?: T): Point { const { x, y, z } = this; - const is0 = this.equals(JacobianPoint.ZERO); + const is0 = this.equals(ProjectivePoint.ZERO); // 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 (invZ == null) invZ = is0 ? Fp.ONE : Fp.invert(z); - const iz1 = invZ; - const iz2 = Fp.square(iz1); // iz1 * iz1 - const iz3 = Fp.multiply(iz2, iz1); // iz2 * iz1 - const ax = Fp.multiply(x, iz2); // x * iz2 - const ay = Fp.multiply(y, iz3); // y * iz3 - const zz = Fp.multiply(z, iz1); // z * iz1 + const ax = Fp.multiply(x, invZ); + const ay = Fp.multiply(y, invZ); + const zz = Fp.multiply(z, invZ); if (is0) return Point.ZERO; if (!Fp.equals(zz, Fp.ONE)) throw new Error('invZ was invalid'); return new Point(ax, ay); } isTorsionFree(): boolean { if (CURVE.h === _1n) return true; // No subgroups, always torsion fee - if (CURVE.isTorsionFree) return CURVE.isTorsionFree(JacobianPoint, this); + if (CURVE.isTorsionFree) return CURVE.isTorsionFree(ProjectivePoint, this); // is multiplyUnsafe(CURVE.n) is always ok, same as for edwards? throw new Error('Unsupported!'); } // Clear cofactor of G1 // https://eprint.iacr.org/2019/403 - clearCofactor(): JacobianPoint { + clearCofactor(): ProjectivePoint { if (CURVE.h === _1n) return this; // Fast-path - if (CURVE.clearCofactor) return CURVE.clearCofactor(JacobianPoint, this) as JacobianPoint; + if (CURVE.clearCofactor) return CURVE.clearCofactor(ProjectivePoint, this) as ProjectivePoint; return this.multiplyUnsafe(CURVE.h); } } - const wnaf = wNAF(JacobianPoint, CURVE.endo ? nBitLength / 2 : nBitLength); + const wnaf = wNAF(ProjectivePoint, CURVE.endo ? nBitLength / 2 : nBitLength); // Stores precomputed values for points. - const pointPrecomputes = new WeakMap(); + const pointPrecomputes = new WeakMap(); /** * Default Point works in default aka affine coordinates: (x, y) @@ -601,12 +628,12 @@ export function weierstrassPoints(opts: CurvePointsType) { // Adds point to itself double() { - return JacobianPoint.fromAffine(this).double().toAffine(); + return ProjectivePoint.fromAffine(this).double().toAffine(); } // Adds point to other point add(other: Point) { - return JacobianPoint.fromAffine(this).add(JacobianPoint.fromAffine(other)).toAffine(); + return ProjectivePoint.fromAffine(this).add(ProjectivePoint.fromAffine(other)).toAffine(); } // Subtracts other point from the point @@ -615,18 +642,18 @@ export function weierstrassPoints(opts: CurvePointsType) { } multiply(scalar: number | bigint) { - return JacobianPoint.fromAffine(this).multiply(scalar, this).toAffine(); + return ProjectivePoint.fromAffine(this).multiply(scalar, this).toAffine(); } multiplyUnsafe(scalar: bigint) { - return JacobianPoint.fromAffine(this).multiplyUnsafe(scalar).toAffine(); + return ProjectivePoint.fromAffine(this).multiplyUnsafe(scalar).toAffine(); } clearCofactor() { - return JacobianPoint.fromAffine(this).clearCofactor().toAffine(); + return ProjectivePoint.fromAffine(this).clearCofactor().toAffine(); } isTorsionFree(): boolean { - return JacobianPoint.fromAffine(this).isTorsionFree(); + return ProjectivePoint.fromAffine(this).isTorsionFree(); } /** @@ -636,12 +663,12 @@ export function weierstrassPoints(opts: CurvePointsType) { * @returns non-zero affine point */ multiplyAndAddUnsafe(Q: Point, a: bigint, b: bigint): Point | undefined { - const P = JacobianPoint.fromAffine(this); + const P = ProjectivePoint.fromAffine(this); const aP = a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a); - const bQ = JacobianPoint.fromAffine(Q).multiplyUnsafe(b); + const bQ = ProjectivePoint.fromAffine(Q).multiplyUnsafe(b); const sum = aP.add(bQ); - return sum.equals(JacobianPoint.ZERO) ? undefined : sum.toAffine(); + return sum.equals(ProjectivePoint.ZERO) ? undefined : sum.toAffine(); } // Encodes byte string to elliptic curve @@ -666,7 +693,7 @@ export function weierstrassPoints(opts: CurvePointsType) { } return { Point: Point as PointConstructor, - JacobianPoint: JacobianPoint as JacobianConstructor, + ProjectivePoint: ProjectivePoint as ProjectiveConstructor, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder, @@ -732,7 +759,7 @@ export type CurveFn = { } ) => boolean; Point: PointConstructor; - JacobianPoint: JacobianConstructor; + ProjectivePoint: ProjectiveConstructor; Signature: SignatureConstructor; utils: { mod: (a: bigint, b?: bigint) => bigint; @@ -812,7 +839,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { return _0n < num && num < Fp.ORDER; } - const { Point, JacobianPoint, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder } = + const { Point, ProjectivePoint, normalizePrivateKey, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({ ...CURVE, toBytes(c, point, isCompressed: boolean): Uint8Array { @@ -1132,16 +1159,17 @@ export function weierstrass(curveDef: CurveType): CurveFn { * @returns Signature with its point on curve Q OR undefined if params were invalid */ function kmdToSig(kBytes: Uint8Array, m: bigint, d: bigint, lowS = true): Signature | undefined { + const { n } = CURVE; const k = truncateHash(kBytes, true); if (!isWithinCurveOrder(k)) return; // Important: all mod() calls in the function must be done over `n` - const { n } = CURVE; + const kinv = mod.invert(k, n); const q = Point.BASE.multiply(k); // r = x mod n const r = mod.mod(q.x, n); if (r === _0n) return; // s = (1/k * (m + dr) mod n - const s = mod.mod(mod.invert(k, n) * mod.mod(m + d * r, n), n); + const s = mod.mod(kinv * mod.mod(m + mod.mod(d * r, n), n), n); if (s === _0n) return; let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n); let normS = s; @@ -1221,7 +1249,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { const u1 = mod.mod(h * sinv, n); const u2 = mod.mod(r * sinv, n); - // Some implementations compare R.x in jacobian, without inversion. + // Some implementations compare R.x in projective, without inversion. // The speed-up is <5%, so we don't complicate the code. const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2); if (!R) return false; @@ -1235,7 +1263,7 @@ export function weierstrass(curveDef: CurveType): CurveFn { sign, verify, Point, - JacobianPoint, + ProjectivePoint, Signature, utils, };