objects.msg
This commit is contained in:
parent
4610701a3e
commit
2e4c91f49b
|
@ -49,7 +49,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
||||||
- [ ] Object types
|
- [ ] Object types
|
||||||
- [x] getpubkey
|
- [x] getpubkey
|
||||||
- [x] pubkey
|
- [x] pubkey
|
||||||
- [ ] msg
|
- [x] msg
|
||||||
- [ ] broadcast
|
- [ ] broadcast
|
||||||
- [x] WIF
|
- [x] WIF
|
||||||
- [x] POW
|
- [x] POW
|
||||||
|
|
14
lib/_util.js
14
lib/_util.js
|
@ -1,5 +1,5 @@
|
||||||
// NOTE(Kagami): End-users shouldn't use this module. While it exports
|
// NOTE(Kagami): End-users shouldn't import this module. While it
|
||||||
// some helper routines, its API is _not_ stable.
|
// exports some helper routines, its API is _not_ stable.
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
@ -47,15 +47,15 @@ exports.readTime64BE = function(buf, offset) {
|
||||||
return new Date(timestamp * 1000);
|
return new Date(timestamp * 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.writeUInt64BE = function(buf, value, offset, noAssert) {
|
function writeUInt64BE(buf, value, offset, noAssert) {
|
||||||
buf = buf || new Buffer(8);
|
buf = buf || new Buffer(8);
|
||||||
offset = offset || 0;
|
offset = offset || 0;
|
||||||
assert(noAssert || value <= MAX_SAFE_INTEGER, "Unsafe integer");
|
assert(noAssert || value <= MAX_SAFE_INTEGER, "Unsafe integer");
|
||||||
buf.writeUInt32BE(Math.floor(value / 4294967296), offset, noAssert);
|
buf.writeUInt32BE(Math.floor(value / 4294967296), offset, noAssert);
|
||||||
buf.writeUInt32BE(value % 4294967296, offset + 4, noAssert);
|
buf.writeUInt32BE(value % 4294967296, offset + 4, noAssert);
|
||||||
return buf;
|
return buf;
|
||||||
};
|
}
|
||||||
var writeUInt64BE = exports.writeUInt64BE;
|
exports.writeUInt64BE = writeUInt64BE;
|
||||||
|
|
||||||
exports.writeTime64BE = function(buf, time, offset, noAssert) {
|
exports.writeTime64BE = function(buf, time, offset, noAssert) {
|
||||||
var timestamp = Math.floor(time.getTime() / 1000);
|
var timestamp = Math.floor(time.getTime() / 1000);
|
||||||
|
@ -67,8 +67,8 @@ exports.tnow = function() {
|
||||||
return Math.floor(time.getTime() / 1000);
|
return Math.floor(time.getTime() / 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
var DEFAULT_TRIALS_PER_BYTE = 1000;
|
var DEFAULT_TRIALS_PER_BYTE = exports.DEFAULT_TRIALS_PER_BYTE = 1000;
|
||||||
var DEFAULT_EXTRA_BYTES = 1000;
|
var DEFAULT_EXTRA_BYTES = exports.DEFAULT_EXTRA_BYTES = 1000;
|
||||||
|
|
||||||
exports.getTrials = function(opts) {
|
exports.getTrials = function(opts) {
|
||||||
var nonceTrialsPerByte = opts.nonceTrialsPerByte;
|
var nonceTrialsPerByte = opts.nonceTrialsPerByte;
|
||||||
|
|
361
lib/objects.js
361
lib/objects.js
|
@ -13,6 +13,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
var objectAssign = Object.assign || require("object-assign");
|
||||||
|
var bufferEqual = require("buffer-equal");
|
||||||
var assert = require("./_util").assert;
|
var assert = require("./_util").assert;
|
||||||
var promise = require("./platform").promise;
|
var promise = require("./platform").promise;
|
||||||
var bmcrypto = require("./crypto");
|
var bmcrypto = require("./crypto");
|
||||||
|
@ -95,8 +96,7 @@ var getpubkey = exports.getpubkey = {
|
||||||
assert(decoded.type === object.GETPUBKEY, "Wrong object type");
|
assert(decoded.type === object.GETPUBKEY, "Wrong object type");
|
||||||
assert(decoded.version >= 2, "getpubkey version is too low");
|
assert(decoded.version >= 2, "getpubkey version is too low");
|
||||||
assert(decoded.version <= 4, "getpubkey version is too high");
|
assert(decoded.version <= 4, "getpubkey version is too high");
|
||||||
var objectPayload = decoded.objectPayload;
|
var objectPayload = util.popkey(decoded, "objectPayload");
|
||||||
delete decoded.objectPayload;
|
|
||||||
if (decoded.version < 4) {
|
if (decoded.version < 4) {
|
||||||
assert(objectPayload.length === 20, "getpubkey ripe is too small");
|
assert(objectPayload.length === 20, "getpubkey ripe is too small");
|
||||||
// Object payload is copied so it's safe to return it right away.
|
// Object payload is copied so it's safe to return it right away.
|
||||||
|
@ -148,11 +148,11 @@ var getpubkey = exports.getpubkey = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function for `pubkey.decode`.
|
|
||||||
// Extract pubkey data from decrypted object payload.
|
// Extract pubkey data from decrypted object payload.
|
||||||
function extractPubkeyV2(buf) {
|
function extractPubkey(buf) {
|
||||||
var decoded = {};
|
var decoded = {length: 132};
|
||||||
// Object payload is copied so it's safe to return it right away.
|
// We assume here that input buffer was copied before so it's safe to
|
||||||
|
// return reference to it.
|
||||||
decoded.behavior = PubkeyBitfield(buf.slice(0, 4));
|
decoded.behavior = PubkeyBitfield(buf.slice(0, 4));
|
||||||
var signPublicKey = decoded.signPublicKey = new Buffer(65);
|
var signPublicKey = decoded.signPublicKey = new Buffer(65);
|
||||||
signPublicKey[0] = 4;
|
signPublicKey[0] = 4;
|
||||||
|
@ -163,23 +163,23 @@ function extractPubkeyV2(buf) {
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function for `pubkey.decode`.
|
// Extract pubkey version 3 data from decrypted object payload.
|
||||||
// Extract pubkey data from decrypted object payload.
|
|
||||||
function extractPubkeyV3(buf) {
|
function extractPubkeyV3(buf) {
|
||||||
var decoded = {};
|
var decoded = extractPubkey(buf);
|
||||||
var length = 0;
|
var decodedTrials = var_int.decode(buf.slice(132));
|
||||||
var decodedTrials = var_int.decode(buf);
|
|
||||||
decoded.nonceTrialsPerByte = decodedTrials.value;
|
decoded.nonceTrialsPerByte = decodedTrials.value;
|
||||||
length += decodedTrials.length;
|
decoded.length += decodedTrials.length;
|
||||||
var decodedExtraBytes = var_int.decode(decodedTrials.rest);
|
var decodedExtraBytes = var_int.decode(decodedTrials.rest);
|
||||||
decoded.payloadLengthExtraBytes = decodedExtraBytes.value;
|
decoded.payloadLengthExtraBytes = decodedExtraBytes.value;
|
||||||
length += decodedExtraBytes.length;
|
decoded.length += decodedExtraBytes.length;
|
||||||
var decodedSigLength = var_int.decode(decodedExtraBytes.rest);
|
var decodedSigLength = var_int.decode(decodedExtraBytes.rest);
|
||||||
decoded.signature = decodedSigLength.rest.slice(0, decodedSigLength.value);
|
var siglen = decodedSigLength.value;
|
||||||
var siglen = decodedSigLength.length + decodedSigLength.value;
|
var rest = decodedSigLength.rest;
|
||||||
length += siglen;
|
assert(rest.length >= siglen, "Bad pubkey object payload length");
|
||||||
|
decoded.signature = rest.slice(0, siglen);
|
||||||
|
siglen += decodedSigLength.length;
|
||||||
decoded._siglen = siglen; // Internal value
|
decoded._siglen = siglen; // Internal value
|
||||||
decoded.length = length;
|
decoded.length += siglen;
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,19 +213,17 @@ var pubkey = exports.pubkey = {
|
||||||
* object structure when fulfilled.
|
* object structure when fulfilled.
|
||||||
*/
|
*/
|
||||||
decodePayloadAsync: function(buf, opts) {
|
decodePayloadAsync: function(buf, opts) {
|
||||||
return new promise(function(resolve, reject) {
|
return new promise(function(resolve) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
var neededPubkeys = opts.neededPubkeys || {};
|
var neededPubkeys = opts.neededPubkeys || {};
|
||||||
var decoded = object.decodePayload(buf);
|
var decoded = object.decodePayload(buf);
|
||||||
assert(decoded.type === object.PUBKEY, "Wrong object type");
|
assert(decoded.type === object.PUBKEY, "Wrong object type");
|
||||||
var objectPayload = decoded.objectPayload;
|
|
||||||
delete decoded.objectPayload;
|
|
||||||
var version = decoded.version;
|
var version = decoded.version;
|
||||||
assert(version >= 2, "Address version is too low");
|
assert(version >= 2, "Address version is too low");
|
||||||
assert(version <= 4, "Address version is too high");
|
assert(version <= 4, "Address version is too high");
|
||||||
var siglen, sig, dataToVerify, pubkeyp;
|
var objectPayload = util.popkey(decoded, "objectPayload");
|
||||||
|
var siglen, pos, sig, dataToVerify, pubkeyp;
|
||||||
var addr, addrs, tag, pubkeyEncPrivateKey, dataToDecrypt;
|
var addr, addrs, tag, pubkeyEncPrivateKey, dataToDecrypt;
|
||||||
var length = 132;
|
|
||||||
|
|
||||||
// v2 pubkey.
|
// v2 pubkey.
|
||||||
if (version === 2) {
|
if (version === 2) {
|
||||||
|
@ -233,9 +231,7 @@ var pubkey = exports.pubkey = {
|
||||||
assert(
|
assert(
|
||||||
objectPayload.length === 132,
|
objectPayload.length === 132,
|
||||||
"Bad pubkey v2 object payload length");
|
"Bad pubkey v2 object payload length");
|
||||||
objectAssign(decoded, extractPubkeyV2(objectPayload));
|
objectAssign(decoded, extractPubkey(objectPayload));
|
||||||
// Real data length.
|
|
||||||
decoded.length = length;
|
|
||||||
return resolve(decoded);
|
return resolve(decoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,14 +241,11 @@ var pubkey = exports.pubkey = {
|
||||||
assert(
|
assert(
|
||||||
objectPayload.length >= 135,
|
objectPayload.length >= 135,
|
||||||
"Bad pubkey v3 object payload length");
|
"Bad pubkey v3 object payload length");
|
||||||
objectAssign(decoded, extractPubkeyV2(objectPayload));
|
objectAssign(decoded, extractPubkeyV3(objectPayload));
|
||||||
objectAssign(decoded, extractPubkeyV3(objectPayload.slice(132)));
|
|
||||||
siglen = util.popkey(decoded, "_siglen");
|
siglen = util.popkey(decoded, "_siglen");
|
||||||
length += decoded.length;
|
pos = decoded.headerLength + decoded.length - siglen;
|
||||||
// Real data length.
|
// Object message payload from `expiresTime` up to `sig_length`.
|
||||||
decoded.length = length;
|
dataToVerify = buf.slice(8, pos);
|
||||||
// Object message payload without nonce up to sigLength.
|
|
||||||
dataToVerify = buf.slice(8, decoded.headerLength + length - siglen);
|
|
||||||
sig = decoded.signature;
|
sig = decoded.signature;
|
||||||
pubkeyp = bmcrypto.verify(decoded.signPublicKey, dataToVerify, sig)
|
pubkeyp = bmcrypto.verify(decoded.signPublicKey, dataToVerify, sig)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
|
@ -281,30 +274,26 @@ var pubkey = exports.pubkey = {
|
||||||
assert(objectPayload.length >= 32, "Bad pubkey v4 object payload length");
|
assert(objectPayload.length >= 32, "Bad pubkey v4 object payload length");
|
||||||
tag = decoded.tag = objectPayload.slice(0, 32);
|
tag = decoded.tag = objectPayload.slice(0, 32);
|
||||||
pubkeyEncPrivateKey = neededPubkeys[tag];
|
pubkeyEncPrivateKey = neededPubkeys[tag];
|
||||||
if (!pubkeyEncPrivateKey) {
|
assert(pubkeyEncPrivateKey, "You are not interested in this pubkey v4");
|
||||||
return reject(new Error("You are not interested in this pubkey v4"));
|
|
||||||
}
|
|
||||||
dataToDecrypt = objectPayload.slice(32);
|
dataToDecrypt = objectPayload.slice(32);
|
||||||
pubkeyp = bmcrypto.decrypt(pubkeyEncPrivateKey, dataToDecrypt)
|
pubkeyp = bmcrypto
|
||||||
|
.decrypt(pubkeyEncPrivateKey, dataToDecrypt)
|
||||||
.then(function(decrypted) {
|
.then(function(decrypted) {
|
||||||
// 4 + 64 + 64 + (1+) + (1+) + (1+)
|
// 4 + 64 + 64 + (1+) + (1+) + (1+)
|
||||||
assert(
|
assert(
|
||||||
decrypted.length >= 135,
|
decrypted.length >= 135,
|
||||||
"Bad pubkey v4 object payload length");
|
"Bad pubkey v4 object payload length");
|
||||||
objectAssign(decoded, extractPubkeyV2(decrypted));
|
objectAssign(decoded, extractPubkeyV3(decrypted));
|
||||||
objectAssign(decoded, extractPubkeyV3(decrypted.slice(132)));
|
|
||||||
siglen = util.popkey(decoded, "_siglen");
|
siglen = util.popkey(decoded, "_siglen");
|
||||||
length += decoded.length;
|
|
||||||
// Real data length.
|
|
||||||
// Since data is encrypted, entire object payload is used.
|
|
||||||
decoded.length = objectPayload.length;
|
|
||||||
dataToVerify = Buffer.concat([
|
dataToVerify = Buffer.concat([
|
||||||
// Object header without nonce + tag.
|
// Object header without nonce + tag.
|
||||||
buf.slice(8, decoded.headerLength + 32),
|
buf.slice(8, decoded.headerLength + 32),
|
||||||
// Unencrypted pubkey data without signature.
|
// Unencrypted pubkey data without signature.
|
||||||
decrypted.slice(0, length - siglen),
|
decrypted.slice(0, decoded.length - siglen),
|
||||||
]);
|
]);
|
||||||
sig = decoded.signature;
|
sig = decoded.signature;
|
||||||
|
// Since data is encrypted, entire object payload is used.
|
||||||
|
decoded.length = objectPayload.length;
|
||||||
return bmcrypto.verify(decoded.signPublicKey, dataToVerify, sig);
|
return bmcrypto.verify(decoded.signPublicKey, dataToVerify, sig);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return decoded;
|
return decoded;
|
||||||
|
@ -420,3 +409,289 @@ var pubkey = exports.pubkey = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Try to decrypt message with all provided identities.
|
||||||
|
function tryDecrypt(identities, buf) {
|
||||||
|
function inner(i) {
|
||||||
|
if (i > last) {
|
||||||
|
return promise.reject("Failed to decrypt msg with given identities");
|
||||||
|
}
|
||||||
|
return bmcrypto
|
||||||
|
.decrypt(identities[i].encPrivateKey, buf)
|
||||||
|
.then(function(decrypted) {
|
||||||
|
return {addr: identities[i], decrypted: decrypted};
|
||||||
|
}).catch(function() {
|
||||||
|
return inner(i + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var last = identities.length - 1;
|
||||||
|
return inner(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loosely decode message in SIMPLE encoding.
|
||||||
|
function decodeSimple(buf) {
|
||||||
|
var decoded = {};
|
||||||
|
var message = buf.toString("utf8");
|
||||||
|
var subject, index;
|
||||||
|
if (message.slice(0, 8) === "Subject:") {
|
||||||
|
subject = message.slice(8);
|
||||||
|
index = subject.indexOf("\nBody:");
|
||||||
|
if (index !== -1) {
|
||||||
|
message = subject.slice(index + 6);
|
||||||
|
subject = subject.slice(0, index);
|
||||||
|
} else {
|
||||||
|
message = "";
|
||||||
|
}
|
||||||
|
decoded.subject = subject;
|
||||||
|
decoded.message = message;
|
||||||
|
} else {
|
||||||
|
decoded.subject = "";
|
||||||
|
decoded.message = message;
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `msg` object.
|
||||||
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#msg}
|
||||||
|
* @namespace
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
var msg = exports.msg = {
|
||||||
|
/**
|
||||||
|
* Any data with this number may be ignored. The sending node might
|
||||||
|
* simply be sharing its public key with you.
|
||||||
|
*/
|
||||||
|
IGNORE: 0,
|
||||||
|
/**
|
||||||
|
* UTF-8. No 'Subject' or 'Body' sections. Useful for simple strings
|
||||||
|
* of data, like URIs or magnet links.
|
||||||
|
*/
|
||||||
|
TRIVIAL: 1,
|
||||||
|
/**
|
||||||
|
* UTF-8. Uses 'Subject' and 'Body' sections. No MIME is used.
|
||||||
|
*/
|
||||||
|
SIMPLE: 2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode `msg` object message.
|
||||||
|
* @param {Buffer} buf - Message
|
||||||
|
* @param {?Object} opts - Decoding options
|
||||||
|
* @return {Promise.<Object>} A promise that contains decoded `msg`
|
||||||
|
* object structure when fulfilled.
|
||||||
|
*/
|
||||||
|
decodeAsync: function(buf, opts) {
|
||||||
|
return new promise(function(resolve) {
|
||||||
|
var decoded = message.decode(buf);
|
||||||
|
assert(decoded.command === "object", "Bad command");
|
||||||
|
resolve(msg.decodePayloadAsync(decoded.payload, opts));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode `msg` object message payload.
|
||||||
|
* @param {Buffer} buf - Message payload
|
||||||
|
* @param {Object} opts - Decoding options
|
||||||
|
* @return {Promise.<Object>} A promise that contains decoded `msg`
|
||||||
|
* object structure when fulfilled.
|
||||||
|
*/
|
||||||
|
decodePayloadAsync: function(buf, opts) {
|
||||||
|
return new promise(function(resolve) {
|
||||||
|
var decoded = object.decodePayload(buf);
|
||||||
|
assert(decoded.type === object.MSG, "Wrong object type");
|
||||||
|
assert(decoded.version === 1, "Wrong msg version");
|
||||||
|
var objectPayload = util.popkey(decoded, "objectPayload");
|
||||||
|
|
||||||
|
var msgp = tryDecrypt(opts.identities, objectPayload)
|
||||||
|
.then(function(decInfo) {
|
||||||
|
var decrypted = decInfo.decrypted;
|
||||||
|
|
||||||
|
// Version, stream.
|
||||||
|
// TODO(Kagami): Validate version range?
|
||||||
|
var decodedVersion = var_int.decode(decrypted);
|
||||||
|
decoded.senderVersion = decodedVersion.value;
|
||||||
|
var decodedStream = var_int.decode(decodedVersion.rest);
|
||||||
|
decoded.senderStream = decodedStream.value;
|
||||||
|
|
||||||
|
// Behavior, keys.
|
||||||
|
assert(
|
||||||
|
decodedStream.rest.length >= 132,
|
||||||
|
"Bad msg object payload length");
|
||||||
|
objectAssign(decoded, extractPubkey(decodedStream.rest));
|
||||||
|
decoded.length += decodedVersion.length + decodedStream.length;
|
||||||
|
var rest = decrypted.slice(decoded.length);
|
||||||
|
|
||||||
|
// Pow extra.
|
||||||
|
if (decoded.senderVersion >= 3) {
|
||||||
|
var decodedTrials = var_int.decode(rest);
|
||||||
|
decoded.nonceTrialsPerByte = decodedTrials.value;
|
||||||
|
decoded.length += decodedTrials.length;
|
||||||
|
var decodedExtraBytes = var_int.decode(decodedTrials.rest);
|
||||||
|
decoded.payloadLengthExtraBytes = decodedExtraBytes.value;
|
||||||
|
decoded.length += decodedExtraBytes.length;
|
||||||
|
rest = decodedExtraBytes.rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ripe, encoding.
|
||||||
|
assert(rest.length >= 20, "Bad msg object payload length");
|
||||||
|
decoded.ripe = rest.slice(0, 20);
|
||||||
|
assert(
|
||||||
|
bufferEqual(decoded.ripe, decInfo.addr.ripe),
|
||||||
|
"Message was decrypted but the destination ripe differs");
|
||||||
|
decoded.length += 20;
|
||||||
|
var decodedEncoding = var_int.decode(rest.slice(20));
|
||||||
|
var encoding = decoded.encoding = decodedEncoding.value;
|
||||||
|
decoded.length += decodedEncoding.length;
|
||||||
|
|
||||||
|
// Message.
|
||||||
|
var decodedMsgLength = var_int.decode(decodedEncoding.rest);
|
||||||
|
var msglen = decodedMsgLength.value;
|
||||||
|
rest = decodedMsgLength.rest;
|
||||||
|
assert(rest.length >= msglen, "Bad msg object payload length");
|
||||||
|
decoded.length += decodedMsgLength.length + msglen;
|
||||||
|
var message = rest.slice(0, msglen);
|
||||||
|
if (encoding === msg.TRIVIAL) {
|
||||||
|
decoded.message = message.toString("utf8");
|
||||||
|
} else if (encoding === msg.SIMPLE) {
|
||||||
|
objectAssign(decoded, decodeSimple(message));
|
||||||
|
} else {
|
||||||
|
decoded.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acknowledgement data.
|
||||||
|
// TODO(Kagami): Validate ack, check a POW.
|
||||||
|
var decodedAckLength = var_int.decode(rest.slice(msglen));
|
||||||
|
var acklen = decodedAckLength.value;
|
||||||
|
rest = decodedAckLength.rest;
|
||||||
|
assert(rest.length >= acklen, "Bad msg object payload length");
|
||||||
|
decoded.length += decodedAckLength.length + acklen;
|
||||||
|
decoded.ack = rest.slice(0, acklen);
|
||||||
|
|
||||||
|
// Signature.
|
||||||
|
var decodedSigLength = var_int.decode(rest.slice(acklen));
|
||||||
|
var siglen = decodedSigLength.value;
|
||||||
|
rest = decodedSigLength.rest;
|
||||||
|
assert(rest.length >= siglen, "Bad msg object payload length");
|
||||||
|
var sig = decoded.signature = rest.slice(0, siglen);
|
||||||
|
|
||||||
|
// Verify signature.
|
||||||
|
var dataToVerify = Buffer.concat([
|
||||||
|
// Object header without nonce.
|
||||||
|
buf.slice(8, decoded.headerLength),
|
||||||
|
// Unencrypted pubkey data without signature.
|
||||||
|
decrypted.slice(0, decoded.length),
|
||||||
|
]);
|
||||||
|
// Since data is encrypted, entire object payload is used.
|
||||||
|
decoded.length = objectPayload.length;
|
||||||
|
return bmcrypto.verify(decoded.signPublicKey, dataToVerify, sig);
|
||||||
|
}).then(function() {
|
||||||
|
return decoded;
|
||||||
|
});
|
||||||
|
resolve(msgp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode `msg` object message.
|
||||||
|
* @param {Object} opts - `msg` object options
|
||||||
|
* @return {Promise.<Buffer>} A promise that contains encoded message
|
||||||
|
* when fulfilled.
|
||||||
|
*/
|
||||||
|
encodeAsync: function(opts) {
|
||||||
|
return msg.encodePayloadAsync(opts).then(function(payload) {
|
||||||
|
return message.encode("object", payload);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode `msg` object message payload.
|
||||||
|
* @param {Object} opts - `msg` object options
|
||||||
|
* @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.
|
||||||
|
opts = objectAssign({}, opts);
|
||||||
|
opts.type = object.MSG;
|
||||||
|
opts.version = 1; // The only known msg version
|
||||||
|
var from = Address.decode(opts.from);
|
||||||
|
var to = Address.decode(opts.to);
|
||||||
|
opts.stream = to.stream;
|
||||||
|
var nonceTrialsPerByte, payloadLengthExtraBytes;
|
||||||
|
if (from.version >= 3) {
|
||||||
|
if (opts.friend) {
|
||||||
|
nonceTrialsPerByte = util.DEFAULT_TRIALS_PER_BYTE;
|
||||||
|
payloadLengthExtraBytes = util.DEFAULT_EXTRA_BYTES;
|
||||||
|
} else {
|
||||||
|
nonceTrialsPerByte = util.getTrials(from);
|
||||||
|
payloadLengthExtraBytes = util.getExtraBytes(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var encoding = opts.encoding || msg.TRIVIAL;
|
||||||
|
var message = opts.message;
|
||||||
|
var subject = opts.subject;
|
||||||
|
if (encoding === msg.IGNORE && !message) {
|
||||||
|
// User may omit message for IGNORE encoding.
|
||||||
|
message = new Buffer(0);
|
||||||
|
} else if (!Buffer.isBuffer(message)) {
|
||||||
|
// User may specify message as a string.
|
||||||
|
message = new Buffer(message, "utf8");
|
||||||
|
}
|
||||||
|
if (encoding === msg.SIMPLE && subject) {
|
||||||
|
// User may specify subject for SIMPLE encoding.
|
||||||
|
if (!Buffer.isBuffer(subject)) {
|
||||||
|
subject = new Buffer(subject, "utf8");
|
||||||
|
}
|
||||||
|
message = Buffer.concat([
|
||||||
|
new Buffer("Subject:"),
|
||||||
|
subject,
|
||||||
|
new Buffer("\nBody:"),
|
||||||
|
message,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assembling the unencrypted message data.
|
||||||
|
var msgData = [
|
||||||
|
var_int.encode(from.version),
|
||||||
|
var_int.encode(from.stream),
|
||||||
|
from.behavior.buffer,
|
||||||
|
from.signPublicKey.slice(1),
|
||||||
|
from.encPublicKey.slice(1),
|
||||||
|
];
|
||||||
|
if (from.version >= 3) {
|
||||||
|
msgData.push(
|
||||||
|
var_int.encode(nonceTrialsPerByte),
|
||||||
|
var_int.encode(payloadLengthExtraBytes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
msgData.push(
|
||||||
|
to.ripe,
|
||||||
|
var_int.encode(encoding),
|
||||||
|
var_int.encode(message.length),
|
||||||
|
message
|
||||||
|
);
|
||||||
|
// TODO(Kagami): Calculate ACK.
|
||||||
|
msgData.push(var_int.encode(0));
|
||||||
|
|
||||||
|
opts.objectPayload = new Buffer(0);
|
||||||
|
var obj = object.encodePayloadWithoutNonce(opts);
|
||||||
|
var dataToSign = Buffer.concat([obj].concat(msgData));
|
||||||
|
var msgp = bmcrypto
|
||||||
|
.sign(from.signPrivateKey, dataToSign)
|
||||||
|
.then(function(sig) {
|
||||||
|
var dataToEnc = msgData.concat(var_int.encode(sig.length), sig);
|
||||||
|
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.
|
||||||
|
return Buffer.concat([nonce, obj, enc]);
|
||||||
|
});
|
||||||
|
resolve(msgp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -23,8 +23,8 @@ exports.decode = var_str.decode;
|
||||||
/**
|
/**
|
||||||
* Parse raw user agent into software stack list. Most underlying
|
* Parse raw user agent into software stack list. Most underlying
|
||||||
* software comes first.
|
* software comes first.
|
||||||
* NOTE: Decoding is rather loose and non-strict, it won't fail on bad
|
* NOTE: Decoding is rather loose, it won't fail on bad user agent
|
||||||
* user agent format because it's not that important.
|
* format because it's not that important.
|
||||||
* @param {string} str - Raw user agent string
|
* @param {string} str - Raw user agent string
|
||||||
* @return {Object[]} Parsed user agent.
|
* @return {Object[]} Parsed user agent.
|
||||||
*/
|
*/
|
||||||
|
|
86
test.js
86
test.js
|
@ -26,6 +26,7 @@ var error = messages.error;
|
||||||
var objects = bitmessage.objects;
|
var objects = bitmessage.objects;
|
||||||
var getpubkey = objects.getpubkey;
|
var getpubkey = objects.getpubkey;
|
||||||
var pubkey = objects.pubkey;
|
var pubkey = objects.pubkey;
|
||||||
|
var msg = objects.msg;
|
||||||
var WIF = bitmessage.WIF;
|
var WIF = bitmessage.WIF;
|
||||||
var POW = bitmessage.POW;
|
var POW = bitmessage.POW;
|
||||||
var Address = bitmessage.Address;
|
var Address = bitmessage.Address;
|
||||||
|
@ -91,11 +92,11 @@ describe("Crypto", function() {
|
||||||
var privateKey = Buffer(32);
|
var privateKey = Buffer(32);
|
||||||
privateKey.fill(1);
|
privateKey.fill(1);
|
||||||
var publicKey = bmcrypto.getPublic(privateKey);
|
var publicKey = bmcrypto.getPublic(privateKey);
|
||||||
var msg = Buffer("test");
|
var message = Buffer("test");
|
||||||
return bmcrypto.sign(privateKey, msg).then(function(sig) {
|
return bmcrypto.sign(privateKey, message).then(function(sig) {
|
||||||
expect(Buffer.isBuffer(sig)).to.be.true;
|
expect(Buffer.isBuffer(sig)).to.be.true;
|
||||||
expect(sig.toString("hex")).to.equal("304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa");
|
expect(sig.toString("hex")).to.equal("304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa");
|
||||||
return bmcrypto.verify(publicKey, msg, sig);
|
return bmcrypto.verify(publicKey, message, sig);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -495,6 +496,15 @@ describe("Message types", function() {
|
||||||
|
|
||||||
// TODO(Kagami): Add tests for encodePayloadAsync/decodePayloadAsync as well.
|
// TODO(Kagami): Add tests for encodePayloadAsync/decodePayloadAsync as well.
|
||||||
describe("Object types", function() {
|
describe("Object types", function() {
|
||||||
|
var signPrivateKey = Buffer("71c95d26c716a5e85e9af9efe26fb5f744dc98005a13d05d23ee92c77e038d9f", "hex");
|
||||||
|
var signPublicKey = bmcrypto.getPublic(signPrivateKey);
|
||||||
|
var encPrivateKey = Buffer("9f9969c93c2d186787a7653f70e49be34c03c4a853e6ad0c867db0946bc433c6", "hex");
|
||||||
|
var encPublicKey = bmcrypto.getPublic(encPrivateKey);
|
||||||
|
var from = Address({
|
||||||
|
signPrivateKey: signPrivateKey,
|
||||||
|
encPrivateKey: encPrivateKey,
|
||||||
|
});
|
||||||
|
|
||||||
it("should get type of the encoded object message", function() {
|
it("should get type of the encoded object message", function() {
|
||||||
var encoded = object.encode({
|
var encoded = object.encode({
|
||||||
nonce: Buffer(8),
|
nonce: Buffer(8),
|
||||||
|
@ -556,15 +566,6 @@ describe("Object types", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("pubkey", function() {
|
describe("pubkey", function() {
|
||||||
var signPrivateKey = Buffer("71c95d26c716a5e85e9af9efe26fb5f744dc98005a13d05d23ee92c77e038d9f", "hex");
|
|
||||||
var signPublicKey = bmcrypto.getPublic(signPrivateKey);
|
|
||||||
var encPrivateKey = Buffer("9f9969c93c2d186787a7653f70e49be34c03c4a853e6ad0c867db0946bc433c6", "hex");
|
|
||||||
var encPublicKey = bmcrypto.getPublic(encPrivateKey);
|
|
||||||
var from = Address({
|
|
||||||
signPrivateKey: signPrivateKey,
|
|
||||||
encPrivateKey: encPrivateKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should encode and decode pubkey v2", function() {
|
it("should encode and decode pubkey v2", function() {
|
||||||
return pubkey.encodeAsync({
|
return pubkey.encodeAsync({
|
||||||
ttl: 123,
|
ttl: 123,
|
||||||
|
@ -627,6 +628,67 @@ describe("Object types", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("msg", function() {
|
||||||
|
it("should encode and decode msg", function() {
|
||||||
|
return msg.encodeAsync({
|
||||||
|
ttl: 111,
|
||||||
|
from: from,
|
||||||
|
to: from,
|
||||||
|
message: "test",
|
||||||
|
}).then(function(buf) {
|
||||||
|
expect(message.decode(buf).command).to.equal("object");
|
||||||
|
return msg.decodeAsync(buf, {identities: [from]});
|
||||||
|
}).then(function(res) {
|
||||||
|
expect(res.ttl).to.be.at.most(111);
|
||||||
|
expect(res.type).to.equal(object.MSG);
|
||||||
|
expect(res.version).to.equal(1);
|
||||||
|
expect(res.stream).to.equal(1);
|
||||||
|
expect(res.senderVersion).to.equal(4);
|
||||||
|
expect(res.senderStream).to.equal(1);
|
||||||
|
expect(res.behavior.get(PubkeyBitfield.DOES_ACK)).to.be.true;
|
||||||
|
expect(bufferEqual(res.signPublicKey, signPublicKey)).to.be.true;
|
||||||
|
expect(bufferEqual(res.encPublicKey, encPublicKey)).to.be.true;
|
||||||
|
expect(res.nonceTrialsPerByte).to.equal(1000);
|
||||||
|
expect(res.payloadLengthExtraBytes).to.equal(1000);
|
||||||
|
expect(bufferEqual(res.ripe, from.ripe)).to.be.true;
|
||||||
|
expect(res.encoding).to.equal(msg.TRIVIAL);
|
||||||
|
expect(res.message).to.equal("test");
|
||||||
|
expect(res).to.not.have.property("subject");
|
||||||
|
expect(Buffer.isBuffer(res.signature)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't decode msg without identities", function(done) {
|
||||||
|
return msg.encodeAsync({
|
||||||
|
ttl: 111,
|
||||||
|
from: from,
|
||||||
|
to: from,
|
||||||
|
message: "test",
|
||||||
|
}).then(function(buf) {
|
||||||
|
return msg.decodeAsync(buf, {identities: []});
|
||||||
|
}).catch(function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should encode and decode SIMPLE msg", function() {
|
||||||
|
return msg.encodeAsync({
|
||||||
|
ttl: 111,
|
||||||
|
from: from,
|
||||||
|
to: from,
|
||||||
|
encoding: msg.SIMPLE,
|
||||||
|
subject: "Тема",
|
||||||
|
message: "Сообщение",
|
||||||
|
}).then(function(buf) {
|
||||||
|
return msg.decodeAsync(buf, {identities: [from]});
|
||||||
|
}).then(function(res) {
|
||||||
|
expect(res.encoding).to.equal(msg.SIMPLE);
|
||||||
|
expect(res.subject).to.equal("Тема");
|
||||||
|
expect(res.message).to.equal("Сообщение");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("WIF", function() {
|
describe("WIF", function() {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user