From 5f6d02f5deb1b45e4812332bf20aa9142cbe8268 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Thu, 17 Jul 2014 13:09:35 -0700 Subject: [PATCH] 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();