diff --git a/lib/ecdsa.js b/lib/ecdsa.js index 38b0ce9..552cd7f 100644 --- a/lib/ecdsa.js +++ b/lib/ecdsa.js @@ -1,5 +1,5 @@ -var bn = require('./bn'); -var point = require('./point'); +var BN = require('./bn'); +var Point = require('./point'); var Signature = require('./signature'); var Key = require('./key'); var Privkey = require('./privkey'); @@ -15,29 +15,93 @@ var ECDSA = function ECDSA(hash, key, sig, k) { this.k = k; }; +ECDSA.prototype.calci = function() { + for (var i = 0; i < 4; i++) { + this.sig.i = i; + try { + var Qprime = this.sig2pubkey(); + } catch (e) { + console.log(e); + continue; + } + + if (Qprime.point.eq(this.key.pubkey.point)) { + return this; + } + } + + this.sig.i = undefined; + throw new Error('Unable to find valid recovery factor'); +}; + ECDSA.prototype.fromString = function(str) { var obj = JSON.parse(str); if (obj.hash) this.hash = new Buffer(obj.hash, 'hex'); if (obj.key) - this.key = (new Key()).fromString(obj.key); + this.key = Key().fromString(obj.key); if (obj.sig) - this.sig = (new Signature()).fromString(obj.sig); + this.sig = Signature().fromString(obj.sig); if (obj.k) - this.k = bn(obj.k, 10); + this.k = BN(obj.k, 10); return this; }; ECDSA.prototype.randomK = function() { - var N = point.getN(); + var N = Point.getN(); var k; do { - k = bn.fromBuffer(Random.getRandomBuffer(32)); + k = BN().fromBuffer(Random.getRandomBuffer(32)); } while (!(k.lt(N) && k.gt(0))); this.k = k; return this; }; +ECDSA.prototype.sig2pubkey = function() { + var i = this.sig.i; + if (!(i === 0 || i === 1 || i === 2 || i === 3)) + throw new Error('signature: i must be equal to 0, 1, 2, or 3'); + + var e = BN().fromBuffer(this.hash); + var r = this.sig.r; + var s = this.sig.s; + + // A set LSB signifies that the y-coordinate is odd + var isYOdd = i & 1; + + // The more significant bit specifies whether we should use the + // first or second candidate key. + var isSecondKey = i >> 1; + + var n = Point.getN(); + var G = Point.getG(); + + // 1.1 Let x = r + jn + var x = isSecondKey ? r.add(n) : r; + var R = Point.fromX(isYOdd, x); + + // 1.4 Check that nR is at infinity + var nR = R.mul(n); + + if (!nR.isInfinity()) + throw new Error('nR is not a valid curve point'); + + // Compute -e from e + var eNeg = e.neg().mod(n); + + // 1.6.1 Compute Q = r^-1 (sR - eG) + // Q = r^-1 (sR + -eG) + var rInv = r.invm(n); + + //var Q = R.multiplyTwo(s, G, eNeg).mul(rInv); + var Q = R.mul(s).add(G.mul(eNeg)).mul(rInv); + + var pubkey = new Pubkey(Q); + pubkey.validate(); + + return pubkey; +}; + ECDSA.prototype.sigError = function() { if (!Buffer.isBuffer(this.hash) || this.hash.length !== 32) return 'Invalid hash'; @@ -46,21 +110,21 @@ ECDSA.prototype.sigError = function() { 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()))) + 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 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.point, u2); + var p = Point.getG().mulAdd(u1, this.key.pubkey.point, u2); if (p.isInfinity()) return 'p is infinity'; @@ -79,9 +143,9 @@ ECDSA.prototype.sign = function() { if (!hash || !privkey || !k || !d) throw new Error('ecdsa: invalid parameters'); - var N = point.getN(); - var G = point.getG(); - var e = bn(hash); + var N = Point.getN(); + var G = Point.getG(); + var e = BN().fromBuffer(hash); do { var Q = G.mul(k); diff --git a/test/test.ecdsa.js b/test/test.ecdsa.js index f7db604..0a623a6 100644 --- a/test/test.ecdsa.js +++ b/test/test.ecdsa.js @@ -4,7 +4,7 @@ var Key = require('../lib/key'); var Privkey = require('../lib/privkey'); var Pubkey = require('../lib/pubkey'); var Signature = require('../lib/signature'); -var bn = require('../lib/bn'); +var BN = require('../lib/bn'); var point = require('../lib/point'); var should = require('chai').should(); @@ -17,9 +17,36 @@ describe("ECDSA", function() { 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')))); + 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('#calci', function() { + + it('should calculate i', function() { + ecdsa.randomK(); + ecdsa.sign(); + ecdsa.calci(); + should.exist(ecdsa.sig.i); + }); + + it('should calulate this known i', function() { + var hash = Hash.sha256(new Buffer('some data')); + var r = BN('71706645040721865894779025947914615666559616020894583599959600180037551395766', 10); + var s = BN('109412465507152403114191008482955798903072313614214706891149785278625167723646', 10); + var ecdsa = new ECDSA(); + ecdsa.key = new Key(); + ecdsa.key.privkey = Privkey(); + ecdsa.key.privkey.bn = BN().fromBuffer(Hash.sha256(new Buffer('test'))); + ecdsa.key.privkey2pubkey(); + ecdsa.hash = hash; + ecdsa.sig = new Signature(r, s); + + ecdsa.calci(); + ecdsa.sig.i.should.equal(1); + }); + + }); describe('#fromString', function() { @@ -46,12 +73,24 @@ describe("ECDSA", function() { it('should generate a random k that is (almost always) greater than this relatively small number', function() { ecdsa.randomK(); var k1 = ecdsa.k; - var k2 = bn(Math.pow(2, 32)).mul(bn(Math.pow(2, 32))).mul(bn(Math.pow(2, 32))); + var k2 = BN(Math.pow(2, 32)).mul(BN(Math.pow(2, 32))).mul(BN(Math.pow(2, 32))); k2.gt(k1).should.equal(false); }); }); + describe('#sig2pubkey', function() { + + it('should calculate the correct public key', function() { + ecdsa.k = BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10); + ecdsa.sign(); + ecdsa.sig.i = 1; + var pubkey = ecdsa.sig2pubkey(); + pubkey.point.eq(ecdsa.key.pubkey.point).should.equal(true); + }); + + }); + describe('#sigError', function() { it('should return an error if the hash is invalid', function() { @@ -73,15 +112,15 @@ describe("ECDSA", function() { ecdsa.key = new Key(); ecdsa.key.pubkey = pk; ecdsa.sig = new Signature(); - ecdsa.sig.r = bn(0); - ecdsa.sig.s = bn(0); + ecdsa.sig.r = BN(0); + ecdsa.sig.s = BN(0); ecdsa.sigError().should.equal("r and s not in range"); }); it('should return an error if the signature is incorrect', function() { ecdsa.sig = new Signature(); ecdsa.sig.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); - ecdsa.sig.r = ecdsa.sig.r.add(bn(1)); + ecdsa.sig.r = ecdsa.sig.r.add(BN(1)); ecdsa.sigError().should.equal("Invalid signature"); });