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);
});
});