From 34868ff01417ec84727f052025cb9d5a76471111 Mon Sep 17 00:00:00 2001 From: Kagami Hiiragi Date: Thu, 29 Jan 2015 16:23:18 +0300 Subject: [PATCH] objects module improvements/refactoring --- lib/address.js | 24 +++++--- lib/objects.js | 162 ++++++++++++++++++++++++++----------------------- test.js | 9 ++- 3 files changed, 109 insertions(+), 86 deletions(-) diff --git a/lib/address.js b/lib/address.js index 8dd6b4d..e72fa9d 100644 --- a/lib/address.js +++ b/lib/address.js @@ -98,36 +98,42 @@ Address.prototype.getShortRipe = function() { return ripe.slice(20 - getripelen(ripe)); }; -function getdoublehash(addr) { +function getaddrhash(addr) { var dataToHash = Buffer.concat([ var_int.encode(addr.version), var_int.encode(addr.stream), addr.ripe, ]); - return bmcrypto.sha512(bmcrypto.sha512(dataToHash)); + return bmcrypto.sha512(dataToHash); } /** * Calculate the encryption key used to encrypt/decrypt {@link pubkey} - * and {@link broadcast} objects. + * objects. * @return {Buffer} A 32-byte private key. - * @static */ -var getPubkeyPrivateKey = Address.prototype.getPubkeyPrivateKey = function() { - return getdoublehash(this).slice(0, 32); +Address.prototype.getPubkeyPrivateKey = function() { + 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. * @return {Buffer} A 32-byte address tag. */ Address.prototype.getTag = function() { - return getdoublehash(this).slice(32); + return bmcrypto.sha512(getaddrhash(this)).slice(32); }; diff --git a/lib/objects.js b/lib/objects.js index 5c315d5..444c5ed 100644 --- a/lib/objects.js +++ b/lib/objects.js @@ -6,7 +6,7 @@ * @module bitmessage/objects */ // 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 // handling may need some refactoring. @@ -183,6 +183,29 @@ function extractPubkeyV3(buf) { 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. * @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. * @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.} A promise that contains decoded `pubkey` * object structure when fulfilled. */ @@ -230,7 +250,7 @@ var pubkey = exports.pubkey = { assert(version <= 4, "Address version is too high"); var objectPayload = util.popkey(decoded, "objectPayload"); var siglen, pos, sig, dataToVerify, pubkeyp; - var addr, addrs, tag, pubkeyEncPrivateKey, dataToDecrypt; + var tag, pubkeyPrivateKey, dataToDecrypt; // v2 pubkey. if (version === 2) { @@ -262,30 +282,13 @@ var pubkey = exports.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"); tag = decoded.tag = objectPayload.slice(0, 32); - pubkeyEncPrivateKey = neededPubkeys[tag]; - assert(pubkeyEncPrivateKey, "You are not interested in this pubkey v4"); + pubkeyPrivateKey = findPubkeyPrivateKey(opts.needed, tag); + assert(pubkeyPrivateKey, "You are not interested in this pubkey v4"); dataToDecrypt = objectPayload.slice(32); pubkeyp = bmcrypto - .decrypt(pubkeyEncPrivateKey, dataToDecrypt) + .decrypt(pubkeyPrivateKey, dataToDecrypt) .then(function(decrypted) { // 4 + 64 + 64 + (1+) + (1+) + (1+) assert( @@ -419,7 +422,7 @@ var pubkey = exports.pubkey = { }; // Try to decrypt message with all provided identities. -function tryDecrypt(identities, buf) { +function tryDecryptMsg(identities, buf) { function inner(i) { if (i > last) { return promise.reject("Failed to decrypt msg with given identities"); @@ -437,10 +440,45 @@ function tryDecrypt(identities, buf) { return inner(0); } -// Loosely decode message in SIMPLE encoding. -function decodeSimple(buf) { +// Encode message from the given options. +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 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; if (message.slice(0, 8) === "Subject:") { 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. * @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.} A promise that contains decoded `msg` * object structure when fulfilled. */ @@ -518,11 +551,11 @@ var msg = exports.msg = { identities = [identities]; } var decoded = object.decodePayload(buf); - assert(decoded.type === object.MSG, "Wrong object type"); - assert(decoded.version === 1, "Wrong msg version"); + assert(decoded.type === object.MSG, "Bad object type"); + assert(decoded.version === 1, "Bad msg version"); var objectPayload = util.popkey(decoded, "objectPayload"); - var msgp = tryDecrypt(identities, objectPayload) + var msgp = tryDecryptMsg(identities, objectPayload) .then(function(decInfo) { var decrypted = decInfo.decrypted; @@ -570,13 +603,7 @@ var msg = exports.msg = { 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; - } + objectAssign(decoded, decodeMessage(message, encoding)); // Acknowledgement data. // TODO(Kagami): Validate ack, check a POW. @@ -649,30 +676,10 @@ var msg = exports.msg = { 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, - ]); - } + var encoding = opts.encoding || DEFAULT_ENCODING; + var message = encodeMessage(opts); - // Assembling the unencrypted message data. + // Assemble the unencrypted message data. var msgData = [ var_int.encode(from.version), var_int.encode(from.stream), @@ -695,6 +702,7 @@ var msg = exports.msg = { // TODO(Kagami): Calculate ACK. msgData.push(var_int.encode(0)); + // Sign and encrypt. opts.objectPayload = new Buffer(0); var obj = object.encodePayloadWithoutNonce(opts); var dataToSign = Buffer.concat([obj].concat(msgData)); @@ -714,3 +722,5 @@ var msg = exports.msg = { }); }, }; + +var DEFAULT_ENCODING = msg.TRIVIAL; diff --git a/test.js b/test.js index 98415d5..05591bd 100644 --- a/test.js +++ b/test.js @@ -830,8 +830,15 @@ describe("High-level classes", function() { 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"); + expect(addr.version).to.equal(4); expect(addr.getBroadcastPrivateKey().toString("hex")).to.equal("15e516173769dc87d4a8e8ed90200362fa58c0228bb2b70b06f26c089a9823a4"); });