Merge pull request #626 from braydonf/ref/crypto-point

Crypto/Point: Added test coverage and documentation, and refactors validation.
This commit is contained in:
Manuel Aráoz 2014-12-01 14:56:13 -03:00
commit 6519888f7b
4 changed files with 219 additions and 88 deletions

View File

@ -1,57 +1,131 @@
'use strict';
var BN = require('./bn');
var elliptic = require('elliptic');
var ec = elliptic.curves.secp256k1;
var ecpoint = ec.curve.point.bind(ec.curve);
var p = ec.curve.point();
var bufferUtil = require('../util/buffer');
var ec = require('elliptic').curves.secp256k1;
var ecPoint = ec.curve.point.bind(ec.curve);
var ecPointFromX = ec.curve.pointFromX.bind(ec.curve);
/**
*
* Instantiate a valid secp256k1 Point from the X and Y coordinates.
*
* @param {BN|String} x - The X coordinate
* @param {BN|String} y - The Y coordinate
* @link https://github.com/indutny/elliptic
* @augments elliptic.curve.point
* @throws {Error} A validation error if exists
* @returns {Point} An instance of Point
* @constructor
*/
var Point = function Point(x, y, isRed) {
return ecpoint(x, y, isRed);
var point = ecPoint(x, y, isRed);
point.validate();
return point;
};
Point.prototype = Object.getPrototypeOf(p);
Point.prototype = Object.getPrototypeOf(ec.curve.point());
Point.fromX = ec.curve.pointFromX.bind(ec.curve);
Point.getG = function() {
var p = Point(ec.curve.g.getX(), ec.curve.g.getY());
return p;
/**
*
* Instantiate a valid secp256k1 Point from only the X coordinate
*
* @param {boolean} odd - If the Y coordinate is odd
* @param {BN|String} x - The X coordinate
* @throws {Error} A validation error if exists
* @returns {Point} An instance of Point
*/
Point.fromX = function fromX(odd, x){
var point = ecPointFromX(odd, x);
point.validate();
return point;
};
Point.getN = function() {
/**
*
* Will return a secp256k1 ECDSA base point.
*
* @link https://en.bitcoin.it/wiki/Secp256k1
* @returns {Point} An instance of the base point.
*/
Point.getG = function getG() {
return Point(ec.curve.g.getX(), ec.curve.g.getY());
};
/**
*
* Will return the max of range of valid private keys as governed by the secp256k1 ECDSA standard.
*
* @link https://en.bitcoin.it/wiki/Private_key#Range_of_valid_ECDSA_private_keys
* @returns {BN} A BN instance of the number of points on the curve
*/
Point.getN = function getN() {
return BN(ec.curve.n.toArray());
};
Point.prototype._getX = Point.prototype.getX;
Point.prototype.getX = function() {
/**
*
* Will return the X coordinate of the Point
*
* @returns {BN} A BN instance of the X coordinate
*/
Point.prototype.getX = function getX() {
return BN(this._getX().toArray());
};
Point.prototype._getY = Point.prototype.getY;
Point.prototype.getY = function() {
/**
*
* Will return the Y coordinate of the Point
*
* @returns {BN} A BN instance of the Y coordinate
*/
Point.prototype.getY = function getY() {
return BN(this._getY().toArray());
};
//https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf
Point.prototype.validate = function() {
/* jshint maxcomplexity: 8 */
var p2 = Point.fromX(this.getY().isOdd(), this.getX());
if (p2.y.cmp(this.y) !== 0) {
throw new Error('Invalid y value of public key');
/**
*
* Will determine if the point is valid
*
* @link https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf
* @param {Point} An instance of Point
* @throws {Error} A validation error if exists
* @returns {Point} An instance of the same Point
*/
Point.prototype.validate = function validate() {
if (this.isInfinity()){
throw new Error('Point cannot be equal to Infinity');
}
if (this.getX().cmp(0) === 0 || this.getY().cmp(0) === 0){
throw new Error('Invalid x,y value for curve, cannot equal 0.');
}
var p2 = ecPointFromX(this.getY().isOdd(), this.getX());
if (p2.y.cmp(this.y) !== 0) {
throw new Error('Invalid y value for curve.');
}
var xValidRange = (this.getX().gt(-1) && this.getX().lt(Point.getN()));
var yValidRange = (this.getY().gt(-1) && this.getY().lt(Point.getN()));
if (!(xValidRange && yValidRange)) {
if ( !xValidRange || !yValidRange ) {
throw new Error('Point does not lie on the curve');
}
//todo: needs test case
if (!(this.mul(Point.getN()).isInfinity())) {
throw new Error('Point times N must be infinity');
}
return this;
};
Point.pointToCompressed = function pointToCompressed(point) {

View File

@ -52,14 +52,6 @@ var PublicKey = function PublicKey(data, compressed) {
}
// validation
if (info.point.isInfinity()){
throw new Error('Point cannot be equal to Infinity');
}
if (info.point.eq(Point(BN(0), BN(0)))){
throw new Error('Point cannot be equal to 0, 0');
}
//https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf
info.point.validate();
this.point = info.point;

View File

@ -2,45 +2,60 @@
var should = require('chai').should();
var bitcore = require('../..');
var point = bitcore.crypto.Point;
var Point = bitcore.crypto.Point;
var BN = bitcore.crypto.BN;
describe('Point', function() {
var valid = {
x: 'ac242d242d23be966085a2b2b893d989f824e06c9ad0395a8a52f055ba39abb2',
y: '4836ab292c105a711ed10fcfd30999c31ff7c02456147747e03e739ad527c380',
};
var invalidPair = {
x: 'ac242d242d23be966085a2b2b893d989f824e06c9ad0395a8a52f055ba39abb2',
y: '0000000000000000000000000000000000000000000000000000000000000000',
};
it('should create a point', function() {
var p = point();
var p = Point(valid.x, valid.y);
should.exist(p);
});
it('should create a point when called with "new"', function() {
var p = new point();
var p = new Point(valid.x,valid.y);
should.exist(p);
});
describe('#getX', function() {
it('should return 0', function() {
var p = point();
p.getX().toString().should.equal('0');
it('should return x', function() {
var p = Point(valid.x,valid.y);
var x = p.getX();
x.toString('hex', 64).should.equal(valid.x);
});
it('should be convertable to a buffer', function() {
var p = point();
p.getX().toBuffer({size: 32}).length.should.equal(32);
var p = Point(valid.x,valid.y);
var a = p.getX().toBuffer({size: 32});
a.length.should.equal(32);
a.should.deep.equal(new Buffer(valid.x, 'hex'));
});
});
describe('#getY', function() {
it('should return 0', function() {
var p = point();
p.getY().toString().should.equal('0');
it('should return y', function() {
var p = Point(valid.x,valid.y);
p.getY().toString('hex', 64).should.equal(valid.y);
});
it('should be convertable to a buffer', function() {
var p = point();
p.getY().toBuffer({size: 32}).length.should.equal(32);
var p = Point(valid.x,valid.y);
var a = p.getY().toBuffer({size: 32});
a.length.should.equal(32);
a.should.deep.equal(new Buffer(valid.y, 'hex'));
});
});
@ -48,11 +63,13 @@ describe('Point', function() {
describe('#add', function() {
it('should accurately add g to itself', function() {
var p1 = point.getG();
var p2 = point.getG();
var p1 = Point.getG();
var p2 = Point.getG();
var p3 = p1.add(p2);
p3.getX().toString().should.equal('89565891926547004231252920425935692360644145829622209833684329913297188986597');
p3.getY().toString().should.equal('12158399299693830322967808612713398636155367887041628176798871954788371653930');
p3.getX().toString().should.equal('89565891926547004231252920425935692360644145829622209'+
'833684329913297188986597');
p3.getY().toString().should.equal('12158399299693830322967808612713398636155367887041628'+
'176798871954788371653930');
});
});
@ -60,29 +77,35 @@ describe('Point', function() {
describe('#mul', function() {
it('should accurately multiply g by 2', function() {
var g = point.getG();
var g = Point.getG();
var b = g.mul(BN(2));
b.getX().toString().should.equal('89565891926547004231252920425935692360644145829622209833684329913297188986597');
b.getY().toString().should.equal('12158399299693830322967808612713398636155367887041628176798871954788371653930');
b.getX().toString().should.equal('8956589192654700423125292042593569236064414582962220983'+
'3684329913297188986597');
b.getY().toString().should.equal('1215839929969383032296780861271339863615536788704162817'+
'6798871954788371653930');
});
it('should accurately multiply g by n-1', function() {
var g = point.getG();
var n = point.getN();
var g = Point.getG();
var n = Point.getN();
var b = g.mul(n.sub(1));
b.getX().toString().should.equal('55066263022277343669578718895168534326250603453777594175500187360389116729240');
b.getY().toString().should.equal('83121579216557378445487899878180864668798711284981320763518679672151497189239');
b.getX().toString().should.equal('55066263022277343669578718895168534326250603453777594175'+
'500187360389116729240');
b.getY().toString().should.equal('83121579216557378445487899878180864668798711284981320763'+
'518679672151497189239');
});
//not sure if this is technically accurate or not...
//normally, you should always multiply g by something less than n
//but it is the same result in OpenSSL
it('should accurately multiply g by n+1', function() {
var g = point.getG();
var n = point.getN();
var g = Point.getG();
var n = Point.getN();
var b = g.mul(n.add(1));
b.getX().toString().should.equal('55066263022277343669578718895168534326250603453777594175500187360389116729240');
b.getY().toString().should.equal('32670510020758816978083085130507043184471273380659243275938904335757337482424');
b.getX().toString().should.equal('550662630222773436695787188951685343262506034537775941755'+
'00187360389116729240');
b.getY().toString().should.equal('326705100207588169780830851305070431844712733806592432759'+
'38904335757337482424');
});
});
@ -90,8 +113,8 @@ describe('Point', function() {
describe('@fromX', function() {
it('should return g', function() {
var g = point.getG();
var p = point.fromX(false, g.getX());
var g = Point.getG();
var p = Point.fromX(false, g.getX());
g.eq(p).should.equal(true);
});
@ -99,20 +122,51 @@ describe('Point', function() {
describe('#validate', function() {
it('should validate this valid point', function() {
var x = BN().fromBuffer(new Buffer('ac242d242d23be966085a2b2b893d989f824e06c9ad0395a8a52f055ba39abb2', 'hex'));
var y = BN().fromBuffer(new Buffer('4836ab292c105a711ed10fcfd30999c31ff7c02456147747e03e739ad527c380', 'hex'));
var p = point(x, y);
it('should describe this point as valid', function() {
var p = Point(valid.x, valid.y);
should.exist(p.validate());
});
it('should invalidate this invalid point', function() {
var x = BN().fromBuffer(new Buffer('ac242d242d23be966085a2b2b893d989f824e06c9ad0395a8a52f055ba39abb2', 'hex'));
var y = BN().fromBuffer(new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'));
var p = point(x, y);
it('should describe this point as invalid because of zero y', function() {
var x = 'ac242d242d23be966085a2b2b893d989f824e06c9ad0395a8a52f055ba39abb2';
var y = '0000000000000000000000000000000000000000000000000000000000000000';
(function() {
p.validate();
}).should.throw('Invalid y value of public key');
var p = Point(x, y);
}).should.throw('Invalid x,y value for curve, cannot equal 0.');
});
it('should describe this point as invalid because of invalid y', function() {
var x = 'ac242d242d23be966085a2b2b893d989f824e06c9ad0395a8a52f055ba39abb2';
var y = '00000000000000000000000000000000000000000000000000000000000000FF';
(function() {
var p = Point(x, y);
}).should.throw('Invalid y value for curve.');
});
it('should describe this point as invalid because out of curve bounds', function() {
// point larger than max
var x = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDCE6AF48A03BBFD25E8CD0364141';
// calculated y of x
var y = 'ed3970f129bc2ca7c7c6cf92fa7da4de6a1dfc9c14da4bf056aa868d3dd74034';
(function() {
// set the point
var p = Point(x, y);
}).should.throw('Point does not lie on the curve');
});
it('should describe this point as invalid because out of curve bounds', function() {
// point larger than max
var x = '0000000000000000000000000000000000000000000000000000000000000000';
(function() {
// set the point
var p = Point.fromX(false, x);
}).should.throw('Invalid x,y value for curve, cannot equal 0.');
});
});

View File

@ -9,22 +9,24 @@ var PrivateKey = bitcore.PrivateKey;
describe('PublicKey', function() {
var invalidPoint = '0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
it('should error because of missing data', function() {
(function() {
var pk = new PublicKey();
}).should.throw('First argument is required, please include public key data.');
});
it('should error because of an invalid point', function() {
(function() {
var pk = new PublicKey(Point());
}).should.throw('Point cannot be equal to 0, 0');
var pk = new PublicKey(invalidPoint);
}).should.throw('Invalid x,y value for curve, cannot equal 0.');
});
it('should error because of an invalid public key point, not on the secp256k1 curve', function() {
(function() {
var pk = new PublicKey(Point(1000, 1000));
}).should.throw('Invalid y value of public key');
}).should.throw('Invalid y value for curve.');
});
it('should error because of an unrecognized data type', function() {
@ -65,13 +67,15 @@ describe('PublicKey', function() {
});
describe('#getValidationError', function(){
it('should recieve an error message', function() {
var error = PublicKey.getValidationError(Point());
it('should recieve an invalid point error', function() {
var error = PublicKey.getValidationError(invalidPoint);
should.exist(error);
error.message.should.equal('Invalid x,y value for curve, cannot equal 0.');
});
it('should recieve a boolean as false', function() {
var valid = PublicKey.isValid(Point());
var valid = PublicKey.isValid(invalidPoint);
valid.should.equal(false);
});
@ -100,7 +104,7 @@ describe('PublicKey', function() {
});
describe('#fromJSON', function() {
it('should input this public key', function() {
var pk = PublicKey.fromJSON('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341');
pk.point.getX().toString(16).should.equal('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a');
@ -120,7 +124,7 @@ describe('PublicKey', function() {
});
describe('#fromPrivateKey', function() {
it('should make a public key from a privkey', function() {
should.exist(PublicKey.fromPrivateKey(PrivateKey.fromRandom()));
});
@ -134,7 +138,7 @@ describe('PublicKey', function() {
});
describe('#fromBuffer', function() {
it('should parse this uncompressed public key', function() {
var pk = PublicKey.fromBuffer(new Buffer('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', 'hex'));
pk.point.getX().toString(16).should.equal('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a');
@ -168,7 +172,7 @@ describe('PublicKey', function() {
});
describe('#fromDER', function() {
it('should parse this uncompressed public key', function() {
var pk = PublicKey.fromDER(new Buffer('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', 'hex'));
pk.point.getX().toString(16).should.equal('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a');
@ -200,7 +204,7 @@ describe('PublicKey', function() {
});
describe('#fromX', function() {
it('should create this known public key', function() {
var x = BN.fromBuffer(new Buffer('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a', 'hex'));
var pk = PublicKey.fromX(true, x);
@ -253,7 +257,7 @@ describe('PublicKey', function() {
});
describe('#toString', function() {
it('should print this known public key', function() {
var hex = '031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a';
var pk = PublicKey.fromString(hex);
@ -281,20 +285,27 @@ describe('PublicKey', function() {
var hex = '031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a';
var pk = PublicKey.fromString(hex);
});
it('should throw an error if pubkey is invalid', function() {
var hex = '041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a0000000000000000000000000000000000000000000000000000000000000000';
(function() {
var pk = PublicKey.fromString(hex);
}).should.throw('Invalid y value of public key');
}).should.throw('Invalid x,y value for curve, cannot equal 0.');
});
it('should throw an error if pubkey is invalid', function() {
var hex = '041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a00000000000000000000000000000000000000000000000000000000000000FF';
(function() {
var pk = PublicKey.fromString(hex);
}).should.throw('Invalid y value for curve.');
});
it('should throw an error if pubkey is infinity', function() {
(function() {
var pk = new PublicKey(Point.getG().mul(Point.getN()));
}).should.throw('Point cannot be equal to Infinity');
});
});
});