diff --git a/lib/crypto/bn.js b/lib/crypto/bn.js index 60874d9..e084f61 100644 --- a/lib/crypto/bn.js +++ b/lib/crypto/bn.js @@ -1,23 +1,30 @@ +/** + * @file ecdsa.js + * @license MIT + * + * Copyright (c) 2014 reddit, Inc. + * Copyright (c) 2014 Ryan X. Charles + * Copyright (c) 2014 BitPay, Inc. + */ 'use strict'; var _BN = require('bn.js'); -var BN = function BN_extended(n) { - if (!(this instanceof BN_extended)) { - return new BN(n); +var BN = function BN(n, base) { + if (!(this instanceof BN)) { + return new BN(n, base); } - arguments[0] = n; - return _BN.apply(this, arguments); + _BN.apply(this, arguments); }; -module.exports = BN; - BN.prototype = _BN.prototype; -var reversebuf = function(buf, nbuf) { +var reversebuf = function(buf) { + var buf2 = new Buffer(buf.length); for (var i = 0; i < buf.length; i++) { - nbuf[i] = buf[buf.length-1-i]; + buf2[i] = buf[buf.length-1-i]; } + return buf2; }; BN.prototype.toJSON = function() { @@ -30,6 +37,16 @@ BN.prototype.fromJSON = function(str) { return this; }; +BN.prototype.fromNumber = function(n) { + var bn = BN(n); + bn.copy(this); + return this; +}; + +BN.prototype.toNumber = function() { + return parseInt(this['toString'](10), 10); +}; + BN.prototype.fromString = function(str) { var bn = BN(str); bn.copy(this); @@ -38,9 +55,7 @@ BN.prototype.fromString = function(str) { BN.fromBuffer = function(buf, opts) { if (typeof opts !== 'undefined' && opts.endian === 'little') { - var nbuf = new Buffer(buf.length); - reversebuf(buf, nbuf); - buf = nbuf; + buf = reversebuf(buf); } var hex = buf.toString('hex'); var bn = new BN(hex, 16); @@ -70,7 +85,6 @@ BN.prototype.toBuffer = function(opts) { else if (natlen < opts.size) { var rbuf = new Buffer(opts.size); - //rbuf.fill(0); for (var i = 0; i < buf.length; i++) rbuf[rbuf.length-1-i] = buf[buf.length-1-i]; for (var i = 0; i < opts.size - natlen; i++) @@ -84,31 +98,116 @@ BN.prototype.toBuffer = function(opts) { } if (typeof opts !== 'undefined' && opts.endian === 'little') { - var nbuf = new Buffer(buf.length); - reversebuf(buf, nbuf); - buf = nbuf; + buf = reversebuf(buf); } return buf; }; +// signed magnitude buffer +// most significant bit represents sign (0 = positive, -1 = negative) +BN.prototype.fromSM = function(buf, opts) { + if (buf.length === 0) + this.fromBuffer(new Buffer([0])); + + var endian = 'big'; + if (opts) + endian = opts.endian; + + if (endian == 'little') + buf = reversebuf(buf); + + if (buf[0] & 0x80) { + buf[0] = buf[0] & 0x7f; + this.fromBuffer(buf); + this.neg().copy(this); + } else { + this.fromBuffer(buf); + } + return this; +}; + +BN.prototype.toSM = function(opts) { + var endian = 'big'; + if (opts) + endian = opts.endian; + + var buf; + if (this.cmp(0) == -1) { + buf = this.neg().toBuffer(); + if (buf[0] & 0x80) + buf = Buffer.concat([new Buffer([0x80]), buf]); + else + buf[0] = buf[0] | 0x80; + } else { + buf = this.toBuffer(); + if (buf[0] & 0x80) + buf = Buffer.concat([new Buffer([0x00]), buf]); + } + + if (buf.length === 1 & buf[0] === 0) + buf = new Buffer([]); + + if (endian == 'little') + buf = reversebuf(buf); + + return buf; +}; + +// This is analogous to the constructor for CScriptNum in bitcoind. Many ops in +// bitcoind's script interpreter use CScriptNum, which is not really a proper +// bignum. Instead, an error is thrown if trying to input a number bigger than +// 4 bytes. We copy that behavior here. +BN.prototype.fromCScriptNumBuffer = function(buf, fRequireMinimal) { + var nMaxNumSize = 4; + if (buf.length > nMaxNumSize) + throw new Error('script number overflow'); + if (fRequireMinimal && buf.length > 0) { + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if ((buf[buf.length - 1] & 0x7f) === 0) { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) === 0) { + throw new Error("non-minimally encoded script number"); + } + } + } + return this.fromSM(buf, {endian: 'little'}); +}; + +// The corollary to the above, with the notable exception that we do not throw +// an error if the output is larger than four bytes. (Which can happen if +// performing a numerical operation that results in an overflow to more than 4 +// bytes). +BN.prototype.toCScriptNumBuffer = function(buf) { + return this.toSM({endian: 'little'}); +}; + function decorate(name) { - BN.prototype['_' + name] = _BN.prototype[name]; + BN.prototype['_' + name] = BN.prototype[name]; var f = function(b) { if (typeof b === 'string') - b = new _BN(b); + b = new BN(b); else if (typeof b === 'number') - b = new _BN(b.toString()); + b = new BN(b.toString()); return this['_' + name](b); }; BN.prototype[name] = f; }; -_BN.prototype.gt = function(b) { +BN.prototype.gt = function(b) { return this.cmp(b) > 0; }; -_BN.prototype.lt = function(b) { +BN.prototype.lt = function(b) { return this.cmp(b) < 0; }; @@ -121,8 +220,4 @@ decorate('cmp'); decorate('gt'); decorate('lt'); -BN.prototype.toNumber = function() { - return parseInt(this['toString'](10), 10); -}; - module.exports = BN; diff --git a/lib/crypto/ecdsa.js b/lib/crypto/ecdsa.js index 8735367..b0d7260 100644 --- a/lib/crypto/ecdsa.js +++ b/lib/crypto/ecdsa.js @@ -1,11 +1,20 @@ +/** + * @file ecdsa.js + * @license MIT + * + * Copyright (c) 2014 reddit, Inc. + * Copyright (c) 2014 Ryan X. Charles + * Copyright (c) 2014 BitPay, Inc. + */ 'use strict'; var BN = require('./bn'); var Point = require('./point'); var Signature = require('./signature'); -var Random = require('./random'); var PublicKey = require('../publickey'); var PrivateKey = require('../privatekey'); +var Random = require('./random'); +var Hash = require('./hash'); var ECDSA = function ECDSA(obj) { if (!(this instanceof ECDSA)) { @@ -18,16 +27,17 @@ var ECDSA = function ECDSA(obj) { ECDSA.prototype.set = function(obj) { this.hashbuf = obj.hashbuf || this.hashbuf; + this.endian = obj.endian || this.endian; //the endianness of hashbuf this.privkey = obj.privkey || this.privkey; - this.pubkey = obj.pubkey || this.pubkey; + this.pubkey = obj.pubkey || (this.privkey ? this.privkey.publicKey : this.pubkey); this.sig = obj.sig || this.sig; this.k = obj.k || this.k; this.verified = obj.verified || this.verified; return this; }; -ECDSA.prototype.privkey2pubkey = function(){ - this.pubkey = PublicKey.fromPrivateKey(this.privkey); +ECDSA.prototype.privkey2pubkey = function() { + this.pubkey = this.privkey.toPublicKey(); }; ECDSA.prototype.calci = function() { @@ -37,9 +47,10 @@ ECDSA.prototype.calci = function() { try { Qprime = this.sig2pubkey(); } catch (e) { - console.log(e); + console.error(e); continue; } + if (Qprime.point.eq(this.pubkey.point)) { this.sig.compressed = this.pubkey.compressed; return this; @@ -52,16 +63,21 @@ ECDSA.prototype.calci = function() { ECDSA.prototype.fromString = function(str) { var obj = JSON.parse(str); - if (obj.hashbuf) + if (obj.hashbuf) { this.hashbuf = new Buffer(obj.hashbuf, 'hex'); - if (obj.pubkey) + } + if (obj.pubkey) { this.pubkey = PublicKey.fromString(obj.pubkey); - if (obj.privkey) + } + if (obj.privkey) { this.privkey = PrivateKey.fromString(obj.privkey); - if (obj.sig) + } + if (obj.sig) { this.sig = Signature().fromString(obj.sig); - if (obj.k) + } + if (obj.k) { this.k = BN(obj.k, 10); + } return this; }; @@ -75,6 +91,37 @@ ECDSA.prototype.randomK = function() { return this; }; +// https://tools.ietf.org/html/rfc6979#section-3.2 +ECDSA.prototype.deterministicK = function(badrs) { + var v = new Buffer(32); + v.fill(0x01); + var k = new Buffer(32); + k.fill(0x00); + var x = this.privkey.bn.toBuffer({size: 32}); + k = Hash.sha256hmac(Buffer.concat([v, new Buffer([0x00]), x, this.hashbuf]), k); + v = Hash.sha256hmac(v, k); + k = Hash.sha256hmac(Buffer.concat([v, new Buffer([0x01]), x, this.hashbuf]), k); + v = Hash.sha256hmac(v, k); + v = Hash.sha256hmac(v, k); + 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); + v = Hash.sha256hmac(v, k); + T = BN().fromBuffer(v); + } + + this.k = T; + return this; +}; + // Information about public key recovery: // 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 @@ -123,8 +170,9 @@ ECDSA.prototype.sig2pubkey = function() { }; ECDSA.prototype.sigError = function() { - if (!Buffer.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) + if (!Buffer.isBuffer(this.hashbuf) || this.hashbuf.length !== 32) { return 'hashbuf must be a 32 byte buffer'; + } var r = this.sig.r; var s = this.sig.s; @@ -132,7 +180,7 @@ ECDSA.prototype.sigError = function() { || !(s.gt(0) && s.lt(Point.getN()))) return 'r and s not in range'; - var e = BN().fromBuffer(this.hashbuf); + var e = BN().fromBuffer(this.hashbuf, this.endian ? {endian: this.endian} : undefined); var n = Point.getN(); var sinv = s.invm(n); var u1 = sinv.mul(e).mod(n); @@ -151,11 +199,8 @@ ECDSA.prototype.sigError = function() { ECDSA.prototype.sign = function() { var hashbuf = this.hashbuf; var privkey = this.privkey; - var k = this.k; - var d = privkey.bn; - if (!k) - throw new Error('You must specify k - perhaps you should run signRandomK instead'); + var d = privkey.bn; if (!hashbuf || !privkey || !d) throw new Error('invalid parameters'); @@ -165,60 +210,79 @@ ECDSA.prototype.sign = function() { var N = Point.getN(); var G = Point.getG(); - var e = BN().fromBuffer(hashbuf); + var e = BN().fromBuffer(hashbuf, this.endian ? {endian: this.endian} : undefined); + // try different values of k until r, s are valid + var badrs = 0; do { + if (!this.k || badrs > 0) + this.deterministicK(badrs); + badrs++; + var k = this.k; 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: r, s: s, compressed: this.privkey.compressed}); - return this.sig; + //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: s, compressed: this.pubkey.compressed}); + return this; }; ECDSA.prototype.signRandomK = function() { - var k = this.randomK(); + this.randomK(); return this.sign(); }; ECDSA.prototype.toString = function() { var obj = {}; - if (this.hashbuf) + if (this.hashbuf) { obj.hashbuf = this.hashbuf.toString('hex'); - if (this.pubkey) - obj.pubkey = this.pubkey.toString(); - if (this.privkey) + } + if (this.privkey) { obj.privkey = this.privkey.toString(); - if (this.keypair) - obj.keypair = this.keypair.toString(); - if (this.sig) + } + if (this.pubkey) { + obj.pubkey = this.pubkey.toString(); + } + if (this.sig) { obj.sig = this.sig.toString(); - if (this.k) + } + if (this.k) { obj.k = this.k.toString(); + } return JSON.stringify(obj); }; ECDSA.prototype.verify = function() { if (!this.sigError()) - return true; + this.verified = true; else - return false; + this.verified = false; + return this; }; -ECDSA.sign = function(hashbuf, privkey) { +ECDSA.sign = function(hashbuf, privkey, endian) { + console.log('Signing', hashbuf, 'with key', privkey); return ECDSA().set({ hashbuf: hashbuf, + endian: endian, privkey: privkey - }).signRandomK(); + }).sign().sig; }; -ECDSA.verify = function(hashbuf, sig, pubkey) { +ECDSA.verify = function(hashbuf, sig, pubkey, endian) { return ECDSA().set({ hashbuf: hashbuf, + endian: endian, sig: sig, pubkey: pubkey - }).verify(); + }).verify().verified; }; module.exports = ECDSA; diff --git a/lib/crypto/signature.js b/lib/crypto/signature.js index 7d9e725..36c58ed 100644 --- a/lib/crypto/signature.js +++ b/lib/crypto/signature.js @@ -1,3 +1,11 @@ +/** + * @file ecdsa.js + * @license MIT + * + * Copyright (c) 2014 reddit, Inc. + * Copyright (c) 2014 Ryan X. Charles + * Copyright (c) 2014 BitPay, Inc. + */ 'use strict'; var BN = require('./bn'); @@ -168,4 +176,9 @@ Signature.prototype.toString = function() { return buf.toString('hex'); }; +Signature.SIGHASH_ALL = 0x01; +Signature.SIGHASH_NONE = 0x02; +Signature.SIGHASH_SINGLE = 0x03; +Signature.SIGHASH_ANYONECANPAY = 0x80; + module.exports = Signature; diff --git a/test/crypto/ecdsa.js b/test/crypto/ecdsa.js index e41559e..e764316 100644 --- a/test/crypto/ecdsa.js +++ b/test/crypto/ecdsa.js @@ -1,24 +1,23 @@ -'use strict'; - +var ECDSA = require('../../lib/crypto/ecdsa'); +var Hash = require('../../lib/crypto/hash'); +var Privkey = require('../../lib/privatekey'); +var Pubkey = require('../../lib/publickey'); +var Signature = require('../../lib/crypto/signature'); +var Point = require('../../lib/crypto/point'); +var BN = require('../../lib/crypto/bn'); +var point = require('../../lib/crypto/point'); var should = require('chai').should(); -var bitcore = require('../..'); -var ECDSA = bitcore.crypto.ECDSA; -var Hash = bitcore.crypto.Hash; -var Signature = bitcore.crypto.Signature; -var PrivateKey = bitcore.PrivateKey; -var PublicKey = bitcore.PublicKey; -var BN = bitcore.crypto.BN; +var vectors = require('./vectors/ecdsa'); -describe('ECDSA', function() { +describe("ECDSA", function() { it('should create a blank ecdsa', function() { var ecdsa = new ECDSA(); - should.exist(ecdsa); }); var ecdsa = new ECDSA(); ecdsa.hashbuf = Hash.sha256(new Buffer('test data')); - ecdsa.privkey = new PrivateKey(BN().fromBuffer(new Buffer('fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e', 'hex'))); + ecdsa.privkey = new Privkey(BN().fromBuffer(new Buffer('fee0a1f7afebf9d2a5a80c0c98a31c709681cce195cbcd06342b517970c0be1e', 'hex'))); ecdsa.privkey2pubkey(); describe('#set', function() { @@ -42,11 +41,12 @@ describe('ECDSA', function() { var hashbuf = Hash.sha256(new Buffer('some data')); var r = BN('71706645040721865894779025947914615666559616020894583599959600180037551395766', 10); var s = BN('109412465507152403114191008482955798903072313614214706891149785278625167723646', 10); - var ecdsa = new ECDSA(); - ecdsa.privkey = PrivateKey(BN().fromBuffer(Hash.sha256(new Buffer('test')))); - ecdsa.privkey2pubkey(); - ecdsa.hashbuf = hashbuf; - ecdsa.sig = new Signature({r: r, s: s}); + var ecdsa = new ECDSA({ + privkey: Privkey(BN().fromBuffer(Hash.sha256(new Buffer('test')))), + hashbuf: hashbuf, + sig: new Signature({r: r, s: s}) + }); + ecdsa.calci(); ecdsa.sig.i.should.equal(1); }); @@ -60,7 +60,6 @@ describe('ECDSA', function() { var ecdsa2 = new ECDSA(); ecdsa2.fromString(str); should.exist(ecdsa.hashbuf); - should.exist(ecdsa.pubkey); should.exist(ecdsa.privkey); }); @@ -85,11 +84,59 @@ describe('ECDSA', function() { }); + describe('#deterministicK', function() { + + it('should generate the same deterministic k', function() { + ecdsa.deterministicK(); + ecdsa.k.toBuffer().toString('hex').should.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e'); + }); + + it('should generate the same deterministic k if badrs is set', function() { + ecdsa.deterministicK(0); + ecdsa.k.toBuffer().toString('hex').should.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e'); + ecdsa.deterministicK(1); + ecdsa.k.toBuffer().toString('hex').should.not.equal('fcce1de7a9bcd6b2d3defade6afa1913fb9229e3b7ddf4749b55c4848b2a196e'); + ecdsa.k.toBuffer().toString('hex').should.equal('6f4dcca6fa7a137ae9d110311905013b3c053c732ad18611ec2752bb3dcef9d8'); + }); + + it('should compute this test vector correctly', function() { + // test fixture from bitcoinjs + // https://github.com/bitcoinjs/bitcoinjs-lib/blob/10630873ebaa42381c5871e20336fbfb46564ac8/test/fixtures/ecdsa.json#L6 + var ecdsa = new ECDSA(); + ecdsa.hashbuf = Hash.sha256(new Buffer('Everything should be made as simple as possible, but not simpler.')); + ecdsa.privkey = Privkey(BN(1)); + ecdsa.privkey2pubkey(); + ecdsa.deterministicK(); + ecdsa.k.toBuffer().toString('hex').should.equal('ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5'); + ecdsa.sign(); + ecdsa.sig.r.toString().should.equal('23362334225185207751494092901091441011938859014081160902781146257181456271561'); + ecdsa.sig.s.toString().should.equal('50433721247292933944369538617440297985091596895097604618403996029256432099938'); + }); + + }); + describe('#sig2pubkey', function() { it('should calculate the correct public key', function() { ecdsa.k = BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10); ecdsa.sign(); + ecdsa.sig.i = 0; + var pubkey = ecdsa.sig2pubkey(); + pubkey.point.eq(ecdsa.pubkey.point).should.equal(true); + }); + + it('should calculate the correct public key for this signature with low s', function() { + ecdsa.k = BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10); + ecdsa.sig = Signature().fromString('3045022100ec3cfe0e335791ad278b4ec8eac93d0347a97877bb1d54d35d189e225c15f6650220278cf15b05ce47fb37d2233802899d94c774d5480bba9f0f2d996baa13370c43'); + ecdsa.sig.i = 0; + var pubkey = ecdsa.sig2pubkey(); + pubkey.point.eq(ecdsa.pubkey.point).should.equal(true); + }); + + it('should calculate the correct public key for this signature with high s', function() { + ecdsa.k = BN('114860389168127852803919605627759231199925249596762615988727970217268189974335', 10); + ecdsa.sign(); + ecdsa.sig = Signature().fromString('3046022100ec3cfe0e335791ad278b4ec8eac93d0347a97877bb1d54d35d189e225c15f665022100d8730ea4fa31b804c82ddcc7fd766269f33a079ea38e012c9238f2e2bcff34fe'); ecdsa.sig.i = 1; var pubkey = ecdsa.sig2pubkey(); pubkey.point.eq(ecdsa.pubkey.point).should.equal(true); @@ -104,22 +151,28 @@ describe('ECDSA', function() { ecdsa.sigError().should.equal('hashbuf must be a 32 byte buffer'); }); + it.skip('should return an error if the pubkey is invalid', function() { + var ecdsa = new ECDSA(); + ecdsa.hashbuf = Hash.sha256(new Buffer('test')); + ecdsa.sigError().indexOf("Invalid pubkey").should.equal(0); + }); + it('should return an error if r, s are invalid', function() { var ecdsa = new ECDSA(); ecdsa.hashbuf = Hash.sha256(new Buffer('test')); - var pk = PublicKey.fromDER(new Buffer('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', 'hex')); + var pk = Pubkey.fromDER(new Buffer('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', 'hex')); ecdsa.pubkey = pk; ecdsa.sig = new Signature(); ecdsa.sig.r = BN(0); ecdsa.sig.s = BN(0); - ecdsa.sigError().should.equal('r and s not in range'); + 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.sigError().should.equal('Invalid signature'); + ecdsa.sigError().should.equal("Invalid signature"); }); }); @@ -129,13 +182,12 @@ describe('ECDSA', function() { it('should create a valid signature', function() { ecdsa.randomK(); ecdsa.sign(); - ecdsa.verify().should.equal(true); + ecdsa.verify().verified.should.equal(true); }); it('should should throw an error if hashbuf is not 32 bytes', function() { var ecdsa2 = ECDSA().set({ hashbuf: ecdsa.hashbuf.slice(0, 31), - pubkey: ecdsa.pubkey, privkey: ecdsa.privkey }); ecdsa2.randomK(); @@ -144,13 +196,29 @@ describe('ECDSA', function() { }).should.throw('hashbuf must be a 32 byte buffer'); }); + it('should default to deterministicK', function() { + var ecdsa2 = new ECDSA(ecdsa); + ecdsa2.k = undefined; + var called = 0; + var deterministicK = ecdsa2.deterministicK.bind(ecdsa2); + ecdsa2.deterministicK = function() { + deterministicK(); + called++; + }; + ecdsa2.sign(); + called.should.equal(1); + }); + }); describe('#signRandomK', function() { - it('should produce a signature', function() { + it('should produce a signature, and be different when called twice', function() { ecdsa.signRandomK(); should.exist(ecdsa.sig); + var ecdsa2 = ECDSA(ecdsa); + ecdsa2.signRandomK(); + ecdsa.sig.toString().should.not.equal(ecdsa2.sig.toString()); }); }); @@ -169,12 +237,12 @@ describe('ECDSA', function() { it('should verify a signature that was just signed', function() { ecdsa.sig = new Signature(); ecdsa.sig.fromString('3046022100e9915e6236695f093a4128ac2a956c40ed971531de2f4f41ba05fac7e2bd019c02210094e6a4a769cc7f2a8ab3db696c7cd8d56bcdbfff860a8c81de4bc6a798b90827'); - ecdsa.verify().should.equal(true); + ecdsa.verify().verified.should.equal(true); }); it('should verify this known good signature', function() { ecdsa.signRandomK(); - ecdsa.verify().should.equal(true); + ecdsa.verify().verified.should.equal(true); }); }); @@ -197,6 +265,53 @@ describe('ECDSA', function() { ECDSA.verify(ecdsa.hashbuf, fakesig, ecdsa.pubkey).should.equal(false); }); + it('should work with big and little endian', function() { + var sig = ECDSA.sign(ecdsa.hashbuf, ecdsa.privkey, 'big'); + ECDSA.verify(ecdsa.hashbuf, sig, ecdsa.pubkey, 'big').should.equal(true); + ECDSA.verify(ecdsa.hashbuf, sig, ecdsa.pubkey, 'little').should.equal(false); + sig = ECDSA.sign(ecdsa.hashbuf, ecdsa.privkey, 'little'); + ECDSA.verify(ecdsa.hashbuf, sig, ecdsa.pubkey, 'big').should.equal(false); + ECDSA.verify(ecdsa.hashbuf, sig, ecdsa.pubkey, 'little').should.equal(true); + }); + + }); + + describe('vectors', function() { + + vectors.valid.forEach(function(obj, i) { + it('should validate valid vector ' + i, function() { + var ecdsa = ECDSA().set({ + privkey: Privkey(BN().fromBuffer(new Buffer(obj.d, 'hex'))), + k: BN().fromBuffer(new Buffer(obj.k, 'hex')), + hashbuf: Hash.sha256(new Buffer(obj.message)), + sig: Signature().set({ + r: BN(obj.signature.r), + s: BN(obj.signature.s), + i: obj.i + }) + }); + var ecdsa2 = ECDSA(ecdsa); + ecdsa2.k = undefined; + ecdsa2.sign(); + ecdsa2.calci(); + ecdsa2.k.toString().should.equal(ecdsa.k.toString()); + ecdsa2.sig.toString().should.equal(ecdsa.sig.toString()); + ecdsa2.sig.i.should.equal(ecdsa.sig.i); + ecdsa.verify().verified.should.equal(true); + }); + }); + + vectors.invalid.sigError.forEach(function(obj, i) { + it('should validate invalid.sigError vector ' + i + ': ' + obj.description, function() { + var ecdsa = ECDSA().set({ + pubkey: Pubkey.fromPoint(point.fromX(true, 1)), + sig: Signature(BN(obj.signature.r), BN(obj.signature.s)), + hashbuf: Hash.sha256(new Buffer(obj.message)) + }); + ecdsa.sigError().should.equal(obj.exception); + }); + }); + }); }); diff --git a/test/crypto/vectors/ecdsa.json b/test/crypto/vectors/ecdsa.json new file mode 100644 index 0000000..fb2bcea --- /dev/null +++ b/test/crypto/vectors/ecdsa.json @@ -0,0 +1,158 @@ +{ + "valid": [ + { + "d": "01", + "k": "ec633bd56a5774a0940cb97e27a9e4e51dc94af737596a0c5cbb3d30332d92a5", + "message": "Everything should be made as simple as possible, but not simpler.", + "i": 0, + "signature": { + "r": "23362334225185207751494092901091441011938859014081160902781146257181456271561", + "s": "50433721247292933944369538617440297985091596895097604618403996029256432099938" + } + }, + { + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "k": "9dc74cbfd383980fb4ae5d2680acddac9dac956dca65a28c80ac9c847c2374e4", + "message": "Equations are more important to me, because politics is for the present, but an equation is something for eternity.", + "i": 0, + "signature": { + "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", + "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" + } + }, + { + "d": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140", + "k": "fd27071f01648ebbdd3e1cfbae48facc9fa97edc43bbbc9a7fdc28eae13296f5", + "message": "Not only is the Universe stranger than we think, it is stranger than we can think.", + "i": 0, + "signature": { + "r": "115464191557905790016094131873849783294273568009648050793030031933291767741904", + "s": "50562520307781850052192542766631199590053690478900449960232079510155113443971" + } + }, + { + "d": "0000000000000000000000000000000000000000000000000000000000000001", + "k": "f0cd2ba5fc7c183de589f6416220a36775a146740798756d8d949f7166dcc87f", + "message": "How wonderful that we have met with a paradox. Now we have some hope of making progress.", + "i": 1, + "signature": { + "r": "87230998027579607140680851455601772643840468630989315269459846730712163783123", + "s": "53231320085894623106179381504478252331065330583563809963303318469380290929875" + } + }, + { + "d": "69ec59eaa1f4f2e36b639716b7c30ca86d9a5375c7b38d8918bd9c0ebc80ba64", + "k": "6bb4a594ad57c1aa22dbe991a9d8501daf4688bf50a4892ef21bd7c711afda97", + "message": "Computer science is no more about computers than astronomy is about telescopes.", + "i": 0, + "signature": { + "r": "51348483531757779992459563033975330355971795607481991320287437101831125115997", + "s": "6277080015686056199074771961940657638578000617958603212944619747099038735862" + } + }, + { + "d": "00000000000000000000000000007246174ab1e92e9149c6e446fe194d072637", + "k": "097b5c8ee22c3ea78a4d3635e0ff6fe85a1eb92ce317ded90b9e71aab2b861cb", + "message": "...if you aren't, at any given time, scandalized by code you wrote five or even three years ago, you're not learning anywhere near enough", + "i": 1, + "signature": { + "r": "113979859486826658566290715281614250298918272782414232881639314569529560769671", + "s": "6517071009538626957379450615706485096874328019806177698938278220732027419959" + } + }, + { + "d": "000000000000000000000000000000000000000000056916d0f9b31dc9b637f3", + "k": "19355c36c8cbcdfb2382e23b194b79f8c97bf650040fc7728dfbf6b39a97c25b", + "message": "The question of whether computers can think is like the question of whether submarines can swim.", + "i": 1, + "signature": { + "r": "93122007060065279508564838030979550535085999589142852106617159184757394422777", + "s": "3078539468410661027472930027406594684630312677495124015420811882501887769839" + } + } + ], + "invalid": { + "sigError": [ + { + "description": "The wrong signature", + "exception": "Invalid signature", + "d": "01", + "message": "foo", + "signature": { + "r": "38341707918488238920692284707283974715538935465589664377561695343399725051885", + "s": "3180566392414476763164587487324397066658063772201694230600609996154610926757" + } + }, + { + "description": "Invalid r value (< 0)", + "exception": "r and s not in range", + "d": "01", + "message": "foo", + "signature": { + "r": "-1", + "s": "2" + } + }, + { + "description": "Invalid r value (== 0)", + "exception": "r and s not in range", + "d": "01", + "message": "foo", + "signature": { + "r": "0", + "s": "2" + } + }, + { + "description": "Invalid r value (>= n)", + "exception": "r and s not in range", + "d": "01", + "message": "foo", + "signature": { + "r": "115792089237316195423570985008687907852837564279074904382605163141518161494337", + "s": "2" + } + }, + { + "description": "Invalid s value (< 0)", + "exception": "r and s not in range", + "d": "01", + "message": "foo", + "signature": { + "r": "2", + "s": "-1" + } + }, + { + "description": "Invalid s value (== 0)", + "exception": "r and s not in range", + "d": "01", + "message": "foo", + "signature": { + "r": "2", + "s": "0" + } + }, + { + "description": "Invalid s value (>= n)", + "exception": "r and s not in range", + "d": "01", + "message": "foo", + "signature": { + "r": "2", + "s": "115792089237316195423570985008687907852837564279074904382605163141518161494337" + } + }, + { + "description": "Invalid r, s values (r = s = -n)", + "exception": "r and s not in range", + "d": "01", + "message": "foo", + "signature": { + "r": "-115792089237316195423570985008687907852837564279074904382605163141518161494337", + "s": "-115792089237316195423570985008687907852837564279074904382605163141518161494337" + } + } + ] + } +}