This commit is contained in:
Kagami Hiiragi 2014-12-30 01:16:51 +03:00
parent b2b660827c
commit a3475d9d6a
4 changed files with 81 additions and 94 deletions

View File

@ -55,8 +55,8 @@ With the help of browserify `bitmessage` provides different implementations for
- [x] encode - [x] encode
- [x] decode - [x] decode
- [x] getRipe - [x] getRipe
- [x] getRandom - [x] fromRandom
- [ ] getDeterministic - [ ] fromPassphrase
- [ ] Message - [ ] Message
- [ ] encode - [ ] encode
- [ ] decode - [ ] decode
@ -78,11 +78,11 @@ With the help of browserify `bitmessage` provides different implementations for
## Usage ## Usage
```js ```js
// Generating a new Bitmessage identity.
var Address = require("bitmessage").Address; var Address = require("bitmessage").Address;
Address.getRandom().then(function(addr) { Address.fromRandom().then(function(addr) {
Address.encode(addr).then(function(str) { addr.encode().then(function(str) {
console.log("New random Bitmessage address:", str); console.log("New random Bitmessage address:", str);
console.log("Private keys and parameters:", addr);
}); });
}); });
``` ```

View File

@ -6,18 +6,40 @@
"use strict"; "use strict";
require("es6-promise").polyfill(); require("es6-promise").polyfill();
require("object.assign").shim();
var assert = require("assert"); var assert = require("assert");
var bufferEqual = require("buffer-equal"); var bufferEqual = require("buffer-equal");
var bs58 = require("bs58"); var bs58 = require("bs58");
var varint = require("./varint"); var varint = require("./varint");
var bmcrypto = require("./crypto"); 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. * Parse Bitmessage address into address object.
* @param {String} str - Address string (with or without `BM-` prefix) * @param {String} str - Address string (with or without `BM-` prefix)
* @return {Promise.<Address,Error>} Decoded address object * @return {Promise.<Address,Error>} Decoded address object
* @static
*/ */
exports.decode = function(str) { Address.decode = function(str) {
str = str.trim(); str = str.trim();
if (str.slice(0, 3) === "BM-") { if (str.slice(0, 3) === "BM-") {
str = str.slice(3); str = str.slice(3);
@ -38,18 +60,15 @@ exports.decode = function(str) {
var decoded = varint.decode(data); var decoded = varint.decode(data);
var version = decoded.value; var version = decoded.value;
assertversion(version);
data = decoded.rest; data = decoded.rest;
decoded = varint.decode(data); decoded = varint.decode(data);
var stream = decoded.value; var stream = decoded.value;
assertstream(stream);
var ripe = decoded.rest; var ripe = decoded.rest;
var ripelen = ripe.length; var ripelen = ripe.length;
assertripelen(ripelen, version);
if (version === 4) { 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 // Prevent extra allocation. God, kill me please for premature
@ -58,7 +77,7 @@ exports.decode = function(str) {
var zeroes = new Buffer(Array(20 - ripelen)); var zeroes = new Buffer(Array(20 - ripelen));
ripe = Buffer.concat([zeroes, ripe]); 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. * Calculate the Ripe hash of the address.
* @param {Address} addr - Address object
* @param {?Object} opts - Options * @param {?Object} opts - Options
* @return {Buffer} Resulting Ripe hash. * @return {Promise.<Buffer,Error>} Resulting Ripe hash
*/ */
function getRipe(addr, opts) { Address.prototype.getRipe = function(opts) {
var signKey = addr.signPrivateKey || addr.signPublicKey; var self = this;
assert(signKey, "No signing key"); var ripepromise;
var encKey = addr.encPrivateKey || addr.encPublicKey; if (self.ripe) {
assert(encKey, "No encryption key"); ripepromise = Promise.resolve(self.ripe);
} else {
opts = opts || {}; opts = opts || {};
return keys2ripe(signKey, encKey).then(function(ripe) { var signKey = self.signPrivateKey || self.signPublicKey;
if (opts.short) { 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); var ripelen = getripelen(ripe);
assertripelen(ripelen, self.version);
if (opts.short) {
return ripe.slice(20 - ripelen); return ripe.slice(20 - ripelen);
} else { } else {
return ripe; 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. // Get truncated Ripe hash length.
function getripelen(ripe) { function getripelen(ripe) {
@ -148,7 +162,7 @@ function assertripelen(ripelen, version) {
assert(ripelen <= 20, "Ripe too long"); assert(ripelen <= 20, "Ripe too long");
break; break;
default: 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. * Encode Bitmessage address object into address string.
* @param {Address} addr - Address object
* @return {Promise.<string,Error>} Address string * @return {Promise.<string,Error>} Address string
*/ */
exports.encode = function(addr) { Address.prototype.encode = function() {
var version, stream, ripepromise; var self = this;
try { return self.getRipe({short: true}).then(function(ripe) {
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);
var data = Buffer.concat([ var data = Buffer.concat([
varint.encode(version), varint.encode(self.version),
varint.encode(stream), varint.encode(self.stream),
ripe, ripe,
]); ]);
return getchecksum(data).then(function(checksum) { 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. * private keys.
* @param {?Object} opts - Address options * @param {?Object} opts - Address options
* @return {Promise.<Address,Error>} Generated address object * @return {Promise.<Address,Error>} Generated address object
* @static
*/ */
exports.getRandom = function(opts) { Address.fromRandom = function(opts) {
var version, stream, ripelen, signPrivateKey;
opts = opts || {}; opts = opts || {};
var version = opts.version || 4;
var ripelen = opts.ripelen || 19;
try { try {
version = opts.version || 4;
assertversion(version);
stream = opts.stream || 1;
assertstream(version);
ripelen = opts.ripelen || 19;
assertripelen(ripelen, version); 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) { } catch(e) {
return Promise.reject(e); return Promise.reject(e);
} }
@ -230,6 +218,8 @@ exports.getRandom = function(opts) {
var nextTick = typeof setImmediate === "undefined" ? var nextTick = typeof setImmediate === "undefined" ?
process.nextTick : process.nextTick :
setImmediate; setImmediate;
var signPrivateKey = bmcrypto.getPrivate();
var signPublicKey = bmcrypto.getPublic(signPrivateKey); var signPublicKey = bmcrypto.getPublic(signPrivateKey);
// FIXME(Kagami): This function is rather slow in browsers so // 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+). // available in Chrome (at least in 39.0+).
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
function tryKey() { function tryKey() {
var encPrivateKey; var encPrivateKey = bmcrypto.getPrivate();
try { var encPublicKey = bmcrypto.getPublic(encPrivateKey);
encPrivateKey = bmcrypto.getPrivate(); return keys2ripe(signPublicKey, encPublicKey).then(function(ripe) {
} catch(e) {
reject(e);
}
return keys2ripe(signPublicKey, encPrivateKey).then(function(ripe) {
var len = getripelen(ripe); var len = getripelen(ripe);
if ( if (
(strictripelen && len === ripelen) || (strictripelen && len === ripelen) ||
(!strictripelen && len <= ripelen && checkripelen(ripelen, version)) (!strictripelen && len <= ripelen && checkripelen(ripelen, version))
) { ) {
resolve({ // XXX(Kagami): Do we need to put all these properties or
version: version, // compute them manually via ECMA5 getters/setters instead?
stream: stream, resolve(new Address(Object.assign({
signPrivateKey: signPrivateKey, signPrivateKey: signPrivateKey,
signPublicKey: signPublicKey,
encPrivateKey: encPrivateKey, encPrivateKey: encPrivateKey,
}); encPublicKey: encPublicKey,
ripe: ripe,
}, opts)));
} else { } else {
nextTick(tryKey); nextTick(tryKey);
} }
@ -268,3 +257,5 @@ exports.getRandom = function(opts) {
tryKey(); tryKey();
}); });
}; };
module.exports = Address;

View File

@ -50,6 +50,7 @@
"es6-promise": "^2.0.1", "es6-promise": "^2.0.1",
"int64-native": "^0.3.2", "int64-native": "^0.3.2",
"node-int64": "^0.3.2", "node-int64": "^0.3.2",
"object.assign": "^1.1.1",
"ripemd160": "^0.2.0" "ripemd160": "^0.2.0"
} }
} }

17
test.js
View File

@ -72,14 +72,9 @@ describe("WIF", function() {
expect(Buffer.isBuffer(key2)).to.be.true; expect(Buffer.isBuffer(key2)).to.be.true;
expect(key2.length).to.equal(32); expect(key2.length).to.equal(32);
expect(key2.toString("hex")).to.equal(encPrivateKey.toString("hex")); expect(key2.toString("hex")).to.equal(encPrivateKey.toString("hex"));
return { return Address({signPrivateKey: key1, encPrivateKey: key2}).encode();
version: 4,
stream: 1,
signPrivateKey: key1,
encPrivateKey: key2,
};
}); });
}).then(Address.encode).then(function(str) { }).then(function(str) {
expect(str).to.equal("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z"); expect(str).to.equal("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
}); });
}); });
@ -158,12 +153,12 @@ describe("Address", function() {
it("should allow to generate new Bitmessage address", function() { it("should allow to generate new Bitmessage address", function() {
this.timeout(10000); this.timeout(10000);
return Address.getRandom().then(function(addr) { return Address.fromRandom().then(function(addr) {
expect(addr.version).to.equal(4); expect(addr.version).to.equal(4);
expect(addr.stream).to.equal(1); expect(addr.stream).to.equal(1);
expect(addr.signPrivateKey.length).to.equal(32); expect(addr.signPrivateKey.length).to.equal(32);
expect(addr.encPrivateKey.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-"); expect(str.slice(0, 3)).to.equal("BM-");
return Address.decode(str).then(function(addr2) { return Address.decode(str).then(function(addr2) {
expect(addr2.version).to.equal(4); expect(addr2.version).to.equal(4);
@ -178,8 +173,8 @@ describe("Address", function() {
if (allTests) { if (allTests) {
it("should allow to generate shorter address", function() { it("should allow to generate shorter address", function() {
this.timeout(60000); this.timeout(60000);
return Address.getRandom({ripelen: 18}).then(function(addr) { return Address.fromRandom({ripelen: 18}).then(function(addr) {
return Address.getRipe(addr, {short: true}).then(function(ripe) { return addr.getRipe({short: true}).then(function(ripe) {
expect(ripe.length).to.be.at.most(18); expect(ripe.length).to.be.at.most(18);
}); });
}); });