Skip to content

Commit

Permalink
Fixes Curve25519/Ed25519
Browse files Browse the repository at this point in the history
This commit fixes point encoding problems for X25519 curves, fixes key
generation for Curve25519, and adds a key generation function for Ed25519.
  • Loading branch information
mahrud committed Jan 5, 2018
1 parent fc8f0ee commit 7e3b7ff
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 24 deletions.
28 changes: 25 additions & 3 deletions lib/elliptic/curve/mont.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ function MontCurve(conf) {
this.i4 = new BN(4).toRed(this.red).redInvm();
this.two = new BN(2).toRed(this.red);
this.a24 = this.i4.redMul(this.a.redAdd(this.two));
// FIXME shouldn't this be (a-2)/4?
// https://tools.ietf.org/html/rfc7748#section-5
// this.a24 = this.i4.redMul(this.a.redSub(this.two));
}
inherits(MontCurve, Base);
module.exports = MontCurve;
Expand Down Expand Up @@ -46,7 +49,16 @@ function Point(curve, x, z) {
inherits(Point, Base.BasePoint);

MontCurve.prototype.decodePoint = function decodePoint(bytes, enc) {
return this.point(utils.toArray(bytes, enc), 1);
var bytes = utils.toArray(bytes, enc);

// FIXME for Curve448
// Montgomery curve points must be represented in the compressed format
// https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-02#appendix-B
if (bytes.length === 33 && bytes[0] === 0x40)
bytes = bytes.slice(1, 33).reverse(); // point must be little-endian
if (bytes.length !== 32)
throw new Error('Unknown point compression format');
return this.point(bytes, 1);
};

MontCurve.prototype.point = function point(x, z) {
Expand All @@ -61,8 +73,16 @@ Point.prototype.precompute = function precompute() {
// No-op
};

Point.prototype._encode = function _encode() {
return this.getX().toArray('be', this.curve.p.byteLength());
Point.prototype._encode = function _encode(compact) {
var len = this.curve.p.byteLength();

// FIXME, really the output should always be little-endian
// https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-02#appendix-B
if (compact) {
return [ 0x40 ].concat(this.getX().toArray('le', len));
} else {
return this.getX().toArray('be', len);
}
};

Point.fromJSON = function fromJSON(curve, obj) {
Expand Down Expand Up @@ -130,6 +150,8 @@ Point.prototype.diffAdd = function diffAdd(p, diff) {
};

Point.prototype.mul = function mul(k) {
k = new BN(k, 16);

var t = k.clone();
var a = this; // (N / 2) * Q + Q
var b = this.curve.point(null, null); // (N / 2) * Q
Expand Down
9 changes: 6 additions & 3 deletions lib/elliptic/curves.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ function PresetCurve(options) {
this.curve = new elliptic.curve.short(options);
else if (options.type === 'edwards')
this.curve = new elliptic.curve.edwards(options);
else
else if (options.type === 'mont')
this.curve = new elliptic.curve.mont(options);
else throw new Error('Unknown curve type.');
this.g = this.curve.g;
this.n = this.curve.n;
this.hash = options.hash;

assert(this.g.validate(), 'Invalid curve');
assert(this.g.mul(this.n).isInfinity(), 'Invalid curve, G*N != O');
assert(this.g.mul(this.n).isInfinity(), 'Invalid curve, n*G != O');
}
curves.PresetCurve = PresetCurve;

Expand Down Expand Up @@ -132,13 +133,15 @@ defineCurve('p521', {
]
});

// https://tools.ietf.org/html/rfc7748#section-4.1
defineCurve('curve25519', {
type: 'mont',
prime: 'p25519',
p: '7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed',
a: '76d06',
b: '1',
n: '1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed',
cofactor: '8',
hash: hash.sha256,
gRed: false,
g: [
Expand All @@ -155,11 +158,11 @@ defineCurve('ed25519', {
// -121665 * (121666^(-1)) (mod P)
d: '52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3',
n: '1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed',
cofactor: '8',
hash: hash.sha256,
gRed: false,
g: [
'216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a',

// 4/5
'6666666666666666666666666666666666666666666666666666666666666658'
]
Expand Down
8 changes: 7 additions & 1 deletion lib/elliptic/ec/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function EC(options) {
this.g = options.curve.g;
this.g.precompute(options.curve.n.bitLength() + 1);

// Hash for function for DRBG
// Hash function for DRBG
this.hash = options.hash || options.curve.hash;
}
module.exports = EC;
Expand Down Expand Up @@ -64,6 +64,12 @@ EC.prototype.genKeyPair = function genKeyPair(options) {
nonce: this.n.toArray()
});

// Key generation for curve25519 is simpler
if (this.curve.type === 'mont') {
var priv = new BN(drbg.generate(32));
return this.keyFromPrivate(priv);
}

var bytes = this.n.byteLength();
var ns2 = this.n.sub(new BN(2));
do {
Expand Down
33 changes: 22 additions & 11 deletions lib/elliptic/ec/key.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ KeyPair.fromPrivate = function fromPrivate(ec, priv, enc) {
});
};

// TODO: should not validate for X25519
KeyPair.prototype.validate = function validate() {
var pub = this.getPublic();

Expand All @@ -51,13 +52,7 @@ KeyPair.prototype.validate = function validate() {
return { result: true, reason: null };
};

KeyPair.prototype.getPublic = function getPublic(compact, enc) {
// compact is optional argument
if (typeof compact === 'string') {
enc = compact;
compact = null;
}

KeyPair.prototype.getPublic = function getPublic(enc, compact) {
if (!this.pub)
this.pub = this.ec.g.mul(this.priv);

Expand All @@ -77,9 +72,17 @@ KeyPair.prototype.getPrivate = function getPrivate(enc) {
KeyPair.prototype._importPrivate = function _importPrivate(key, enc) {
this.priv = new BN(key, enc || 16);

// Ensure that the priv won't be bigger than n, otherwise we may fail
// in fixed multiplication method
this.priv = this.priv.umod(this.ec.curve.n);
// For Curve25519/Curve448 we have a specific procedure.
// FIXME for Curve448
if (this.ec.curve.type === 'mont') {
var one = this.ec.curve.one;
var mask = one.ushln(255 - 3).sub(one).ushln(3);
this.priv = this.priv.or(one.ushln(255 - 1));
this.priv = this.priv.and(mask);
} else
// Ensure that the priv won't be bigger than n, otherwise we may fail
// in fixed multiplication method
this.priv = this.priv.umod(this.ec.curve.n);
};

KeyPair.prototype._importPublic = function _importPublic(key, enc) {
Expand All @@ -101,7 +104,15 @@ KeyPair.prototype._importPublic = function _importPublic(key, enc) {

// ECDH
KeyPair.prototype.derive = function derive(pub) {
return pub.mul(this.priv).getX();
var x = pub.mul(this.priv).getX();
var len = x.byteLength();

// FIXME this is not ideal
if (this.ec.curve.type === 'mont') {
return x.toArray('le', len);
} else {
return x.toArray('be', len);
}
};

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

var hash = require('hash.js');
var HmacDRBG = require('hmac-drbg');
var elliptic = require('../../elliptic');
var utils = elliptic.utils;
var assert = utils.assert;
Expand Down Expand Up @@ -66,6 +67,10 @@ EDDSA.prototype.hashInt = function hashInt() {
return utils.intFromLE(hash.digest()).umod(this.curve.n);
};

EDDSA.prototype.keyPair = function keyPair(options) {
return new KeyPair(this, options);
};

EDDSA.prototype.keyFromPublic = function keyFromPublic(pub) {
return KeyPair.fromPublic(this, pub);
};
Expand All @@ -74,6 +79,23 @@ EDDSA.prototype.keyFromSecret = function keyFromSecret(secret) {
return KeyPair.fromSecret(this, secret);
};

EDDSA.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 || elliptic.rand(this.hash.hmacStrength),
entropyEnc: options.entropy && options.entropyEnc || 'utf8',
nonce: this.curve.n.toArray()
});

return this.keyFromSecret(drbg.generate(32));
};

EDDSA.prototype.makeSignature = function makeSignature(sig) {
if (sig instanceof Signature)
return sig;
Expand Down
16 changes: 12 additions & 4 deletions lib/elliptic/eddsa/key.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@ var cachedProperty = utils.cachedProperty;
*/
function KeyPair(eddsa, params) {
this.eddsa = eddsa;
this._secret = parseBytes(params.secret);
if (params.hasOwnProperty('secret'))
this._secret = parseBytes(params.secret);
if (eddsa.isPoint(params.pub))
this._pub = params.pub;
else
else {
this._pubBytes = parseBytes(params.pub);
if (this._pubBytes && this._pubBytes.length === 33 &&
this._pubBytes[0] === 0x40)
this._pubBytes = this._pubBytes.slice(1, 33);
if (this._pubBytes && this._pubBytes.length !== 32)
throw new Error('Unknown point compression format');
}
}

KeyPair.fromPublic = function fromPublic(eddsa, pub) {
Expand Down Expand Up @@ -55,6 +62,7 @@ cachedProperty(KeyPair, 'privBytes', function privBytes() {
var hash = this.hash();
var lastIx = eddsa.encodingLength - 1;

// https://tools.ietf.org/html/rfc8032#section-5.1.5
var a = hash.slice(0, eddsa.encodingLength);
a[0] &= 248;
a[lastIx] &= 127;
Expand Down Expand Up @@ -89,8 +97,8 @@ KeyPair.prototype.getSecret = function getSecret(enc) {
return utils.encode(this.secret(), enc);
};

KeyPair.prototype.getPublic = function getPublic(enc) {
return utils.encode(this.pubBytes(), enc);
KeyPair.prototype.getPublic = function getPublic(enc, compact) {
return utils.encode((compact ? [ 0x40 ] : []).concat(this.pubBytes()), enc);
};

module.exports = KeyPair;
4 changes: 2 additions & 2 deletions test/ecdsa-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ describe('ECDSA', function() {
});

it('should load public key from compact hex value', function() {
var pub = keys.getPublic(true, 'hex');
var pub = keys.getPublic('hex', true);
var copy = ecdsa.keyFromPublic(pub, 'hex');
assert.equal(copy.getPublic(true, 'hex'), pub);
assert.equal(copy.getPublic('hex', true), pub);
});

it('should load public key from hex value', function() {
Expand Down

0 comments on commit 7e3b7ff

Please sign in to comment.