Skip to content

Commit

Permalink
eddsa: implementation and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sublimator authored and indutny committed Jun 25, 2015
1 parent d86cd2a commit c0690b3
Show file tree
Hide file tree
Showing 13 changed files with 5,368 additions and 6 deletions.
1 change: 1 addition & 0 deletions lib/elliptic.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ elliptic.curves = require('./elliptic/curves');

// Protocols
elliptic.ec = require('./elliptic/ec');
elliptic.eddsa = require('./elliptic/eddsa');
38 changes: 36 additions & 2 deletions lib/elliptic/curve/edwards.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ EdwardsCurve.prototype.jpoint = function jpoint(x, y, z, t) {
return this.point(x, y, z, t);
};

EdwardsCurve.prototype.pointFromX = function pointFromX(odd, x) {
EdwardsCurve.prototype.pointFromX = function pointFromX(x, odd) {
x = new bn(x, 16);
if (!x.red)
x = x.toRed(this.red);
Expand All @@ -61,7 +61,35 @@ EdwardsCurve.prototype.pointFromX = function pointFromX(odd, x) {
if (odd && !isOdd || !odd && isOdd)
y = y.redNeg();

return this.point(x, y, curve.one);
return this.point(x, y);
};

EdwardsCurve.prototype.pointFromY = function pointFromY(y, odd) {
y = new bn(y, 16);
if (!y.red)
y = y.toRed(this.red);

// x^2 = (y^2 - 1) / (d y^2 + 1)
var y2 = y.redSqr();
var lhs = y2.redSub(this.one);
var rhs = y2.redMul(this.d).redAdd(this.one);
var x2 = lhs.redMul(rhs.redInvm());

if (x2.cmp(this.zero) === 0) {
if (odd)
throw new Error('invalid point');
else
return this.point(this.zero, y);
}

var x = x2.redSqrt();
if (x.redSqr().redSub(x2).cmp(this.zero) !== 0)
throw new Error('invalid point');

if (x.isOdd() !== odd)
x = x.redNeg();

return this.point(x, y);
};

EdwardsCurve.prototype.validate = function validate(point) {
Expand Down Expand Up @@ -366,6 +394,12 @@ Point.prototype.getY = function getY() {
return this.y.fromRed();
};

Point.prototype.eq = function eq(other) {
return this === other ||
this.getX().cmp(other.getX()) === 0 &&
this.getY().cmp(other.getY()) === 0;
};

// Compatibility with BaseCurve
Point.prototype.toP = Point.prototype.normalize;
Point.prototype.mixedAdd = Point.prototype.add;
2 changes: 1 addition & 1 deletion lib/elliptic/curve/short.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ ShortCurve.prototype._endoSplit = function _endoSplit(k) {
return { k1: k1, k2: k2 };
};

ShortCurve.prototype.pointFromX = function pointFromX(odd, x) {
ShortCurve.prototype.pointFromX = function pointFromX(x, odd) {
x = new bn(x, 16);
if (!x.red)
x = x.toRed(this.red);
Expand Down
2 changes: 1 addition & 1 deletion lib/elliptic/ec/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ EC.prototype.recoverPubKey = function(msg, signature, j, enc) {
throw new Error('Unable to find sencond key candinate');

// 1.1. Let x = r + jn.
r = this.curve.pointFromX(isYOdd, r);
r = this.curve.pointFromX(r, isYOdd);
var eNeg = e.neg().mod(n);

// 1.6.1 Compute Q = r^-1 (sR - eG)
Expand Down
2 changes: 1 addition & 1 deletion lib/elliptic/ec/key.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ KeyPair.prototype._importPublicShort = function _importPublicShort(key) {
key.slice(1, 1 + len),
key.slice(1 + len, 1 + 2 * len));
} else if ((key[0] === 0x02 || key[0] === 0x03) && key.length - 1 === len) {
this.pub = this.ec.curve.pointFromX(key[0] === 0x03, key.slice(1, 1 + len));
this.pub = this.ec.curve.pointFromX(key.slice(1, 1 + len), key[0] === 0x03);
}
};

Expand Down
110 changes: 110 additions & 0 deletions lib/elliptic/eddsa/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use strict';

var hash = require('hash.js');
var elliptic = require('../../elliptic');
var utils = elliptic.utils;
var assert = utils.assert;
var parseBytes = utils.parseBytes;
var KeyPair = require('./key');
var Signature = require('./signature');

function EDDSA(curve) {
assert(curve === 'ed25519', 'only tested with ed25519 so far');

if (!(this instanceof EDDSA))
return new EDDSA(curve);

var curve = elliptic.curves[curve].curve;
this.curve = curve;
this.g = curve.g;
this.g.precompute(curve.n.bitLength() + 1);

this.pointClass = curve.point().constructor;
this.encodingLength = Math.ceil(curve.n.bitLength() / 8);
this.hash = hash.sha512;
}

module.exports = EDDSA;

/**
* @param {Array|String} message - message bytes
* @param {Array|String|KeyPair} secret - secret bytes or a keypair
* @returns {Signature} - signature
*/
EDDSA.prototype.sign = function sign(message, secret) {
message = parseBytes(message);
var key = this.keyFromSecret(secret);
var r = this.hashInt(key.messagePrefix(), message);
var R = this.g.mul(r);
var Rencoded = this.encodePoint(R);
var s_ = this.hashInt(Rencoded, key.pubBytes(), message)
.mul(key.priv());
var S = r.add(s_).mod(this.curve.n);
return this.makeSignature({ R: R, S: S, Rencoded: Rencoded });
};

/**
* @param {Array} message - message bytes
* @param {Array|String|Signature} sig - sig bytes
* @param {Array|String|Point|KeyPair} pub - public key
* @returns {Boolean} - true if public key matches sig of message
*/
EDDSA.prototype.verify = function verify(message, sig, pub) {
message = parseBytes(message);
sig = this.makeSignature(sig);
var key = this.keyFromPublic(pub);
var h = this.hashInt(sig.Rencoded(), key.pubBytes(), message);
var SG = this.g.mul(sig.S());
var RplusAh = sig.R().add(key.pub().mul(h));
return RplusAh.eq(SG);
};

EDDSA.prototype.hashInt = function hashInt() {
var hash = this.hash();
for (var i = 0; i < arguments.length; i++) {
hash.update(arguments[i]);
}
return utils.intFromLE(hash.digest()).mod(this.curve.n);
};

EDDSA.prototype.keyFromPublic = function keyFromPublic(pub) {
return KeyPair.fromPublic(this, pub);
};

EDDSA.prototype.keyFromSecret = function keyFromSecret(secret) {
return KeyPair.fromSecret(this, secret);
};

EDDSA.prototype.makeSignature = function makeSignature(sig) {
if (sig instanceof Signature)
return sig;
return new Signature(this, sig);
};

/**
* * https://tools.ietf.org/html/draft-josefsson-eddsa-ed25519-03#section-5.2
*
* EDDSA defines methods for encoding and decoding points and integers. These are
* helper convenience methods, that pass along to utility functions implied
* parameters.
*
*/
EDDSA.prototype.encodePoint = function encodePoint(point) {
return utils.pointToLEYoddX(point, this.encodingLength);
};

EDDSA.prototype.decodePoint = function decodePoint(bytes) {
return utils.pointFromLEYoddX(this.curve, bytes, bytes.length);
};

EDDSA.prototype.encodeInt = function encodeInt(num) {
return utils.intToLE(num, this.encodingLength);
};

EDDSA.prototype.decodeInt = function decodeInt(bytes) {
return utils.intFromLE(bytes);
};

EDDSA.prototype.isPoint = function isPoint(val) {
return val instanceof this.pointClass;
};
96 changes: 96 additions & 0 deletions lib/elliptic/eddsa/key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use strict';

var elliptic = require('../../elliptic');
var utils = elliptic.utils;
var assert = utils.assert;
var parseBytes = utils.parseBytes;
var lazyComputed = utils.lazyComputed;

/**
* @param {EDDSA} eddsa - instance
* @param {Object} params - public/private key parameters
*
* @param {Array<Byte>} [params.secret] - secret seed bytes
* @param {Point} [params.pub] - public key point (aka `A` in eddsa terms)
* @param {Array<Byte>} [params.pub] - public key point encoded as bytes
*
*/
function KeyPair(eddsa, params) {
this.eddsa = eddsa;
this._secret = parseBytes(params.secret);
if (eddsa.isPoint(params.pub))
this._pub = params.pub;
else
this._pubBytes = parseBytes(params.pub);
}

KeyPair.fromPublic = function fromPublic(eddsa, pub) {
if (pub instanceof KeyPair)
return pub;
return new KeyPair(eddsa, { pub: pub });
};

KeyPair.fromSecret = function fromSecret(eddsa, secret) {
if (secret instanceof KeyPair)
return secret;
return new KeyPair(eddsa, { secret: secret });
};

KeyPair.prototype.secret = function secret() {
return this._secret;
};

lazyComputed(KeyPair, 'pubBytes', function pubBytes() {
return this.eddsa.encodePoint(this.pub());
});

lazyComputed(KeyPair, 'pub', function pub() {
if (this._pubBytes)
return this.eddsa.decodePoint(this._pubBytes);
return this.eddsa.g.mul(this.priv());
});

lazyComputed(KeyPair, 'privBytes', function privBytes() {
var eddsa = this.eddsa;
var hash = this.hash();
var lastIx = eddsa.encodingLength - 1;

var a = hash.slice(0, eddsa.encodingLength);
a[0] &= 248;
a[lastIx] &= 127;
a[lastIx] |= 64;

return a;
});

lazyComputed(KeyPair, 'priv', function priv() {
return this.eddsa.decodeInt(this.privBytes());
});

lazyComputed(KeyPair, 'hash', function hash() {
return this.eddsa.hash().update(this.secret()).digest();
});

lazyComputed(KeyPair, 'messagePrefix', function messagePrefix() {
return this.hash().slice(this.eddsa.encodingLength);
});

KeyPair.prototype.sign = function sign(message) {
assert(this._secret, 'KeyPair can only verify');
return this.eddsa.sign(message, this);
};

KeyPair.prototype.verify = function verify(message, sig) {
return this.eddsa.verify(message, sig, this);
};

KeyPair.prototype.getSecret = function getSecret(enc) {
assert(this._secret, 'KeyPair is public only');
return utils.encode(this.secret(), enc);
};

KeyPair.prototype.getPublic = function getPublic(enc) {
return utils.encode(this.pubBytes(), enc);
};

module.exports = KeyPair;
66 changes: 66 additions & 0 deletions lib/elliptic/eddsa/signature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';

var bn = require('bn.js');
var elliptic = require('../../elliptic');
var utils = elliptic.utils;
var assert = utils.assert;
var lazyComputed = utils.lazyComputed;
var parseBytes = utils.parseBytes;

/**
* @param {EDDSA} eddsa - eddsa instance
* @param {Array<Bytes>|Object} sig -
* @param {Array<Bytes>|Point} [sig.R] - R point as Point or bytes
* @param {Array<Bytes>|bn} [sig.S] - S scalar as bn or bytes
* @param {Array<Bytes>} [sig.Rencoded] - R point encoded
* @param {Array<Bytes>} [sig.Sencoded] - S scalar encoded
*/
function Signature(eddsa, sig) {
this.eddsa = eddsa;

if (typeof sig !== 'object')
sig = parseBytes(sig);

if (Array.isArray(sig)) {
sig = {
R: sig.slice(0, eddsa.encodingLength),
S: sig.slice(eddsa.encodingLength)
};
}

assert(sig.R && sig.S, 'Signature without R or S');

if (eddsa.isPoint(sig.R))
this._R = sig.R;
if (sig.S instanceof bn)
this._S = sig.S;

this._Rencoded = Array.isArray(sig.R) ? sig.R : sig.Rencoded;
this._Sencoded = Array.isArray(sig.S) ? sig.S : sig.Sencoded;
}

lazyComputed(Signature, 'S', function S() {
return this.eddsa.decodeInt(this.Sencoded());
});

lazyComputed(Signature, 'R', function S() {
return this.eddsa.decodePoint(this.Rencoded());
});

lazyComputed(Signature, 'Rencoded', function S() {
return this.eddsa.encodePoint(this.R());
});

lazyComputed(Signature, 'Sencoded', function S() {
return this.eddsa.encodeInt(this.S());
});

Signature.prototype.toBytes = function toBytes() {
return this.Rencoded().concat(this.Sencoded());
};

Signature.prototype.toHex = function toHex() {
return utils.encode(this.toBytes(), 'hex').toUpperCase();
};

module.exports = Signature;
Loading

0 comments on commit c0690b3

Please sign in to comment.