Skip to content

Commit

Permalink
curve: implement Greg Maxwell's trick
Browse files Browse the repository at this point in the history
TL;DR:

Prior to this patch `.mulAdd()` converted point from Jacobian to
Cartesian representation to check its' `.getX()` value against
`signature.r`. However, it is possible to avoid the expensive inversion
in `.toP()` conversion function by multiplying `signature.r` with
`point.z.redSqr()`. Some adjustments (by adding `curve.n` several times)
may be needed, but overall it saves about 5% of execution time during
`.verify()`.

Big kudos to @gmaxwell for implementing it, and to @briansmith for
sharing it with me.
  • Loading branch information
indutny committed Jun 14, 2016
1 parent 236f373 commit b950448
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 8 deletions.
19 changes: 17 additions & 2 deletions lib/elliptic/curve/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ function BaseCurve(type, conf) {
this._wnafT2 = new Array(4);
this._wnafT3 = new Array(4);
this._wnafT4 = new Array(4);

// Generalized Greg Maxwell's trick
var adjustCount = this.n && this.p.div(this.n);
if (!adjustCount || adjustCount.cmpn(100) > 0) {
this._maxwellAdjust = 0;
this.redN = null;
} else {
this._maxwellAdjust = adjustCount.toNumber();
this.redN = this.n.toRed(this.red);
}
}
module.exports = BaseCurve;

Expand Down Expand Up @@ -116,7 +126,8 @@ BaseCurve.prototype._wnafMul = function _wnafMul(p, k) {
BaseCurve.prototype._wnafMulAdd = function _wnafMulAdd(defW,
points,
coeffs,
len) {
len,
jacobianResult) {
var wndWidth = this._wnafT1;
var wnd = this._wnafT2;
var naf = this._wnafT3;
Expand Down Expand Up @@ -229,7 +240,11 @@ BaseCurve.prototype._wnafMulAdd = function _wnafMulAdd(defW,
// Zeroify references
for (var i = 0; i < len; i++)
wnd[i] = null;
return acc.toP();

if (jacobianResult)
return acc;
else
return acc.toP();
};

function BasePoint(curve, type) {
Expand Down
20 changes: 19 additions & 1 deletion lib/elliptic/curve/edwards.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,11 @@ Point.prototype.mul = function mul(k) {
};

Point.prototype.mulAdd = function mulAdd(k1, p, k2) {
return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2);
return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2, false);
};

Point.prototype.jmulAdd = function jmulAdd(k1, p, k2) {
return this.curve._wnafMulAdd(1, [ this, p ], [ k1, k2 ], 2, true);
};

Point.prototype.normalize = function normalize() {
Expand Down Expand Up @@ -405,6 +409,20 @@ Point.prototype.eq = function eq(other) {
this.getY().cmp(other.getY()) === 0;
};

Point.prototype.eqXToP = function eqXToP(x) {
var rx = x.toRed(this.curve.red).redMul(this.z);
if (this.x.cmp(rx) === 0)
return true;

var t = this.curve.redN.redMul(this.z);
for (var i = 1; i <= this.curve._maxwellAdjust; i++) {
rx.redIAdd(t);
if (this.x.cmp(rx) === 0)
return true;
}
return false;
};

// Compatibility with BaseCurve
Point.prototype.toP = Point.prototype.normalize;
Point.prototype.mixedAdd = Point.prototype.add;
4 changes: 4 additions & 0 deletions lib/elliptic/curve/mont.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ Point.prototype.mulAdd = function mulAdd() {
throw new Error('Not supported on Montgomery curve');
};

Point.prototype.jumlAdd = function jumlAdd() {
throw new Error('Not supported on Montgomery curve');
};

Point.prototype.eq = function eq(other) {
return this.getX().cmp(other.getX()) === 0;
};
Expand Down
28 changes: 26 additions & 2 deletions lib/elliptic/curve/short.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ ShortCurve.prototype.validate = function validate(point) {
};

ShortCurve.prototype._endoWnafMulAdd =
function _endoWnafMulAdd(points, coeffs) {
function _endoWnafMulAdd(points, coeffs, jacobianResult) {
var npoints = this._endoWnafT1;
var ncoeffs = this._endoWnafT2;
for (var i = 0; i < points.length; i++) {
Expand All @@ -239,7 +239,7 @@ ShortCurve.prototype._endoWnafMulAdd =
ncoeffs[i * 2] = split.k1;
ncoeffs[i * 2 + 1] = split.k2;
}
var res = this._wnafMulAdd(1, npoints, ncoeffs, i * 2);
var res = this._wnafMulAdd(1, npoints, ncoeffs, i * 2, jacobianResult);

// Clean-up references to points and coefficients
for (var j = 0; j < i * 2; j++) {
Expand Down Expand Up @@ -440,6 +440,15 @@ Point.prototype.mulAdd = function mulAdd(k1, p2, k2) {
return this.curve._wnafMulAdd(1, points, coeffs, 2);
};

Point.prototype.jmulAdd = function jmulAdd(k1, p2, k2) {
var points = [ this, p2 ];
var coeffs = [ k1, k2 ];
if (this.curve.endo)
return this.curve._endoWnafMulAdd(points, coeffs, true);
else
return this.curve._wnafMulAdd(1, points, coeffs, 2, true);
};

Point.prototype.eq = function eq(p) {
return this === p ||
this.inf === p.inf &&
Expand Down Expand Up @@ -895,6 +904,21 @@ JPoint.prototype.eq = function eq(p) {
return this.y.redMul(pz3).redISub(p.y.redMul(z3)).cmpn(0) === 0;
};

JPoint.prototype.eqXToP = function eqXToP(x) {
var zs = this.z.redSqr();
var rx = x.toRed(this.curve.red).redMul(zs);
if (this.x.cmp(rx) === 0)
return true;

var t = this.curve.redN.redMul(zs);
for (var i = 1; i <= this.curve._maxwellAdjust; i++) {
rx.redIAdd(t);
if (this.x.cmp(rx) === 0)
return true;
}
return false;
};

JPoint.prototype.inspect = function inspect() {
if (this.isInfinity())
return '<EC JPoint Infinity>';
Expand Down
18 changes: 16 additions & 2 deletions lib/elliptic/ec/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,25 @@ EC.prototype.verify = function verify(msg, signature, key, enc) {
var u1 = sinv.mul(msg).umod(this.n);
var u2 = sinv.mul(r).umod(this.n);

var p = this.g.mulAdd(u1, key.getPublic(), u2);
if (this.curve._maxwellAdjust === 0) {
var p = this.g.mulAdd(u1, key.getPublic(), u2);
if (p.isInfinity())
return false;

return p.getX().umod(this.n).cmp(r) === 0;
}

// NOTE: Greg Maxwell's trick, inspired by:
// https://git.io/vad3K

var p = this.g.jmulAdd(u1, key.getPublic(), u2);
if (p.isInfinity())
return false;

return p.getX().umod(this.n).cmp(r) === 0;
// Compare `p.x` of Jacobian point with `r`,
// this will do `p.x == r * p.z^2` instead of multiplying `p.x` by the
// inverse of `p.z^2`
return p.eqXToP(r);
};

EC.prototype.recoverPubKey = function(msg, signature, j, enc) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"mocha": "^2.1.0"
},
"dependencies": {
"bn.js": "^4.0.0",
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"inherits": "^2.0.1"
Expand Down

0 comments on commit b950448

Please sign in to comment.