diff --git a/README.md b/README.md index c291aef..cde1025 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ With the help of browserify `bitmessage` provides different implementations for - [x] encode - [x] decode - [x] getRipe - - [x] getRandom - - [ ] getDeterministic + - [x] fromRandom + - [ ] fromPassphrase - [ ] Message - [ ] encode - [ ] decode @@ -78,11 +78,11 @@ With the help of browserify `bitmessage` provides different implementations for ## Usage ```js +// Generating a new Bitmessage identity. var Address = require("bitmessage").Address; -Address.getRandom().then(function(addr) { - Address.encode(addr).then(function(str) { +Address.fromRandom().then(function(addr) { + addr.encode().then(function(str) { console.log("New random Bitmessage address:", str); - console.log("Private keys and parameters:", addr); }); }); ``` diff --git a/lib/address.js b/lib/address.js index 9e6cfbd..178be7e 100644 --- a/lib/address.js +++ b/lib/address.js @@ -6,18 +6,40 @@ "use strict"; require("es6-promise").polyfill(); +require("object.assign").shim(); var assert = require("assert"); var bufferEqual = require("buffer-equal"); var bs58 = require("bs58"); var varint = require("./varint"); var bmcrypto = require("./crypto"); +/** + * Create a new Bitmessage address object. + * @param {?Object} opts - Address options + * @constructor + */ +function Address(opts) { + if (!(this instanceof Address)) { + return new Address(opts); + } + opts = opts || {}; + Object.assign(this, opts); + this.version = this.version || 4; + assert(this.version <= 4, "Version too high"); + assert(this.version >= 1, "Version too low"); + this.stream = this.stream || 1; + if (this.ripe) { + assertripelen(getripelen(this.ripe), this.version); + } +} + /** * Parse Bitmessage address into address object. * @param {String} str - Address string (with or without `BM-` prefix) * @return {Promise.} Decoded address object + * @static */ -exports.decode = function(str) { +Address.decode = function(str) { str = str.trim(); if (str.slice(0, 3) === "BM-") { str = str.slice(3); @@ -38,18 +60,15 @@ exports.decode = function(str) { var decoded = varint.decode(data); var version = decoded.value; - assertversion(version); data = decoded.rest; decoded = varint.decode(data); var stream = decoded.value; - assertstream(stream); var ripe = decoded.rest; var ripelen = ripe.length; - assertripelen(ripelen, version); if (version === 4) { - assert(ripe[0] !== 0, "Ripe decode error"); + assert(ripe[0] !== 0, "Ripe encode error"); } // Prevent extra allocation. God, kill me please for premature @@ -58,7 +77,7 @@ exports.decode = function(str) { var zeroes = new Buffer(Array(20 - ripelen)); ripe = Buffer.concat([zeroes, ripe]); } - return {version: version, stream: stream, ripe: ripe}; + return new Address({version: version, stream: stream, ripe: ripe}); }); }; @@ -89,38 +108,33 @@ function keys2ripe(signKey, encKey) { } /** - * Get Ripe hash for the given address object. - * @param {Address} addr - Address object + * Calculate the Ripe hash of the address. * @param {?Object} opts - Options - * @return {Buffer} Resulting Ripe hash. + * @return {Promise.} Resulting Ripe hash */ -function getRipe(addr, opts) { - var signKey = addr.signPrivateKey || addr.signPublicKey; - assert(signKey, "No signing key"); - var encKey = addr.encPrivateKey || addr.encPublicKey; - assert(encKey, "No encryption key"); - opts = opts || {}; - return keys2ripe(signKey, encKey).then(function(ripe) { +Address.prototype.getRipe = function(opts) { + var self = this; + var ripepromise; + if (self.ripe) { + ripepromise = Promise.resolve(self.ripe); + } else { + opts = opts || {}; + var signKey = self.signPrivateKey || self.signPublicKey; + assert(signKey, "No signing key"); + var encKey = self.encPrivateKey || self.encPublicKey; + assert(encKey, "No encryption key"); + ripepromise = keys2ripe(signKey, encKey); + } + return ripepromise.then(function(ripe) { + var ripelen = getripelen(ripe); + assertripelen(ripelen, self.version); if (opts.short) { - var ripelen = getripelen(ripe); return ripe.slice(20 - ripelen); } else { return ripe; } }); -} -exports.getRipe = getRipe; - -// Do neccessary checkings of the address version. -function assertversion(version) { - assert(version <= 4, "Version too high"); - assert(version >= 1, "Version too low"); -} - -// Do neccessary checkings of the stream number. -function assertstream(stream) { - assert(stream, "No stream"); -} +}; // Get truncated Ripe hash length. function getripelen(ripe) { @@ -148,7 +162,7 @@ function assertripelen(ripelen, version) { assert(ripelen <= 20, "Ripe too long"); break; default: - throw new Error("Wrong version"); + throw new Error("Bad version"); } } @@ -165,34 +179,14 @@ function checkripelen(ripelen, version) { /** * Encode Bitmessage address object into address string. - * @param {Address} addr - Address object * @return {Promise.} Address string */ -exports.encode = function(addr) { - var version, stream, ripepromise; - try { - version = addr.version; - assertversion(version); - stream = addr.stream; - assertstream(stream); - - if (addr.ripe) { - ripepromise = Promise.resolve(addr.ripe); - } else { - ripepromise = getRipe(addr); - } - } catch (e) { - return Promise.reject(e); - } - - return ripepromise.then(function(ripe) { - var ripelen = getripelen(ripe); - assertripelen(ripelen, version); - // Skip leading zeroes. - ripe = ripe.slice(20 - ripelen); +Address.prototype.encode = function() { + var self = this; + return self.getRipe({short: true}).then(function(ripe) { var data = Buffer.concat([ - varint.encode(version), - varint.encode(stream), + varint.encode(self.version), + varint.encode(self.stream), ripe, ]); return getchecksum(data).then(function(checksum) { @@ -203,24 +197,18 @@ exports.encode = function(addr) { }; /** - * Create new Bitmessage address using random encryption and signing + * Create new Bitmessage address from random encryption and signing * private keys. * @param {?Object} opts - Address options * @return {Promise.} Generated address object + * @static */ -exports.getRandom = function(opts) { - var version, stream, ripelen, signPrivateKey; +Address.fromRandom = function(opts) { opts = opts || {}; + var version = opts.version || 4; + var ripelen = opts.ripelen || 19; try { - version = opts.version || 4; - assertversion(version); - stream = opts.stream || 1; - assertstream(version); - ripelen = opts.ripelen || 19; assertripelen(ripelen, version); - // Place it to try-catch since there might be not enough entropy to - // generate the key and the function will fail. - signPrivateKey = bmcrypto.getPrivate(); } catch(e) { return Promise.reject(e); } @@ -230,6 +218,8 @@ exports.getRandom = function(opts) { var nextTick = typeof setImmediate === "undefined" ? process.nextTick : setImmediate; + + var signPrivateKey = bmcrypto.getPrivate(); var signPublicKey = bmcrypto.getPublic(signPrivateKey); // FIXME(Kagami): This function is rather slow in browsers so @@ -242,24 +232,23 @@ exports.getRandom = function(opts) { // available in Chrome (at least in 39.0+). return new Promise(function(resolve, reject) { function tryKey() { - var encPrivateKey; - try { - encPrivateKey = bmcrypto.getPrivate(); - } catch(e) { - reject(e); - } - return keys2ripe(signPublicKey, encPrivateKey).then(function(ripe) { + var encPrivateKey = bmcrypto.getPrivate(); + var encPublicKey = bmcrypto.getPublic(encPrivateKey); + return keys2ripe(signPublicKey, encPublicKey).then(function(ripe) { var len = getripelen(ripe); if ( (strictripelen && len === ripelen) || (!strictripelen && len <= ripelen && checkripelen(ripelen, version)) ) { - resolve({ - version: version, - stream: stream, + // XXX(Kagami): Do we need to put all these properties or + // compute them manually via ECMA5 getters/setters instead? + resolve(new Address(Object.assign({ signPrivateKey: signPrivateKey, + signPublicKey: signPublicKey, encPrivateKey: encPrivateKey, - }); + encPublicKey: encPublicKey, + ripe: ripe, + }, opts))); } else { nextTick(tryKey); } @@ -268,3 +257,5 @@ exports.getRandom = function(opts) { tryKey(); }); }; + +module.exports = Address; diff --git a/package.json b/package.json index 25d50bf..c251c7c 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "es6-promise": "^2.0.1", "int64-native": "^0.3.2", "node-int64": "^0.3.2", + "object.assign": "^1.1.1", "ripemd160": "^0.2.0" } } diff --git a/test.js b/test.js index a58148c..7578e3b 100644 --- a/test.js +++ b/test.js @@ -72,14 +72,9 @@ describe("WIF", function() { expect(Buffer.isBuffer(key2)).to.be.true; expect(key2.length).to.equal(32); expect(key2.toString("hex")).to.equal(encPrivateKey.toString("hex")); - return { - version: 4, - stream: 1, - signPrivateKey: key1, - encPrivateKey: key2, - }; + return Address({signPrivateKey: key1, encPrivateKey: key2}).encode(); }); - }).then(Address.encode).then(function(str) { + }).then(function(str) { expect(str).to.equal("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z"); }); }); @@ -158,12 +153,12 @@ describe("Address", function() { it("should allow to generate new Bitmessage address", function() { this.timeout(10000); - return Address.getRandom().then(function(addr) { + return Address.fromRandom().then(function(addr) { expect(addr.version).to.equal(4); expect(addr.stream).to.equal(1); expect(addr.signPrivateKey.length).to.equal(32); expect(addr.encPrivateKey.length).to.equal(32); - return Address.encode(addr).then(function(str) { + return addr.encode().then(function(str) { expect(str.slice(0, 3)).to.equal("BM-"); return Address.decode(str).then(function(addr2) { expect(addr2.version).to.equal(4); @@ -178,8 +173,8 @@ describe("Address", function() { if (allTests) { it("should allow to generate shorter address", function() { this.timeout(60000); - return Address.getRandom({ripelen: 18}).then(function(addr) { - return Address.getRipe(addr, {short: true}).then(function(ripe) { + return Address.fromRandom({ripelen: 18}).then(function(addr) { + return addr.getRipe({short: true}).then(function(ripe) { expect(ripe.length).to.be.at.most(18); }); });