From 2615c25425a0251e43ad60be32db9fa56f9a5ed1 Mon Sep 17 00:00:00 2001 From: Kagami Hiiragi Date: Fri, 30 Jan 2015 22:45:45 +0300 Subject: [PATCH] Do a POW for objects --- lib/objects.js | 86 ++++++++++++++++++++------------------- lib/platform.js | 2 +- lib/pow.js | 21 +++++++--- lib/structs.js | 8 ++++ test.js | 104 +++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 171 insertions(+), 50 deletions(-) diff --git a/lib/objects.js b/lib/objects.js index 72f8ac5..08030f0 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -19,6 +19,7 @@ var promise = require("./platform").promise; var bmcrypto = require("./crypto"); var Address = require("./address"); var structs = require("./structs"); +var POW = require("./pow"); var util = require("./_util"); var var_int = structs.var_int; @@ -45,11 +46,11 @@ exports.getType = function(buf) { }; /** - * Try to get type of the given object message payoad. + * Try to get type of the given object message payload. * Note that this function doesn't do any validation because it is * already provided by `object.decodePayload` routine. Normally you call - * this for each incoming object message and then call decode function - * of the appropriate object handler. + * this for each incoming object message payload and then call decode + * function of the appropriate object handler. * @param {Buffer} buf - Buffer that starts with object message payload * @return {?integer} Object's type if any */ @@ -61,6 +62,33 @@ exports.getPayloadType = function(buf) { return buf.readUInt32BE(16, true); }; +// Prepend nonce to a given object without nonce. +function prependNonce(obj, opts) { + return new promise(function(resolve) { + assert(obj.length <= 262136, "object message payload is too big"); + opts = objectAssign({}, opts); + var nonce, target, powp; + if (opts.skipPow) { + nonce = new Buffer(8); + nonce.fill(0); + resolve(Buffer.concat([nonce, obj])); + } else { + opts.payloadLength = obj.length + 8; // Compensate for nonce + target = POW.getTarget(opts); + powp = POW.doAsync({target: target, data: obj}) + .then(function(nonce) { + // TODO(Kagami): We may want to receive nonce as a Buffer from + // POW module to skip conversion step. + var payload = new Buffer(opts.payloadLength); + util.writeUInt64BE(payload, nonce, 0, true); + obj.copy(payload, 8); + return payload; + }); + resolve(powp); + } + }); +} + /** * `getpubkey` object. When a node has the hash of a public key (from an * address) but not the public key itself, it must send out a request @@ -130,7 +158,6 @@ var getpubkey = exports.getpubkey = { * @return {Promise.} A promise that contains encoded message * payload when fulfilled. */ - // FIXME(Kagami): Do a POW. encodePayloadAsync: function(opts) { return new promise(function(resolve) { opts = objectAssign({}, opts); @@ -142,10 +169,8 @@ var getpubkey = exports.getpubkey = { opts.version = to.version; opts.stream = to.stream; opts.objectPayload = to.version < 4 ? to.ripe : to.getTag(); - // POW calculation here. - var nonce = new Buffer(8); - opts.nonce = nonce; - resolve(object.encodePayload(opts)); + var obj = object.encodePayloadWithoutNonce(opts); + resolve(prependNonce(obj, opts)); }); }, }; @@ -333,7 +358,6 @@ var pubkey = exports.pubkey = { * @return {Promise.} A promise that contains encoded message * payload when fulfilled. */ - // FIXME(Kagami): Do a POW. encodePayloadAsync: function(opts) { return new promise(function(resolve) { opts = objectAssign({}, opts); @@ -366,10 +390,7 @@ var pubkey = exports.pubkey = { from.encPublicKey.slice(1), ]); obj = object.encodePayloadWithoutNonce(opts); - // POW calculation here. - var nonce = new Buffer(8); - obj = Buffer.concat([nonce, obj]); - return resolve(obj); + return resolve(prependNonce(obj, opts)); } var pubkeyData = [ @@ -387,17 +408,9 @@ var pubkey = exports.pubkey = { pubkeyp = bmcrypto .sign(from.signPrivateKey, obj) .then(function(sig) { - // POW calculation here. - var nonce = new Buffer(8); // Append signature to the encoded object and we are done. - obj = Buffer.concat([ - nonce, - obj, - var_int.encode(sig.length), - sig, - ]); - assert(obj.length <= 262144, "object message payload is too big"); - return obj; + obj = Buffer.concat([obj, var_int.encode(sig.length), sig]); + return prependNonce(obj, opts); }); return resolve(pubkeyp); } @@ -413,12 +426,9 @@ var pubkey = exports.pubkey = { dataToEnc = Buffer.concat(dataToEnc); return bmcrypto.encrypt(from.getPubkeyPublicKey(), dataToEnc); }).then(function(enc) { - // POW calculation here. - var nonce = new Buffer(8); // Concat object header with ecnrypted data and we are done. - obj = Buffer.concat([nonce, obj, enc]); - assert(obj.length <= 262144, "object message payload is too big"); - return obj; + obj = Buffer.concat([obj, enc]); + return prependNonce(obj, opts); }); resolve(pubkeyp); }); @@ -663,7 +673,6 @@ var msg = exports.msg = { * @return {Promise.} A promise that contains encoded message * payload when fulfilled. */ - // FIXME(Kagami): Do a POW. encodePayloadAsync: function(opts) { return new promise(function(resolve) { // Deal with options. @@ -722,12 +731,12 @@ var msg = exports.msg = { dataToEnc = Buffer.concat(dataToEnc); return bmcrypto.encrypt(to.encPublicKey, dataToEnc); }).then(function(enc) { - // POW calculation here. - var nonce = new Buffer(8); // Concat object header with ecnrypted data and we are done. - obj = Buffer.concat([nonce, obj, enc]); - assert(obj.length <= 262144, "object message payload is too big"); - return obj; + obj = Buffer.concat([obj, enc]); + // TODO(Kagami): Merge receiver's trials/extra bytes options + // so we can calculate right POW (now we need to pass them to + // opts manually). + return prependNonce(obj, opts); }); resolve(msgp); }); @@ -941,7 +950,6 @@ var broadcast = exports.broadcast = { * @return {Promise.} A promise that contains encoded message * payload when fulfilled. */ - // FIXME(Kagami): Do a POW. encodePayloadAsync: function(opts) { return new promise(function(resolve) { // Deal with options. @@ -986,12 +994,8 @@ var broadcast = exports.broadcast = { dataToEnc = Buffer.concat(dataToEnc); return bmcrypto.encrypt(from.getBroadcastPublicKey(), dataToEnc); }).then(function(enc) { - // POW calculation here. - var nonce = new Buffer(8); - // Concat object header with ecnrypted data and we are done. - obj = Buffer.concat([nonce, obj, enc]); - assert(obj.length <= 262144, "object message payload is too big"); - return obj; + obj = Buffer.concat([obj, enc]); + return prependNonce(obj, opts); }); resolve(broadp); }); diff --git a/lib/platform.js b/lib/platform.js index 7597bdc..8304dba 100644 --- a/lib/platform.js +++ b/lib/platform.js @@ -54,9 +54,9 @@ exports.getTarget = function(opts) { }; exports.pow = function(opts) { - var poolSize = opts.poolSize || os.cpus().length; // TODO(Kagami): Allow to cancel a POW (see `platform.browser.js`). return new promise(function(resolve, reject) { + var poolSize = opts.poolSize || os.cpus().length; worker.powAsync( poolSize, opts.target, diff --git a/lib/pow.js b/lib/pow.js index bf81d22..c41e623 100644 --- a/lib/pow.js +++ b/lib/pow.js @@ -16,9 +16,10 @@ var util = require("./_util"); * Calculate target * @param {Object} opts - Target options * @return {number} Target. + * @static */ // Just a wrapper around platform-specific implementation. -exports.getTarget = function(opts) { +var getTarget = exports.getTarget = function(opts) { var payloadLength = opts.payloadLength || opts.payload.length; return platform.getTarget({ ttl: opts.ttl, @@ -36,6 +37,10 @@ exports.getTarget = function(opts) { exports.check = function(opts) { var initialHash; var nonce; + var target = opts.target; + if (target === undefined) { + target = getTarget(opts); + } if (opts.payload) { nonce = opts.payload.slice(0, 8); initialHash = bmcrypto.sha512(opts.payload.slice(8)); @@ -51,8 +56,8 @@ exports.check = function(opts) { } initialHash = opts.initialHash; } - var targetHi = Math.floor(opts.target / 4294967296); - var targetLo = opts.target % 4294967296; + var targetHi = Math.floor(target / 4294967296); + var targetLo = target % 4294967296; var dataToHash = Buffer.concat([nonce, initialHash]); var resultHash = bmcrypto.sha512(bmcrypto.sha512(dataToHash)); var trialHi = resultHash.readUInt32BE(0, true); @@ -69,13 +74,17 @@ exports.check = function(opts) { /** * Do a POW. * @param {Object} opts - Proof of work options - * @return {Promise.} A promise that contains computed nonce for + * @param {?Buffer} opts.data - Object message payload without nonce to + * get the initial hash from + * @param {?Buffer} opts.initialHash - Or already computed initial hash + * @param {number} opts.target - POW target + * @return {Promise.} A promise that contains computed nonce for * the given target when fulfilled. */ exports.doAsync = function(opts) { var initialHash; - if (opts.payload) { - initialHash = bmcrypto.sha512(opts.payload); + if (opts.data) { + initialHash = bmcrypto.sha512(opts.data); } else { initialHash = opts.initialHash; } diff --git a/lib/structs.js b/lib/structs.js index fe90fa2..54a8c48 100644 --- a/lib/structs.js +++ b/lib/structs.js @@ -188,6 +188,14 @@ var object = exports.object = { * @return {Buffer} Encoded payload. */ encodePayload: function(opts) { + // NOTE(Kagami): We do not try to calculate nonce here if it is not + // provided because: + // 1) It's async operation but in `structs` module all operations + // are synchronous. + // 2) It shouldn't be useful because almost all objects signatures + // include object header and POW is computed for entire object so at + // first the object header should be assembled and only then we can + // do a POW. assert(opts.nonce.length === 8, "Bad nonce"); // NOTE(Kagami): This may be a bit inefficient since we allocate // twice. diff --git a/test.js b/test.js index 31b9d4e..a3fbe30 100644 --- a/test.js +++ b/test.js @@ -191,6 +191,16 @@ describe("Common structures", function() { encoded = Buffer.concat([encoded, Buffer(300000)]); expect(object.decodePayload.bind(null, encoded)).to.throw(/too big/i); }); + + it("shouldn't decode object with insufficient nonce", function() { + expect(object.decode.bind(null, object.encode({ + nonce: Buffer(8), + ttl: 100, + type: 2, + version: 1, + objectPayload: Buffer("test"), + }))).to.throw(/insufficient/i); + }); }); describe("var_int", function() { @@ -570,6 +580,7 @@ describe("Object types", function() { return getpubkey.encodeAsync({ ttl: 100, to: "BM-2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return getpubkey.decodeAsync(buf, skipPow); @@ -587,6 +598,7 @@ describe("Object types", function() { return getpubkey.encodeAsync({ ttl: 100, to: "2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return getpubkey.decodeAsync(buf, skipPow); @@ -599,6 +611,33 @@ describe("Object types", function() { expect(res.tag.toString("hex")).to.equal("facf1e3e6c74916203b7f714ca100d4d60604f0917696d0f09330f82f52bed1a"); }); }); + + it("shouldn't decode getpubkey with insufficient nonce", function(done) { + return getpubkey.encodeAsync({ + ttl: 100, + to: "2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z", + skipPow: true, + }).then(getpubkey.decodeAsync).catch(function(err) { + expect(err.message).to.match(/insufficient/i); + done(); + }); + }); + + if (allTests) { + it("should encode and decode getpubkey with nonce", function() { + this.timeout(300000); + return getpubkey.encodePayloadAsync({ + ttl: 100, + to: "2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z", + }).then(function(payload) { + expect(POW.check({ttl: 100, payload: payload})).to.be.true;; + return getpubkey.decodePayloadAsync(payload); + }).then(function(res) { + expect(res.ttl).to.be.at.most(100); + expect(res.tag.toString("hex")).to.equal("facf1e3e6c74916203b7f714ca100d4d60604f0917696d0f09330f82f52bed1a"); + }); + }); + } }); describe("pubkey", function() { @@ -607,6 +646,7 @@ describe("Object types", function() { ttl: 123, from: from, to: "BM-onhypnh1UMhbQpmvdiPuG6soLLytYJAfH", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return pubkey.decodeAsync(buf, skipPow); @@ -627,6 +667,7 @@ describe("Object types", function() { ttl: 456, from: from, to: "BM-2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return pubkey.decodeAsync(buf, skipPow); @@ -645,7 +686,7 @@ describe("Object types", function() { }); it("should encode and decode pubkey v4", function() { - return pubkey.encodeAsync({ttl: 789, from: from, to: from}) + return pubkey.encodeAsync({ttl: 789, from: from, to: from, skipPow: true}) .then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return pubkey.decodeAsync(buf, {needed: from, skipPow: true}); @@ -663,6 +704,20 @@ describe("Object types", function() { expect(bufferEqual(res.tag, from.getTag())).to.be.true; }); }); + + if (allTests) { + it("should encode and decode pubkey with nonce", function() { + this.timeout(300000); + return pubkey.encodePayloadAsync({ttl: 789, from: from, to: from}) + .then(function(payload) { + expect(POW.check({ttl: 789, payload: payload})).to.be.true;; + return pubkey.decodePayloadAsync(payload, {needed: from}); + }).then(function(res) { + expect(res.ttl).to.be.at.most(789); + expect(bufferEqual(res.tag, from.getTag())).to.be.true; + }); + }); + } }); describe("msg", function() { @@ -672,6 +727,7 @@ describe("Object types", function() { from: from, to: from, message: "test", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return msg.decodeAsync(buf, {identities: [from], skipPow: true}); @@ -701,6 +757,7 @@ describe("Object types", function() { from: fromV2, to: fromV2, message: "test", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return msg.decodeAsync(buf, {identities: [fromV2], skipPow: true}); @@ -730,6 +787,7 @@ describe("Object types", function() { from: from, to: from, message: "test", + skipPow: true, }).then(function(buf) { return msg.decodeAsync(buf, {identities: [], skipPow: true}); }).catch(function(err) { @@ -746,6 +804,7 @@ describe("Object types", function() { encoding: msg.SIMPLE, subject: "Тема", message: "Сообщение", + skipPow: true, }).then(function(buf) { return msg.decodeAsync(buf, {identities: [from], skipPow: true}); }).then(function(res) { @@ -761,11 +820,30 @@ describe("Object types", function() { from: from, to: from, message: Buffer(300000), + skipPow: true, }).catch(function(err) { expect(err.message).to.match(/too big/i); done(); }); }); + + if (allTests) { + it("should encode and decode msg with nonce", function() { + this.timeout(300000); + return msg.encodePayloadAsync({ + ttl: 111, + from: from, + to: from, + message: "test", + }).then(function(payload) { + expect(POW.check({ttl: 111, payload: payload})).to.be.true;; + return msg.decodePayloadAsync(payload, {identities: from}); + }).then(function(res) { + expect(res.ttl).to.be.at.most(111); + expect(res.message).to.equal("test"); + }); + }); + } }); describe("broadcast", function() { @@ -774,6 +852,7 @@ describe("Object types", function() { ttl: 987, from: fromV3, message: "test", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return broadcast.decodeAsync(buf, {subscriptions: fromV3, skipPow: true}); @@ -801,6 +880,7 @@ describe("Object types", function() { ttl: 999, from: fromV2, message: "test", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return broadcast.decodeAsync(buf, {subscriptions: fromV2, skipPow: true}); @@ -828,11 +908,12 @@ describe("Object types", function() { ttl: 101, from: from, message: "キタ━━━(゜∀゜)━━━!!!!!", + skipPow: true, }).then(function(buf) { expect(message.decode(buf).command).to.equal("object"); return broadcast.decodeAsync(buf, {subscriptions: [from], skipPow: true}); }).then(function(res) { - expect(res.ttl).to.be.at.most(987); + expect(res.ttl).to.be.at.most(101); expect(res.type).to.equal(object.BROADCAST); expect(res.version).to.equal(5); expect(res.stream).to.equal(1); @@ -855,6 +936,7 @@ describe("Object types", function() { ttl: 101, from: from, message: "test", + skipPow: true, }).then(function(buf) { return broadcast.decodeAsync(buf, { subscriptions: [fromV3], @@ -871,11 +953,29 @@ describe("Object types", function() { ttl: 101, from: from, message: Buffer(300000), + skipPow: true, }).catch(function(err) { expect(err.message).to.match(/too big/i); done(); }); }); + + if (allTests) { + it("should encode and decode broadcast with nonce", function() { + this.timeout(300000); + return broadcast.encodePayloadAsync({ + ttl: 101, + from: from, + message: "test", + }).then(function(payload) { + expect(POW.check({ttl: 101, payload: payload})).to.be.true;; + return broadcast.decodePayloadAsync(payload, {subscriptions: from}); + }).then(function(res) { + expect(res.ttl).to.be.at.most(101); + expect(res.message).to.equal("test"); + }); + }); + } }); });