diff --git a/lib/platform.browser.js b/lib/platform.browser.js index 0240fc2..c96d2f9 100644 --- a/lib/platform.browser.js +++ b/lib/platform.browser.js @@ -5,6 +5,8 @@ "use strict"; var hash = require("hash.js"); +var BN = require("bn.js"); +var assert = require("./util").assert; exports.sha512 = function(buf) { return new Buffer(hash.sha512().update(buf).digest()); @@ -23,3 +25,19 @@ exports.randomBytes = function(size) { window.crypto.getRandomValues(arr); return new Buffer(arr); }; + +var B64 = new BN("18446744073709551616"); + +exports.getTarget = function(opts) { + var length = new BN(opts.payloadLength); + length.iaddn(8); + length.iaddn(opts.payloadLengthExtraBytes); + var denominator = new BN(opts.ttl); + denominator.imul(length); + denominator.idivn(65536); + denominator.iadd(length); + denominator.imul(new BN(opts.nonceTrialsPerByte)); + var target = parseInt(B64.div(denominator).toString(16), 16); + assert(target <= 9007199254740991, "Unsafe target"); + return target; +}; diff --git a/lib/platform.js b/lib/platform.js index 2c7f0b1..4953c22 100644 --- a/lib/platform.js +++ b/lib/platform.js @@ -5,6 +5,9 @@ "use strict"; var crypto = require("crypto"); +var bignum = require("bignum"); +var assert = require("./util").assert; + var createHash = crypto.createHash; exports.sha512 = function(buf) { @@ -20,3 +23,25 @@ exports.ripemd160 = function(buf) { }; exports.randomBytes = crypto.randomBytes; + +// 2^64. +var B64 = bignum("18446744073709551616"); + +// NOTE(Kagami): We can't calculate entire target in JavaScript but the +// result can be represented in native number type without losing +// precision (targets mainly much less than 2^53). +exports.getTarget = function(opts) { + // Calculate it bottom-up, right-to-left. + var length = bignum(opts.payloadLength) + // To account for the nonce which we will append later. + .add(8) + .add(opts.payloadLengthExtraBytes); + var denominator = bignum(opts.ttl) + .mul(length) + .div(65536) + .add(length) + .mul(opts.nonceTrialsPerByte); + var target = B64.div(denominator).toNumber(); + assert(target <= 9007199254740991, "Unsafe target"); + return target; +}; diff --git a/lib/pow.js b/lib/pow.js index 795bfba..622c134 100644 --- a/lib/pow.js +++ b/lib/pow.js @@ -3,3 +3,34 @@ * @see {@link https://bitmessage.org/wiki/Proof_of_work} * @module bitmessage/pow */ + +"use strict"; + +var platform = require("./platform"); + +var DEFAULT_TRIALS_PER_BYTE = 1000; +var DEFAULT_EXTRA_BYTES = 1000; + +/** + * Calculate target + * @param {{ttl: number, payloadLength: number}} opts - Target options + * @return {number} Target. + */ +// Just a wrapper around platform-specific implementation. +exports.getTarget = function(opts) { + var nonceTrialsPerByte = opts.nonceTrialsPerByte; + // Automatically raise lower values per spec. + if (!nonceTrialsPerByte || nonceTrialsPerByte < DEFAULT_TRIALS_PER_BYTE) { + nonceTrialsPerByte = DEFAULT_TRIALS_PER_BYTE; + } + var payloadLengthExtraBytes = opts.payloadLengthExtraBytes; + if (!payloadLengthExtraBytes || payloadLengthExtraBytes < DEFAULT_EXTRA_BYTES) { + payloadLengthExtraBytes = DEFAULT_EXTRA_BYTES; + } + return platform.getTarget({ + ttl: opts.ttl, + payloadLength: opts.payloadLength, + nonceTrialsPerByte: nonceTrialsPerByte, + payloadLengthExtraBytes: payloadLengthExtraBytes, + }); +}; diff --git a/package.json b/package.json index 11784b4..e007065 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "./lib/platform.js": "./lib/platform.browser.js" }, "scripts": { - "test": "ALL_TESTS=1 mocha && xvfb-run -a karma start && jshint .", + "test": "ALL_TESTS=1 mocha && ALL_TESTS=1 xvfb-run -a karma start && jshint .", "m": "mocha", "k": "xvfb-run -a karma start", "kc": "xvfb-run -a karma start --browsers Chromium", @@ -46,10 +46,13 @@ "mocha": "*" }, "dependencies": { + "bignum": "^0.9.0", + "bn.js": "^1.0.0", "bs58": "^2.0.0", "buffer-equal": "~0.0.1", "eccrypto": "^0.1.2", "hash.js": "^1.0.2", - "object-assign": "^2.0.0" + "object-assign": "^2.0.0", + "sha.js": "^2.3.0" } } diff --git a/test.js b/test.js index 83a01be..7fd014a 100644 --- a/test.js +++ b/test.js @@ -15,6 +15,7 @@ var messageEncodings = structs.messageEncodings; var serviceFeatures = structs.serviceFeatures; var pubkeyFeatures = structs.pubkeyFeatures; var WIF = bitmessage.WIF; +var POW = bitmessage.POW; var Address = bitmessage.Address; describe("Crypto", function() { @@ -263,6 +264,13 @@ describe("WIF", function() { }); }); +describe("POW", function() { + it("should calculate target", function() { + expect(POW.getTarget({ttl: 2418984, payloadLength: 628, nonceTrialsPerByte: 1000, payloadLengthExtraBytes: 1000})).to.equal(297422593171); + expect(POW.getTarget({ttl: 86400, payloadLength: 628})).to.equal(4864647698763); + }); +}); + describe("High-level classes", function() { // FIXME(Kagami): Add more fail tests. describe("Address", function() { @@ -296,7 +304,9 @@ describe("High-level classes", function() { expect(addr2.ripe[0]).to.equal(0); }); - if (allTests) { + // FIXME(Kagami): Don't run it in browser currently because it's + // very slow. This need to be fixed. + if (allTests && typeof window === "undefined") { it("should allow to generate shorter address", function() { this.timeout(60000); var addr = Address.fromRandom({ripelen: 18});