diff --git a/.gitignore b/.gitignore index 840ae7a..f6678ad 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /npm-debug.log /docs/ /worker.browserify.js +/build/ diff --git a/.npmignore b/.npmignore index c633857..2787d08 100644 --- a/.npmignore +++ b/.npmignore @@ -4,3 +4,4 @@ /docs/ /jsdoc.json /worker.browserify.js +/build/ diff --git a/README.md b/README.md index 5d9a37e..9de621a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/ - [ ] msg - [ ] broadcast - [x] WIF -- [ ] POW +- [x] POW - [ ] High-level classes - [ ] Address - [x] encode diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..d78f6eb --- /dev/null +++ b/binding.gyp @@ -0,0 +1,10 @@ +{ + "targets": [ + { + "target_name": "worker", + "include_dirs": ["). exports.doPOW = function(opts) { - // Try to get native cores count then fallback to more or less - // reasonable value. See for - // details. + // Try to get CPU cores count otherwise fallback to default value. + // Currenty navigator's concurrency property available in Chrome and + // not available in Firefox; hope default value won't slow down POW + // speed much on systems with 1 or 2 cores. There are core estimator + // libraries exist (see ) but + // they are buggy. Ulimately library user could adjust pool size + // manually. 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(poolSize > 0, "Pool size is too low"); assert(opts.workerUrl, "Bad worker URL"); assert(typeof opts.target === "number", "Bad target"); assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash"); @@ -79,8 +83,8 @@ exports.doPOW = function(opts) { // 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(); + // implementation). It's 4G double hashes. + reject(new Error("uint32_t nonce overflow")); } } @@ -115,7 +119,7 @@ exports.doPOW = function(opts) { reject(e); }; }); - // Allow to stop POW via custom function added to the Promise + // Allow to stop a POW via custom function added to the Promise // instance. promise.cancel = cancel; return promise; diff --git a/lib/platform.js b/lib/platform.js index 4953c22..c54a9f7 100644 --- a/lib/platform.js +++ b/lib/platform.js @@ -4,9 +4,14 @@ "use strict"; +var os = require("os"); var crypto = require("crypto"); +var promise = typeof Promise === "undefined" ? + require("es6-promise").Promise : + Promise; var bignum = require("bignum"); var assert = require("./util").assert; +var worker = require("./worker"); var createHash = crypto.createHash; @@ -45,3 +50,34 @@ exports.getTarget = function(opts) { assert(target <= 9007199254740991, "Unsafe target"); return target; }; + +exports.doPOW = function(opts) { + var poolSize = opts.poolSize || os.cpus().length; + + // Check all input params prematurely to not let promise executor or + // worker to fail because of it. + // 1 - UINT32_MAX + assert(poolSize > 0, "Pool size is too low"); + assert(poolSize <= 4294967295, "Pool size is too high"); + // 0 - (2^53 - 1) + assert(typeof opts.target === "number", "Bad target"); + assert(opts.target >= 0, "Target is too low"); + assert(opts.target <= 9007199254740991, "Target is too high"); + assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash"); + + // TODO(Kagami): Allow to cancel a POW (see `platform.browser.js`). + return new promise(function(resolve, reject) { + worker.powAsync( + poolSize, + opts.target, + opts.initialHash, + function(err, nonce) { + if (err) { + reject(err); + } else { + resolve(nonce); + } + } + ); + }); +}; diff --git a/lib/worker.js b/lib/worker.js new file mode 100644 index 0000000..05bf84e --- /dev/null +++ b/lib/worker.js @@ -0,0 +1,12 @@ +/** + * Worker routines for Node platform. This module tries to load native + * addon and makes it available for Node. + */ + +try { + module.exports = require("../build/Release/worker"); +} catch(e) { + // Do nothing for a moment. Everything will work except the functions + // that uses worker routines. + // TODO(Kagami) Provide pure JS fallback. +} diff --git a/package.json b/package.json index 1d77bed..041f520 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "./lib/platform.js": "./lib/platform.browser.js" }, "scripts": { + "install": "node-gyp rebuild || exit 0", "test": "ALL_TESTS=1 mocha && ALL_TESTS=1 npm run -s kc && ALL_TESTS=1 npm run -s kf && jshint .", "m": "mocha", "k": "npm run -s w && xvfb-run -a karma start", @@ -53,7 +54,9 @@ "bs58": "^2.0.0", "buffer-equal": "~0.0.1", "eccrypto": "^0.1.2", + "es6-promise": "^2.0.1", "hash.js": "^1.0.2", + "nan": "^1.4.1", "object-assign": "^2.0.0", "sha.js": "^2.3.0" } diff --git a/src/pow.cc b/src/pow.cc new file mode 100644 index 0000000..7ec762c --- /dev/null +++ b/src/pow.cc @@ -0,0 +1,50 @@ +// Based on +// fastcpu implementation. +// TODO(Kagami): Port it to WIN32 (see bitmessage-powfaster for an +// example). + +#include +#include +#include +#include + +#define HASH_SIZE 64 +#define NTOHLL(x) ( ( (uint64_t)(ntohl( (unsigned int)((x << 32) >> 32) )) << 32) | ntohl( ((unsigned int)(x >> 32)) ) ) +#define MAX_SAFE_JS_INTEGER 9007199254740991 + +int pow(uint32_t pool_size, + int64_t target, + const uint8_t* initial_hash, + int64_t* nonce) { + uint8_t message[HASH_SIZE+sizeof(uint64_t)]; + uint8_t digest[HASH_SIZE]; + uint64_t* be_nonce; + uint64_t* be_trial; + uint64_t i; + SHA512_CTX sha; + + memcpy(message+sizeof(uint64_t), initial_hash, HASH_SIZE); + be_nonce = (uint64_t *)message; + be_trial = (uint64_t *)digest; + i = 0; + while (1) { + // This is very unlikely to be ever happen but it's better to be + // sure anyway. + if (i > MAX_SAFE_JS_INTEGER) { + return -1; + } + *be_nonce = NTOHLL(i); + SHA512_Init(&sha); + SHA512_Update(&sha, message, HASH_SIZE+sizeof(uint64_t)); + SHA512_Final(digest, &sha); + SHA512_Init(&sha); + SHA512_Update(&sha, digest, HASH_SIZE); + SHA512_Final(digest, &sha); + if (NTOHLL(*be_trial) <= (uint64_t)target) { + break; + } + i++; + } + *nonce = i; + return 0; +} diff --git a/src/pow.h b/src/pow.h new file mode 100644 index 0000000..0d0e3e6 --- /dev/null +++ b/src/pow.h @@ -0,0 +1,9 @@ +#ifndef BITMESSAGE_POW_H +#define BITMESSAGE_POW_H + +int pow(uint32_t pool_size, + int64_t target, + const uint8_t* initial_hash, + int64_t* nonce); + +#endif diff --git a/src/worker.cc b/src/worker.cc new file mode 100644 index 0000000..aab764f --- /dev/null +++ b/src/worker.cc @@ -0,0 +1,90 @@ +#include +#include +#include +#include "./pow.h" + +using node::Buffer; +using v8::Handle; +using v8::Local; +using v8::FunctionTemplate; +using v8::Function; +using v8::Value; +using v8::Object; +using v8::String; +using v8::Integer; + +class PowWorker : public NanAsyncWorker { + public: + PowWorker(NanCallback* callback, + uint32_t pool_size, + int64_t target, + uint8_t* initial_hash) + : NanAsyncWorker(callback), + pool_size(pool_size), + target(target), + initial_hash(initial_hash) {} + ~PowWorker() { + free(initial_hash); + } + + // Executed inside the worker-thread. + // It is not safe to access V8, or V8 data structures + // here, so everything we need for input and output + // should go on `this`. + void Execute () { + error = pow(pool_size, target, initial_hash, &nonce); + } + + // Executed when the async work is complete + // this function will be run inside the main event loop + // so it is safe to use V8 again + void HandleOKCallback () { + NanScope(); + if (error) { + Local argv[] = {NanError("Max safe integer overflow")}; + callback->Call(1, argv); + } else { + Local argv[] = {NanNull(), NanNew(nonce)}; + callback->Call(2, argv); + } + } + + private: + uint32_t pool_size; + int64_t target; + uint8_t* initial_hash; + int64_t nonce; + int error; +}; + +NAN_METHOD(PowAsync) { + NanScope(); + + NanCallback *callback = new NanCallback(args[3].As()); + uint32_t pool_size = args[0]->Uint32Value(); + int64_t target = args[1]->IntegerValue(); + size_t length = Buffer::Length(args[2]->ToObject()); + char* buf = Buffer::Data(args[2]->ToObject()); + uint8_t* initial_hash = (uint8_t *)malloc(length); + + if (initial_hash == NULL) { + Local argv[] = {NanError("Cannot allocate memory")}; + callback->Call(1, argv); + } else { + memcpy(initial_hash, buf, length); + NanAsyncQueueWorker( + new PowWorker(callback, pool_size, target, initial_hash)); + } + + NanReturnUndefined(); +} + +// Expose synchronous and asynchronous access to our +// Estimate() function +void InitAll(Handle exports) { + exports->Set( + NanNew("powAsync"), + NanNew(PowAsync)->GetFunction()); +} + +NODE_MODULE(worker, InitAll) diff --git a/test.js b/test.js index 971e568..afa6e4c 100644 --- a/test.js +++ b/test.js @@ -279,7 +279,7 @@ describe("POW", function() { expect(POW.check({nonce: 3122436, target: 4864647698763, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})).to.be.false; }); - if (allTests && typeof window !== "undefined") { + if (allTests) { it("should do a POW", function() { this.timeout(120000); return POW.do({workerUrl: "/base/worker.browserify.js", target: 10693764680411, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})