Implement Address.encode and Address.getRandom
This commit is contained in:
parent
d934624c48
commit
53ae383d35
1
karma-all-tests.js
Normal file
1
karma-all-tests.js
Normal file
|
@ -0,0 +1 @@
|
|||
window.ALL_TESTS = true;
|
|
@ -1,4 +1,10 @@
|
|||
process.env.CHROME_BIN = "chromium-browser";
|
||||
var allTests = !!process.env.ALL_TESTS;
|
||||
var files = ["test.js"];
|
||||
// Kludgy way to pass a variable to `test.js`.
|
||||
if (allTests) {
|
||||
files.unshift("karma-all-tests.js");
|
||||
};
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
|
@ -13,9 +19,7 @@ module.exports = function(config) {
|
|||
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
"test.js"
|
||||
],
|
||||
files: files,
|
||||
|
||||
|
||||
// preprocess matching files before serving them to the browser
|
||||
|
@ -67,5 +71,8 @@ module.exports = function(config) {
|
|||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: true,
|
||||
|
||||
|
||||
browserNoActivityTimeout: allTests ? 60000 : 10000,
|
||||
});
|
||||
};
|
||||
|
|
239
lib/address.js
239
lib/address.js
|
@ -5,6 +5,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
require("es6-promise").polyfill();
|
||||
var assert = require("assert");
|
||||
var bufferEqual = require("buffer-equal");
|
||||
var bs58 = require("bs58");
|
||||
|
@ -12,8 +13,9 @@ var varint = require("./varint");
|
|||
var bmcrypto = require("./crypto");
|
||||
|
||||
/**
|
||||
* Parse Bitmessage Base58 encoded address (with or without `BM-`
|
||||
* prefix) into address object.
|
||||
* Parse Bitmessage address into address object.
|
||||
* @param {String} str - Address string (with or without `BM-` prefix)
|
||||
* @return {Promise.<Address,Error>} Decoded address object
|
||||
*/
|
||||
exports.decode = function(str) {
|
||||
str = str.trim();
|
||||
|
@ -31,34 +33,23 @@ exports.decode = function(str) {
|
|||
// Checksum validating.
|
||||
var data = new Buffer(bytes.slice(0, -4));
|
||||
var checksum = new Buffer(bytes.slice(-4));
|
||||
return bmcrypto.sha512(data).then(bmcrypto.sha512).then(function(dhash) {
|
||||
assert(bufferEqual(dhash.slice(0, 4), checksum), "Bad checkum");
|
||||
return getchecksum(data).then(function(realchecksum) {
|
||||
assert(bufferEqual(checksum, realchecksum), "Bad checkum");
|
||||
|
||||
var decoded = varint.decode(data);
|
||||
var version = decoded.value;
|
||||
assert(version <= 4, "Version too high");
|
||||
assert(version >= 1, "Version too low");
|
||||
assertversion(version);
|
||||
|
||||
data = decoded.rest;
|
||||
decoded = varint.decode(data);
|
||||
var stream = decoded.value;
|
||||
assertstream(stream);
|
||||
|
||||
var ripe = decoded.rest;
|
||||
var ripelen = ripe.length;
|
||||
switch (version) {
|
||||
case 1:
|
||||
assert(ripelen === 20);
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
assert(ripelen >= 18, "Ripe too short");
|
||||
assert(ripelen <= 20, "Ripe too long");
|
||||
break;
|
||||
case 4:
|
||||
assert(ripelen >= 4, "Ripe too short");
|
||||
assert(ripelen <= 20, "Ripe too long");
|
||||
assert(ripe[0] !== 0, "Ripe encode error");
|
||||
break;
|
||||
assertripelen(ripelen, version);
|
||||
if (version === 4) {
|
||||
assert(ripe[0] !== 0, "Ripe decode error");
|
||||
}
|
||||
|
||||
// Prevent extra allocation. God, kill me please for premature
|
||||
|
@ -70,3 +61,211 @@ exports.decode = function(str) {
|
|||
return {version: version, stream: stream, ripe: ripe};
|
||||
});
|
||||
};
|
||||
|
||||
// Compute the Bitmessage checksum for the given data.
|
||||
function getchecksum(data) {
|
||||
return bmcrypto.sha512(data).then(bmcrypto.sha512).then(function(dhash) {
|
||||
return dhash.slice(0, 4);
|
||||
});
|
||||
}
|
||||
|
||||
// Get RIPEMD160(SHA512(SIGN_PUBLIC_KEY || ENC_PUBLIC_KEY))
|
||||
// Arguments could be either private or public keys. Private keys are
|
||||
// **always** 32 bytes in length.
|
||||
function keys2ripe(signKey, encKey) {
|
||||
var signPublicKey, encPublicKey;
|
||||
if (signKey.length === 32) {
|
||||
signPublicKey = bmcrypto.getPublic(signKey);
|
||||
} else {
|
||||
signPublicKey = signKey;
|
||||
}
|
||||
if (encKey.length === 32) {
|
||||
encPublicKey = bmcrypto.getPublic(encKey);
|
||||
} else {
|
||||
encPublicKey = encKey;
|
||||
}
|
||||
var concat = Buffer.concat([signPublicKey, encPublicKey]);
|
||||
return bmcrypto.sha512(concat).then(bmcrypto.ripemd160);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Ripe hash for the given address object.
|
||||
* @param {Address} addr - Address object
|
||||
* @param {?Object} opts - Options
|
||||
* @return {Buffer} 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) {
|
||||
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) {
|
||||
var zeroes = 0;
|
||||
for (var i = 0; i < 20, ripe[i] === 0; i++) {
|
||||
zeroes++;
|
||||
}
|
||||
return 20 - zeroes;
|
||||
}
|
||||
|
||||
// Do neccessary checkings of the truncated ripe hash length depending
|
||||
// on the address version.
|
||||
function assertripelen(ripelen, version) {
|
||||
switch (version) {
|
||||
case 1:
|
||||
assert(ripelen === 20, "Bad ripe length");
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
assert(ripelen >= 18, "Ripe too short");
|
||||
assert(ripelen <= 20, "Ripe too long");
|
||||
break;
|
||||
case 4:
|
||||
assert(ripelen >= 4, "Ripe too short");
|
||||
assert(ripelen <= 20, "Ripe too long");
|
||||
break;
|
||||
default:
|
||||
throw new Error("Wrong version");
|
||||
}
|
||||
}
|
||||
|
||||
// The same as `assertripelen` but return true/false instead of throw an
|
||||
// Error.
|
||||
function checkripelen(ripelen, version) {
|
||||
try {
|
||||
assertripelen(ripelen, version);
|
||||
return true;
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode Bitmessage address object into address string.
|
||||
* @param {Address} addr - Address object
|
||||
* @return {Promise.<string,Error>} 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);
|
||||
var data = Buffer.concat([
|
||||
varint.encode(version),
|
||||
varint.encode(stream),
|
||||
ripe,
|
||||
]);
|
||||
return getchecksum(data).then(function(checksum) {
|
||||
var addr = Buffer.concat([data, checksum]);
|
||||
return "BM-" + bs58.encode(addr);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new Bitmessage address using random encryption and signing
|
||||
* private keys.
|
||||
* @param {?Object} opts - Address options
|
||||
* @return {Promise.<Address,Error>} Generated address object
|
||||
*/
|
||||
exports.getRandom = function(opts) {
|
||||
var version, stream, ripelen, signPrivateKey;
|
||||
try {
|
||||
opts = opts || {};
|
||||
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);
|
||||
}
|
||||
var nextTick = typeof setImmediate === "undefined" ?
|
||||
process.nextTick :
|
||||
setImmediate;
|
||||
// Should the generated Ripe length be strictly equal to the specified
|
||||
// (less-or-equal by default);
|
||||
var strictripelen = !!opts.strictripelen;
|
||||
var signPublicKey = bmcrypto.getPublic(signPrivateKey);
|
||||
|
||||
// FIXME(Kagami): This function is rather slow in browsers so
|
||||
// generation of ripelen=18 currently is disabled (see `test.js`). It
|
||||
// should be heavilty profiled to determine the bottleneck.
|
||||
// TODO(Kagami): We may want to run this in the web worker to speedup
|
||||
// the search. Currently WebCryptoAPI is not available in Firefox in
|
||||
// web workers (see
|
||||
// <https://bugzilla.mozilla.org/show_bug.cgi?id=842818>) but is
|
||||
// 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) {
|
||||
console.log(ripe);
|
||||
var len = getripelen(ripe);
|
||||
if (
|
||||
(strictripelen && len === ripelen) ||
|
||||
(!strictripelen && len <= ripelen && checkripelen(ripelen, version))
|
||||
) {
|
||||
resolve({
|
||||
version: version,
|
||||
stream: stream,
|
||||
signPrivateKey: signPrivateKey,
|
||||
encPrivateKey: encPrivateKey,
|
||||
});
|
||||
} else {
|
||||
nextTick(tryKey);
|
||||
}
|
||||
}).catch(reject);
|
||||
}
|
||||
tryKey();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var assert = require("assert");
|
||||
require("es6-promise").polyfill();
|
||||
var assert = require("assert");
|
||||
var ripemd160 = require("ripemd160");
|
||||
|
||||
// Support `webkit` prefix for Safari (not tested yet).
|
||||
|
|
|
@ -6,8 +6,19 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var eccrypto = require("eccrypto");
|
||||
var cryptoPlatform = require("./crypto-platform");
|
||||
|
||||
Object.keys(cryptoPlatform).forEach(function(key) {
|
||||
exports[key] = cryptoPlatform[key];
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate new random private key.
|
||||
* @return {Buffer} New private key.
|
||||
*/
|
||||
exports.getPrivate = function() {
|
||||
return cryptoPlatform.randomBytes(32);
|
||||
};
|
||||
|
||||
exports.getPublic = eccrypto.getPublic;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
"./lib/crypto-platform.js": "./lib/crypto-platform.browser.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha && xvfb-run -a karma start && jshint .",
|
||||
"test": "ALL_TESTS=1 mocha && xvfb-run -a karma start && jshint .",
|
||||
"m": "mocha",
|
||||
"k": "xvfb-run -a karma start",
|
||||
"kc": "xvfb-run -a karma start --browsers Chromium",
|
||||
|
@ -46,6 +46,7 @@
|
|||
"dependencies": {
|
||||
"bs58": "^2.0.0",
|
||||
"buffer-equal": "~0.0.1",
|
||||
"eccrypto": "^0.1.1",
|
||||
"es6-promise": "^2.0.1",
|
||||
"int64-native": "^0.3.2",
|
||||
"node-int64": "^0.3.2",
|
||||
|
|
33
test.js
33
test.js
|
@ -1,4 +1,8 @@
|
|||
var expect = require("chai").expect;
|
||||
var allTests = typeof window === "undefined" ?
|
||||
!!process.env.ALL_TESTS :
|
||||
window.ALL_TESTS;
|
||||
|
||||
var bitmessage = require("./lib");
|
||||
var Int64 = bitmessage.Int64;
|
||||
var Address = bitmessage.Address;
|
||||
|
@ -112,4 +116,33 @@ describe("Address", function() {
|
|||
expect(addr.ripe.toString("hex")).to.equal("003ab6655de4bd8c603eba9b00dd5970725fdd56");
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow to generate new Bitmessage address", function() {
|
||||
return Address.getRandom().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) {
|
||||
expect(str.slice(0, 3)).to.equal("BM-");
|
||||
return Address.decode(str).then(function(addr2) {
|
||||
expect(addr2.version).to.equal(4);
|
||||
expect(addr2.stream).to.equal(1);
|
||||
expect(addr2.ripe.length).to.equal(20);
|
||||
expect(addr2.ripe[0]).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
expect(ripe.length).to.be.at.most(18);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user