'use strict';
|
|
var BN = require('bn.js');
|
var HmacDRBG = require('hmac-drbg');
|
var utils = require('../utils');
|
var curves = require('../curves');
|
var rand = require('brorand');
|
var assert = utils.assert;
|
|
var KeyPair = require('./key');
|
var Signature = require('./signature');
|
|
function EC(options) {
|
if (!(this instanceof EC))
|
return new EC(options);
|
|
// Shortcut `elliptic.ec(curve-name)`
|
if (typeof options === 'string') {
|
assert(Object.prototype.hasOwnProperty.call(curves, options),
|
'Unknown curve ' + options);
|
|
options = curves[options];
|
}
|
|
// Shortcut for `elliptic.ec(elliptic.curves.curveName)`
|
if (options instanceof curves.PresetCurve)
|
options = { curve: options };
|
|
this.curve = options.curve.curve;
|
this.n = this.curve.n;
|
this.nh = this.n.ushrn(1);
|
this.g = this.curve.g;
|
|
// Point on curve
|
this.g = options.curve.g;
|
this.g.precompute(options.curve.n.bitLength() + 1);
|
|
// Hash for function for DRBG
|
this.hash = options.hash || options.curve.hash;
|
}
|
module.exports = EC;
|
|
EC.prototype.keyPair = function keyPair(options) {
|
return new KeyPair(this, options);
|
};
|
|
EC.prototype.keyFromPrivate = function keyFromPrivate(priv, enc) {
|
return KeyPair.fromPrivate(this, priv, enc);
|
};
|
|
EC.prototype.keyFromPublic = function keyFromPublic(pub, enc) {
|
return KeyPair.fromPublic(this, pub, enc);
|
};
|
|
EC.prototype.genKeyPair = function genKeyPair(options) {
|
if (!options)
|
options = {};
|
|
// Instantiate Hmac_DRBG
|
var drbg = new HmacDRBG({
|
hash: this.hash,
|
pers: options.pers,
|
persEnc: options.persEnc || 'utf8',
|
entropy: options.entropy || rand(this.hash.hmacStrength),
|
entropyEnc: options.entropy && options.entropyEnc || 'utf8',
|
nonce: this.n.toArray(),
|
});
|
|
var bytes = this.n.byteLength();
|
var ns2 = this.n.sub(new BN(2));
|
for (;;) {
|
var priv = new BN(drbg.generate(bytes));
|
if (priv.cmp(ns2) > 0)
|
continue;
|
|
priv.iaddn(1);
|
return this.keyFromPrivate(priv);
|
}
|
};
|
|
EC.prototype._truncateToN = function _truncateToN(msg, truncOnly) {
|
var delta = msg.byteLength() * 8 - this.n.bitLength();
|
if (delta > 0)
|
msg = msg.ushrn(delta);
|
if (!truncOnly && msg.cmp(this.n) >= 0)
|
return msg.sub(this.n);
|
else
|
return msg;
|
};
|
|
EC.prototype.sign = function sign(msg, key, enc, options) {
|
if (typeof enc === 'object') {
|
options = enc;
|
enc = null;
|
}
|
if (!options)
|
options = {};
|
|
key = this.keyFromPrivate(key, enc);
|
msg = this._truncateToN(new BN(msg, 16));
|
|
// Zero-extend key to provide enough entropy
|
var bytes = this.n.byteLength();
|
var bkey = key.getPrivate().toArray('be', bytes);
|
|
// Zero-extend nonce to have the same byte size as N
|
var nonce = msg.toArray('be', bytes);
|
|
// Instantiate Hmac_DRBG
|
var drbg = new HmacDRBG({
|
hash: this.hash,
|
entropy: bkey,
|
nonce: nonce,
|
pers: options.pers,
|
persEnc: options.persEnc || 'utf8',
|
});
|
|
// Number of bytes to generate
|
var ns1 = this.n.sub(new BN(1));
|
|
for (var iter = 0; ; iter++) {
|
var k = options.k ?
|
options.k(iter) :
|
new BN(drbg.generate(this.n.byteLength()));
|
k = this._truncateToN(k, true);
|
if (k.cmpn(1) <= 0 || k.cmp(ns1) >= 0)
|
continue;
|
|
var kp = this.g.mul(k);
|
if (kp.isInfinity())
|
continue;
|
|
var kpX = kp.getX();
|
var r = kpX.umod(this.n);
|
if (r.cmpn(0) === 0)
|
continue;
|
|
var s = k.invm(this.n).mul(r.mul(key.getPrivate()).iadd(msg));
|
s = s.umod(this.n);
|
if (s.cmpn(0) === 0)
|
continue;
|
|
var recoveryParam = (kp.getY().isOdd() ? 1 : 0) |
|
(kpX.cmp(r) !== 0 ? 2 : 0);
|
|
// Use complement of `s`, if it is > `n / 2`
|
if (options.canonical && s.cmp(this.nh) > 0) {
|
s = this.n.sub(s);
|
recoveryParam ^= 1;
|
}
|
|
return new Signature({ r: r, s: s, recoveryParam: recoveryParam });
|
}
|
};
|
|
EC.prototype.verify = function verify(msg, signature, key, enc) {
|
msg = this._truncateToN(new BN(msg, 16));
|
key = this.keyFromPublic(key, enc);
|
signature = new Signature(signature, 'hex');
|
|
// Perform primitive values validation
|
var r = signature.r;
|
var s = signature.s;
|
if (r.cmpn(1) < 0 || r.cmp(this.n) >= 0)
|
return false;
|
if (s.cmpn(1) < 0 || s.cmp(this.n) >= 0)
|
return false;
|
|
// Validate signature
|
var sinv = s.invm(this.n);
|
var u1 = sinv.mul(msg).umod(this.n);
|
var u2 = sinv.mul(r).umod(this.n);
|
var p;
|
|
if (!this.curve._maxwellTrick) {
|
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
|
|
p = this.g.jmulAdd(u1, key.getPublic(), u2);
|
if (p.isInfinity())
|
return false;
|
|
// 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) {
|
assert((3 & j) === j, 'The recovery param is more than two bits');
|
signature = new Signature(signature, enc);
|
|
var n = this.n;
|
var e = new BN(msg);
|
var r = signature.r;
|
var s = signature.s;
|
|
// A set LSB signifies that the y-coordinate is odd
|
var isYOdd = j & 1;
|
var isSecondKey = j >> 1;
|
if (r.cmp(this.curve.p.umod(this.curve.n)) >= 0 && isSecondKey)
|
throw new Error('Unable to find sencond key candinate');
|
|
// 1.1. Let x = r + jn.
|
if (isSecondKey)
|
r = this.curve.pointFromX(r.add(this.curve.n), isYOdd);
|
else
|
r = this.curve.pointFromX(r, isYOdd);
|
|
var rInv = signature.r.invm(n);
|
var s1 = n.sub(e).mul(rInv).umod(n);
|
var s2 = s.mul(rInv).umod(n);
|
|
// 1.6.1 Compute Q = r^-1 (sR - eG)
|
// Q = r^-1 (sR + -eG)
|
return this.g.mulAdd(s1, r, s2);
|
};
|
|
EC.prototype.getKeyRecoveryParam = function(e, signature, Q, enc) {
|
signature = new Signature(signature, enc);
|
if (signature.recoveryParam !== null)
|
return signature.recoveryParam;
|
|
for (var i = 0; i < 4; i++) {
|
var Qprime;
|
try {
|
Qprime = this.recoverPubKey(e, signature, i);
|
} catch (e) {
|
continue;
|
}
|
|
if (Qprime.eq(Q))
|
return i;
|
}
|
throw new Error('Unable to find valid recovery factor');
|
};
|