From c4064cc6e14fdb51935b5f50edc11927f247697d Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Sat, 9 Aug 2014 17:43:24 -0700 Subject: [PATCH] ecdsa --- index.js | 1 + lib/ecdsa.js | 92 +++++++++++++++++++++++++++++++++++++++++++++ lib/key.js | 32 ++++++++-------- lib/point.js | 13 +++++++ lib/pubkey.js | 12 ++++++ test/test.ecdsa.js | 41 ++++++++++++++++++++ test/test.key.js | 64 +++++++++++++++---------------- test/test.point.js | 20 ++++++++++ test/test.pubkey.js | 28 ++++++++++++++ 9 files changed, 255 insertions(+), 48 deletions(-) create mode 100644 lib/ecdsa.js create mode 100644 test/test.ecdsa.js diff --git a/index.js b/index.js index fb3ab50..150f5ef 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ privsec.base58 = require('./lib/base58'); privsec.base58check = require('./lib/base58check'); privsec.bn = require('./lib/bn'); privsec.constants = require('./lib/constants'); +privsec.ecdsa = require('./lib/ecdsa'); privsec.hash = require('./lib/hash'); privsec.key = require('./lib/key'); privsec.point = require('./lib/point'); diff --git a/lib/ecdsa.js b/lib/ecdsa.js new file mode 100644 index 0000000..4892a22 --- /dev/null +++ b/lib/ecdsa.js @@ -0,0 +1,92 @@ +var bn = require('./bn'); +var point = require('./point'); +var Signature = require('./signature'); +var Privkey = require('./privkey'); +var Pubkey = require('./pubkey'); +var Random = require('./random'); + +var ECDSA = function(hash, key, sig, k) { + this.hash = hash; + this.key = key; + this.sig = sig; + this.k = k; +}; + +ECDSA.prototype.sigError = function() { + if (!Buffer.isBuffer(this.hash) || this.hash.length !== 32) + return 'Invalid hash'; + + try { + this.key.pubkey.validate(); + } catch (e) { + return 'Invalid pubkey: ' + e; + }; + + var r = this.sig.r; + var s = this.sig.s; + if (!(r.gt(0) && r.lt(point.getN())) + || !(s.gt(0) && s.lt(point.getN()))) + return 'r and s not in range'; + + var e = bn.fromBuffer(this.hash); + var n = point.getN(); + var sinv = s.invm(n); + var u1 = sinv.mul(e).mod(n); + var u2 = sinv.mul(r).mod(n); + + var p = point.getG().mulAdd(u1, this.key.pubkey.p, u2); + if (p.isInfinity()) + return 'p is infinity'; + + if (!(p.getX().mod(n).cmp(r) === 0)) + return 'Invalid signature'; + else + return false; +}; + +ECDSA.prototype.randomK = function() { + var N = point.getN(); + var k; + do { + k = bn.fromBuffer(Random.getRandomBuffer(32)); + } while (!(k.lt(N) && k.gt(0))); + this.k = k; + return this; +}; + +ECDSA.prototype.sign = function() { + var hash = this.hash; + var privkey = this.key.privkey; + var k = this.k; + var d = privkey.n; + + if (!hash || !privkey || !k || !d) + throw new Error('ecdsa: invalid parameters'); + + var N = point.getN(); + var G = point.getG(); + var e = bn(hash); + + do { + var Q = G.mul(k); + var r = Q.x.mod(N); + var s = k.invm(N).mul(e.add(d.mul(r))).mod(N); + } while (r.cmp(0) <= 0 || s.cmp(0) <= 0); + + this.sig = new Signature(r, s); + return this.sig; +}; + +ECDSA.prototype.signRandomK = function() { + var k = this.randomK(); + return this.sign(); +}; + +ECDSA.prototype.verify = function() { + if (!this.sigError()) + return true; + else + return false; +}; + +module.exports = ECDSA; diff --git a/lib/key.js b/lib/key.js index 8bae38e..68cad5b 100644 --- a/lib/key.js +++ b/lib/key.js @@ -4,42 +4,42 @@ var Random = require('./random'); var bn = require('./bn'); var point = require('./point'); -function Key(priv, pub) { - this.priv = priv; - this.pub = pub; +function Key(privkey, pubkey) { + this.privkey = privkey; + this.pubkey = pubkey; }; Key.prototype.fromRandom = function() { do { var privbuf = Random.getRandomBuffer(32); - this.priv = new Privkey(bn(privbuf)); - var condition = this.priv.n.lt(point.getN()); + this.privkey = new Privkey(bn(privbuf)); + var condition = this.privkey.n.lt(point.getN()); } while (!condition); - this.priv2pub(); + this.privkey2pubkey(); }; Key.prototype.fromString = function(str) { var obj = JSON.parse(str); if (obj.priv) { - this.priv = new Privkey(); - this.priv.fromString(obj.priv); + this.privkey = new Privkey(); + this.privkey.fromString(obj.priv); } if (obj.pub) { - this.pub = new Pubkey(); - this.pub.fromString(obj.pub); + this.pubkey = new Pubkey(); + this.pubkey.fromString(obj.pub); } }; -Key.prototype.priv2pub = function() { - this.pub = new Pubkey(point.getG().mul(this.priv.n)); +Key.prototype.privkey2pubkey = function() { + this.pubkey = new Pubkey(point.getG().mul(this.privkey.n)); }; Key.prototype.toString = function() { var obj = {}; - if (this.priv) - obj.priv = this.priv.toString(); - if (this.pub) - obj.pub = this.pub.toString(); + if (this.privkey) + obj.priv = this.privkey.toString(); + if (this.pubkey) + obj.pub = this.pubkey.toString(); return JSON.stringify(obj); }; diff --git a/lib/point.js b/lib/point.js index 32e41e6..6a301c5 100644 --- a/lib/point.js +++ b/lib/point.js @@ -29,4 +29,17 @@ Point.prototype.getY = function() { return bn(this._getY().toArray()); }; +//https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf +Point.prototype.validate = function() { + var p2 = Point.fromX(this.getY().isOdd(), this.getX()); + if (!(p2.y.cmp(this.y) === 0)) + throw new Error('point: Invalid y value of public key'); + if (!(this.getX().gt(-1) && this.getX().lt(Point.getN())) + ||!(this.getY().gt(-1) && this.getY().lt(Point.getN()))) + throw new Error('point: Point does not lie on the curve'); + if (!(this.mul(Point.getN()).isInfinity())) + throw new Error('point: Point times N must be infinity'); + return this; +}; + module.exports = Point; diff --git a/lib/pubkey.js b/lib/pubkey.js index 8da4b91..20b4978 100644 --- a/lib/pubkey.js +++ b/lib/pubkey.js @@ -2,6 +2,8 @@ var point = require('./point'); var bn = require('./bn'); var Pubkey = function(p) { + if (p && !p.getX() && !p.getY()) + throw new Error('pubkey: Invalid point'); this.p = p; }; @@ -64,4 +66,14 @@ Pubkey.prototype.toString = function() { return this.toDER(true).toString('hex'); }; +//https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf +Pubkey.prototype.validate = function() { + if (this.p.isInfinity()) + throw new Error('point: Point cannot be equal to Infinity'); + if (this.p.eq(point(bn(0), bn(0)))) + throw new Error('point: Point cannot be equal to 0, 0'); + this.p.validate(); + return this; +}; + module.exports = Pubkey; diff --git a/test/test.ecdsa.js b/test/test.ecdsa.js new file mode 100644 index 0000000..674b744 --- /dev/null +++ b/test/test.ecdsa.js @@ -0,0 +1,41 @@ +var ECDSA = require('../lib/ecdsa'); +var Hash = require('../lib/hash'); +var Key = require('../lib/key'); +var Privkey = require('../lib/privkey'); +var Pubkey = require('../lib/pubkey'); +var bn = require('../lib/bn'); +var point = require('../lib/point'); +var should = require('chai').should(); + +describe("ecdsa", function() { + + it('should create a blank ecdsa', function() { + var ecdsa = new ECDSA(); + }); + + var ecdsa = new ECDSA(); + ecdsa.hash = Hash.sha256(new Buffer('test data')); + ecdsa.key = new Key(); + ecdsa.key.privkey = new Privkey(bn.fromBuffer(new Buffer('fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e', 'hex'))); + ecdsa.key.pubkey = new Pubkey(point(bn.fromBuffer(new Buffer('ac242d242d23be966085a2b2b893d989f824e06c9ad0395a8a52f055ba39abb2', 'hex')), + bn.fromBuffer(new Buffer('4836ab292c105a711ed10fcfd30999c31ff7c02456147747e03e739ad527c380', 'hex')))); + + describe('#signRandomK', function() { + + it('should produce a signature', function() { + ecdsa.signRandomK(); + should.exist(ecdsa.sig); + }); + + }); + + describe('#verify', function() { + + it('should verify a signature that was just signed', function() { + ecdsa.signRandomK(); + ecdsa.verify().should.equal(true); + }); + + }); + +}); diff --git a/test/test.key.js b/test/test.key.js index a3caf0a..2ba0382 100644 --- a/test/test.key.js +++ b/test/test.key.js @@ -1,8 +1,8 @@ var should = require('chai').should(); var bn = require('../lib/bn'); var point = require('../lib/point'); -var privkey = require('../lib/privkey'); -var pubkey = require('../lib/pubkey'); +var Privkey = require('../lib/privkey'); +var Pubkey = require('../lib/pubkey'); var Key = require('../lib/key'); describe('key', function() { @@ -13,12 +13,12 @@ describe('key', function() { }); it('should make a key with a priv and pub', function() { - var priv = new privkey(); - var pub = new pubkey(); + var priv = new Privkey(); + var pub = new Pubkey(); var key = new Key(priv, pub); should.exist(key); - should.exist(key.priv); - should.exist(key.pub); + should.exist(key.privkey); + should.exist(key.pubkey); }); describe("#fromRandom", function() { @@ -26,11 +26,11 @@ describe('key', function() { it('should make a new priv and pub', function() { var key = new Key(); key.fromRandom(); - should.exist(key.priv); - should.exist(key.pub); - key.priv.n.gt(bn(0)).should.equal(true); - key.pub.p.getX().gt(bn(0)).should.equal(true); - key.pub.p.getY().gt(bn(0)).should.equal(true); + should.exist(key.privkey); + should.exist(key.pubkey); + key.privkey.n.gt(bn(0)).should.equal(true); + key.pubkey.p.getX().gt(bn(0)).should.equal(true); + key.pubkey.p.getY().gt(bn(0)).should.equal(true); }); }); @@ -40,49 +40,49 @@ describe('key', function() { it('should recover a key creating with toString', function() { var key = new Key(); key.fromRandom(); - var priv = key.priv; - var pub = key.pub; + var priv = key.privkey; + var pub = key.pubkey; var str = key.toString(); key.fromString(str); - should.exist(key.priv); - should.exist(key.pub); - key.priv.toString().should.equal(priv.toString()); - key.pub.toString().should.equal(pub.toString()); + should.exist(key.privkey); + should.exist(key.pubkey); + key.privkey.toString().should.equal(priv.toString()); + key.pubkey.toString().should.equal(pub.toString()); }); - it('should work with only privkey set', function() { + it('should work with only Privkey set', function() { var key = new Key(); key.fromRandom(); - key.pub = undefined; - var priv = key.priv; + key.pubkey = undefined; + var priv = key.privkey; var str = key.toString(); key.fromString(str); - should.exist(key.priv); - key.priv.toString().should.equal(priv.toString()); + should.exist(key.privkey); + key.privkey.toString().should.equal(priv.toString()); }); - it('should work with only pubkey set', function() { + it('should work with only Pubkey set', function() { var key = new Key(); key.fromRandom(); - key.priv = undefined; - var pub = key.pub; + key.privkey = undefined; + var pub = key.pubkey; var str = key.toString(); key.fromString(str); - should.exist(key.pub); - key.pub.toString().should.equal(pub.toString()); + should.exist(key.pubkey); + key.pubkey.toString().should.equal(pub.toString()); }); }); - describe("#priv2pub", function() { + describe("#privkey2pubkey", function() { - it('should convert this known privkey to known pubkey', function() { + it('should convert this known Privkey to known Pubkey', function() { var privhex = '906977a061af29276e40bf377042ffbde414e496ae2260bbf1fa9d085637bfff'; var pubhex = '02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc'; var key = new Key(); - key.priv = new privkey(bn(new Buffer(privhex, 'hex'))); - key.priv2pub(); - key.pub.toString().should.equal(pubhex); + key.privkey = new Privkey(bn(new Buffer(privhex, 'hex'))); + key.privkey2pubkey(); + key.pubkey.toString().should.equal(pubhex); }); }); diff --git a/test/test.point.js b/test/test.point.js index 01fbc31..272b399 100644 --- a/test/test.point.js +++ b/test/test.point.js @@ -89,4 +89,24 @@ 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); + 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); + (function() { + p.validate(); + }).should.throw('point: Invalid y value of public key'); + }); + + }); + }); diff --git a/test/test.pubkey.js b/test/test.pubkey.js index e362a27..8caeead 100644 --- a/test/test.pubkey.js +++ b/test/test.pubkey.js @@ -93,4 +93,32 @@ describe('pubkey', function() { }); + describe('#validate', function() { + + it('should not throw an error if pubkey is valid', function() { + var hex = '031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'; + var pk = new pubkey(); + pk.fromString(hex); + should.exist(pk.validate()); + }); + + it('should not throw an error if pubkey is invalid', function() { + var hex = '041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a0000000000000000000000000000000000000000000000000000000000000000'; + var pk = new pubkey(); + pk.fromString(hex); + (function() { + pk.validate(); + }).should.throw('point: Invalid y value of public key'); + }); + + it('should not throw an error if pubkey is infinity', function() { + var pk = new pubkey(); + pk.p = point.getG().mul(point.getN()); + (function() { + pk.validate(); + }).should.throw('point: Point cannot be equal to Infinity'); + }); + + }); + });