objects module improvements/refactoring
This commit is contained in:
parent
2416094686
commit
34868ff014
|
@ -98,36 +98,42 @@ Address.prototype.getShortRipe = function() {
|
||||||
return ripe.slice(20 - getripelen(ripe));
|
return ripe.slice(20 - getripelen(ripe));
|
||||||
};
|
};
|
||||||
|
|
||||||
function getdoublehash(addr) {
|
function getaddrhash(addr) {
|
||||||
var dataToHash = Buffer.concat([
|
var dataToHash = Buffer.concat([
|
||||||
var_int.encode(addr.version),
|
var_int.encode(addr.version),
|
||||||
var_int.encode(addr.stream),
|
var_int.encode(addr.stream),
|
||||||
addr.ripe,
|
addr.ripe,
|
||||||
]);
|
]);
|
||||||
return bmcrypto.sha512(bmcrypto.sha512(dataToHash));
|
return bmcrypto.sha512(dataToHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the encryption key used to encrypt/decrypt {@link pubkey}
|
* Calculate the encryption key used to encrypt/decrypt {@link pubkey}
|
||||||
* and {@link broadcast} objects.
|
* objects.
|
||||||
* @return {Buffer} A 32-byte private key.
|
* @return {Buffer} A 32-byte private key.
|
||||||
* @static
|
|
||||||
*/
|
*/
|
||||||
var getPubkeyPrivateKey = Address.prototype.getPubkeyPrivateKey = function() {
|
Address.prototype.getPubkeyPrivateKey = function() {
|
||||||
return getdoublehash(this).slice(0, 32);
|
return bmcrypto.sha512(getaddrhash(this)).slice(0, 32);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Just an alias to {@link Address#getPubkeyPrivateKey} for convinience.
|
* Calculate the encryption key used to encrypt/decrypt
|
||||||
|
* {@link broadcast} objects.
|
||||||
*/
|
*/
|
||||||
Address.prototype.getBroadcastPrivateKey = getPubkeyPrivateKey;
|
Address.prototype.getBroadcastPrivateKey = function() {
|
||||||
|
if (this.version >= 4) {
|
||||||
|
return bmcrypto.sha512(getaddrhash(this)).slice(0, 32);
|
||||||
|
} else {
|
||||||
|
return getaddrhash(this).slice(0, 32);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the address tag.
|
* Calculate the address tag.
|
||||||
* @return {Buffer} A 32-byte address tag.
|
* @return {Buffer} A 32-byte address tag.
|
||||||
*/
|
*/
|
||||||
Address.prototype.getTag = function() {
|
Address.prototype.getTag = function() {
|
||||||
return getdoublehash(this).slice(32);
|
return bmcrypto.sha512(getaddrhash(this)).slice(32);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
162
lib/objects.js
162
lib/objects.js
|
@ -6,7 +6,7 @@
|
||||||
* @module bitmessage/objects
|
* @module bitmessage/objects
|
||||||
*/
|
*/
|
||||||
// TODO(Kagami): Document object-like params.
|
// TODO(Kagami): Document object-like params.
|
||||||
// FIXME(Kagami): Think through API, we may want to get decoded
|
// FIXME(Kagami): Think through the API, we may want to get decoded
|
||||||
// structure even if it contains unsupported version. Also error
|
// structure even if it contains unsupported version. Also error
|
||||||
// handling may need some refactoring.
|
// handling may need some refactoring.
|
||||||
|
|
||||||
|
@ -183,6 +183,29 @@ function extractPubkeyV3(buf) {
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findPubkeyPrivateKey(neededPubkeys, tag) {
|
||||||
|
// `neededPubkeys` is either single address or addresses array or
|
||||||
|
// Object key-by-tag. Time to match the tag is O(1), O(n), O(1)
|
||||||
|
// respectfully.
|
||||||
|
neededPubkeys = neededPubkeys || {};
|
||||||
|
var addr, addrs, i;
|
||||||
|
if (Address.isAddress(neededPubkeys)) {
|
||||||
|
addr = neededPubkeys;
|
||||||
|
if (bufferEqual(addr.getTag(), tag)) {
|
||||||
|
return addr.getPubkeyPrivateKey();
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(neededPubkeys)) {
|
||||||
|
addrs = neededPubkeys;
|
||||||
|
for (i = 0; i < addrs.length; i++) {
|
||||||
|
if (bufferEqual(addrs[i].getTag(), tag)) {
|
||||||
|
return addrs[i].getPubkeyPrivateKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return neededPubkeys[tag];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `pubkey` object.
|
* `pubkey` object.
|
||||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#pubkey}
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#pubkey}
|
||||||
|
@ -205,18 +228,15 @@ var pubkey = exports.pubkey = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for decoding pubkey
|
|
||||||
* @typedef {Object} PubkeyDecodeOpts
|
|
||||||
* @property {?(Address[]|Address|Object)} needed - Address objects
|
|
||||||
* which represent pubkeys that we are interested in; this is used
|
|
||||||
* only for pubkeys v4
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode `pubkey` object message payload.
|
* Decode `pubkey` object message payload.
|
||||||
* @param {Buffer} buf - Message payload
|
* @param {Buffer} buf - Message payload
|
||||||
* @param {?PubkeyDecodeOpts} opts - Decoding options
|
* @param {?Object} opts - Decoding options
|
||||||
|
* @param {?(Address[]|Address|Object)} opts.needed - Address objects
|
||||||
|
* which represent pubkeys that we are interested in. This is used
|
||||||
|
* only for pubkeys v4. `needed` is either single address or addresses
|
||||||
|
* array or Object key-by-tag. Time to match the tag is O(1), O(n),
|
||||||
|
* O(1) respectfully.
|
||||||
* @return {Promise.<Object>} A promise that contains decoded `pubkey`
|
* @return {Promise.<Object>} A promise that contains decoded `pubkey`
|
||||||
* object structure when fulfilled.
|
* object structure when fulfilled.
|
||||||
*/
|
*/
|
||||||
|
@ -230,7 +250,7 @@ var pubkey = exports.pubkey = {
|
||||||
assert(version <= 4, "Address version is too high");
|
assert(version <= 4, "Address version is too high");
|
||||||
var objectPayload = util.popkey(decoded, "objectPayload");
|
var objectPayload = util.popkey(decoded, "objectPayload");
|
||||||
var siglen, pos, sig, dataToVerify, pubkeyp;
|
var siglen, pos, sig, dataToVerify, pubkeyp;
|
||||||
var addr, addrs, tag, pubkeyEncPrivateKey, dataToDecrypt;
|
var tag, pubkeyPrivateKey, dataToDecrypt;
|
||||||
|
|
||||||
// v2 pubkey.
|
// v2 pubkey.
|
||||||
if (version === 2) {
|
if (version === 2) {
|
||||||
|
@ -262,30 +282,13 @@ var pubkey = exports.pubkey = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// v4 pubkey.
|
// v4 pubkey.
|
||||||
|
|
||||||
// `neededPubkeys` is either single address or addresses array or
|
|
||||||
// Object key-by-tag. Time to match the tag is O(1), O(n), O(1)
|
|
||||||
// respectfully.
|
|
||||||
var neededPubkeys = opts.needed || {};
|
|
||||||
if (Address.isAddress(neededPubkeys)) {
|
|
||||||
addr = neededPubkeys;
|
|
||||||
neededPubkeys = {};
|
|
||||||
neededPubkeys[addr.getTag()] = addr.getPubkeyPrivateKey();
|
|
||||||
} else if (Array.isArray(neededPubkeys)) {
|
|
||||||
addrs = neededPubkeys;
|
|
||||||
neededPubkeys = {};
|
|
||||||
addrs.forEach(function(a) {
|
|
||||||
neededPubkeys[a.getTag()] = a.getPubkeyPrivateKey();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
pubkeyPrivateKey = findPubkeyPrivateKey(opts.needed, tag);
|
||||||
assert(pubkeyEncPrivateKey, "You are not interested in this pubkey v4");
|
assert(pubkeyPrivateKey, "You are not interested in this pubkey v4");
|
||||||
dataToDecrypt = objectPayload.slice(32);
|
dataToDecrypt = objectPayload.slice(32);
|
||||||
pubkeyp = bmcrypto
|
pubkeyp = bmcrypto
|
||||||
.decrypt(pubkeyEncPrivateKey, dataToDecrypt)
|
.decrypt(pubkeyPrivateKey, dataToDecrypt)
|
||||||
.then(function(decrypted) {
|
.then(function(decrypted) {
|
||||||
// 4 + 64 + 64 + (1+) + (1+) + (1+)
|
// 4 + 64 + 64 + (1+) + (1+) + (1+)
|
||||||
assert(
|
assert(
|
||||||
|
@ -419,7 +422,7 @@ var pubkey = exports.pubkey = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try to decrypt message with all provided identities.
|
// Try to decrypt message with all provided identities.
|
||||||
function tryDecrypt(identities, buf) {
|
function tryDecryptMsg(identities, buf) {
|
||||||
function inner(i) {
|
function inner(i) {
|
||||||
if (i > last) {
|
if (i > last) {
|
||||||
return promise.reject("Failed to decrypt msg with given identities");
|
return promise.reject("Failed to decrypt msg with given identities");
|
||||||
|
@ -437,10 +440,45 @@ function tryDecrypt(identities, buf) {
|
||||||
return inner(0);
|
return inner(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loosely decode message in SIMPLE encoding.
|
// Encode message from the given options.
|
||||||
function decodeSimple(buf) {
|
function encodeMessage(opts) {
|
||||||
|
var encoding = opts.encoding || DEFAULT_ENCODING;
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode message to the given encoding.
|
||||||
|
function decodeMessage(message, encoding) {
|
||||||
var decoded = {};
|
var decoded = {};
|
||||||
var message = buf.toString("utf8");
|
if (encoding === msg.TRIVIAL || encoding === msg.SIMPLE) {
|
||||||
|
message = message.toString("utf8");
|
||||||
|
}
|
||||||
|
if (encoding !== msg.SIMPLE) {
|
||||||
|
decoded.message = message;
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIMPLE.
|
||||||
var subject, index;
|
var subject, index;
|
||||||
if (message.slice(0, 8) === "Subject:") {
|
if (message.slice(0, 8) === "Subject:") {
|
||||||
subject = message.slice(8);
|
subject = message.slice(8);
|
||||||
|
@ -497,17 +535,12 @@ var msg = exports.msg = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Options for decoding pubkey
|
|
||||||
* @typedef {Object} MsgDecodeOpts
|
|
||||||
* @property {(Address[]|Address)} identities - Our identities used to
|
|
||||||
* decrypt the message
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode `msg` object message payload.
|
* Decode `msg` object message payload.
|
||||||
* @param {Buffer} buf - Message payload
|
* @param {Buffer} buf - Message payload
|
||||||
* @param {MsgDecodeOpts} opts - Decoding options
|
* @param {Object} opts - Decoding options
|
||||||
|
* @param {(Address[]|Address)} opts.identities - Address objects used
|
||||||
|
* to decrypt the message
|
||||||
* @return {Promise.<Object>} A promise that contains decoded `msg`
|
* @return {Promise.<Object>} A promise that contains decoded `msg`
|
||||||
* object structure when fulfilled.
|
* object structure when fulfilled.
|
||||||
*/
|
*/
|
||||||
|
@ -518,11 +551,11 @@ var msg = exports.msg = {
|
||||||
identities = [identities];
|
identities = [identities];
|
||||||
}
|
}
|
||||||
var decoded = object.decodePayload(buf);
|
var decoded = object.decodePayload(buf);
|
||||||
assert(decoded.type === object.MSG, "Wrong object type");
|
assert(decoded.type === object.MSG, "Bad object type");
|
||||||
assert(decoded.version === 1, "Wrong msg version");
|
assert(decoded.version === 1, "Bad msg version");
|
||||||
var objectPayload = util.popkey(decoded, "objectPayload");
|
var objectPayload = util.popkey(decoded, "objectPayload");
|
||||||
|
|
||||||
var msgp = tryDecrypt(identities, objectPayload)
|
var msgp = tryDecryptMsg(identities, objectPayload)
|
||||||
.then(function(decInfo) {
|
.then(function(decInfo) {
|
||||||
var decrypted = decInfo.decrypted;
|
var decrypted = decInfo.decrypted;
|
||||||
|
|
||||||
|
@ -570,13 +603,7 @@ var msg = exports.msg = {
|
||||||
assert(rest.length >= msglen, "Bad msg object payload length");
|
assert(rest.length >= msglen, "Bad msg object payload length");
|
||||||
decoded.length += decodedMsgLength.length + msglen;
|
decoded.length += decodedMsgLength.length + msglen;
|
||||||
var message = rest.slice(0, msglen);
|
var message = rest.slice(0, msglen);
|
||||||
if (encoding === msg.TRIVIAL) {
|
objectAssign(decoded, decodeMessage(message, encoding));
|
||||||
decoded.message = message.toString("utf8");
|
|
||||||
} else if (encoding === msg.SIMPLE) {
|
|
||||||
objectAssign(decoded, decodeSimple(message));
|
|
||||||
} else {
|
|
||||||
decoded.message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Acknowledgement data.
|
// Acknowledgement data.
|
||||||
// TODO(Kagami): Validate ack, check a POW.
|
// TODO(Kagami): Validate ack, check a POW.
|
||||||
|
@ -649,30 +676,10 @@ var msg = exports.msg = {
|
||||||
payloadLengthExtraBytes = util.getExtraBytes(from);
|
payloadLengthExtraBytes = util.getExtraBytes(from);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var encoding = opts.encoding || msg.TRIVIAL;
|
var encoding = opts.encoding || DEFAULT_ENCODING;
|
||||||
var message = opts.message;
|
var message = encodeMessage(opts);
|
||||||
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.
|
// Assemble the unencrypted message data.
|
||||||
var msgData = [
|
var msgData = [
|
||||||
var_int.encode(from.version),
|
var_int.encode(from.version),
|
||||||
var_int.encode(from.stream),
|
var_int.encode(from.stream),
|
||||||
|
@ -695,6 +702,7 @@ var msg = exports.msg = {
|
||||||
// TODO(Kagami): Calculate ACK.
|
// TODO(Kagami): Calculate ACK.
|
||||||
msgData.push(var_int.encode(0));
|
msgData.push(var_int.encode(0));
|
||||||
|
|
||||||
|
// Sign and encrypt.
|
||||||
opts.objectPayload = new Buffer(0);
|
opts.objectPayload = new Buffer(0);
|
||||||
var obj = object.encodePayloadWithoutNonce(opts);
|
var obj = object.encodePayloadWithoutNonce(opts);
|
||||||
var dataToSign = Buffer.concat([obj].concat(msgData));
|
var dataToSign = Buffer.concat([obj].concat(msgData));
|
||||||
|
@ -714,3 +722,5 @@ var msg = exports.msg = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var DEFAULT_ENCODING = msg.TRIVIAL;
|
||||||
|
|
9
test.js
9
test.js
|
@ -830,8 +830,15 @@ describe("High-level classes", function() {
|
||||||
expect(addr.getPubkeyPrivateKey().toString("hex")).to.equal("15e516173769dc87d4a8e8ed90200362fa58c0228bb2b70b06f26c089a9823a4");
|
expect(addr.getPubkeyPrivateKey().toString("hex")).to.equal("15e516173769dc87d4a8e8ed90200362fa58c0228bb2b70b06f26c089a9823a4");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should calculate a private key to encrypt broadcast object", function() {
|
it("should calculate a private key to encrypt broadcast v4", function() {
|
||||||
|
var addr = Address.decode(" 2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU ");
|
||||||
|
expect(addr.version).to.equal(3);
|
||||||
|
expect(addr.getBroadcastPrivateKey().toString("hex")).to.equal("664420eaed1b6b3208fc04905c2f6ca758594c537eb5a08f2f0c2bbe6f07fb44");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should calculate a private key to encrypt broadcast v5", function() {
|
||||||
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
||||||
|
expect(addr.version).to.equal(4);
|
||||||
expect(addr.getBroadcastPrivateKey().toString("hex")).to.equal("15e516173769dc87d4a8e8ed90200362fa58c0228bb2b70b06f26c089a9823a4");
|
expect(addr.getBroadcastPrivateKey().toString("hex")).to.equal("15e516173769dc87d4a8e8ed90200362fa58c0228bb2b70b06f26c089a9823a4");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user