Do a POW for objects
This commit is contained in:
parent
05658e9e56
commit
2615c25425
|
@ -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.<Buffer>} 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.<Buffer>} 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.<Buffer>} 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.<Buffer>} 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);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
21
lib/pow.js
21
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.<Buffer>} 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.<number>} 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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
104
test.js
104
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");
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user