diff --git a/karma-all-tests.js b/karma-all-tests.js new file mode 100644 index 0000000..8b55cbe --- /dev/null +++ b/karma-all-tests.js @@ -0,0 +1 @@ +window.ALL_TESTS = true; diff --git a/karma.conf.js b/karma.conf.js index eec8a7a..b931c5a 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -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, }); }; diff --git a/lib/address.js b/lib/address.js index c055106..268ed0a 100644 --- a/lib/address.js +++ b/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.
} 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.