diff --git a/index.js b/index.js index c245ad6e3..fb3ab50a9 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,7 @@ privsec.point = require('./lib/point'); privsec.privkey = require('./lib/privkey'); privsec.pubkey = require('./lib/pubkey'); privsec.random = require('./lib/random'); +privsec.signature = require('./lib/signature'); //privsec.script = require('lib/script'); //privsec.scriptexec = require('lib/scriptexec'); diff --git a/lib/signature.js b/lib/signature.js new file mode 100644 index 000000000..451358e9f --- /dev/null +++ b/lib/signature.js @@ -0,0 +1,127 @@ +var bn = require('./bn'); + +var Signature = function(r, s) { + this.r = r; + this.s = s; +}; + +Signature.prototype.fromCompressed = function(buf) { + var b1 = buf.slice(0, 1)[0]; + var b2 = buf.slice(1, 33); + var b3 = buf.slice(33, 65); + + if (!(b1 === 0 || b1 === 1 || b1 === 2 || b1 === 3)) + throw new Error('signature: i must be 0, 1, 2, or 3'); + if (b2.length !== 32) + throw new Error('signature: r must be 32 bytes'); + if (b3.length !== 32) + throw new Error('signature: s must be 32 bytes'); + + this.r = bn.fromBuffer(b2); + this.s = bn.fromBuffer(b3); +}; + +Signature.prototype.fromDER = function(buf) { + var obj = Signature.parseDER(buf); + this.r = obj.r; + this.s = obj.s; +}; + +Signature.prototype.fromString = function(str) { + var buf = new Buffer(str, 'hex'); + this.fromDER(buf); +}; + +Signature.parseDER = function(buf) { + if (!Buffer.isBuffer(buf)) + throw new Error('signature: DER formatted signature should be a buffer'); + + var header = buf[0]; + + if (header !== 0x30) + throw new Error('signature: Header byte should be 0x30'); + + var length = buf[1]; + if (length !== buf.slice(2).length) + throw new Error('signature: Length byte should length of what follows'); + + var rheader = buf[2 + 0]; + if (rheader !== 0x02) + throw new Error('signature: Integer byte for r should be 0x02'); + + var rlength = buf[2 + 1]; + var rbuf = buf.slice(2 + 2, 2 + 2 + rlength); + var r = bn.fromBuffer(rbuf); + var rneg = buf[2 + 1 + 1] === 0x00 ? true : false; + if (rlength !== rbuf.length) + throw new Error('signature: Length of r incorrect'); + + var sheader = buf[2 + 2 + rlength + 0]; + if (sheader !== 0x02) + throw new Error('signature: Integer byte for s should be 0x02'); + + var slength = buf[2 + 2 + rlength + 1]; + var sbuf = buf.slice(2 + 2 + rlength + 2, 2 + 2 + rlength + 2 + slength); + var s = bn.fromBuffer(sbuf); + var sneg = buf[2 + 2 + rlength + 2 + 2] === 0x00 ? true : false; + if (slength !== sbuf.length) + throw new Error('signature: Length of s incorrect'); + + var sumlength = 2 + 2 + rlength + 2 + slength; + if (length !== sumlength - 2) + throw new Error('signature: 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; +}; + +Signature.prototype.toCompressed = function(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 b1 = new Buffer([i]); + var b2 = this.r.toBuffer({size: 32}); + var b3 = this.s.toBuffer({size: 32}); + return Buffer.concat([b1, b2, b3]); +}; + +Signature.prototype.toDER = function() { + var rnbuf = this.r.toBuffer(); + var snbuf = this.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; +}; + +Signature.prototype.toString = function() { + var buf = this.toDER(); + return buf.toString('hex'); +}; +module.exports = Signature; diff --git a/test/test.signature.js b/test/test.signature.js new file mode 100644 index 000000000..870dd094a --- /dev/null +++ b/test/test.signature.js @@ -0,0 +1,132 @@ +var bn = require('../lib/bn'); +var should = require('chai').should(); +var Signature = require('../lib/signature'); + +describe('Signature', function() { + + it('should make a blank signature', function() { + var sig = new Signature(); + should.exist(sig); + }); + + describe('#fromCompressed', function() { + + it('should create a signature from a compressed signature', function() { + var blank = new Buffer(32); + blank.fill(0); + var compressed = Buffer.concat([ + new Buffer([0]), + blank, + blank + ]); + var sig = new Signature(); + sig.fromCompressed(compressed); + sig.r.cmp(0).should.equal(0); + sig.s.cmp(0).should.equal(0); + }); + + }); + + describe('#fromDER', function() { + + var buf = new Buffer('3044022075fc517e541bd54769c080b64397e32161c850f6c1b2b67a5c433affbb3e62770220729e85cc46ffab881065ec07694220e71d4df9b2b8c8fd12c3122cf3a5efbcf2', 'hex'); + + it('should parse this DER format signature', function() { + var sig = new Signature(); + sig.fromDER(buf); + sig.r.toBuffer({size: 32}).toString('hex').should.equal('75fc517e541bd54769c080b64397e32161c850f6c1b2b67a5c433affbb3e6277'); + sig.s.toBuffer({size: 32}).toString('hex').should.equal('729e85cc46ffab881065ec07694220e71d4df9b2b8c8fd12c3122cf3a5efbcf2'); + }); + + }); + + describe('#fromString', function() { + + var buf = new Buffer('3044022075fc517e541bd54769c080b64397e32161c850f6c1b2b67a5c433affbb3e62770220729e85cc46ffab881065ec07694220e71d4df9b2b8c8fd12c3122cf3a5efbcf2', 'hex'); + + it('should parse this DER format signature in hex', function() { + var sig = new Signature(); + sig.fromString(buf.toString('hex')); + sig.r.toBuffer({size: 32}).toString('hex').should.equal('75fc517e541bd54769c080b64397e32161c850f6c1b2b67a5c433affbb3e6277'); + sig.s.toBuffer({size: 32}).toString('hex').should.equal('729e85cc46ffab881065ec07694220e71d4df9b2b8c8fd12c3122cf3a5efbcf2'); + }); + + }); + + describe('#parseDER', function() { + + it('should parse this signature generated in node', function() { + var sighex = '30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72'; + var sig = new Buffer(sighex, 'hex'); + var parsed = Signature.parseDER(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'); + }); + + it('should parse this 69 byte signature', function() { + var sighex = '3043021f59e4705959cc78acbfcf8bd0114e9cc1b389a4287fb33152b73a38c319b50302202f7428a27284c757e409bf41506183e9e49dfb54d5063796dfa0d403a4deccfa'; + var sig = new Buffer(sighex, 'hex'); + var parsed = Signature.parseDER(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 = Signature.parseDER(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('#toDER', function() { + + it('should convert these known r and s values into a known signature', function() { + var r = bn('63173831029936981022572627018246571655303050627048489594159321588908385378810'); + var s = bn('4331694221846364448463828256391194279133231453999942381442030409253074198130'); + var sig = new Signature(r, s); + var der = sig.toDER(r, s); + der.toString('hex').should.equal('30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72'); + }); + + }); + + describe('#toString', function() { + + it('should convert this signature in to hex DER', function() { + var r = bn('63173831029936981022572627018246571655303050627048489594159321588908385378810'); + var s = bn('4331694221846364448463828256391194279133231453999942381442030409253074198130'); + var sig = new Signature(r, s); + var hex = sig.toString(); + hex.should.equal('30450221008bab1f0a2ff2f9cb8992173d8ad73c229d31ea8e10b0f4d4ae1a0d8ed76021fa02200993a6ec81755b9111762fc2cf8e3ede73047515622792110867d12654275e72'); + }); + + }); + +});