curve: implement Greg Maxwell's trick

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.
This commit is contained in:
Fedor Indutny
2016-06-14 00:02:12 -04:00
parent 236f37395b
commit b950448bc9
6 changed files with 83 additions and 8 deletions
+17 -2
View File
@@ -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;
@@ -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;
@@ -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) {
+19 -1
View File
@@ -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() {
@@ -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
View File
@@ -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;
};
+26 -2
View File
@@ -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++) {
@@ -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++) {
@@ -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 &&
@@ -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>';
+16 -2
View File
@@ -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) {
+1 -1
View File
@@ -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"