Do a POW (Browser)
This commit is contained in:
parent
9407e27529
commit
8c799f4e91
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/npm-debug.log
|
/npm-debug.log
|
||||||
/docs/
|
/docs/
|
||||||
|
/worker.browserify.js
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
node_modules
|
/node_modules/
|
||||||
test*
|
/test*
|
||||||
karma*
|
/karma*
|
||||||
docs
|
/docs/
|
||||||
|
/worker.browserify.js
|
||||||
|
|
11
.npmignore
11
.npmignore
|
@ -1,5 +1,6 @@
|
||||||
.travis.yml
|
/.travis.yml
|
||||||
.jshint*
|
/.jshint*
|
||||||
karma*
|
/karma*
|
||||||
docs
|
/docs/
|
||||||
jsdoc.json
|
/jsdoc.json
|
||||||
|
/worker.browserify.js
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
process.env.CHROME_BIN = "chromium-browser";
|
process.env.CHROME_BIN = "chromium-browser";
|
||||||
var allTests = !!process.env.ALL_TESTS;
|
var allTests = !!process.env.ALL_TESTS;
|
||||||
var files = ["test.js"];
|
var files = ["test.js"];
|
||||||
// Kludgy way to pass a variable to `test.js`.
|
|
||||||
if (allTests) {
|
if (allTests) {
|
||||||
|
// Kludgy way to pass a variable to `test.js`.
|
||||||
files.unshift("karma-all-tests.js");
|
files.unshift("karma-all-tests.js");
|
||||||
|
// Worker code.
|
||||||
|
files.push({pattern: "worker.browserify.js", included: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function(config) {
|
module.exports = function(config) {
|
||||||
|
@ -15,7 +17,7 @@ module.exports = function(config) {
|
||||||
|
|
||||||
// frameworks to use
|
// frameworks to use
|
||||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||||
frameworks: ["mocha", "browserify"],
|
frameworks: ["browserify", "mocha"],
|
||||||
|
|
||||||
|
|
||||||
// list of files / patterns to load in the browser
|
// list of files / patterns to load in the browser
|
||||||
|
@ -73,6 +75,6 @@ module.exports = function(config) {
|
||||||
singleRun: true,
|
singleRun: true,
|
||||||
|
|
||||||
|
|
||||||
browserNoActivityTimeout: allTests ? 60000 : 10000,
|
browserNoActivityTimeout: allTests ? 120000 : 10000,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,3 +41,82 @@ exports.getTarget = function(opts) {
|
||||||
assert(target <= 9007199254740991, "Unsafe target");
|
assert(target <= 9007199254740991, "Unsafe target");
|
||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var FAILBACK_POOL_SIZE = 8;
|
||||||
|
|
||||||
|
// NOTE(Kagami): We don't use promise shim in Browser implementation
|
||||||
|
// because it's supported natively in new browsers (see
|
||||||
|
// <http://caniuse.com/#feat=promises>) and we can use only new browsers
|
||||||
|
// because of the WebCryptoAPI (see
|
||||||
|
// <http://caniuse.com/#feat=cryptography>).
|
||||||
|
exports.doPOW = function(opts) {
|
||||||
|
// Try to get native cores count then fallback to more or less
|
||||||
|
// reasonable value. See <https://stackoverflow.com/q/3289465> for
|
||||||
|
// details.
|
||||||
|
var poolSize = opts.poolSize || navigator.hardwareConcurrency;
|
||||||
|
poolSize = poolSize || FAILBACK_POOL_SIZE;
|
||||||
|
|
||||||
|
// Check all input params prematurely to not let promise executor or
|
||||||
|
// worker to fail because of it.
|
||||||
|
assert(poolSize > 0, "Bad pool size");
|
||||||
|
assert(opts.workerUrl, "Bad worker URL");
|
||||||
|
assert(typeof opts.target === "number", "Bad target");
|
||||||
|
assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash");
|
||||||
|
|
||||||
|
var cancel;
|
||||||
|
var promise = new Promise(function(resolve, reject) {
|
||||||
|
function terminateAll() {
|
||||||
|
while (workers.length) {
|
||||||
|
workers.shift().terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onmessage(e) {
|
||||||
|
terminateAll();
|
||||||
|
if (e.data >= 0) {
|
||||||
|
resolve(e.data);
|
||||||
|
} else {
|
||||||
|
// It's very unlikely that execution will ever reach this place.
|
||||||
|
// Currently the only reason why Worker may return value less
|
||||||
|
// than zero is a 32-bit nonce overflow (see worker
|
||||||
|
// implementation). It's more than 4G double hashes.
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onerror(e) {
|
||||||
|
// XXX(Kagami): `onerror` events fires in Chrome even after all
|
||||||
|
// workers were terminated. It doesn't cause wrong behaviour but
|
||||||
|
// beware that this function may be executed several times.
|
||||||
|
terminateAll();
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
var workers = [];
|
||||||
|
var worker;
|
||||||
|
for (var i = 0; i < poolSize; i++) {
|
||||||
|
worker = new Worker(opts.workerUrl);
|
||||||
|
workers.push(worker);
|
||||||
|
// NOTE(Kagami): There is no race condition here. `onmessage` can
|
||||||
|
// only be called _after_ this for-loop finishes. See
|
||||||
|
// <https://stackoverflow.com/a/18192122> for details.
|
||||||
|
worker.onmessage = onmessage;
|
||||||
|
worker.onerror = onerror;
|
||||||
|
worker.postMessage({
|
||||||
|
num: i,
|
||||||
|
poolSize: poolSize,
|
||||||
|
target: opts.target,
|
||||||
|
initialHash: opts.initialHash,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel = function(e) {
|
||||||
|
terminateAll();
|
||||||
|
reject(e);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// Allow to stop POW via custom function added to the Promise
|
||||||
|
// instance.
|
||||||
|
promise.cancel = cancel;
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
20
lib/pow.js
20
lib/pow.js
|
@ -3,9 +3,11 @@
|
||||||
* @see {@link https://bitmessage.org/wiki/Proof_of_work}
|
* @see {@link https://bitmessage.org/wiki/Proof_of_work}
|
||||||
* @module bitmessage/pow
|
* @module bitmessage/pow
|
||||||
*/
|
*/
|
||||||
|
// TODO(Kagami): Find a way how to document object params properly.
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var objectAssign = Object.assign || require("object-assign");
|
||||||
var bmcrypto = require("./crypto");
|
var bmcrypto = require("./crypto");
|
||||||
var platform = require("./platform");
|
var platform = require("./platform");
|
||||||
|
|
||||||
|
@ -17,7 +19,6 @@ var DEFAULT_EXTRA_BYTES = 1000;
|
||||||
* @param {Object} opts - Target options
|
* @param {Object} opts - Target options
|
||||||
* @return {number} Target.
|
* @return {number} Target.
|
||||||
*/
|
*/
|
||||||
// TODO(Kagami): Find a way how to document object params properly.
|
|
||||||
// Just a wrapper around platform-specific implementation.
|
// Just a wrapper around platform-specific implementation.
|
||||||
exports.getTarget = function(opts) {
|
exports.getTarget = function(opts) {
|
||||||
var payloadLength = opts.payloadLength || opts.payload.length;
|
var payloadLength = opts.payloadLength || opts.payload.length;
|
||||||
|
@ -75,3 +76,20 @@ exports.check = function(opts) {
|
||||||
return trialLo <= targetLo;
|
return trialLo <= targetLo;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a POW.
|
||||||
|
* @param {Object} opts - Proof of work options
|
||||||
|
* @return {Promise.<Buffer>} A promise that contains computed nonce for
|
||||||
|
* the given target when fulfilled.
|
||||||
|
*/
|
||||||
|
exports.do = function(opts) {
|
||||||
|
var initialHash;
|
||||||
|
if (opts.payload) {
|
||||||
|
initialHash = bmcrypto.sha512(opts.payload);
|
||||||
|
} else {
|
||||||
|
initialHash = opts.initialHash;
|
||||||
|
}
|
||||||
|
opts = objectAssign({}, opts, {initialHash: initialHash});
|
||||||
|
return platform.doPOW(opts);
|
||||||
|
};
|
||||||
|
|
65
lib/worker.browser.js
Normal file
65
lib/worker.browser.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* Web Worker routines for Browser platform.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// NOTE(Kagami): In order to use it you need to create separate
|
||||||
|
// browserify bundle for this file, place it somewhere in your HTTP
|
||||||
|
// server assets path (under the same origin with your application code)
|
||||||
|
// and then pass appropriate `workerUrl` value to the function that
|
||||||
|
// spawns workers.
|
||||||
|
// You may also try to pass object URL instead. See
|
||||||
|
// <http://mdn.io/worker.worker>, <https://stackoverflow.com/q/10343913>
|
||||||
|
// for details.
|
||||||
|
|
||||||
|
// XXX(Kagami): This is rather unpleasent that we use different SHA-2
|
||||||
|
// implementations for main library code and for worker code (we use
|
||||||
|
// `sha.js` here because it's faster). Though worker code lays in
|
||||||
|
// separate file so it shouldn't result in any download overhead.
|
||||||
|
var createHash = require("sha.js");
|
||||||
|
|
||||||
|
function sha512(buf) {
|
||||||
|
return createHash("sha512").update(buf).digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function pow(opts) {
|
||||||
|
var nonce = opts.num;
|
||||||
|
var poolSize = opts.poolSize;
|
||||||
|
var message = new Buffer(72);
|
||||||
|
message.fill(0);
|
||||||
|
Buffer(opts.initialHash).copy(message, 8);
|
||||||
|
var targetHi = Math.floor(opts.target / 4294967296);
|
||||||
|
var targetLo = opts.target % 4294967296;
|
||||||
|
var digest, trialHi, trialLo;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
// uint32_t overflow. There is no much need to fix it since 4G
|
||||||
|
// double hashes would we computed too long anyway in a Browser.
|
||||||
|
if (nonce > 4294967295) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.writeUInt32BE(nonce, 4, true);
|
||||||
|
digest = sha512(sha512(message));
|
||||||
|
trialHi = digest.readUInt32BE(0, true);
|
||||||
|
|
||||||
|
if (trialHi > targetHi) {
|
||||||
|
nonce += poolSize;
|
||||||
|
} else if (trialHi === targetHi) {
|
||||||
|
trialLo = digest.readUInt32BE(4, true);
|
||||||
|
if (trialLo > targetLo) {
|
||||||
|
nonce += poolSize;
|
||||||
|
} else {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage = function(e) { // jshint ignore:line
|
||||||
|
var nonce = pow(e.data);
|
||||||
|
postMessage(nonce); // jshint ignore:line
|
||||||
|
};
|
16
package.json
16
package.json
|
@ -7,11 +7,12 @@
|
||||||
"./lib/platform.js": "./lib/platform.browser.js"
|
"./lib/platform.js": "./lib/platform.browser.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "ALL_TESTS=1 mocha && ALL_TESTS=1 xvfb-run -a karma start && jshint .",
|
"test": "ALL_TESTS=1 mocha && ALL_TESTS=1 npm run -s kc && ALL_TESTS=1 npm run -s kf && jshint .",
|
||||||
"m": "mocha",
|
"m": "mocha",
|
||||||
"k": "xvfb-run -a karma start",
|
"k": "npm run -s w && xvfb-run -a karma start",
|
||||||
"kc": "xvfb-run -a karma start --browsers Chromium",
|
"kc": "npm run -s w && xvfb-run -a karma start --browsers Chromium",
|
||||||
"kf": "xvfb-run -a karma start --browsers Firefox",
|
"kf": "npm run -s w && xvfb-run -a karma start --browsers Firefox",
|
||||||
|
"w": "browserify lib/worker.browser.js > worker.browserify.js",
|
||||||
"j": "jshint .",
|
"j": "jshint .",
|
||||||
"d": "jsdoc -c jsdoc.json",
|
"d": "jsdoc -c jsdoc.json",
|
||||||
"mv-docs": "rm -rf docs && jsdoc -c jsdoc.json && D=`mktemp -d` && mv docs \"$D\" && git checkout gh-pages && rm -rf docs && mv \"$D/docs\" . && rm -rf \"$D\""
|
"mv-docs": "rm -rf docs && jsdoc -c jsdoc.json && D=`mktemp -d` && mv docs \"$D\" && git checkout gh-pages && rm -rf docs && mv \"$D/docs\" . && rm -rf \"$D\""
|
||||||
|
@ -33,14 +34,15 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/bitchan/bitmessage",
|
"homepage": "https://github.com/bitchan/bitmessage",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"browserify": "^8.1.0",
|
||||||
"chai": "*",
|
"chai": "*",
|
||||||
"jsdoc": "^3.3.0-alpha13",
|
"jsdoc": "^3.3.0-alpha13",
|
||||||
"jshint": "*",
|
"jshint": "*",
|
||||||
"karma": "^0.12.28",
|
"karma": "^0.12.31",
|
||||||
"karma-browserify": "^1.0.1",
|
"karma-browserify": "^2.0.0",
|
||||||
"karma-chrome-launcher": "^0.1.7",
|
"karma-chrome-launcher": "^0.1.7",
|
||||||
"karma-cli": "~0.0.4",
|
"karma-cli": "~0.0.4",
|
||||||
"karma-firefox-launcher": "^0.1.3",
|
"karma-firefox-launcher": "^0.1.4",
|
||||||
"karma-mocha": "^0.1.10",
|
"karma-mocha": "^0.1.10",
|
||||||
"karma-mocha-reporter": "^0.3.1",
|
"karma-mocha-reporter": "^0.3.1",
|
||||||
"mocha": "*"
|
"mocha": "*"
|
||||||
|
|
12
test.js
12
test.js
|
@ -278,6 +278,16 @@ describe("POW", function() {
|
||||||
expect(POW.check({nonce: 3122437, target: 4864647698763, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})).to.be.true;
|
expect(POW.check({nonce: 3122437, target: 4864647698763, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})).to.be.true;
|
||||||
expect(POW.check({nonce: 3122436, target: 4864647698763, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})).to.be.false;
|
expect(POW.check({nonce: 3122436, target: 4864647698763, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (allTests && typeof window !== "undefined") {
|
||||||
|
it("should do a POW", function() {
|
||||||
|
this.timeout(120000);
|
||||||
|
return POW.do({workerUrl: "/base/worker.browserify.js", target: 10693764680411, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})
|
||||||
|
.then(function(nonce) {
|
||||||
|
expect(nonce).to.equal(2373146);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("High-level classes", function() {
|
describe("High-level classes", function() {
|
||||||
|
@ -317,7 +327,7 @@ describe("High-level classes", function() {
|
||||||
// very slow. This need to be fixed.
|
// very slow. This need to be fixed.
|
||||||
if (allTests && typeof window === "undefined") {
|
if (allTests && typeof window === "undefined") {
|
||||||
it("should allow to generate shorter address", function() {
|
it("should allow to generate shorter address", function() {
|
||||||
this.timeout(60000);
|
this.timeout(120000);
|
||||||
var addr = Address.fromRandom({ripelen: 18});
|
var addr = Address.fromRandom({ripelen: 18});
|
||||||
var ripe = addr.getRipe({short: true});
|
var ripe = addr.getRipe({short: true});
|
||||||
expect(ripe.length).to.be.at.most(18);
|
expect(ripe.length).to.be.at.most(18);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user