objects.broadcast
This commit is contained in:
parent
34868ff014
commit
ea9e8e60ef
|
@ -46,11 +46,11 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
||||||
- [x] inv
|
- [x] inv
|
||||||
- [x] getdata
|
- [x] getdata
|
||||||
- [x] error
|
- [x] error
|
||||||
- [ ] Object types
|
- [x] Object types
|
||||||
- [x] getpubkey
|
- [x] getpubkey
|
||||||
- [x] pubkey
|
- [x] pubkey
|
||||||
- [x] msg
|
- [x] msg
|
||||||
- [ ] broadcast
|
- [x] broadcast
|
||||||
- [x] WIF
|
- [x] WIF
|
||||||
- [x] POW
|
- [x] POW
|
||||||
- [x] High-level classes
|
- [x] High-level classes
|
||||||
|
|
|
@ -116,9 +116,19 @@ Address.prototype.getPubkeyPrivateKey = function() {
|
||||||
return bmcrypto.sha512(getaddrhash(this)).slice(0, 32);
|
return bmcrypto.sha512(getaddrhash(this)).slice(0, 32);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the corresponding public key for encryption key used to
|
||||||
|
* encrypt/decrypt {@link pubkey} objects.
|
||||||
|
* @return {Buffer} A 65-byte public key.
|
||||||
|
*/
|
||||||
|
Address.prototype.getPubkeyPublicKey = function() {
|
||||||
|
return bmcrypto.getPublic(this.getPubkeyPrivateKey());
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the encryption key used to encrypt/decrypt
|
* Calculate the encryption key used to encrypt/decrypt
|
||||||
* {@link broadcast} objects.
|
* {@link broadcast} objects.
|
||||||
|
* @return {Buffer} A 32-byte private key.
|
||||||
*/
|
*/
|
||||||
Address.prototype.getBroadcastPrivateKey = function() {
|
Address.prototype.getBroadcastPrivateKey = function() {
|
||||||
if (this.version >= 4) {
|
if (this.version >= 4) {
|
||||||
|
@ -128,6 +138,15 @@ Address.prototype.getBroadcastPrivateKey = function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the corresponding public key for encryption key used to
|
||||||
|
* encrypt/decrypt {@link broadcast} objects.
|
||||||
|
* @return {Buffer} A 65-byte public key.
|
||||||
|
*/
|
||||||
|
Address.prototype.getBroadcastPublicKey = function() {
|
||||||
|
return bmcrypto.getPublic(this.getBroadcastPrivateKey());
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the address tag.
|
* Calculate the address tag.
|
||||||
* @return {Buffer} A 32-byte address tag.
|
* @return {Buffer} A 32-byte address tag.
|
||||||
|
|
327
lib/objects.js
327
lib/objects.js
|
@ -183,26 +183,25 @@ function extractPubkeyV3(buf) {
|
||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPubkeyPrivateKey(neededPubkeys, tag) {
|
// Note that tag matching only works for address version >= 4.
|
||||||
// `neededPubkeys` is either single address or addresses array or
|
function findAddrByTag(addrs, tag) {
|
||||||
// Object key-by-tag. Time to match the tag is O(1), O(n), O(1)
|
var i, addr;
|
||||||
// respectfully.
|
addrs = addrs || [];
|
||||||
neededPubkeys = neededPubkeys || {};
|
if (Address.isAddress(addrs)) {
|
||||||
var addr, addrs, i;
|
addrs = [addrs];
|
||||||
if (Address.isAddress(neededPubkeys)) {
|
}
|
||||||
addr = neededPubkeys;
|
if (Array.isArray(addrs)) {
|
||||||
if (bufferEqual(addr.getTag(), tag)) {
|
|
||||||
return addr.getPubkeyPrivateKey();
|
|
||||||
}
|
|
||||||
} else if (Array.isArray(neededPubkeys)) {
|
|
||||||
addrs = neededPubkeys;
|
|
||||||
for (i = 0; i < addrs.length; i++) {
|
for (i = 0; i < addrs.length; i++) {
|
||||||
if (bufferEqual(addrs[i].getTag(), tag)) {
|
addr = addrs[i];
|
||||||
return addrs[i].getPubkeyPrivateKey();
|
if (addr.version >= 4 && bufferEqual(addr.getTag(), tag)) {
|
||||||
|
return addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return neededPubkeys[tag];
|
addr = addrs[tag];
|
||||||
|
if (addr && addr.version >= 4) {
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +234,7 @@ var pubkey = exports.pubkey = {
|
||||||
* @param {?(Address[]|Address|Object)} opts.needed - Address objects
|
* @param {?(Address[]|Address|Object)} opts.needed - Address objects
|
||||||
* which represent pubkeys that we are interested in. This is used
|
* which represent pubkeys that we are interested in. This is used
|
||||||
* only for pubkeys v4. `needed` is either single address or addresses
|
* 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),
|
* array or Object addr-by-tag. Time to match the key is O(1), O(n),
|
||||||
* O(1) respectfully.
|
* 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.
|
||||||
|
@ -250,7 +249,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 tag, pubkeyPrivateKey, dataToDecrypt;
|
var tag, addr, pubkeyPrivateKey, dataToDecrypt;
|
||||||
|
|
||||||
// v2 pubkey.
|
// v2 pubkey.
|
||||||
if (version === 2) {
|
if (version === 2) {
|
||||||
|
@ -284,8 +283,9 @@ var pubkey = exports.pubkey = {
|
||||||
// v4 pubkey.
|
// v4 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);
|
||||||
pubkeyPrivateKey = findPubkeyPrivateKey(opts.needed, tag);
|
addr = findAddrByTag(opts.needed, tag);
|
||||||
assert(pubkeyPrivateKey, "You are not interested in this pubkey v4");
|
assert(addr, "You are not interested in this pubkey v4");
|
||||||
|
pubkeyPrivateKey = addr.getPubkeyPrivateKey();
|
||||||
dataToDecrypt = objectPayload.slice(32);
|
dataToDecrypt = objectPayload.slice(32);
|
||||||
pubkeyp = bmcrypto
|
pubkeyp = bmcrypto
|
||||||
.decrypt(pubkeyPrivateKey, dataToDecrypt)
|
.decrypt(pubkeyPrivateKey, dataToDecrypt)
|
||||||
|
@ -402,14 +402,12 @@ var pubkey = exports.pubkey = {
|
||||||
opts.objectPayload = from.getTag();
|
opts.objectPayload = from.getTag();
|
||||||
obj = object.encodePayloadWithoutNonce(opts);
|
obj = object.encodePayloadWithoutNonce(opts);
|
||||||
var dataToSign = Buffer.concat([obj].concat(pubkeyData));
|
var dataToSign = Buffer.concat([obj].concat(pubkeyData));
|
||||||
var pubkeyEncPrivateKey = from.getPubkeyPrivateKey();
|
|
||||||
var pubkeyEncPublicKey = bmcrypto.getPublic(pubkeyEncPrivateKey);
|
|
||||||
pubkeyp = bmcrypto
|
pubkeyp = bmcrypto
|
||||||
.sign(from.signPrivateKey, dataToSign)
|
.sign(from.signPrivateKey, dataToSign)
|
||||||
.then(function(sig) {
|
.then(function(sig) {
|
||||||
var dataToEnc = pubkeyData.concat(var_int.encode(sig.length), sig);
|
var dataToEnc = pubkeyData.concat(var_int.encode(sig.length), sig);
|
||||||
dataToEnc = Buffer.concat(dataToEnc);
|
dataToEnc = Buffer.concat(dataToEnc);
|
||||||
return bmcrypto.encrypt(pubkeyEncPublicKey, dataToEnc);
|
return bmcrypto.encrypt(from.getPubkeyPublicKey(), dataToEnc);
|
||||||
}).then(function(enc) {
|
}).then(function(enc) {
|
||||||
// POW calculation here.
|
// POW calculation here.
|
||||||
var nonce = new Buffer(8);
|
var nonce = new Buffer(8);
|
||||||
|
@ -425,7 +423,8 @@ var pubkey = exports.pubkey = {
|
||||||
function tryDecryptMsg(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");
|
var err = new Error("Failed to decrypt msg with given identities");
|
||||||
|
return promise.reject(err);
|
||||||
}
|
}
|
||||||
return bmcrypto
|
return bmcrypto
|
||||||
.decrypt(identities[i].encPrivateKey, buf)
|
.decrypt(identities[i].encPrivateKey, buf)
|
||||||
|
@ -436,6 +435,9 @@ function tryDecryptMsg(identities, buf) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Address.isAddress(identities)) {
|
||||||
|
identities = [identities];
|
||||||
|
}
|
||||||
var last = identities.length - 1;
|
var last = identities.length - 1;
|
||||||
return inner(0);
|
return inner(0);
|
||||||
}
|
}
|
||||||
|
@ -546,23 +548,20 @@ var msg = exports.msg = {
|
||||||
*/
|
*/
|
||||||
decodePayloadAsync: function(buf, opts) {
|
decodePayloadAsync: function(buf, opts) {
|
||||||
return new promise(function(resolve) {
|
return new promise(function(resolve) {
|
||||||
var identities = opts.identities;
|
|
||||||
if (Address.isAddress(identities)) {
|
|
||||||
identities = [identities];
|
|
||||||
}
|
|
||||||
var decoded = object.decodePayload(buf);
|
var decoded = object.decodePayload(buf);
|
||||||
assert(decoded.type === object.MSG, "Bad object type");
|
assert(decoded.type === object.MSG, "Bad object type");
|
||||||
assert(decoded.version === 1, "Bad msg version");
|
assert(decoded.version === 1, "Bad msg version");
|
||||||
var objectPayload = util.popkey(decoded, "objectPayload");
|
var objectPayload = util.popkey(decoded, "objectPayload");
|
||||||
|
|
||||||
var msgp = tryDecryptMsg(identities, objectPayload)
|
var msgp = tryDecryptMsg(opts.identities, objectPayload)
|
||||||
.then(function(decInfo) {
|
.then(function(decInfo) {
|
||||||
var decrypted = decInfo.decrypted;
|
var decrypted = decInfo.decrypted;
|
||||||
|
|
||||||
// Version, stream.
|
// Version, stream.
|
||||||
// TODO(Kagami): Validate version range?
|
|
||||||
var decodedVersion = var_int.decode(decrypted);
|
var decodedVersion = var_int.decode(decrypted);
|
||||||
decoded.senderVersion = decodedVersion.value;
|
var senderVersion = decoded.senderVersion = decodedVersion.value;
|
||||||
|
assert(senderVersion >= 2, "Sender version is too low");
|
||||||
|
assert(senderVersion <= 4, "Sender version is too high");
|
||||||
var decodedStream = var_int.decode(decodedVersion.rest);
|
var decodedStream = var_int.decode(decodedVersion.rest);
|
||||||
decoded.senderStream = decodedStream.value;
|
decoded.senderStream = decodedStream.value;
|
||||||
|
|
||||||
|
@ -588,9 +587,11 @@ var msg = exports.msg = {
|
||||||
// Ripe, encoding.
|
// Ripe, encoding.
|
||||||
assert(rest.length >= 20, "Bad msg object payload length");
|
assert(rest.length >= 20, "Bad msg object payload length");
|
||||||
decoded.ripe = rest.slice(0, 20);
|
decoded.ripe = rest.slice(0, 20);
|
||||||
|
// TODO(Kagami): Also check against the calculatedRipe (see
|
||||||
|
// GH-6)?
|
||||||
assert(
|
assert(
|
||||||
bufferEqual(decoded.ripe, decInfo.addr.ripe),
|
bufferEqual(decoded.ripe, decInfo.addr.ripe),
|
||||||
"msg was decrypted but the destination ripe differs");
|
"msg was decrypted but the destination ripe doesn't match");
|
||||||
decoded.length += 20;
|
decoded.length += 20;
|
||||||
var decodedEncoding = var_int.decode(rest.slice(20));
|
var decodedEncoding = var_int.decode(rest.slice(20));
|
||||||
var encoding = decoded.encoding = decodedEncoding.value;
|
var encoding = decoded.encoding = decodedEncoding.value;
|
||||||
|
@ -664,6 +665,8 @@ var msg = exports.msg = {
|
||||||
opts.type = object.MSG;
|
opts.type = object.MSG;
|
||||||
opts.version = 1; // The only known msg version
|
opts.version = 1; // The only known msg version
|
||||||
var from = Address.decode(opts.from);
|
var from = Address.decode(opts.from);
|
||||||
|
assert(from.version >= 2, "Address version is too low");
|
||||||
|
assert(from.version <= 4, "Address version is too high");
|
||||||
var to = Address.decode(opts.to);
|
var to = Address.decode(opts.to);
|
||||||
opts.stream = to.stream;
|
opts.stream = to.stream;
|
||||||
var nonceTrialsPerByte, payloadLengthExtraBytes;
|
var nonceTrialsPerByte, payloadLengthExtraBytes;
|
||||||
|
@ -724,3 +727,263 @@ var msg = exports.msg = {
|
||||||
};
|
};
|
||||||
|
|
||||||
var DEFAULT_ENCODING = msg.TRIVIAL;
|
var DEFAULT_ENCODING = msg.TRIVIAL;
|
||||||
|
|
||||||
|
// Try to decrypt broadcast v4 with all provided subscription objects.
|
||||||
|
function tryDecryptBroadcastV4(subscriptions, buf) {
|
||||||
|
function inner(i) {
|
||||||
|
if (i > last) {
|
||||||
|
var err = new Error("Failed to decrypt msg with given identities");
|
||||||
|
return promise.reject(err);
|
||||||
|
}
|
||||||
|
return bmcrypto
|
||||||
|
.decrypt(subscriptions[i].getBroadcastPrivateKey(), buf)
|
||||||
|
.then(function(decrypted) {
|
||||||
|
return {addr: subscriptions[i], decrypted: decrypted};
|
||||||
|
}).catch(function() {
|
||||||
|
return inner(i + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Address.isAddress(subscriptions)) {
|
||||||
|
subscriptions = [subscriptions];
|
||||||
|
} else if (!Array.isArray(subscriptions)) {
|
||||||
|
subscriptions = Object.keys(subscriptions).map(function(k) {
|
||||||
|
return subscriptions[k];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Only addresses with version < 4 may be used to encode broadcast v4.
|
||||||
|
subscriptions = subscriptions.filter(function(a) {
|
||||||
|
return a.version < 4;
|
||||||
|
});
|
||||||
|
var last = subscriptions.length - 1;
|
||||||
|
return inner(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `broadcast` object.
|
||||||
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#broadcast}
|
||||||
|
* @namespace
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
var broadcast = exports.broadcast = {
|
||||||
|
/**
|
||||||
|
* Decode `broadcast` object message.
|
||||||
|
* @param {Buffer} buf - Message
|
||||||
|
* @param {?Object} opts - Decoding options
|
||||||
|
* @return {Promise.<Object>} A promise that contains decoded
|
||||||
|
* `broadcast` 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(broadcast.decodePayloadAsync(decoded.payload, opts));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode `broadcast` object message payload.
|
||||||
|
* @param {Buffer} buf - Message payload
|
||||||
|
* @param {Object} opts - Decoding options
|
||||||
|
* @param {(Address[]|Address|Object)} opts.subscriptions - Address
|
||||||
|
* objects which represent broadcast subscriptions. `subscriptions` is
|
||||||
|
* either single address or addresses array or Object
|
||||||
|
* addr-by-tag/addr-by-ripe. Time to match the key is O(1), O(n), O(1)
|
||||||
|
* respectfully.
|
||||||
|
* @return {Promise.<Object>} A promise that contains decoded `pubkey`
|
||||||
|
* object structure when fulfilled.
|
||||||
|
*/
|
||||||
|
decodePayloadAsync: function(buf, opts) {
|
||||||
|
return new promise(function(resolve) {
|
||||||
|
var decoded = object.decodePayload(buf);
|
||||||
|
assert(decoded.type === object.BROADCAST, "Bad object type");
|
||||||
|
var version = decoded.version;
|
||||||
|
assert(version === 4 || version === 5, "Bad broadcast version");
|
||||||
|
var objectPayload = util.popkey(decoded, "objectPayload");
|
||||||
|
var tag, addr, broadPrivateKey, dataToDecrypt, broadp;
|
||||||
|
|
||||||
|
if (version === 4) {
|
||||||
|
broadp = tryDecryptBroadcastV4(opts.subscriptions, objectPayload);
|
||||||
|
} else {
|
||||||
|
assert(
|
||||||
|
objectPayload.length >= 32,
|
||||||
|
"Bad broadcast v5 object payload length");
|
||||||
|
tag = decoded.tag = objectPayload.slice(0, 32);
|
||||||
|
addr = findAddrByTag(opts.subscriptions, tag);
|
||||||
|
assert(addr, "You are not interested in this broadcast v5");
|
||||||
|
broadPrivateKey = addr.getBroadcastPrivateKey();
|
||||||
|
dataToDecrypt = objectPayload.slice(32);
|
||||||
|
broadp = bmcrypto
|
||||||
|
.decrypt(broadPrivateKey, dataToDecrypt)
|
||||||
|
.then(function(decrypted) {
|
||||||
|
return {addr: addr, decrypted: decrypted};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
broadp = broadp.then(function(decInfo) {
|
||||||
|
var decrypted = decInfo.decrypted;
|
||||||
|
|
||||||
|
// Version, stream.
|
||||||
|
var decodedVersion = var_int.decode(decrypted);
|
||||||
|
var senderVersion = decoded.senderVersion = decodedVersion.value;
|
||||||
|
if (version === 4) {
|
||||||
|
assert(senderVersion >= 2, "Sender version is too low");
|
||||||
|
assert(senderVersion <= 3, "Sender version is too high");
|
||||||
|
} else {
|
||||||
|
assert(senderVersion === 4, "Bad sender version");
|
||||||
|
}
|
||||||
|
var decodedStream = var_int.decode(decodedVersion.rest);
|
||||||
|
var senderStream = decoded.senderStream = decodedStream.value;
|
||||||
|
assert(
|
||||||
|
senderStream === decoded.stream,
|
||||||
|
"Cleartext broadcast object stream doesn't match encrypted");
|
||||||
|
|
||||||
|
// Behavior, keys.
|
||||||
|
assert(
|
||||||
|
decodedStream.rest.length >= 132,
|
||||||
|
"Bad broadcast object payload length");
|
||||||
|
objectAssign(decoded, extractPubkey(decodedStream.rest));
|
||||||
|
decoded.length += decodedVersion.length + decodedStream.length;
|
||||||
|
var rest = decrypted.slice(decoded.length);
|
||||||
|
var sender = new Address({
|
||||||
|
version: senderVersion,
|
||||||
|
stream: senderStream,
|
||||||
|
signPublicKey: decoded.signPublicKey,
|
||||||
|
encPublicKey: decoded.encPublicKey,
|
||||||
|
});
|
||||||
|
if (version === 4) {
|
||||||
|
assert(
|
||||||
|
bufferEqual(sender.ripe, decInfo.addr.ripe),
|
||||||
|
"The keys used to encrypt the broadcast doesn't match the keys "+
|
||||||
|
"embedded into the object");
|
||||||
|
} else {
|
||||||
|
assert(
|
||||||
|
bufferEqual(sender.getTag(), tag),
|
||||||
|
"The tag used to encrypt the broadcast doesn't match the keys "+
|
||||||
|
"and version/stream embedded into the object");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pow extra.
|
||||||
|
if (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding, message
|
||||||
|
var decodedEncoding = var_int.decode(rest);
|
||||||
|
var encoding = decoded.encoding = decodedEncoding.value;
|
||||||
|
decoded.length += decodedEncoding.length;
|
||||||
|
var decodedMsgLength = var_int.decode(decodedEncoding.rest);
|
||||||
|
var msglen = decodedMsgLength.value;
|
||||||
|
rest = decodedMsgLength.rest;
|
||||||
|
assert(rest.length >= msglen, "Bad broadcast object payload length");
|
||||||
|
decoded.length += decodedMsgLength.length + msglen;
|
||||||
|
var message = rest.slice(0, msglen);
|
||||||
|
objectAssign(decoded, decodeMessage(message, encoding));
|
||||||
|
|
||||||
|
// Signature.
|
||||||
|
var decodedSigLength = var_int.decode(rest.slice(msglen));
|
||||||
|
var siglen = decodedSigLength.value;
|
||||||
|
rest = decodedSigLength.rest;
|
||||||
|
assert(rest.length >= siglen, "Bad broadcast object payload length");
|
||||||
|
var sig = decoded.signature = rest.slice(0, siglen);
|
||||||
|
|
||||||
|
// Verify signature.
|
||||||
|
var headerLength = decoded.headerLength;
|
||||||
|
if (version !== 4) {
|
||||||
|
// Compensate for tag.
|
||||||
|
headerLength += 32;
|
||||||
|
}
|
||||||
|
var dataToVerify = Buffer.concat([
|
||||||
|
// Object header without nonce.
|
||||||
|
buf.slice(8, 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(broadp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode `broadcast` object message.
|
||||||
|
* @param {Object} opts - `broadcast` object options
|
||||||
|
* @return {Promise.<Buffer>} A promise that contains encoded message
|
||||||
|
* when fulfilled.
|
||||||
|
*/
|
||||||
|
encodeAsync: function(opts) {
|
||||||
|
return broadcast.encodePayloadAsync(opts).then(function(payload) {
|
||||||
|
return message.encode("object", payload);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode `broadcast` object message payload.
|
||||||
|
* @param {Object} opts - `broadcast` 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.BROADCAST;
|
||||||
|
var from = Address.decode(opts.from);
|
||||||
|
assert(from.version >= 2, "Address version is too low");
|
||||||
|
assert(from.version <= 4, "Address version is too high");
|
||||||
|
opts.version = from.version >= 4 ? 5 : 4;
|
||||||
|
opts.stream = from.stream;
|
||||||
|
var encoding = opts.encoding || DEFAULT_ENCODING;
|
||||||
|
var message = encodeMessage(opts);
|
||||||
|
|
||||||
|
// Assemble the unencrypted message data.
|
||||||
|
var broadData = [
|
||||||
|
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) {
|
||||||
|
broadData.push(
|
||||||
|
var_int.encode(util.getTrials(from)),
|
||||||
|
var_int.encode(util.getExtraBytes(from))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
broadData.push(
|
||||||
|
var_int.encode(encoding),
|
||||||
|
var_int.encode(message.length),
|
||||||
|
message
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sign and encrypt.
|
||||||
|
opts.objectPayload = from.version >= 4 ? from.getTag() : new Buffer(0);
|
||||||
|
var obj = object.encodePayloadWithoutNonce(opts);
|
||||||
|
var dataToSign = Buffer.concat([obj].concat(broadData));
|
||||||
|
var broadp = bmcrypto
|
||||||
|
.sign(from.signPrivateKey, dataToSign)
|
||||||
|
.then(function(sig) {
|
||||||
|
var dataToEnc = broadData.concat(var_int.encode(sig.length), sig);
|
||||||
|
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.
|
||||||
|
return Buffer.concat([nonce, obj, enc]);
|
||||||
|
});
|
||||||
|
resolve(broadp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
97
test.js
97
test.js
|
@ -27,6 +27,7 @@ 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 msg = objects.msg;
|
||||||
|
var broadcast = objects.broadcast;
|
||||||
var WIF = bitmessage.WIF;
|
var WIF = bitmessage.WIF;
|
||||||
var POW = bitmessage.POW;
|
var POW = bitmessage.POW;
|
||||||
var Address = bitmessage.Address;
|
var Address = bitmessage.Address;
|
||||||
|
@ -504,6 +505,11 @@ describe("Object types", function() {
|
||||||
signPrivateKey: signPrivateKey,
|
signPrivateKey: signPrivateKey,
|
||||||
encPrivateKey: encPrivateKey,
|
encPrivateKey: encPrivateKey,
|
||||||
});
|
});
|
||||||
|
var fromV3 = Address({
|
||||||
|
version: 3,
|
||||||
|
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({
|
||||||
|
@ -689,6 +695,74 @@ describe("Object types", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("broadcast", function() {
|
||||||
|
it("should encode and decode broadcast v4", function() {
|
||||||
|
return broadcast.encodeAsync({
|
||||||
|
ttl: 987,
|
||||||
|
from: fromV3,
|
||||||
|
message: "test",
|
||||||
|
}).then(function(buf) {
|
||||||
|
expect(message.decode(buf).command).to.equal("object");
|
||||||
|
return broadcast.decodeAsync(buf, {subscriptions: fromV3});
|
||||||
|
}).then(function(res) {
|
||||||
|
expect(res.ttl).to.be.at.most(987);
|
||||||
|
expect(res.type).to.equal(object.BROADCAST);
|
||||||
|
expect(res.version).to.equal(4);
|
||||||
|
expect(res.stream).to.equal(1);
|
||||||
|
expect(res.senderVersion).to.equal(3);
|
||||||
|
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(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("should encode and decode broadcast v5", function() {
|
||||||
|
return broadcast.encodeAsync({
|
||||||
|
ttl: 101,
|
||||||
|
from: from,
|
||||||
|
message: "キタ━━━(゜∀゜)━━━!!!!!",
|
||||||
|
}).then(function(buf) {
|
||||||
|
expect(message.decode(buf).command).to.equal("object");
|
||||||
|
return broadcast.decodeAsync(buf, {subscriptions: [from]});
|
||||||
|
}).then(function(res) {
|
||||||
|
expect(res.ttl).to.be.at.most(987);
|
||||||
|
expect(res.type).to.equal(object.BROADCAST);
|
||||||
|
expect(res.version).to.equal(5);
|
||||||
|
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(res.encoding).to.equal(msg.TRIVIAL);
|
||||||
|
expect(res.message).to.equal("キタ━━━(゜∀゜)━━━!!!!!");
|
||||||
|
expect(res).to.not.have.property("subject");
|
||||||
|
expect(Buffer.isBuffer(res.signature)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't decode broadcast without subscriptions", function(done) {
|
||||||
|
return broadcast.encodeAsync({
|
||||||
|
ttl: 101,
|
||||||
|
from: from,
|
||||||
|
message: "test",
|
||||||
|
}).then(function(buf) {
|
||||||
|
return broadcast.decodeAsync(buf, {subscriptions: [fromV3]});
|
||||||
|
}).catch(function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("WIF", function() {
|
describe("WIF", function() {
|
||||||
|
@ -825,23 +899,40 @@ describe("High-level classes", function() {
|
||||||
expect(addr.getTag().toString("hex")).to.equal("facf1e3e6c74916203b7f714ca100d4d60604f0917696d0f09330f82f52bed1a");
|
expect(addr.getTag().toString("hex")).to.equal("facf1e3e6c74916203b7f714ca100d4d60604f0917696d0f09330f82f52bed1a");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should calculate a private key to encrypt pubkey object", function() {
|
it("should calculate a private key to decrypt pubkey object", function() {
|
||||||
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
||||||
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 v4", function() {
|
it("should calculate a public key to encrypt pubkey object", function() {
|
||||||
|
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
||||||
|
expect(addr.getPubkeyPublicKey().toString("hex")).to.equal("04ee196be97db61886beeec9ebc2c28b7d4cafbc407c31d8aac2f867068f727874e2d305ba970bd09a951aa2cde52b66061a5a8e709cda1125635a97e1c7b85ab4");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should calculate a private key to decrypt broadcast v4", function() {
|
||||||
var addr = Address.decode(" 2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU ");
|
var addr = Address.decode(" 2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU ");
|
||||||
expect(addr.version).to.equal(3);
|
expect(addr.version).to.equal(3);
|
||||||
expect(addr.getBroadcastPrivateKey().toString("hex")).to.equal("664420eaed1b6b3208fc04905c2f6ca758594c537eb5a08f2f0c2bbe6f07fb44");
|
expect(addr.getBroadcastPrivateKey().toString("hex")).to.equal("664420eaed1b6b3208fc04905c2f6ca758594c537eb5a08f2f0c2bbe6f07fb44");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should calculate a private key to encrypt broadcast v5", function() {
|
it("should calculate a public key to encrypt broadcast v4", function() {
|
||||||
|
var addr = Address.decode(" 2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU ");
|
||||||
|
expect(addr.version).to.equal(3);
|
||||||
|
expect(addr.getBroadcastPublicKey().toString("hex")).to.equal("04da633350cf2ef8194b83ae028555971df56a64948940693e54b8b4c2597b8f9e833ac1285b37487121c271346fb29684e723a992aeb37b20962406ccade6c8d3");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should calculate a private key to decrypt broadcast v5", function() {
|
||||||
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
||||||
expect(addr.version).to.equal(4);
|
expect(addr.version).to.equal(4);
|
||||||
expect(addr.getBroadcastPrivateKey().toString("hex")).to.equal("15e516173769dc87d4a8e8ed90200362fa58c0228bb2b70b06f26c089a9823a4");
|
expect(addr.getBroadcastPrivateKey().toString("hex")).to.equal("15e516173769dc87d4a8e8ed90200362fa58c0228bb2b70b06f26c089a9823a4");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should calculate a public key to encrypt broadcast v5", function() {
|
||||||
|
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
||||||
|
expect(addr.version).to.equal(4);
|
||||||
|
expect(addr.getBroadcastPublicKey().toString("hex")).to.equal("04ee196be97db61886beeec9ebc2c28b7d4cafbc407c31d8aac2f867068f727874e2d305ba970bd09a951aa2cde52b66061a5a8e709cda1125635a97e1c7b85ab4");
|
||||||
|
});
|
||||||
|
|
||||||
it("should allow to decode Address instance", function() {
|
it("should allow to decode Address instance", function() {
|
||||||
var addr = Address.decode("2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
var addr = Address.decode("2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
||||||
expect(addr.ripe.toString("hex")).to.equal("003ab6655de4bd8c603eba9b00dd5970725fdd56");
|
expect(addr.ripe.toString("hex")).to.equal("003ab6655de4bd8c603eba9b00dd5970725fdd56");
|
||||||
|
|
Loading…
Reference in New Issue
Block a user