From 9a76bf30ff50a0dd70a06c8da7a31273d11d20da Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 28 Nov 2014 18:51:37 -0500 Subject: [PATCH] Crypto/Point: Added test coverage and documentation, and refactored validation --- lib/crypto/point.js | 120 ++++++++++++++++++++++++++++++-------- lib/publickey.js | 8 --- test/crypto/point.js | 134 ++++++++++++++++++++++++++++++------------- test/publickey.js | 45 +++++++++------ 4 files changed, 219 insertions(+), 88 deletions(-) diff --git a/lib/crypto/point.js b/lib/crypto/point.js index 22ac66a..f635b3e 100644 --- a/lib/crypto/point.js +++ b/lib/crypto/point.js @@ -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) { diff --git a/lib/publickey.js b/lib/publickey.js index 3bffc6e..dd1b60f 100644 --- a/lib/publickey.js +++ b/lib/publickey.js @@ -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; diff --git a/test/crypto/point.js b/test/crypto/point.js index 43b5ed5..1728939 100644 --- a/test/crypto/point.js +++ b/test/crypto/point.js @@ -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.'); }); }); diff --git a/test/publickey.js b/test/publickey.js index e9160f7..fe54c8a 100644 --- a/test/publickey.js +++ b/test/publickey.js @@ -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'); }); - + }); });