From 5f6d02f5deb1b45e4812332bf20aa9142cbe8268 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 17 Jul 2014 13:09:35 -0700 Subject: [PATCH 1/6] add proper DER signature support to Key both creating DER signature from the r and s values, and parsing a DER signature into the r, s, and other properties. --- lib/Key.js | 10 +++++- lib/browser/Key.js | 41 ++++++++-------------- lib/common/Key.js | 83 +++++++++++++++++++++++++++++++++++++++++++++ lib/common/Point.js | 23 +++++++++++++ test/test.Key.js | 27 +++++++++++++++ 5 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 lib/common/Key.js diff --git a/lib/Key.js b/lib/Key.js index 8e079d9..6d69639 100644 --- a/lib/Key.js +++ b/lib/Key.js @@ -1 +1,9 @@ -module.exports = require('bindings')('KeyModule').Key; +var Key = require('bindings')('KeyModule').Key; +var CommonKey = require('./common/Key'); + +for (var i in CommonKey) { + if (CommonKey.hasOwnProperty(i)) + Key[i] = CommonKey[i]; +} + +module.exports = Key; diff --git a/lib/browser/Key.js b/lib/browser/Key.js index 8c42a96..4318d1b 100644 --- a/lib/browser/Key.js +++ b/lib/browser/Key.js @@ -2,12 +2,19 @@ var SecureRandom = require('../SecureRandom'); var bignum = require('bignum'); var elliptic = require('elliptic'); var Point = require('./Point'); +var CommonKey = require('../common/Key'); +var util = require('util'); var Key = function() { this._pub = null; this._compressed = true; // default }; +for (var i in CommonKey) { + if (CommonKey.hasOwnProperty(i)) + Key[i] = CommonKey[i]; +} + var bufferToArray = Key.bufferToArray = function(buffer) { var ret = []; @@ -117,37 +124,18 @@ Key.prototype.signSync = function(hash) { var sign = function(hash, priv) { var d = priv; - var n = ec.n; + var n = Point.getN(); var e = new bignum(hash); do { var k = genk(); - var G = ec.g; - var Q = G.mul(k); - var r = Q.getX().mod(n); + var G = Point.getG(); + var Q = Point.multiply(G, k); + var r = Q.x.mod(n); var s = k.invm(n).mul(e.add(d.mul(r))).mod(n); } while (r.cmp(new bignum(0)) <= 0 || s.cmp(new bignum(0)) <= 0); - return serializeSig(r, s); - }; - - var serializeSig = function(r, s) { - var rBa = r.toArray(); - var sBa = s.toArray(); - - var sequence = []; - sequence.push(0x02); // INTEGER - sequence.push(rBa.length); - sequence = sequence.concat(rBa); - - sequence.push(0x02); // INTEGER - sequence.push(sBa.length); - sequence = sequence.concat(sBa); - - sequence.unshift(sequence.length); - sequence.unshift(0x30); // SEQUENCE - - return sequence; + return {r: r, s: s}; }; if (!this.private) { @@ -158,9 +146,10 @@ Key.prototype.signSync = function(hash) { throw new Error('Arg should be a 32 bytes hash buffer'); } var privnum = new bignum(this.private); - var signature = sign(hash, privnum); + var sigrs = sign(hash, privnum); + var der = Key.rs2DER(sigrs.r, sigrs.s); - return new Buffer(signature); + return der; }; Key.prototype.verifySignature = function(hash, sig, callback) { diff --git a/lib/common/Key.js b/lib/common/Key.js new file mode 100644 index 0000000..4f7a446 --- /dev/null +++ b/lib/common/Key.js @@ -0,0 +1,83 @@ +var bignum = require('bignum'); +var Key = function() {} + +Key.parseDERsig = function(sig) { + if (!Buffer.isBuffer(sig)) + throw new Error('DER formatted signature should be a buffer'); + + var header = sig[0]; + + if (header !== 0x30) + throw new Error('Header byte should be 0x30'); + + var length = sig[1]; + if (length !== sig.slice(2).length) + throw new Error('Length byte should length of what follows'); + + var rheader = sig[2 + 0]; + if (rheader !== 0x02) + throw new Error('Integer byte for r should be 0x02'); + + var rlength = sig[2 + 1]; + var rbuf = sig.slice(2 + 2, 2 + 2 + rlength); + var r = bignum.fromBuffer(rbuf); + var rneg = sig[2 + 1 + 1] === 0x00 ? true : false; + if (rlength !== rbuf.length) + throw new Error('Length of r incorrect'); + + var sheader = sig[2 + 2 + rlength + 0]; + if (sheader !== 0x02) + throw new Error('Integer byte for s should be 0x02'); + + var slength = sig[2 + 2 + rlength + 1]; + var sbuf = sig.slice(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength); + var s = bignum.fromBuffer(sbuf); + var sneg = sig[2 + 2 + rlength + 2 + 2] === 0x00 ? true : false; + if (slength !== sbuf.length) + throw new Error('Length of s incorrect'); + + var sumlength = 2 + 2 + rlength + 2 + slength; + if (length !== sumlength - 2) + throw new Error('Length of signature incorrect'); + + + var obj = { + header: header, + length: length, + rheader: rheader, + rlength: rlength, + rneg: rneg, + rbuf: rbuf, + r: r, + sheader: sheader, + slength: slength, + sneg: sneg, + sbuf: sbuf, + s: s + }; + + return obj; +}; + +Key.rs2DER = function(r, s) { + var rnbuf = r.toBuffer(); + var snbuf = s.toBuffer(); + + var rneg = rnbuf[0] & 0x80 ? true : false; + var sneg = snbuf[0] & 0x80 ? true : false; + + var rbuf = rneg ? Buffer.concat([new Buffer([0x00]), rnbuf]) : rnbuf; + var sbuf = sneg ? Buffer.concat([new Buffer([0x00]), snbuf]) : snbuf; + + var length = 2 + rbuf.length + 2 + sbuf.length; + var rlength = rbuf.length; + var slength = sbuf.length; + var rheader = 0x02; + var sheader = 0x02; + var header = 0x30; + + var der = Buffer.concat([new Buffer([header, length, rheader, rlength]), rbuf, new Buffer([sheader, slength]), sbuf]); + return der; +}; + +module.exports = Key; diff --git a/lib/common/Point.js b/lib/common/Point.js index 7f5518d..64682f8 100644 --- a/lib/common/Point.js +++ b/lib/common/Point.js @@ -6,6 +6,29 @@ var Point = function(x, y) { this.y = y; }; +var n = bignum.fromBuffer(new Buffer("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 'hex'), { + size: 32 +}); + +Point.getN = function() { + return n; +}; + +var G; +Point.getG = function() { + // don't use Point in top scope, causes exception in browser + // when Point is not loaded yet + + // use cached version if available + G = G || new Point(bignum.fromBuffer(new Buffer("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 'hex'), { + size: 32 + }), + bignum.fromBuffer(new Buffer("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 'hex'), { + size: 32 + })); + return G; +}; + //convert the public key of a Key into a Point Point.fromUncompressedPubKey = function(pubkey) { var point = new Point(); diff --git a/test/test.Key.js b/test/test.Key.js index 5b2df64..da0797c 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -132,6 +132,33 @@ describe('Key (ECKey)', function() { ret.should.equal(false); }); + describe('#parseDERsig', function() { + it('should parse this signature generated in node', function() { + var sighex = '30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72'; + var sig = new Buffer(sighex, 'hex'); + var parsed = Key.parseDERsig(sig); + parsed.header.should.equal(0x30) + parsed.length.should.equal(69) + parsed.rlength.should.equal(33); + parsed.rneg.should.equal(true); + parsed.rbuf.toString('hex').should.equal('008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa'); + parsed.r.toString().should.equal('63173831029936981022572627018246571655303050627048489594159321588908385378810'); + parsed.slength.should.equal(32); + parsed.sneg.should.equal(false); + parsed.sbuf.toString('hex').should.equal('0993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72'); + parsed.s.toString().should.equal('4331694221846364448463828256391194279133231453999942381442030409253074198130'); + }); + }); + + describe('#rs2DER', function() { + it('should convert these known r and s values into a known signature', function() { + var r = new bignum('63173831029936981022572627018246571655303050627048489594159321588908385378810'); + var s = new bignum('4331694221846364448463828256391194279133231453999942381442030409253074198130'); + var der = Key.rs2DER(r, s); + der.toString('hex').should.equal('30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72'); + }); + }); + describe('generateSync', function() { it('should not generate the same key twice in a row', function() { var key1 = Key.generateSync(); From 2c136d4dcce8e60dcf52e2453ca2158cec2ee8c4 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 17 Jul 2014 13:14:49 -0700 Subject: [PATCH 2/6] remove obsolete Curve class (G and n now accessible from Point) --- bitcore.js | 1 - browser/build.js | 1 - lib/Curve.js | 32 -------------------------------- test/index.html | 1 - test/test.Curve.js | 37 ------------------------------------- test/test.Point.js | 2 +- 6 files changed, 1 insertion(+), 73 deletions(-) delete mode 100644 lib/Curve.js delete mode 100644 test/test.Curve.js diff --git a/bitcore.js b/bitcore.js index 658a31d..d9d8cdf 100644 --- a/bitcore.js +++ b/bitcore.js @@ -33,7 +33,6 @@ requireWhenAccessed('buffertools', 'buffertools'); requireWhenAccessed('Buffers.monkey', './patches/Buffers.monkey'); requireWhenAccessed('config', './config'); requireWhenAccessed('const', './const'); -requireWhenAccessed('Curve', './lib/Curve'); requireWhenAccessed('Deserialize', './lib/Deserialize'); requireWhenAccessed('ECIES', './lib/ECIES'); requireWhenAccessed('log', './util/log'); diff --git a/browser/build.js b/browser/build.js index a34aae6..39d1080 100644 --- a/browser/build.js +++ b/browser/build.js @@ -20,7 +20,6 @@ var modules = [ 'lib/Block', 'lib/Bloom', 'lib/Connection', - 'lib/Curve', 'lib/Deserialize', 'lib/ECIES', 'lib/Electrum', diff --git a/lib/Curve.js b/lib/Curve.js deleted file mode 100644 index 842bea7..0000000 --- a/lib/Curve.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; -var bignum = require('bignum'); -var Point = require('./Point'); - -var n = bignum.fromBuffer(new Buffer("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 'hex'), { - size: 32 -}); - - -var Curve = function() {}; - -/* secp256k1 curve */ -var G; -Curve.getG = function() { - // don't use Point in top scope, causes exception in browser - // when Point is not loaded yet - - // use cached version if available - G = G || new Point(bignum.fromBuffer(new Buffer("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 'hex'), { - size: 32 - }), - bignum.fromBuffer(new Buffer("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 'hex'), { - size: 32 - })); - return G; -}; - -Curve.getN = function() { - return n; -}; - -module.exports = Curve; diff --git a/test/index.html b/test/index.html index 8dd4bd5..aa53631 100644 --- a/test/index.html +++ b/test/index.html @@ -23,7 +23,6 @@ - diff --git a/test/test.Curve.js b/test/test.Curve.js deleted file mode 100644 index bb6ba04..0000000 --- a/test/test.Curve.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -var chai = chai || require('chai'); -var bitcore = bitcore || require('../bitcore'); -var coinUtil = coinUtil || bitcore.util; -var buffertools = require('buffertools'); -var bignum = bitcore.Bignum; - -var should = chai.should(); -var assert = chai.assert; - -var Curve = bitcore.Curve; - -describe('Curve', function() { - - it('should initialize the main object', function() { - should.exist(Curve); - }); - - describe('getN', function() { - it('should return a big number', function() { - var N = Curve.getN(); - should.exist(N); - N.toBuffer({size: 32}).toString('hex').length.should.equal(64); - }); - }); - - describe('getG', function() { - it('should return a Point', function() { - var G = Curve.getG(); - should.exist(G.x); - G.x.toBuffer({size: 32}).toString('hex').length.should.equal(64); - G.y.toBuffer({size: 32}).toString('hex').length.should.equal(64); - }); - }); - -}); diff --git a/test/test.Point.js b/test/test.Point.js index 722e49b..43f7a5b 100644 --- a/test/test.Point.js +++ b/test/test.Point.js @@ -184,7 +184,7 @@ describe('Point', function() { describe('secp256k1 test vectors', function() { //test vectors from http://crypto.stackexchange.com/questions/784/are-there-any-secp256k1-ecdsa-test-examples-available - var G = bitcore.Curve.getG(); + var G = Point.getG(); testdata.dataSecp256k1.nTimesG.forEach(function(val) { it('should multiply n by G and get p from test data', function() { var n = new Buffer(val.n, 'hex'); From 57a55d0863d4194efca64fb00260d85dcc96658b Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 17 Jul 2014 15:24:19 -0700 Subject: [PATCH 3/6] expose signature internal functions "sign" and "genk" ... and add some signature tests --- lib/browser/Key.js | 23 +---------------------- lib/common/Key.js | 21 +++++++++++++++++++++ test/test.Key.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/lib/browser/Key.js b/lib/browser/Key.js index 4318d1b..6de6edc 100644 --- a/lib/browser/Key.js +++ b/lib/browser/Key.js @@ -117,27 +117,6 @@ Key.prototype.regenerateSync = function() { Key.prototype.signSync = function(hash) { var ec = elliptic.curves.secp256k1; - var genk = function() { - //TODO: account for when >= n - return new bignum(SecureRandom.getRandomBuffer(8)); - }; - - var sign = function(hash, priv) { - var d = priv; - var n = Point.getN(); - var e = new bignum(hash); - - do { - var k = genk(); - var G = Point.getG(); - var Q = Point.multiply(G, k); - var r = Q.x.mod(n); - var s = k.invm(n).mul(e.add(d.mul(r))).mod(n); - } while (r.cmp(new bignum(0)) <= 0 || s.cmp(new bignum(0)) <= 0); - - return {r: r, s: s}; - }; - if (!this.private) { throw new Error('Key does not have a private key set'); } @@ -146,7 +125,7 @@ Key.prototype.signSync = function(hash) { throw new Error('Arg should be a 32 bytes hash buffer'); } var privnum = new bignum(this.private); - var sigrs = sign(hash, privnum); + var sigrs = Key.sign(hash, privnum); var der = Key.rs2DER(sigrs.r, sigrs.s); return der; diff --git a/lib/common/Key.js b/lib/common/Key.js index 4f7a446..e3a288b 100644 --- a/lib/common/Key.js +++ b/lib/common/Key.js @@ -80,4 +80,25 @@ Key.rs2DER = function(r, s) { return der; }; +Key.sign = function(hash, priv, k) { + var d = priv; + var n = Point.getN(); + var e = new bignum(hash); + + do { + var k = k || Key.genk(); + var G = Point.getG(); + var Q = Point.multiply(G, k); + var r = Q.x.mod(n); + var s = k.invm(n).mul(e.add(d.mul(r))).mod(n); + } while (r.cmp(new bignum(0)) <= 0 || s.cmp(new bignum(0)) <= 0); + + return {r: r, s: s}; +}; + +Key.genk = function() { + //TODO: account for when >= n + return new bignum(SecureRandom.getRandomBuffer(8)); +}; + module.exports = Key; diff --git a/test/test.Key.js b/test/test.Key.js index da0797c..5597b57 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -148,6 +148,38 @@ describe('Key (ECKey)', function() { parsed.sbuf.toString('hex').should.equal('0993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72'); parsed.s.toString().should.equal('4331694221846364448463828256391194279133231453999942381442030409253074198130'); }); + + it('should parse this 69 byte signature', function() { + var sighex = '3043021f59e4705959cc78acbfcf8bd0114e9cc1b389a4287fb33152b73a38c319b50302202f7428a27284c757e409bf41506183e9e49dfb54d5063796dfa0d403a4deccfa'; + var sig = new Buffer(sighex, 'hex'); + var parsed = Key.parseDERsig(sig); + parsed.header.should.equal(0x30) + parsed.length.should.equal(67) + parsed.rlength.should.equal(31); + parsed.rneg.should.equal(false); + parsed.rbuf.toString('hex').should.equal('59e4705959cc78acbfcf8bd0114e9cc1b389a4287fb33152b73a38c319b503'); + parsed.r.toString().should.equal('158826015856106182499128681792325160381907915189052224498209222621383996675'); + parsed.slength.should.equal(32); + parsed.sneg.should.equal(false); + parsed.sbuf.toString('hex').should.equal('2f7428a27284c757e409bf41506183e9e49dfb54d5063796dfa0d403a4deccfa'); + parsed.s.toString().should.equal('21463938592353267769710297084836796652964571266930856168996063301532842380538'); + }); + + it('should parse this 68 byte signature', function() { + var sighex = '3042021e17cfe77536c3fb0526bd1a72d7a8e0973f463add210be14063c8a9c37632022061bfa677f825ded82ba0863fb0c46ca1388dd3e647f6a93c038168b59d131a51'; + var sig = new Buffer(sighex, 'hex'); + var parsed = Key.parseDERsig(sig); + parsed.header.should.equal(0x30) + parsed.length.should.equal(66) + parsed.rlength.should.equal(30); + parsed.rneg.should.equal(false); + parsed.rbuf.toString('hex').should.equal('17cfe77536c3fb0526bd1a72d7a8e0973f463add210be14063c8a9c37632'); + parsed.r.toString().should.equal('164345250294671732127776123343329699648286106708464198588053542748255794'); + parsed.slength.should.equal(32); + parsed.sneg.should.equal(false); + parsed.sbuf.toString('hex').should.equal('61bfa677f825ded82ba0863fb0c46ca1388dd3e647f6a93c038168b59d131a51'); + parsed.s.toString().should.equal('44212963026209759051804639008236126356702363229859210154760104982946304432721'); + }); }); describe('#rs2DER', function() { From 49ff6c7b766bf6b0aa1a86c98b2aea0f9aeb4513 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 17 Jul 2014 15:44:28 -0700 Subject: [PATCH 4/6] add further tests for Key --- test/test.Key.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test.Key.js b/test/test.Key.js index 5597b57..244035c 100644 --- a/test/test.Key.js +++ b/test/test.Key.js @@ -230,6 +230,17 @@ describe('Key (ECKey)', function() { key.private = bitcore.util.sha256('my fake private key'); key.regenerateSync(); + it('should verify a signature created right here', function() { + var sig = key.signSync(hash); + key.verifySignatureSync(hash, sig).should.equal(true); + }); + + it('should fail on an invalid signature', function() { + var sig = key.signSync(hash); + sig[15] = !sig[15]; + key.verifySignatureSync(hash, sig).should.equal(false); + }); + it('should verify this example generated in the browser', function() { var sig = new Buffer('304402200e02016b816e1b229559b6db97abc528438c64056a412eee2b7c41887ddf17010220ad9f1cd56fd382650286f51a842bba0a7664da164093db956b51f623b0d8e64f', 'hex'); key.verifySignatureSync(hash, sig).should.equal(true); From bfe5877ee73ec8111e029431d8497e13dcea8eed Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 17 Jul 2014 16:30:22 -0700 Subject: [PATCH 5/6] require SecureRandom and Point ... woops --- lib/common/Key.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/common/Key.js b/lib/common/Key.js index e3a288b..0665c49 100644 --- a/lib/common/Key.js +++ b/lib/common/Key.js @@ -1,4 +1,6 @@ var bignum = require('bignum'); +var Point = require('./Point'); +var SecureRandom = require('./SecureRandom'); var Key = function() {} Key.parseDERsig = function(sig) { From 0c8f7d4d2167fbb00d1b3bf6849e7e0ef46acbe6 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 17 Jul 2014 16:30:47 -0700 Subject: [PATCH 6/6] move bitcore-dev.js -> bundle.js To make it easier to test the master branch of bitcore with other apps, especially Copay. --- browser/build.js | 2 +- test/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/build.js b/browser/build.js index 39d1080..6d6de0a 100644 --- a/browser/build.js +++ b/browser/build.js @@ -166,7 +166,7 @@ if (require.main === module) { var pjson = require('../package.json'); bitcoreBundle.pipe( program.stdout ? process.stdout : - fs.createWriteStream('browser/bitcore-dev.js')); + fs.createWriteStream('browser/bundle.js')); } module.exports.createBitcore = createBitcore; diff --git a/test/index.html b/test/index.html index aa53631..db755ff 100644 --- a/test/index.html +++ b/test/index.html @@ -11,7 +11,7 @@ - +