Do a POW (Node)
This commit is contained in:
parent
8c799f4e91
commit
225fba30e1
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
/npm-debug.log
|
/npm-debug.log
|
||||||
/docs/
|
/docs/
|
||||||
/worker.browserify.js
|
/worker.browserify.js
|
||||||
|
/build/
|
||||||
|
|
|
@ -4,3 +4,4 @@
|
||||||
/docs/
|
/docs/
|
||||||
/jsdoc.json
|
/jsdoc.json
|
||||||
/worker.browserify.js
|
/worker.browserify.js
|
||||||
|
/build/
|
||||||
|
|
|
@ -49,7 +49,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
||||||
- [ ] msg
|
- [ ] msg
|
||||||
- [ ] broadcast
|
- [ ] broadcast
|
||||||
- [x] WIF
|
- [x] WIF
|
||||||
- [ ] POW
|
- [x] POW
|
||||||
- [ ] High-level classes
|
- [ ] High-level classes
|
||||||
- [ ] Address
|
- [ ] Address
|
||||||
- [x] encode
|
- [x] encode
|
||||||
|
|
10
binding.gyp
Normal file
10
binding.gyp
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"target_name": "worker",
|
||||||
|
"include_dirs": ["<!(node -e \"require('nan')\")"],
|
||||||
|
"cflags": ["-Wall", "-O2"],
|
||||||
|
"sources": ["src/worker.cc", "src/pow.cc"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -26,8 +26,8 @@ exports.randomBytes = function(size) {
|
||||||
return new Buffer(arr);
|
return new Buffer(arr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// See `platform.js` for comments.
|
||||||
var B64 = new BN("18446744073709551616");
|
var B64 = new BN("18446744073709551616");
|
||||||
|
|
||||||
exports.getTarget = function(opts) {
|
exports.getTarget = function(opts) {
|
||||||
var length = new BN(opts.payloadLength);
|
var length = new BN(opts.payloadLength);
|
||||||
length.iaddn(8);
|
length.iaddn(8);
|
||||||
|
@ -50,15 +50,19 @@ var FAILBACK_POOL_SIZE = 8;
|
||||||
// because of the WebCryptoAPI (see
|
// because of the WebCryptoAPI (see
|
||||||
// <http://caniuse.com/#feat=cryptography>).
|
// <http://caniuse.com/#feat=cryptography>).
|
||||||
exports.doPOW = function(opts) {
|
exports.doPOW = function(opts) {
|
||||||
// Try to get native cores count then fallback to more or less
|
// Try to get CPU cores count otherwise fallback to default value.
|
||||||
// reasonable value. See <https://stackoverflow.com/q/3289465> for
|
// Currenty navigator's concurrency property available in Chrome and
|
||||||
// details.
|
// 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 <https://stackoverflow.com/q/3289465>) but
|
||||||
|
// they are buggy. Ulimately library user could adjust pool size
|
||||||
|
// manually.
|
||||||
var poolSize = opts.poolSize || navigator.hardwareConcurrency;
|
var poolSize = opts.poolSize || navigator.hardwareConcurrency;
|
||||||
poolSize = poolSize || FAILBACK_POOL_SIZE;
|
poolSize = poolSize || FAILBACK_POOL_SIZE;
|
||||||
|
|
||||||
// Check all input params prematurely to not let promise executor or
|
// Check all input params prematurely to not let promise executor or
|
||||||
// worker to fail because of it.
|
// 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(opts.workerUrl, "Bad worker URL");
|
||||||
assert(typeof opts.target === "number", "Bad target");
|
assert(typeof opts.target === "number", "Bad target");
|
||||||
assert(Buffer.isBuffer(opts.initialHash), "Bad initial hash");
|
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.
|
// It's very unlikely that execution will ever reach this place.
|
||||||
// Currently the only reason why Worker may return value less
|
// Currently the only reason why Worker may return value less
|
||||||
// than zero is a 32-bit nonce overflow (see worker
|
// than zero is a 32-bit nonce overflow (see worker
|
||||||
// implementation). It's more than 4G double hashes.
|
// implementation). It's 4G double hashes.
|
||||||
reject();
|
reject(new Error("uint32_t nonce overflow"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +119,7 @@ exports.doPOW = function(opts) {
|
||||||
reject(e);
|
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.
|
// instance.
|
||||||
promise.cancel = cancel;
|
promise.cancel = cancel;
|
||||||
return promise;
|
return promise;
|
||||||
|
|
|
@ -4,9 +4,14 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
var os = require("os");
|
||||||
var crypto = require("crypto");
|
var crypto = require("crypto");
|
||||||
|
var promise = typeof Promise === "undefined" ?
|
||||||
|
require("es6-promise").Promise :
|
||||||
|
Promise;
|
||||||
var bignum = require("bignum");
|
var bignum = require("bignum");
|
||||||
var assert = require("./util").assert;
|
var assert = require("./util").assert;
|
||||||
|
var worker = require("./worker");
|
||||||
|
|
||||||
var createHash = crypto.createHash;
|
var createHash = crypto.createHash;
|
||||||
|
|
||||||
|
@ -45,3 +50,34 @@ exports.getTarget = function(opts) {
|
||||||
assert(target <= 9007199254740991, "Unsafe target");
|
assert(target <= 9007199254740991, "Unsafe target");
|
||||||
return 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
12
lib/worker.js
Normal file
12
lib/worker.js
Normal file
|
@ -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.
|
||||||
|
}
|
|
@ -7,6 +7,7 @@
|
||||||
"./lib/platform.js": "./lib/platform.browser.js"
|
"./lib/platform.js": "./lib/platform.browser.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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 .",
|
"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": "npm run -s w && xvfb-run -a karma start",
|
"k": "npm run -s w && xvfb-run -a karma start",
|
||||||
|
@ -53,7 +54,9 @@
|
||||||
"bs58": "^2.0.0",
|
"bs58": "^2.0.0",
|
||||||
"buffer-equal": "~0.0.1",
|
"buffer-equal": "~0.0.1",
|
||||||
"eccrypto": "^0.1.2",
|
"eccrypto": "^0.1.2",
|
||||||
|
"es6-promise": "^2.0.1",
|
||||||
"hash.js": "^1.0.2",
|
"hash.js": "^1.0.2",
|
||||||
|
"nan": "^1.4.1",
|
||||||
"object-assign": "^2.0.0",
|
"object-assign": "^2.0.0",
|
||||||
"sha.js": "^2.3.0"
|
"sha.js": "^2.3.0"
|
||||||
}
|
}
|
||||||
|
|
50
src/pow.cc
Normal file
50
src/pow.cc
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// Based on <https://github.com/grant-olson/bitmessage-powfaster>
|
||||||
|
// fastcpu implementation.
|
||||||
|
// TODO(Kagami): Port it to WIN32 (see bitmessage-powfaster for an
|
||||||
|
// example).
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
9
src/pow.h
Normal file
9
src/pow.h
Normal file
|
@ -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
|
90
src/worker.cc
Normal file
90
src/worker.cc
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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<Value> argv[] = {NanError("Max safe integer overflow")};
|
||||||
|
callback->Call(1, argv);
|
||||||
|
} else {
|
||||||
|
Local<Value> argv[] = {NanNull(), NanNew<Integer>(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<Function>());
|
||||||
|
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<Value> 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<Object> exports) {
|
||||||
|
exports->Set(
|
||||||
|
NanNew<String>("powAsync"),
|
||||||
|
NanNew<FunctionTemplate>(PowAsync)->GetFunction());
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_MODULE(worker, InitAll)
|
2
test.js
2
test.js
|
@ -279,7 +279,7 @@ describe("POW", function() {
|
||||||
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") {
|
if (allTests) {
|
||||||
it("should do a POW", function() {
|
it("should do a POW", function() {
|
||||||
this.timeout(120000);
|
this.timeout(120000);
|
||||||
return POW.do({workerUrl: "/base/worker.browserify.js", target: 10693764680411, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})
|
return POW.do({workerUrl: "/base/worker.browserify.js", target: 10693764680411, initialHash: Buffer("8ff2d685db89a0af2e3dbfd3f700ae96ef4d9a1eac72fd778bbb368c7510cddda349e03207e1c4965bd95c6f7265e8f1a481a08afab3874eaafb9ade09a10880", "hex")})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user