diff --git a/lib/crypto/ecdsa.js b/lib/crypto/ecdsa.js index e9060a00e..6e5c09146 100644 --- a/lib/crypto/ecdsa.js +++ b/lib/crypto/ecdsa.js @@ -4,10 +4,11 @@ var BN = require('./bn'); var Point = require('./point'); var Signature = require('./signature'); var PublicKey = require('../publickey'); -var PrivateKey = require('../privatekey'); var Random = require('./random'); var Hash = require('./hash'); var BufferUtil = require('../util/buffer'); +var _ = require('lodash'); +var $ = require('../util/preconditions'); var ECDSA = function ECDSA(obj) { if (!(this instanceof ECDSA)) { @@ -70,8 +71,13 @@ ECDSA.prototype.randomK = function() { return this; }; + // https://tools.ietf.org/html/rfc6979#section-3.2 ECDSA.prototype.deterministicK = function(badrs) { + /* jshint maxstatements: 25 */ + if (_.isUndefined(badrs)) { + badrs = 0; + } var v = new Buffer(32); v.fill(0x01); var k = new Buffer(32); @@ -87,11 +93,6 @@ ECDSA.prototype.deterministicK = function(badrs) { var T = BN.fromBuffer(v); var N = Point.getN(); - // if r or s were invalid when this function was used in signing, - // we do not want to actually compute r, s here for efficiency, so, - // we can increment badrs. explained at end of RFC 6979 section 3.2 - if (typeof badrs === 'undefined') - badrs = 0; // also explained in 3.2, we must ensure T is in the proper range (0, N) for (var i = 0; i < badrs || !(T.lt(N) && T.gt(0)); i++) { k = Hash.sha256hmac(Buffer.concat([v, new Buffer([0x00])]), k); @@ -107,10 +108,9 @@ ECDSA.prototype.deterministicK = function(badrs) { // https://bitcointalk.org/index.php?topic=6430.0 // http://stackoverflow.com/questions/19665491/how-do-i-get-an-ecdsa-public-key-from-just-a-bitcoin-signature-sec1-4-1-6-k ECDSA.prototype.toPublicKey = function() { + /* jshint maxstatements: 25 */ var i = this.sig.i; - if (!(i === 0 || i === 1 || i === 2 || i === 3)) { - throw new Error('i must be equal to 0, 1, 2, or 3'); - } + $.checkArgument(i === 0 || i === 1 || i === 2 || i === 3, new Error('i must be equal to 0, 1, 2, or 3')); var e = BN.fromBuffer(this.hashbuf); var r = this.sig.r; @@ -153,7 +153,8 @@ ECDSA.prototype.toPublicKey = function() { }; ECDSA.prototype.sigError = function() { - if (!Buffer.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { + /* jshint maxstatements: 25 */ + if (!BufferUtil.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { return 'hashbuf must be a 32 byte buffer'; } @@ -172,35 +173,29 @@ ECDSA.prototype.sigError = function() { var u2 = sinv.mul(r).mod(n); var p = Point.getG().mulAdd(u1, this.pubkey.point, u2); - if (p.isInfinity()) + if (p.isInfinity()) { return 'p is infinity'; + } - if (!(p.getX().mod(n).cmp(r) === 0)) + if (p.getX().mod(n).cmp(r) !== 0) { return 'Invalid signature'; - else + } else { return false; + } }; -ECDSA.prototype.sign = function() { - var hashbuf = this.hashbuf; - var privkey = this.privkey; - - var d = privkey.bn; - - if (!hashbuf || !privkey || !d) { - throw new Error('invalid parameters'); - } - - if (!BufferUtil.isBuffer(hashbuf) || hashbuf.length !== 32) { - throw new Error('hashbuf must be a 32 byte buffer'); +ECDSA.toLowS = function(s) { + //enforce low s + //see BIP 62, "low S values in signatures" + if (s.gt(BN.fromBuffer(new Buffer('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex')))) { + s = Point.getN().sub(s); } + return s; +}; +ECDSA.prototype._findSignature = function(d, e) { var N = Point.getN(); var G = Point.getG(); - var e = BN.fromBuffer(hashbuf, this.endian ? { - endian: this.endian - } : undefined); - // try different values of k until r, s are valid var badrs = 0; var k, Q, r, s; @@ -215,17 +210,30 @@ ECDSA.prototype.sign = function() { s = k.invm(N).mul(e.add(d.mul(r))).mod(N); } while (r.cmp(0) <= 0 || s.cmp(0) <= 0); - //enforce low s - //see BIP 62, "low S values in signatures" - if (s.gt(BN.fromBuffer(new Buffer('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0', 'hex')))) { - s = Point.getN().sub(s); - } - - this.sig = new Signature({ - r: r, + s = ECDSA.toLowS(s); + return { s: s, - compressed: this.pubkey.compressed - }); + r: r + }; + +}; + +ECDSA.prototype.sign = function() { + var hashbuf = this.hashbuf; + var privkey = this.privkey; + var d = privkey.bn; + + $.checkState(hashbuf && privkey && d, new Error('invalid parameters')); + $.checkState(BufferUtil.isBuffer(hashbuf) && hashbuf.length === 32, new Error('hashbuf must be a 32 byte buffer')); + + var e = BN.fromBuffer(hashbuf, this.endian ? { + endian: this.endian + } : undefined); + + var obj = this._findSignature(d, e); + obj.compressed = this.pubkey.compressed; + + this.sig = new Signature(obj); return this; }; @@ -255,10 +263,11 @@ ECDSA.prototype.toString = function() { }; ECDSA.prototype.verify = function() { - if (!this.sigError()) + if (!this.sigError()) { this.verified = true; - else + } else { this.verified = false; + } return this; }; diff --git a/test/crypto/hash.js b/test/crypto/hash.js index 7509d924e..c079feee2 100644 --- a/test/crypto/hash.js +++ b/test/crypto/hash.js @@ -10,121 +10,125 @@ describe('Hash', function() { describe('@sha1', function() { - it('should calculate the hash of this buffer correctly', function() { + it('calculates the hash of this buffer correctly', function() { var hash = Hash.sha1(buf); hash.toString('hex').should.equal('de69b8a4a5604d0486e6420db81e39eb464a17b2'); hash = Hash.sha1(new Buffer(0)); hash.toString('hex').should.equal('da39a3ee5e6b4b0d3255bfef95601890afd80709'); }); - it('should throw an error when the input is not a buffer', function() { - (function() { - Hash.sha1(str); - }).should.throw('Invalid Argument'); + it('throws an error when the input is not a buffer', function() { + Hash.sha1.bind(Hash, str).should.throw('Invalid Argument'); }); }); describe('#sha256', function() { - it('should calculate the hash of this buffer correctly', function() { + it('calculates the hash of this buffer correctly', function() { var hash = Hash.sha256(buf); hash.toString('hex').should.equal('6f2c7b22fd1626998287b3636089087961091de80311b9279c4033ec678a83e8'); }); - it('should throw an error when the input is not a buffer', function() { - (function() { - Hash.sha256(str); - }).should.throw('Invalid Argument'); + it('fails when the input is not a buffer', function() { + Hash.sha256.bind(Hash, str).should.throw('Invalid Argument'); }); }); describe('#sha256hmac', function() { - it('should compute this known empty test vector correctly', function() { - var key = new Buffer(''); + it('computes this known big key correctly', function() { + var key = new Buffer('b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad' + + 'b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad' + + 'b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad' + + 'b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad'); var data = new Buffer(''); - Hash.sha256hmac(data, key).toString('hex').should.equal('b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad'); + Hash.sha256hmac(data, key).toString('hex') + .should.equal('fb1f87218671f1c0c4593a88498e02b6dfe8afd814c1729e89a1f1f6600faa23'); }); - it('should compute this known non-empty test vector correctly', function() { + it('computes this known empty test vector correctly', function() { + var key = new Buffer(''); + var data = new Buffer(''); + Hash.sha256hmac(data, key).toString('hex') + .should.equal('b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad'); + }); + + it('computes this known non-empty test vector correctly', function() { var key = new Buffer('key'); var data = new Buffer('The quick brown fox jumps over the lazy dog'); - Hash.sha256hmac(data, key).toString('hex').should.equal('f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8'); + Hash.sha256hmac(data, key).toString('hex') + .should.equal('f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8'); }); }); describe('#sha256sha256', function() { - it('should calculate the hash of this buffer correctly', function() { + it('calculates the hash of this buffer correctly', function() { var hash = Hash.sha256sha256(buf); hash.toString('hex').should.equal('be586c8b20dee549bdd66018c7a79e2b67bb88b7c7d428fa4c970976d2bec5ba'); }); - it('should throw an error when the input is not a buffer', function() { - (function() { - Hash.sha256sha256(str); - }).should.throw('Invalid Argument'); + it('fails when the input is not a buffer', function() { + Hash.sha256sha256.bind(Hash, str).should.throw('Invalid Argument'); }); }); describe('#sha256ripemd160', function() { - it('should calculate the hash of this buffer correctly', function() { + it('calculates the hash of this buffer correctly', function() { var hash = Hash.sha256ripemd160(buf); hash.toString('hex').should.equal('7322e2bd8535e476c092934e16a6169ca9b707ec'); }); - it('should throw an error when the input is not a buffer', function() { - (function() { - Hash.sha256ripemd160(str); - }).should.throw('Invalid Argument'); + it('fails when the input is not a buffer', function() { + Hash.sha256ripemd160.bind(Hash, str).should.throw('Invalid Argument'); }); }); describe('#ripemd160', function() { - it('should calculate the hash of this buffer correctly', function() { + it('calculates the hash of this buffer correctly', function() { var hash = Hash.ripemd160(buf); hash.toString('hex').should.equal('fa0f4565ff776fee0034c713cbf48b5ec06b7f5c'); }); - it('should throw an error when the input is not a buffer', function() { - (function() { - Hash.ripemd160(str); - }).should.throw('Invalid Argument'); + it('fails when the input is not a buffer', function() { + Hash.ripemd160.bind(Hash, str).should.throw('Invalid Argument'); }); }); describe('#sha512', function() { - it('should calculate the hash of this buffer correctly', function() { + it('calculates the hash of this buffer correctly', function() { var hash = Hash.sha512(buf); - hash.toString('hex').should.equal('c0530aa32048f4904ae162bc14b9eb535eab6c465e960130005feddb71613e7d62aea75f7d3333ba06e805fc8e45681454524e3f8050969fe5a5f7f2392e31d0'); + hash.toString('hex') + .should.equal('c0530aa32048f4904ae162bc14b9eb535eab6c465e960130005fedd' + + 'b71613e7d62aea75f7d3333ba06e805fc8e45681454524e3f8050969fe5a5f7f2392e31d0'); }); - it('should throw an error when the input is not a buffer', function() { - (function() { - Hash.sha512(str); - }).should.throw('Invalid Argument'); + it('fails when the input is not a buffer', function() { + Hash.sha512.bind(Hash, str).should.throw('Invalid Argument'); }); }); describe('#sha512hmac', function() { - it('should calculate this known empty test vector correctly', function() { - var hex = 'b936cee86c9f87aa5d3c6f2e84cb5a4239a5fe50480a6ec66b70ab5b1f4ac6730c6c515421b327ec1d69402e53dfb49ad7381eb067b338fd7b0cb22247225d47'; + it('calculates this known empty test vector correctly', function() { + var hex = 'b936cee86c9f87aa5d3c6f2e84cb5a4239a5fe50480a6ec66b70ab5b1f4a' + + 'c6730c6c515421b327ec1d69402e53dfb49ad7381eb067b338fd7b0cb22247225d47'; Hash.sha512hmac(new Buffer([]), new Buffer([])).toString('hex').should.equal(hex); }); - it('should calculate this known non-empty test vector correctly', function() { - var hex = 'c40bd7c15aa493b309c940e08a73ffbd28b2e4cb729eb94480d727e4df577b13cc403a78e6150d83595f3b17c4cc331f12ca5952691de3735a63c1d4c69a2bac'; + it('calculates this known non-empty test vector correctly', function() { + var hex = 'c40bd7c15aa493b309c940e08a73ffbd28b2e4cb729eb94480d727e4df577' + + 'b13cc403a78e6150d83595f3b17c4cc331f12ca5952691de3735a63c1d4c69a2bac'; var data = new Buffer('test1'); var key = new Buffer('test2'); Hash.sha512hmac(data, key).toString('hex').should.equal(hex);