diff --git a/index.js b/index.js index 7371c59f6..4c943e936 100644 --- a/index.js +++ b/index.js @@ -16,9 +16,9 @@ privsec.constants = require('./lib/constants'); privsec.hash = require('./lib/hash'); privsec.point = require('./lib/point'); privsec.privkey = require('./lib/privkey'); +privsec.pubkey = require('./lib/pubkey'); //privsec.key = require('lib/key'); -//privsec.pubkey = require('lib/pubkey'); //privsec.script = require('lib/script'); //privsec.scriptexec = require('lib/scriptexec'); //privsec.tx = require('lib/tx'); diff --git a/lib/pubkey.js b/lib/pubkey.js new file mode 100644 index 000000000..8da4b9161 --- /dev/null +++ b/lib/pubkey.js @@ -0,0 +1,67 @@ +var point = require('./point'); +var bn = require('./bn'); + +var Pubkey = function(p) { + this.p = p; +}; + +Pubkey.prototype.fromDER = function(buf) { + if (buf[0] == 0x04) { + var xbuf = buf.slice(1, 33); + var ybuf = buf.slice(33, 65); + if (xbuf.length !== 32 || ybuf.length !== 32 || buf.length !== 65) + throw new Error('pubkey: Length of x and y must be 32 bytes'); + var x = bn(xbuf); + var y = bn(ybuf); + this.p = point(x, y); + } else if (buf[0] == 0x03) { + var xbuf = buf.slice(1); + var x = bn(xbuf); + this.fromX(true, x); + } else if (buf[0] == 0x02) { + var xbuf = buf.slice(1); + var x = bn(xbuf); + this.fromX(false, x); + } else { + throw new Error('pubkey: Invalid DER format pubkey'); + } +}; + +Pubkey.prototype.fromString = function(str) { + this.fromDER(new Buffer(str, 'hex')); +}; + +Pubkey.prototype.fromX = function(odd, x) { + if (typeof odd !== 'boolean') + throw new Error('pubkey: Must specify whether y is odd or not (true or false)'); + this.p = point.fromX(odd, x); +}; + +Pubkey.prototype.toDER = function(compressed) { + if (typeof compressed !== 'boolean') + throw new Error('pubkey: Must specify whether the public key is compressed or not (true or false)'); + + var x = this.p.getX(); + var y = this.p.getY(); + + var xbuf = x.toBuffer({size: 32}); + var ybuf = y.toBuffer({size: 32}); + + if (!compressed) { + var prefix = new Buffer([0x04]); + return Buffer.concat([prefix, xbuf, ybuf]); + } else { + var odd = ybuf[ybuf.length - 1] % 2; + if (odd) + var prefix = new Buffer([0x03]); + else + var prefix = new Buffer([0x02]); + return Buffer.concat([prefix, xbuf]); + } +}; + +Pubkey.prototype.toString = function() { + return this.toDER(true).toString('hex'); +}; + +module.exports = Pubkey; diff --git a/test/test.pubkey.js b/test/test.pubkey.js new file mode 100644 index 000000000..e362a27e9 --- /dev/null +++ b/test/test.pubkey.js @@ -0,0 +1,96 @@ +var should = require('chai').should(); +var pubkey = require('../lib/pubkey'); +var point = require('../lib/point'); +var bn = require('../lib/bn'); + +describe('pubkey', function() { + + it('should create a blank public key', function() { + var pk = new pubkey(); + should.exist(pk); + }); + + it('should create a public key with a point', function() { + var p = point(); + var pk = new pubkey(p); + should.exist(pk.p); + }); + + describe('#fromDER', function() { + + it('should parse this uncompressed public key', function() { + var pk = new pubkey(); + pk.fromDER(new Buffer('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341', 'hex')); + pk.p.getX().toString(16).should.equal('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'); + pk.p.getY().toString(16).should.equal('7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); + }); + + it('should parse this compressed public key', function() { + var pk = new pubkey(); + pk.fromDER(new Buffer('031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a', 'hex')); + pk.p.getX().toString(16).should.equal('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'); + pk.p.getY().toString(16).should.equal('7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); + }); + + it('should throw an error on this invalid public key', function() { + var pk = new pubkey(); + (function() { + pk.fromDER(new Buffer('091ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a', 'hex')); + }).should.throw(); + }); + + }); + + describe('#fromString', function() { + + it('should parse this known valid public key', function() { + pk = new pubkey(); + pk.fromString('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); + pk.p.getX().toString(16).should.equal('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'); + pk.p.getY().toString(16).should.equal('7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); + }); + + }); + + describe('#fromX', function() { + + it('should create this known public key', function() { + var x = bn.fromBuffer(new Buffer('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a', 'hex')); + var pk = new pubkey(); + pk.fromX(true, x); + pk.p.getX().toString(16).should.equal('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'); + pk.p.getY().toString(16).should.equal('7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); + }); + + }); + + describe('#toDER', function() { + + it('should return this compressed DER format', function() { + var x = bn.fromBuffer(new Buffer('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a', 'hex')); + var pk = new pubkey(); + pk.fromX(true, x); + pk.toDER(true).toString('hex').should.equal('031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'); + }); + + it('should return this uncompressed DER format', function() { + var x = bn.fromBuffer(new Buffer('1ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a', 'hex')); + var pk = new pubkey(); + pk.fromX(true, x); + pk.toDER(false).toString('hex').should.equal('041ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a7baad41d04514751e6851f5304fd243751703bed21b914f6be218c0fa354a341'); + }); + + }); + + describe('#toString', function() { + + it('should print this known public key', function() { + var hex = '031ff0fe0f7b15ffaa85ff9f4744d539139c252a49710fb053bb9f2b933173ff9a'; + var pk = new pubkey(); + pk.fromString(hex); + pk.toString().should.equal(hex); + }); + + }); + +});