Utilize complete formulas for weierstrass curves

This commit is contained in:
Paul Miller 2022-12-27 00:27:09 +00:00
parent 7a34c16c2b
commit 135e69bd7b
No known key found for this signature in database
GPG Key ID: 697079DA6878B89B
9 changed files with 194 additions and 172 deletions

@ -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;

@ -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": {

@ -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<Fp2>, P: JacobianPointType<Fp2>) {
function G2psi(c: ProjectiveConstructor<Fp2>, P: ProjectivePointType<Fp2>) {
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<Fp2>, P: JacobianPointType<Fp2>) {
function G2psi2(c: ProjectiveConstructor<Fp2>, P: ProjectivePointType<Fp2>) {
const affine = P.toAffine();
const p = psi2(affine.x, affine.y);
return new c(p[0], p[1], Fp2.ONE);

@ -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
*/

@ -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<bigint>;
type ProjectivePoint = ProjectivePointType<bigint>;
// 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));

@ -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];
}

@ -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', () => {

@ -337,7 +337,7 @@ export function bls<Fp2, Fp6, Fp12>(
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<Fp2, Fp6, Fp12>(
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();

@ -41,8 +41,11 @@ export type BasicCurve<T> = utils.BasicCurve<T> & {
// Endomorphism options for Koblitz curves
endo?: EndomorphismOpts;
// Torsions, can be optimized via endomorphisms
isTorsionFree?: (c: JacobianConstructor<T>, point: JacobianPointType<T>) => boolean;
clearCofactor?: (c: JacobianConstructor<T>, point: JacobianPointType<T>) => JacobianPointType<T>;
isTorsionFree?: (c: ProjectiveConstructor<T>, point: ProjectivePointType<T>) => boolean;
clearCofactor?: (
c: ProjectiveConstructor<T>,
point: ProjectivePointType<T>
) => ProjectivePointType<T>;
// 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<T> extends Group<JacobianPointType<T>> {
export interface ProjectivePointType<T> extends Group<ProjectivePointType<T>> {
readonly x: T;
readonly y: T;
readonly z: T;
multiply(scalar: number | bigint, affinePoint?: PointType<T>): JacobianPointType<T>;
multiplyUnsafe(scalar: bigint): JacobianPointType<T>;
multiply(scalar: number | bigint, affinePoint?: PointType<T>): ProjectivePointType<T>;
multiplyUnsafe(scalar: bigint): ProjectivePointType<T>;
toAffine(invZ?: T): PointType<T>;
}
// Static methods
export interface JacobianConstructor<T> extends GroupConstructor<JacobianPointType<T>> {
new (x: T, y: T, z: T): JacobianPointType<T>;
fromAffine(p: PointType<T>): JacobianPointType<T>;
toAffineBatch(points: JacobianPointType<T>[]): PointType<T>[];
normalizeZ(points: JacobianPointType<T>[]): JacobianPointType<T>[];
export interface ProjectiveConstructor<T> extends GroupConstructor<ProjectivePointType<T>> {
new (x: T, y: T, z: T): ProjectivePointType<T>;
fromAffine(p: PointType<T>): ProjectivePointType<T>;
toAffineBatch(points: ProjectivePointType<T>[]): PointType<T>[];
normalizeZ(points: ProjectivePointType<T>[]): ProjectivePointType<T>[];
}
// Instance
export interface PointType<T> extends Group<PointType<T>> {
@ -199,7 +200,7 @@ function validatePointOpts<T>(curve: CurvePointsType<T>) {
export type CurvePointsRes<T> = {
Point: PointConstructor<T>;
JacobianPoint: JacobianConstructor<T>;
ProjectivePoint: ProjectiveConstructor<T>;
normalizePrivateKey: (key: PrivKey) => bigint;
weierstrassEquation: (x: T) => T;
isWithinCurveOrder: (num: bigint) => boolean;
@ -267,130 +268,160 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
}
/**
* 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<T> {
class ProjectivePoint implements ProjectivePointType<T> {
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<T>(opts: CurvePointsType<T>) {
* 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<T>(opts: CurvePointsType<T>) {
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<T>(opts: CurvePointsType<T>) {
}
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<T>(opts: CurvePointsType<T>) {
* @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<T>(opts: CurvePointsType<T>) {
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<Point, JacobianPoint[]>();
const pointPrecomputes = new WeakMap<Point, ProjectivePoint[]>();
/**
* Default Point works in default aka affine coordinates: (x, y)
@ -601,12 +628,12 @@ export function weierstrassPoints<T>(opts: CurvePointsType<T>) {
// 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<T>(opts: CurvePointsType<T>) {
}
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<T>(opts: CurvePointsType<T>) {
* @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<T>(opts: CurvePointsType<T>) {
}
return {
Point: Point as PointConstructor<T>,
JacobianPoint: JacobianPoint as JacobianConstructor<T>,
ProjectivePoint: ProjectivePoint as ProjectiveConstructor<T>,
normalizePrivateKey,
weierstrassEquation,
isWithinCurveOrder,
@ -732,7 +759,7 @@ export type CurveFn = {
}
) => boolean;
Point: PointConstructor<bigint>;
JacobianPoint: JacobianConstructor<bigint>;
ProjectivePoint: ProjectiveConstructor<bigint>;
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,
};