Huge commit with partial objects.getpubkey impl
This commit is contained in:
parent
33432c6735
commit
d50dcac4a2
|
@ -30,6 +30,7 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
||||||
- [x] HMAC-SHA-256
|
- [x] HMAC-SHA-256
|
||||||
- [x] Common structures
|
- [x] Common structures
|
||||||
- [x] message
|
- [x] message
|
||||||
|
- [x] object
|
||||||
- [x] var_int
|
- [x] var_int
|
||||||
- [x] var_str
|
- [x] var_str
|
||||||
- [x] var_int_list
|
- [x] var_int_list
|
||||||
|
@ -45,10 +46,9 @@ API documentation is available [here](https://bitchan.github.io/bitmessage/docs/
|
||||||
- [x] inv
|
- [x] inv
|
||||||
- [x] getdata
|
- [x] getdata
|
||||||
- [x] error
|
- [x] error
|
||||||
- [x] object
|
|
||||||
- [ ] Object types
|
- [ ] Object types
|
||||||
- [x] getpubkey
|
- [x] getpubkey
|
||||||
- [ ] pubkey
|
- [x] pubkey
|
||||||
- [ ] msg
|
- [ ] msg
|
||||||
- [ ] broadcast
|
- [ ] broadcast
|
||||||
- [x] WIF
|
- [x] WIF
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// NOTE(Kagami): End-users shouldn't use this module. While it exports
|
||||||
|
// some helper routines, its API is _not_ stable.
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var assert = exports.assert = function(condition, message) {
|
var assert = exports.assert = function(condition, message) {
|
||||||
|
@ -6,6 +9,8 @@ var assert = exports.assert = function(condition, message) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.PROTOCOL_VERSION = 3;
|
||||||
|
|
||||||
// Missing methods to read/write 64 bits integers from/to buffers.
|
// Missing methods to read/write 64 bits integers from/to buffers.
|
||||||
// TODO(Kagami): Use this helpers in structs, pow, platform.
|
// TODO(Kagami): Use this helpers in structs, pow, platform.
|
||||||
|
|
||||||
|
@ -61,3 +66,31 @@ exports.tnow = function() {
|
||||||
var time = new Date();
|
var time = new Date();
|
||||||
return Math.floor(time.getTime() / 1000);
|
return Math.floor(time.getTime() / 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var DEFAULT_TRIALS_PER_BYTE = 1000;
|
||||||
|
var DEFAULT_EXTRA_BYTES = 1000;
|
||||||
|
|
||||||
|
exports.getTrials = function(opts) {
|
||||||
|
var nonceTrialsPerByte = opts.nonceTrialsPerByte;
|
||||||
|
// Automatically raise lower values per spec.
|
||||||
|
if (!nonceTrialsPerByte || nonceTrialsPerByte < DEFAULT_TRIALS_PER_BYTE) {
|
||||||
|
nonceTrialsPerByte = DEFAULT_TRIALS_PER_BYTE;
|
||||||
|
}
|
||||||
|
return nonceTrialsPerByte;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getExtraBytes = function(opts) {
|
||||||
|
var payloadLengthExtraBytes = opts.payloadLengthExtraBytes;
|
||||||
|
// Automatically raise lower values per spec.
|
||||||
|
if (!payloadLengthExtraBytes ||
|
||||||
|
payloadLengthExtraBytes < DEFAULT_EXTRA_BYTES) {
|
||||||
|
payloadLengthExtraBytes = DEFAULT_EXTRA_BYTES;
|
||||||
|
}
|
||||||
|
return payloadLengthExtraBytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.popkey = function(obj, key) {
|
||||||
|
var value = obj[key];
|
||||||
|
delete obj[key];
|
||||||
|
return value;
|
||||||
|
};
|
|
@ -9,10 +9,11 @@
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
var objectAssign = Object.assign || require("object-assign");
|
||||||
var bufferEqual = require("buffer-equal");
|
var bufferEqual = require("buffer-equal");
|
||||||
var bs58 = require("bs58");
|
var bs58 = require("bs58");
|
||||||
var assert = require("./util").assert;
|
var assert = require("./_util").assert;
|
||||||
var var_int = require("./structs").var_int;
|
var var_int = require("./structs").var_int;
|
||||||
var PubkeyBitfield = require("./structs").PubkeyBitfield;
|
var PubkeyBitfield = require("./structs").PubkeyBitfield;
|
||||||
var bmcrypto = require("./crypto");
|
var bmcrypto = require("./crypto");
|
||||||
|
var popkey = require("./_util").popkey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Bitmessage address object.
|
* Create a new Bitmessage address object.
|
||||||
|
@ -103,21 +104,40 @@ Address.prototype.getRipe = function(opts) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getdoublehash(addr) {
|
||||||
|
var ripe = addr.getRipe();
|
||||||
|
var dataToHash = Buffer.concat([
|
||||||
|
var_int.encode(addr.version),
|
||||||
|
var_int.encode(addr.stream),
|
||||||
|
ripe,
|
||||||
|
]);
|
||||||
|
return bmcrypto.sha512(bmcrypto.sha512(dataToHash));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the encryption key used to encrypt/decrypt {@link pubkey}
|
||||||
|
* and {@link broadcast} objects.
|
||||||
|
* @return {Buffer} A 32-byte private key.
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
var getPubkeyPrivateKey = Address.prototype.getPubkeyPrivateKey = function() {
|
||||||
|
return getdoublehash(this).slice(0, 32);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just an alias to {@link Address#getPubkeyPrivateKey} for convinience.
|
||||||
|
*/
|
||||||
|
Address.prototype.getBroadcastPrivateKey = getPubkeyPrivateKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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() {
|
||||||
var ripe = this.getRipe();
|
return getdoublehash(this).slice(32);
|
||||||
var dataToHash = Buffer.concat([
|
|
||||||
var_int.encode(this.version),
|
|
||||||
var_int.encode(this.stream),
|
|
||||||
ripe,
|
|
||||||
]);
|
|
||||||
var hash = bmcrypto.sha512(bmcrypto.sha512(dataToHash));
|
|
||||||
return hash.slice(32);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Get truncated ripe hash length.
|
// Get truncated ripe hash length.
|
||||||
function getripelen(ripe) {
|
function getripelen(ripe) {
|
||||||
var zeroes = 0;
|
var zeroes = 0;
|
||||||
|
@ -177,12 +197,6 @@ Address.prototype.encode = function() {
|
||||||
return "BM-" + bs58.encode(addr);
|
return "BM-" + bs58.encode(addr);
|
||||||
};
|
};
|
||||||
|
|
||||||
function popkey(obj, key) {
|
|
||||||
var value = obj[key];
|
|
||||||
delete obj[key];
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new Bitmessage address from random encryption and signing
|
* Create new Bitmessage address from random encryption and signing
|
||||||
* private keys.
|
* private keys.
|
||||||
|
|
110
lib/crypto.js
110
lib/crypto.js
|
@ -7,15 +7,19 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var eccrypto = require("eccrypto");
|
var eccrypto = require("eccrypto");
|
||||||
|
var assert = require("./_util").assert;
|
||||||
var platform = require("./platform");
|
var platform = require("./platform");
|
||||||
|
|
||||||
|
var promise = platform.promise;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate SHA-1 hash.
|
* Calculate SHA-1 hash.
|
||||||
* @param {Buffer} buf - Input data
|
* @param {Buffer} buf - Input data
|
||||||
* @return {Buffer} Resulting hash.
|
* @return {Buffer} Resulting hash.
|
||||||
* @function
|
* @function
|
||||||
|
* @static
|
||||||
*/
|
*/
|
||||||
exports.sha1 = platform.sha1;
|
var sha1 = exports.sha1 = platform.sha1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate SHA-256 hash.
|
* Calculate SHA-256 hash.
|
||||||
|
@ -64,3 +68,107 @@ exports.getPrivate = function() {
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
exports.getPublic = eccrypto.getPublic;
|
exports.getPublic = eccrypto.getPublic;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign message using ecdsa-with-sha1 scheme.
|
||||||
|
* @param {Buffer} privateKey - A 32-byte private key
|
||||||
|
* @msg {Buffer} msg - The message being signed
|
||||||
|
* @return {Promise.<Buffer>} A promise that contains signature in DER
|
||||||
|
* format when fulfilled.
|
||||||
|
*/
|
||||||
|
exports.sign = function(privateKey, msg) {
|
||||||
|
var hash = sha1(msg);
|
||||||
|
return eccrypto.sign(privateKey, hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify signature using ecdsa-with-sha1 scheme.
|
||||||
|
* @param {Buffer} publicKey - A 65-byte public key
|
||||||
|
* @msg {Buffer} msg - The message being verified
|
||||||
|
* @param {Buffer} sig - The signature in DER format
|
||||||
|
* @return {Promise.<undefined>} A promise that resolves on correct
|
||||||
|
* signature and rejects on bad key or signature.
|
||||||
|
*/
|
||||||
|
exports.verify = function(publicKey, msg, sig) {
|
||||||
|
var hash = sha1(msg);
|
||||||
|
return eccrypto.verify(publicKey, hash, sig);
|
||||||
|
};
|
||||||
|
|
||||||
|
var SECP256K1_TYPE = 714;
|
||||||
|
|
||||||
|
// We define this structure here to avoid circular imports. However we
|
||||||
|
// rexport and document it in `structs` module for consistency.
|
||||||
|
var encrypted = exports.encrypted = {
|
||||||
|
decode: function(buf) {
|
||||||
|
assert(buf.length >= 118, "Buffer is too small");
|
||||||
|
assert(buf.readUInt16BE(16, true) === SECP256K1_TYPE, "Bad curve type");
|
||||||
|
assert(buf.readUInt16BE(18, true) === 32, "Bad Rx length");
|
||||||
|
assert(buf.readUInt16BE(52, true) === 32, "Bad Ry length");
|
||||||
|
var iv = new Buffer(16);
|
||||||
|
buf.copy(iv, 0, 0, 16);
|
||||||
|
var ephemPublicKey = new Buffer(65);
|
||||||
|
ephemPublicKey[0] = 0x04;
|
||||||
|
buf.copy(ephemPublicKey, 1, 20, 52);
|
||||||
|
buf.copy(ephemPublicKey, 33, 54, 86);
|
||||||
|
// NOTE(Kagami): We do copy instead of slice to protect against
|
||||||
|
// possible source buffer modification by user.
|
||||||
|
var ciphertext = new Buffer(buf.length - 118);
|
||||||
|
buf.copy(ciphertext, 0, 86, buf.length - 32);
|
||||||
|
var mac = new Buffer(32);
|
||||||
|
buf.copy(mac, 0, buf.length - 32);
|
||||||
|
return {
|
||||||
|
iv: iv,
|
||||||
|
ephemPublicKey: ephemPublicKey,
|
||||||
|
ciphertext: ciphertext,
|
||||||
|
mac: mac,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
encode: function(opts) {
|
||||||
|
assert(opts.iv.length === 16, "Bad IV");
|
||||||
|
assert(opts.ephemPublicKey.length === 65, "Bad public key");
|
||||||
|
assert(opts.mac.length === 32, "Bad MAC");
|
||||||
|
// 16 + 2 + 2 + 32 + 2 + 32 + ? + 32
|
||||||
|
var buf = new Buffer(118 + opts.ciphertext.length);
|
||||||
|
opts.iv.copy(buf);
|
||||||
|
buf.writeUInt16BE(SECP256K1_TYPE, 16, true); // Curve type
|
||||||
|
buf.writeUInt16BE(32, 18, true); // Rx length
|
||||||
|
opts.ephemPublicKey.copy(buf, 20, 1, 33); // Rx
|
||||||
|
buf.writeUInt16BE(32, 52, true); // Ry length
|
||||||
|
opts.ephemPublicKey.copy(buf, 54, 33); // Ry
|
||||||
|
opts.ciphertext.copy(buf, 86);
|
||||||
|
opts.mac.copy(buf, 86 + opts.ciphertext.length);
|
||||||
|
return buf;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt message for given recepient's public key.
|
||||||
|
* @param {Buffer} publicKeyTo - Recipient's public key (65 bytes)
|
||||||
|
* @param {Buffer} msg - The message being encrypted
|
||||||
|
* @param {?{?iv: Buffer, ?ephemPrivateKey: Buffer}} opts - You may also
|
||||||
|
* specify initialization vector (16 bytes) and ephemeral private key
|
||||||
|
* (32 bytes) to get deterministic results.
|
||||||
|
* @return {Promise.<Buffer>} - A promise that resolves with the buffer
|
||||||
|
* in `encrypted` format successful encryption and rejects on failure.
|
||||||
|
*/
|
||||||
|
exports.encrypt = function(publicKeyTo, msg, opts) {
|
||||||
|
return eccrypto.encrypt(publicKeyTo, msg, opts).then(function(encObj) {
|
||||||
|
return encrypted.encode(encObj);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt message using given private key.
|
||||||
|
* @param {Buffer} privateKey - A 32-byte private key of recepient of
|
||||||
|
* the mesage
|
||||||
|
* @param {Buffer} buf - encrypted data
|
||||||
|
* @return {Promise.<Buffer>} - A promise that resolves with the
|
||||||
|
* plaintext on successful decryption and rejects on failure.
|
||||||
|
*/
|
||||||
|
exports.decrypt = function(privateKey, buf) {
|
||||||
|
return new promise(function(resolve) {
|
||||||
|
var encObj = encrypted.decode(buf);
|
||||||
|
resolve(eccrypto.decrypt(privateKey, encObj));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/** Current protocol version. */
|
/** Current protocol version. */
|
||||||
exports.PROTOCOL_VERSION = 3;
|
exports.PROTOCOL_VERSION = require("./_util").PROTOCOL_VERSION;
|
||||||
|
|
||||||
/** [Common structures.]{@link module:bitmessage/structs} */
|
/** [Common structures.]{@link module:bitmessage/structs} */
|
||||||
exports.structs = require("./structs");
|
exports.structs = require("./structs");
|
||||||
|
|
|
@ -9,11 +9,12 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var assert = require("./util").assert;
|
var assert = require("./_util").assert;
|
||||||
var structs = require("./structs");
|
var structs = require("./structs");
|
||||||
var ServicesBitfield = structs.ServicesBitfield;
|
|
||||||
var UserAgent = require("./user-agent");
|
var UserAgent = require("./user-agent");
|
||||||
var util = require("./util");
|
var util = require("./_util");
|
||||||
|
|
||||||
|
var ServicesBitfield = structs.ServicesBitfield;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `version` message.
|
* `version` message.
|
||||||
|
@ -77,7 +78,7 @@ exports.version = {
|
||||||
var streamNumbers = opts.streamNumbers || [1];
|
var streamNumbers = opts.streamNumbers || [1];
|
||||||
// Start encoding.
|
// Start encoding.
|
||||||
var protoVersion = new Buffer(4);
|
var protoVersion = new Buffer(4);
|
||||||
protoVersion.writeUInt32BE(require("./").PROTOCOL_VERSION, 0);
|
protoVersion.writeUInt32BE(util.PROTOCOL_VERSION, 0);
|
||||||
var addrRecv = structs.net_addr.encode({
|
var addrRecv = structs.net_addr.encode({
|
||||||
services: services,
|
services: services,
|
||||||
host: opts.remoteHost,
|
host: opts.remoteHost,
|
||||||
|
@ -263,91 +264,3 @@ var error = exports.error = {
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* `object` message. An `object` is a message which is shared throughout
|
|
||||||
* a stream. It is the only message which propagates; all others are
|
|
||||||
* only between two nodes.
|
|
||||||
* NOTE: You shouldn't use `encode` and `decode` methods directly.
|
|
||||||
* Instead, get type of the object and process it using appropriate
|
|
||||||
* namespace from `objects` module.
|
|
||||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#object}
|
|
||||||
* @namespace
|
|
||||||
*/
|
|
||||||
exports.object = {
|
|
||||||
// Known types.
|
|
||||||
GETPUBKEY: 0,
|
|
||||||
PUBKEY: 1,
|
|
||||||
MSG: 2,
|
|
||||||
BROADCAST: 3,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns type of the `object` message if we can parse it. Per spec
|
|
||||||
* we should still relay unknown objects.
|
|
||||||
* @param {Buffer} buf - Message payload
|
|
||||||
* @return {?number} Object type.
|
|
||||||
*/
|
|
||||||
getType: function(buf) {
|
|
||||||
assert(buf.length >= 22, "object message payload is too small");
|
|
||||||
return buf.readUInt32BE(16, true);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode `object` message payload.
|
|
||||||
* NOTE: `nonce` and `payload` are copied.
|
|
||||||
* @param {Buffer} buf - Message payload
|
|
||||||
* @return {Object} Decoded `object` structure.
|
|
||||||
*/
|
|
||||||
// FIXME(Kagami): Check for POW.
|
|
||||||
// TODO(Kagami): Option to not fail on bad POW (may be useful for
|
|
||||||
// bitchan).
|
|
||||||
// TODO(Kagami): Option to not fail on expired object (would be useful
|
|
||||||
// for bitchan).
|
|
||||||
decode: function(buf) {
|
|
||||||
// 8 + 8 + 4 + (1+) + (1+)
|
|
||||||
assert(buf.length >= 22, "object message payload is too small");
|
|
||||||
var nonce = new Buffer(8);
|
|
||||||
buf.copy(nonce, 0, 0, 8);
|
|
||||||
var expiresTime = util.readTimestamp64BE(buf.slice(8, 16));
|
|
||||||
var ttl = expiresTime - util.tnow();
|
|
||||||
assert(ttl >= -3600, "Object expired more than a hour ago");
|
|
||||||
assert(ttl <= 2430000, "expiresTime is too far in the future");
|
|
||||||
var type = buf.readUInt32BE(16, true);
|
|
||||||
var decodedVersion = structs.var_int.decode(buf.slice(20));
|
|
||||||
var decodedStream = structs.var_int.decode(decodedVersion.rest);
|
|
||||||
var payload = new Buffer(decodedStream.rest.length);
|
|
||||||
decodedStream.rest.copy(payload);
|
|
||||||
return {
|
|
||||||
nonce: nonce,
|
|
||||||
ttl: ttl,
|
|
||||||
type: type,
|
|
||||||
version: decodedVersion.value,
|
|
||||||
stream: decodedStream.value,
|
|
||||||
payload: payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode `object` message payload.
|
|
||||||
* @param {Object} opts - Object options
|
|
||||||
* @return {Buffer} Encoded payload.
|
|
||||||
*/
|
|
||||||
// TODO(Kagami): Do a POW if nonce is not provided.
|
|
||||||
encode: function(opts) {
|
|
||||||
assert(opts.nonce.length === 8, "Bad nonce");
|
|
||||||
assert(opts.ttl > 0, "Bad TTL");
|
|
||||||
assert(opts.ttl <= 2430000, "TTL may not be larger than 28 days + 3 hours");
|
|
||||||
var expiresTime = util.tnow() + opts.ttl;
|
|
||||||
var type = new Buffer(4);
|
|
||||||
type.writeUInt32BE(opts.type, 0);
|
|
||||||
var stream = opts.stream || 1;
|
|
||||||
return Buffer.concat([
|
|
||||||
opts.nonce,
|
|
||||||
util.writeUInt64BE(null, expiresTime),
|
|
||||||
type,
|
|
||||||
structs.var_int.encode(opts.version),
|
|
||||||
structs.var_int.encode(stream),
|
|
||||||
opts.payload,
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
272
lib/objects.js
272
lib/objects.js
|
@ -6,14 +6,21 @@
|
||||||
* @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
|
||||||
|
// structure even if it contains unsupported version. Also error
|
||||||
|
// handling may need some refactoring.
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
var objectAssign = Object.assign || require("object-assign");
|
||||||
var assert = require("./util").assert;
|
var assert = require("./_util").assert;
|
||||||
var promise = require("./platform").promise;
|
var promise = require("./platform").promise;
|
||||||
var object = require("./messages").object;
|
var bmcrypto = require("./crypto");
|
||||||
var Address = require("./address");
|
var Address = require("./address");
|
||||||
|
var var_int = require("./structs").var_int;
|
||||||
|
var PubkeyBitfield = require("./structs").PubkeyBitfield;
|
||||||
|
var object = require("./structs").object;
|
||||||
|
var util = require("./_util");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `getpubkey` object. When a node has the hash of a public key (from an
|
* `getpubkey` object. When a node has the hash of a public key (from an
|
||||||
|
@ -26,13 +33,15 @@ exports.getpubkey = {
|
||||||
/**
|
/**
|
||||||
* Decode `getpubkey` object message payload.
|
* Decode `getpubkey` object message payload.
|
||||||
* @param {Buffer} buf - Message payload
|
* @param {Buffer} buf - Message payload
|
||||||
* @return {Promise.<Object>} A promise that contained decoded
|
* @return {Promise.<Object>} A promise that contains decoded
|
||||||
* `getpubkey` object structure when fulfilled.
|
* `getpubkey` object structure when fulfilled.
|
||||||
*/
|
*/
|
||||||
decodeAsync: function(buf) {
|
decodeAsync: function(buf) {
|
||||||
return new promise(function(resolve) {
|
return new promise(function(resolve) {
|
||||||
var decoded = object.decode(buf);
|
var decoded = object.decode(buf);
|
||||||
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 <= 4, "getpubkey version is too high");
|
||||||
var payload = decoded.payload;
|
var payload = decoded.payload;
|
||||||
delete decoded.payload;
|
delete decoded.payload;
|
||||||
if (decoded.version < 4) {
|
if (decoded.version < 4) {
|
||||||
|
@ -51,18 +60,265 @@ exports.getpubkey = {
|
||||||
/**
|
/**
|
||||||
* Encode `getpubkey` object message payload.
|
* Encode `getpubkey` object message payload.
|
||||||
* @param {Object} opts - `getpubkey` object options
|
* @param {Object} opts - `getpubkey` object options
|
||||||
* @return {Promise.<Buffer>} A promise that contained encoded message
|
* @return {Promise.<Buffer>} A promise that contains encoded message
|
||||||
* payload when fulfilled.
|
* payload when fulfilled.
|
||||||
*/
|
*/
|
||||||
|
// FIXME(Kagami): Do a POW.
|
||||||
encodeAsync: function(opts) {
|
encodeAsync: function(opts) {
|
||||||
return new promise(function(resolve) {
|
return new promise(function(resolve) {
|
||||||
opts = objectAssign({}, opts);
|
opts = objectAssign({}, opts);
|
||||||
opts.type = object.GETPUBKEY;
|
opts.type = object.GETPUBKEY;
|
||||||
var addr = Address.decode(opts.to);
|
// Bitmessage address of recepeint of `getpubkey` message.
|
||||||
opts.version = addr.version;
|
var to = Address.decode(opts.to);
|
||||||
opts.stream = addr.stream;
|
assert(to.version >= 2, "Address version is too low");
|
||||||
opts.payload = addr.version < 4 ? addr.getRipe() : addr.getTag();
|
assert(to.version <= 4, "Address version is too high");
|
||||||
|
opts.version = to.version;
|
||||||
|
opts.stream = to.stream;
|
||||||
|
opts.payload = to.version < 4 ? to.getRipe() : to.getTag();
|
||||||
|
// POW calculation here.
|
||||||
|
var nonce = new Buffer(8);
|
||||||
|
opts.nonce = nonce;
|
||||||
resolve(object.encode(opts));
|
resolve(object.encode(opts));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function for `pubkey.decode`.
|
||||||
|
function extractPubkeyV2(payload) {
|
||||||
|
var decoded = {};
|
||||||
|
// Payload is copied so it's safe to return it right away.
|
||||||
|
decoded.behavior = PubkeyBitfield(payload.slice(0, 4));
|
||||||
|
var signPublicKey = decoded.signPublicKey = new Buffer(65);
|
||||||
|
signPublicKey[0] = 4;
|
||||||
|
payload.copy(signPublicKey, 1, 4, 68);
|
||||||
|
var encPublicKey = decoded.encPublicKey = new Buffer(65);
|
||||||
|
encPublicKey[0] = 4;
|
||||||
|
payload.copy(encPublicKey, 1, 68, 132);
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for `pubkey.decode`.
|
||||||
|
function extractPubkeyV3(payload) {
|
||||||
|
var decoded = {};
|
||||||
|
var length = 0;
|
||||||
|
var decodedTrials = var_int.decode(payload);
|
||||||
|
decoded.nonceTrialsPerByte = decodedTrials.value;
|
||||||
|
length += decodedTrials.length;
|
||||||
|
var decodedExtraBytes = var_int.decode(decodedTrials.rest);
|
||||||
|
decoded.payloadLengthExtraBytes = decodedExtraBytes.value;
|
||||||
|
length += decodedExtraBytes.length;
|
||||||
|
var decodedSigLength = var_int.decode(decodedExtraBytes.rest);
|
||||||
|
decoded.signature = decodedSigLength.rest.slice(0, decodedSigLength.value);
|
||||||
|
var siglen = decodedSigLength.length + decodedSigLength.value;
|
||||||
|
length += siglen;
|
||||||
|
// Internal value.
|
||||||
|
decoded._siglen = siglen;
|
||||||
|
decoded.length = length;
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `pubkey` object.
|
||||||
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#pubkey}
|
||||||
|
* @namespace
|
||||||
|
*/
|
||||||
|
exports.pubkey = {
|
||||||
|
/**
|
||||||
|
* Decode `pubkey` object message payload.
|
||||||
|
* @param {Buffer} buf - Message payload
|
||||||
|
* @param {?Object} opts - Decoding options
|
||||||
|
* @return {Promise.<Object>} A promise that contains decoded `pubkey`
|
||||||
|
* object structure when fulfilled.
|
||||||
|
*/
|
||||||
|
decodeAsync: function(buf, opts) {
|
||||||
|
return new promise(function(resolve, reject) {
|
||||||
|
opts = opts || {};
|
||||||
|
var neededPubkeys = opts.neededPubkeys || {};
|
||||||
|
var decoded = object.decode(buf);
|
||||||
|
assert(decoded.type === object.PUBKEY, "Wrong object type");
|
||||||
|
var payload = decoded.payload;
|
||||||
|
delete decoded.payload;
|
||||||
|
var version = decoded.version;
|
||||||
|
assert(version >= 2, "Address version is too low");
|
||||||
|
assert(version <= 4, "Address version is too high");
|
||||||
|
var siglen, sig, dataToVerify, pubkeyp;
|
||||||
|
var addr, addrs, tag, pubkeyEncPrivateKey, dataToDecrypt;
|
||||||
|
var length = 132;
|
||||||
|
|
||||||
|
// v2 pubkey.
|
||||||
|
if (version === 2) {
|
||||||
|
// 4 + 64 + 64
|
||||||
|
assert(payload.length === 132, "Bad pubkey v2 object payload length");
|
||||||
|
objectAssign(decoded, extractPubkeyV2(payload));
|
||||||
|
// Real data length.
|
||||||
|
decoded.length = length;
|
||||||
|
return resolve(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v3 pubkey.
|
||||||
|
if (version === 3) {
|
||||||
|
// 4 + 64 + 64 + (1+) + (1+) + (1+)
|
||||||
|
assert(payload.length >= 135, "Bad pubkey v3 object payload length");
|
||||||
|
objectAssign(decoded, extractPubkeyV2(payload));
|
||||||
|
objectAssign(decoded, extractPubkeyV3(payload.slice(132)));
|
||||||
|
siglen = util.popkey(decoded, "_siglen");
|
||||||
|
length += decoded.length;
|
||||||
|
// Real data length.
|
||||||
|
decoded.length = length;
|
||||||
|
// Object message payload without nonce up to sigLength.
|
||||||
|
dataToVerify = buf.slice(8, decoded.headerLength + length - siglen);
|
||||||
|
sig = decoded.signature;
|
||||||
|
pubkeyp = bmcrypto.verify(decoded.signPublicKey, dataToVerify, sig)
|
||||||
|
.then(function() {
|
||||||
|
return decoded;
|
||||||
|
});
|
||||||
|
return resolve(pubkeyp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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(payload.length >= 32, "Bad pubkey v4 object payload length");
|
||||||
|
tag = decoded.tag = payload.slice(0, 32);
|
||||||
|
pubkeyEncPrivateKey = neededPubkeys[tag];
|
||||||
|
if (!pubkeyEncPrivateKey) {
|
||||||
|
return reject(new Error("You are not interested in this pubkey v4"));
|
||||||
|
}
|
||||||
|
dataToDecrypt = payload.slice(32);
|
||||||
|
pubkeyp = bmcrypto.decrypt(pubkeyEncPrivateKey, dataToDecrypt)
|
||||||
|
.then(function(decrypted) {
|
||||||
|
// 4 + 64 + 64 + (1+) + (1+) + (1+)
|
||||||
|
assert(
|
||||||
|
decrypted.length >= 135,
|
||||||
|
"Bad pubkey v4 object payload length");
|
||||||
|
objectAssign(decoded, extractPubkeyV2(decrypted));
|
||||||
|
objectAssign(decoded, extractPubkeyV3(decrypted.slice(132)));
|
||||||
|
siglen = util.popkey(decoded, "_siglen");
|
||||||
|
length += decoded.length;
|
||||||
|
// Real data length.
|
||||||
|
// Since data is encrypted, entire payload is used.
|
||||||
|
decoded.length = payload.length;
|
||||||
|
dataToVerify = Buffer.concat([
|
||||||
|
// Object header without nonce + tag.
|
||||||
|
buf.slice(8, decoded.headerLength + 32),
|
||||||
|
// Unencrypted pubkey data without signature.
|
||||||
|
decrypted.slice(0, length - siglen),
|
||||||
|
]);
|
||||||
|
sig = decoded.signature;
|
||||||
|
return bmcrypto.verify(decoded.signPublicKey, dataToVerify, sig);
|
||||||
|
}).then(function() {
|
||||||
|
return decoded;
|
||||||
|
});
|
||||||
|
resolve(pubkeyp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode `pubkey` object message payload.
|
||||||
|
* @param {Object} opts - `pubkey` object options
|
||||||
|
* @return {Promise.<Buffer>} A promise that contains encoded message
|
||||||
|
* payload when fulfilled.
|
||||||
|
*/
|
||||||
|
// FIXME(Kagami): Do a POW.
|
||||||
|
encodeAsync: function(opts) {
|
||||||
|
return new promise(function(resolve) {
|
||||||
|
opts = objectAssign({}, opts);
|
||||||
|
opts.type = object.PUBKEY;
|
||||||
|
// Originator of `pubkey` message.
|
||||||
|
var from = Address.decode(opts.from);
|
||||||
|
var nonceTrialsPerByte = util.getTrials(from);
|
||||||
|
var payloadLengthExtraBytes = util.getExtraBytes(from);
|
||||||
|
// Bitmessage address of recepient of `pubkey` message.
|
||||||
|
var to, version, stream;
|
||||||
|
if (opts.to) {
|
||||||
|
to = Address.decode(opts.to);
|
||||||
|
version = to.version;
|
||||||
|
stream = to.stream;
|
||||||
|
} else {
|
||||||
|
version = opts.version || 4;
|
||||||
|
stream = opts.stream || 1;
|
||||||
|
}
|
||||||
|
assert(version >= 2, "Address version is too low");
|
||||||
|
assert(version <= 4, "Address version is too high");
|
||||||
|
opts.version = version;
|
||||||
|
opts.stream = stream;
|
||||||
|
var obj, pubkeyp;
|
||||||
|
|
||||||
|
// v2 pubkey.
|
||||||
|
if (version === 2) {
|
||||||
|
opts.payload = Buffer.concat([
|
||||||
|
from.behavior.buffer,
|
||||||
|
from.signPublicKey.slice(1),
|
||||||
|
from.encPublicKey.slice(1),
|
||||||
|
]);
|
||||||
|
obj = object.encodeWithoutNonce(opts);
|
||||||
|
// POW calculation here.
|
||||||
|
var nonce = new Buffer(8);
|
||||||
|
obj = Buffer.concat([nonce, obj]);
|
||||||
|
return resolve(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubkeyData = [
|
||||||
|
from.behavior.buffer,
|
||||||
|
from.signPublicKey.slice(1),
|
||||||
|
from.encPublicKey.slice(1),
|
||||||
|
var_int.encode(nonceTrialsPerByte),
|
||||||
|
var_int.encode(payloadLengthExtraBytes),
|
||||||
|
];
|
||||||
|
|
||||||
|
// v3 pubkey.
|
||||||
|
if (version === 3) {
|
||||||
|
opts.payload = Buffer.concat(pubkeyData);
|
||||||
|
obj = object.encodeWithoutNonce(opts);
|
||||||
|
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.
|
||||||
|
return Buffer.concat([
|
||||||
|
nonce,
|
||||||
|
obj,
|
||||||
|
var_int.encode(sig.length),
|
||||||
|
sig,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
return resolve(pubkeyp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v4 pubkey.
|
||||||
|
opts.payload = from.getTag();
|
||||||
|
obj = object.encodeWithoutNonce(opts);
|
||||||
|
var dataToSign = Buffer.concat([obj].concat(pubkeyData));
|
||||||
|
var pubkeyEncPrivateKey = from.getPubkeyPrivateKey();
|
||||||
|
var pubkeyEncPublicKey = bmcrypto.getPublic(pubkeyEncPrivateKey);
|
||||||
|
pubkeyp = bmcrypto
|
||||||
|
.sign(from.signPrivateKey, dataToSign)
|
||||||
|
.then(function(sig) {
|
||||||
|
var dataToEnc = pubkeyData.concat(var_int.encode(sig.length), sig);
|
||||||
|
dataToEnc = Buffer.concat(dataToEnc);
|
||||||
|
return bmcrypto.encrypt(pubkeyEncPublicKey, 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(pubkeyp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -14,7 +14,9 @@ var hash = require("hash.js");
|
||||||
var Sha512 = require("sha.js/sha512");
|
var Sha512 = require("sha.js/sha512");
|
||||||
var BN = require("bn.js");
|
var BN = require("bn.js");
|
||||||
var work = require("webworkify");
|
var work = require("webworkify");
|
||||||
var assert = require("./util").assert;
|
var assert = require("./_util").assert;
|
||||||
|
|
||||||
|
var cryptoObj = window.crypto || window.msCrypto;
|
||||||
|
|
||||||
exports.sha1 = function(buf) {
|
exports.sha1 = function(buf) {
|
||||||
return new Buffer(hash.sha1().update(buf).digest());
|
return new Buffer(hash.sha1().update(buf).digest());
|
||||||
|
@ -34,7 +36,7 @@ exports.ripemd160 = function(buf) {
|
||||||
|
|
||||||
exports.randomBytes = function(size) {
|
exports.randomBytes = function(size) {
|
||||||
var arr = new Uint8Array(size);
|
var arr = new Uint8Array(size);
|
||||||
window.crypto.getRandomValues(arr);
|
cryptoObj.getRandomValues(arr);
|
||||||
return new Buffer(arr);
|
return new Buffer(arr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -139,4 +141,4 @@ exports.pow = function(opts) {
|
||||||
return powp;
|
return powp;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.promise = Promise;
|
exports.promise = window.Promise;
|
||||||
|
|
|
@ -10,7 +10,7 @@ var promise = typeof Promise === "undefined" ?
|
||||||
require("es6-promise").Promise :
|
require("es6-promise").Promise :
|
||||||
Promise;
|
Promise;
|
||||||
var bignum = require("bignum");
|
var bignum = require("bignum");
|
||||||
var assert = require("./util").assert;
|
var assert = require("./_util").assert;
|
||||||
var worker = require("./worker");
|
var worker = require("./worker");
|
||||||
|
|
||||||
var createHash = crypto.createHash;
|
var createHash = crypto.createHash;
|
||||||
|
|
17
lib/pow.js
17
lib/pow.js
|
@ -10,9 +10,7 @@
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
var objectAssign = Object.assign || require("object-assign");
|
||||||
var bmcrypto = require("./crypto");
|
var bmcrypto = require("./crypto");
|
||||||
var platform = require("./platform");
|
var platform = require("./platform");
|
||||||
|
var util = require("./_util");
|
||||||
var DEFAULT_TRIALS_PER_BYTE = 1000;
|
|
||||||
var DEFAULT_EXTRA_BYTES = 1000;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate target
|
* Calculate target
|
||||||
|
@ -22,20 +20,11 @@ var DEFAULT_EXTRA_BYTES = 1000;
|
||||||
// Just a wrapper around platform-specific implementation.
|
// Just a wrapper around platform-specific implementation.
|
||||||
exports.getTarget = function(opts) {
|
exports.getTarget = function(opts) {
|
||||||
var payloadLength = opts.payloadLength || opts.payload.length;
|
var payloadLength = opts.payloadLength || opts.payload.length;
|
||||||
var nonceTrialsPerByte = opts.nonceTrialsPerByte;
|
|
||||||
// Automatically raise lower values per spec.
|
|
||||||
if (!nonceTrialsPerByte || nonceTrialsPerByte < DEFAULT_TRIALS_PER_BYTE) {
|
|
||||||
nonceTrialsPerByte = DEFAULT_TRIALS_PER_BYTE;
|
|
||||||
}
|
|
||||||
var payloadLengthExtraBytes = opts.payloadLengthExtraBytes;
|
|
||||||
if (!payloadLengthExtraBytes || payloadLengthExtraBytes < DEFAULT_EXTRA_BYTES) {
|
|
||||||
payloadLengthExtraBytes = DEFAULT_EXTRA_BYTES;
|
|
||||||
}
|
|
||||||
return platform.getTarget({
|
return platform.getTarget({
|
||||||
ttl: opts.ttl,
|
ttl: opts.ttl,
|
||||||
payloadLength: payloadLength,
|
payloadLength: payloadLength,
|
||||||
nonceTrialsPerByte: nonceTrialsPerByte,
|
nonceTrialsPerByte: util.getTrials(opts),
|
||||||
payloadLengthExtraBytes: payloadLengthExtraBytes,
|
payloadLengthExtraBytes: util.getExtraBytes(opts),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
147
lib/structs.js
147
lib/structs.js
|
@ -9,8 +9,9 @@
|
||||||
|
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
var objectAssign = Object.assign || require("object-assign");
|
||||||
var bufferEqual = require("buffer-equal");
|
var bufferEqual = require("buffer-equal");
|
||||||
var assert = require("./util").assert;
|
var assert = require("./_util").assert;
|
||||||
var bmcrypto = require("./crypto");
|
var bmcrypto = require("./crypto");
|
||||||
|
var util = require("./_util");
|
||||||
|
|
||||||
function isAscii(str) {
|
function isAscii(str) {
|
||||||
for (var i = 0; i < str.length; i++) {
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
@ -91,6 +92,95 @@ var message = exports.message = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `object` message. An `object` is a message which is shared throughout
|
||||||
|
* a stream. It is the only message which propagates; all others are
|
||||||
|
* only between two nodes.
|
||||||
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#object}
|
||||||
|
* @namespace
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
var object = exports.object = {
|
||||||
|
// Known types.
|
||||||
|
GETPUBKEY: 0,
|
||||||
|
PUBKEY: 1,
|
||||||
|
MSG: 2,
|
||||||
|
BROADCAST: 3,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode `object` message payload.
|
||||||
|
* NOTE: `nonce` and `payload` are copied.
|
||||||
|
* @param {Buffer} buf - Message payload
|
||||||
|
* @return {Object} Decoded `object` structure.
|
||||||
|
*/
|
||||||
|
// FIXME(Kagami): Check a POW.
|
||||||
|
// TODO(Kagami): Option to not fail on bad POW (may be useful for
|
||||||
|
// bitchan).
|
||||||
|
// TODO(Kagami): Option to not fail on expired objects (would be
|
||||||
|
// useful for bitchan).
|
||||||
|
decode: function(buf) {
|
||||||
|
// 8 + 8 + 4 + (1+) + (1+)
|
||||||
|
assert(buf.length >= 22, "object message payload is too small");
|
||||||
|
var nonce = new Buffer(8);
|
||||||
|
buf.copy(nonce, 0, 0, 8);
|
||||||
|
var expiresTime = util.readTimestamp64BE(buf.slice(8, 16));
|
||||||
|
var ttl = expiresTime - util.tnow();
|
||||||
|
assert(ttl >= -3600, "Object expired more than a hour ago");
|
||||||
|
assert(ttl <= 2430000, "expiresTime is too far in the future");
|
||||||
|
var type = buf.readUInt32BE(16, true);
|
||||||
|
var decodedVersion = var_int.decode(buf.slice(20));
|
||||||
|
var decodedStream = var_int.decode(decodedVersion.rest);
|
||||||
|
var headerLength = 20 + decodedVersion.length + decodedStream.length;
|
||||||
|
var payload = new Buffer(decodedStream.rest.length);
|
||||||
|
decodedStream.rest.copy(payload);
|
||||||
|
return {
|
||||||
|
nonce: nonce,
|
||||||
|
ttl: ttl,
|
||||||
|
type: type,
|
||||||
|
version: decodedVersion.value,
|
||||||
|
stream: decodedStream.value,
|
||||||
|
headerLength: headerLength,
|
||||||
|
payload: payload,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode `object` message payload without leading nonce field (may be
|
||||||
|
* useful if you are going to calculate it later).
|
||||||
|
* @param {Object} opts - Object options
|
||||||
|
* @return {Buffer} Encoded payload.
|
||||||
|
*/
|
||||||
|
encodeWithoutNonce: function(opts) {
|
||||||
|
assert(opts.ttl > 0, "Bad TTL");
|
||||||
|
assert(opts.ttl <= 2430000, "TTL may not be larger than 28 days + 3 hours");
|
||||||
|
var expiresTime = util.tnow() + opts.ttl;
|
||||||
|
var type = new Buffer(4);
|
||||||
|
type.writeUInt32BE(opts.type, 0);
|
||||||
|
var stream = opts.stream || 1;
|
||||||
|
return Buffer.concat([
|
||||||
|
util.writeUInt64BE(null, expiresTime),
|
||||||
|
type,
|
||||||
|
var_int.encode(opts.version),
|
||||||
|
var_int.encode(stream),
|
||||||
|
opts.payload,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode `object` message payload.
|
||||||
|
* @param {Object} opts - Object options
|
||||||
|
* @return {Buffer} Encoded payload.
|
||||||
|
*/
|
||||||
|
encode: function(opts) {
|
||||||
|
assert(opts.nonce.length === 8, "Bad nonce");
|
||||||
|
// NOTE(Kagami): This may be a bit inefficient since we allocate
|
||||||
|
// twice.
|
||||||
|
return Buffer.concat([
|
||||||
|
opts.nonce,
|
||||||
|
object.encodeWithoutNonce(opts),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variable length integer.
|
* Variable length integer.
|
||||||
|
@ -435,67 +525,26 @@ exports.inv_vect = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var SECP256K1_TYPE = 714;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypted payload.
|
* Encrypted payload.
|
||||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Encrypted_payload}
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Encrypted_payload}
|
||||||
* @namespace
|
* @namespace encrypted
|
||||||
*/
|
*/
|
||||||
exports.encrypted = {
|
/**
|
||||||
/**
|
|
||||||
* Decode encrypted payload.
|
* Decode encrypted payload.
|
||||||
* NOTE: all structure members are copied.
|
* NOTE: all structure members are copied.
|
||||||
* @param {Buffer} buf - A buffer that contains encrypted payload
|
* @param {Buffer} buf - A buffer that contains encrypted payload
|
||||||
* @return {Object} Decoded encrypted structure.
|
* @return {Object} Decoded encrypted structure.
|
||||||
|
* @function encrypted.decode
|
||||||
*/
|
*/
|
||||||
decode: function(buf) {
|
/**
|
||||||
assert(buf.length >= 118, "Buffer is too small");
|
|
||||||
assert(buf.readUInt16BE(16, true) === SECP256K1_TYPE, "Bad curve type");
|
|
||||||
assert(buf.readUInt16BE(18, true) === 32, "Bad Rx length");
|
|
||||||
assert(buf.readUInt16BE(52, true) === 32, "Bad Ry length");
|
|
||||||
var iv = new Buffer(16);
|
|
||||||
buf.copy(iv, 0, 0, 16);
|
|
||||||
var ephemPublicKey = new Buffer(65);
|
|
||||||
ephemPublicKey[0] = 0x04;
|
|
||||||
buf.copy(ephemPublicKey, 1, 20, 52);
|
|
||||||
buf.copy(ephemPublicKey, 33, 54, 86);
|
|
||||||
// NOTE(Kagami): We do copy instead of slice to protect against
|
|
||||||
// possible source buffer modification by user.
|
|
||||||
var ciphertext = new Buffer(buf.length - 118);
|
|
||||||
buf.copy(ciphertext, 0, 86, buf.length - 32);
|
|
||||||
var mac = new Buffer(32);
|
|
||||||
buf.copy(mac, 0, buf.length - 32);
|
|
||||||
return {
|
|
||||||
iv: iv,
|
|
||||||
ephemPublicKey: ephemPublicKey,
|
|
||||||
ciphertext: ciphertext,
|
|
||||||
mac: mac,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode `encrypted`.
|
* Encode `encrypted`.
|
||||||
* @param {Object} opts - Encode options
|
* @param {Object} opts - Encode options
|
||||||
* @return {Buffer} Encoded encrypted payload.
|
* @return {Buffer} Encoded encrypted payload.
|
||||||
|
* @function encrypted.encode
|
||||||
*/
|
*/
|
||||||
encode: function(opts) {
|
// Reexport struct.
|
||||||
assert(opts.iv.length === 16, "Bad IV");
|
exports.encrypted = bmcrypto.encrypted;
|
||||||
assert(opts.ephemPublicKey.length === 65, "Bad public key");
|
|
||||||
assert(opts.mac.length === 32, "Bad MAC");
|
|
||||||
// 16 + 2 + 2 + 32 + 2 + 32 + ? + 32
|
|
||||||
var buf = new Buffer(118 + opts.ciphertext.length);
|
|
||||||
opts.iv.copy(buf);
|
|
||||||
buf.writeUInt16BE(SECP256K1_TYPE, 16, true); // Curve type
|
|
||||||
buf.writeUInt16BE(32, 18, true); // Rx length
|
|
||||||
opts.ephemPublicKey.copy(buf, 20, 1, 33); // Rx
|
|
||||||
buf.writeUInt16BE(32, 52, true); // Ry length
|
|
||||||
opts.ephemPublicKey.copy(buf, 54, 33); // Ry
|
|
||||||
opts.ciphertext.copy(buf, 86);
|
|
||||||
opts.mac.copy(buf, 86 + opts.ciphertext.length);
|
|
||||||
return buf;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Creates bitfield (MSB 0) class of the specified size.
|
// Creates bitfield (MSB 0) class of the specified size.
|
||||||
var Bitfield = function(size) {
|
var Bitfield = function(size) {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
var bufferEqual = require("buffer-equal");
|
var bufferEqual = require("buffer-equal");
|
||||||
var bs58 = require("bs58");
|
var bs58 = require("bs58");
|
||||||
var assert = require("./util").assert;
|
var assert = require("./_util").assert;
|
||||||
var bmcrypto = require("./crypto");
|
var bmcrypto = require("./crypto");
|
||||||
|
|
||||||
// Compute the WIF checksum for the given data.
|
// Compute the WIF checksum for the given data.
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
"bn.js": "^1.0.0",
|
"bn.js": "^1.0.0",
|
||||||
"bs58": "^2.0.0",
|
"bs58": "^2.0.0",
|
||||||
"buffer-equal": "~0.0.1",
|
"buffer-equal": "~0.0.1",
|
||||||
"eccrypto": "^0.9.0",
|
"eccrypto": "^0.9.3",
|
||||||
"es6-promise": "^2.0.1",
|
"es6-promise": "^2.0.1",
|
||||||
"hash.js": "^1.0.2",
|
"hash.js": "^1.0.2",
|
||||||
"nan": "^1.4.1",
|
"nan": "^1.4.1",
|
||||||
|
|
193
test.js
193
test.js
|
@ -8,6 +8,7 @@ var bitmessage = require("./lib");
|
||||||
var bmcrypto = require("./lib/crypto");
|
var bmcrypto = require("./lib/crypto");
|
||||||
var structs = bitmessage.structs;
|
var structs = bitmessage.structs;
|
||||||
var message = structs.message;
|
var message = structs.message;
|
||||||
|
var object = structs.object;
|
||||||
var var_int = structs.var_int;
|
var var_int = structs.var_int;
|
||||||
var var_str = structs.var_str;
|
var var_str = structs.var_str;
|
||||||
var var_int_list = structs.var_int_list;
|
var var_int_list = structs.var_int_list;
|
||||||
|
@ -21,9 +22,9 @@ var version = messages.version;
|
||||||
var addr = messages.addr;
|
var addr = messages.addr;
|
||||||
var inv = messages.inv;
|
var inv = messages.inv;
|
||||||
var error = messages.error;
|
var error = messages.error;
|
||||||
var object = messages.object;
|
|
||||||
var objects = bitmessage.objects;
|
var objects = bitmessage.objects;
|
||||||
var getpubkey = objects.getpubkey;
|
var getpubkey = objects.getpubkey;
|
||||||
|
var pubkey = objects.pubkey;
|
||||||
var WIF = bitmessage.WIF;
|
var WIF = bitmessage.WIF;
|
||||||
var POW = bitmessage.POW;
|
var POW = bitmessage.POW;
|
||||||
var Address = bitmessage.Address;
|
var Address = bitmessage.Address;
|
||||||
|
@ -65,8 +66,48 @@ describe("Crypto", function() {
|
||||||
expect(bytes[value]).to.be.below(7);
|
expect(bytes[value]).to.be.below(7);
|
||||||
}
|
}
|
||||||
// Ideal sum = (255 / 2) * size = 12750
|
// Ideal sum = (255 / 2) * size = 12750
|
||||||
expect(sum).to.be.above(10000);
|
expect(sum).to.be.above(5000);
|
||||||
expect(sum).to.be.below(15000);
|
expect(sum).to.be.below(20000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should generate private keys", function() {
|
||||||
|
var privateKey = bmcrypto.getPrivate();
|
||||||
|
expect(Buffer.isBuffer(privateKey)).to.be.true;
|
||||||
|
expect(privateKey.length).to.equal(32);
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < 32; i++) { sum += privateKey[i]; }
|
||||||
|
expect(sum).to.be.above(0);
|
||||||
|
expect(sum).to.be.below(8160);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow to convert private key to public", function() {
|
||||||
|
var privateKey = Buffer(32);
|
||||||
|
privateKey.fill(1);
|
||||||
|
expect(bmcrypto.getPublic(privateKey).toString("hex")).to.equal("041b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f70beaf8f588b541507fed6a642c5ab42dfdf8120a7f639de5122d47a69a8e8d1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow to sign and verify message", function() {
|
||||||
|
var privateKey = Buffer(32);
|
||||||
|
privateKey.fill(1);
|
||||||
|
var publicKey = bmcrypto.getPublic(privateKey);
|
||||||
|
var msg = Buffer("test");
|
||||||
|
return bmcrypto.sign(privateKey, msg).then(function(sig) {
|
||||||
|
expect(Buffer.isBuffer(sig)).to.be.true;
|
||||||
|
expect(sig.toString("hex")).to.equal("304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa");
|
||||||
|
return bmcrypto.verify(publicKey, msg, sig);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow to encrypt and decrypt message", function() {
|
||||||
|
var privateKeyA = bmcrypto.getPrivate();
|
||||||
|
var publicKeyA = bmcrypto.getPublic(privateKeyA);
|
||||||
|
return bmcrypto.encrypt(publicKeyA, Buffer("msg to a")).then(function(buf) {
|
||||||
|
expect(Buffer.isBuffer(buf)).to.be.true;
|
||||||
|
return bmcrypto.decrypt(privateKeyA, buf).then(function(plaintext) {
|
||||||
|
expect(Buffer.isBuffer(plaintext)).to.be.true;
|
||||||
|
expect(plaintext.toString()).to.equal("msg to a");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,6 +134,37 @@ describe("Common structures", function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("object", function() {
|
||||||
|
it("should encode and decode", function() {
|
||||||
|
var nonce = Buffer(8);
|
||||||
|
var res = object.decode(object.encode({
|
||||||
|
nonce: nonce,
|
||||||
|
ttl: 100,
|
||||||
|
type: 2,
|
||||||
|
version: 1,
|
||||||
|
payload: Buffer("test"),
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(bufferEqual(nonce, res.nonce)).to.be.true;
|
||||||
|
expect(res.ttl).to.be.at.least(100);
|
||||||
|
expect(res.type).to.equal(2);
|
||||||
|
expect(res.version).to.equal(1);
|
||||||
|
expect(res.stream).to.equal(1);
|
||||||
|
expect(res.headerLength).to.equal(22);
|
||||||
|
expect(res.payload.toString()).to.equal("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shouldn't encode too big TTL", function() {
|
||||||
|
expect(object.encode.bind(null, {
|
||||||
|
nonce: Buffer(8),
|
||||||
|
ttl: 10000000,
|
||||||
|
type: 2,
|
||||||
|
version: 1,
|
||||||
|
payload: Buffer("test"),
|
||||||
|
})).to.throw(Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("var_int", function() {
|
describe("var_int", function() {
|
||||||
it("should decode", function() {
|
it("should decode", function() {
|
||||||
var res;
|
var res;
|
||||||
|
@ -372,55 +444,12 @@ describe("Message types", function() {
|
||||||
expect(res.length).to.equal(18);
|
expect(res.length).to.equal(18);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("object", function() {
|
|
||||||
it("should encode and decode", function() {
|
|
||||||
var nonce = Buffer(8);
|
|
||||||
var res = object.decode(object.encode({
|
|
||||||
nonce: nonce,
|
|
||||||
ttl: 100,
|
|
||||||
type: 2,
|
|
||||||
version: 1,
|
|
||||||
payload: Buffer("test"),
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(bufferEqual(nonce, res.nonce)).to.be.true;
|
|
||||||
expect(res.ttl).to.be.at.least(100);
|
|
||||||
expect(res.type).to.equal(2);
|
|
||||||
expect(res.version).to.equal(1);
|
|
||||||
expect(res.stream).to.equal(1);
|
|
||||||
expect(res.payload.toString()).to.equal("test");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shouldn't encode too big TTL", function() {
|
|
||||||
expect(object.encode.bind(null, {
|
|
||||||
nonce: Buffer(8),
|
|
||||||
ttl: 10000000,
|
|
||||||
type: 2,
|
|
||||||
version: 1,
|
|
||||||
payload: Buffer("test"),
|
|
||||||
})).to.throw(Error);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return type of the object", function() {
|
|
||||||
var encoded = object.encode({
|
|
||||||
nonce: Buffer(8),
|
|
||||||
ttl: 100,
|
|
||||||
type: object.BROADCAST,
|
|
||||||
version: 1,
|
|
||||||
payload: Buffer("test"),
|
|
||||||
});
|
|
||||||
expect(object.getType(encoded)).to.equal(3);
|
|
||||||
expect(object.decode(encoded).type).to.equal(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Object types", function() {
|
describe("Object types", function() {
|
||||||
describe("getpubkey", function() {
|
describe("getpubkey", function() {
|
||||||
it("should encode and decode getpubkey v3", function() {
|
it("should encode and decode getpubkey v3", function() {
|
||||||
return getpubkey.encodeAsync({
|
return getpubkey.encodeAsync({
|
||||||
nonce: Buffer(8),
|
|
||||||
ttl: 100,
|
ttl: 100,
|
||||||
to: "BM-2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU",
|
to: "BM-2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU",
|
||||||
}).then(getpubkey.decodeAsync)
|
}).then(getpubkey.decodeAsync)
|
||||||
|
@ -436,7 +465,6 @@ describe("Object types", function() {
|
||||||
|
|
||||||
it("should encode and decode getpubkey v4", function() {
|
it("should encode and decode getpubkey v4", function() {
|
||||||
return getpubkey.encodeAsync({
|
return getpubkey.encodeAsync({
|
||||||
nonce: Buffer(8),
|
|
||||||
ttl: 100,
|
ttl: 100,
|
||||||
to: "2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z",
|
to: "2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z",
|
||||||
}).then(getpubkey.decodeAsync)
|
}).then(getpubkey.decodeAsync)
|
||||||
|
@ -450,6 +478,72 @@ describe("Object types", 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() {
|
||||||
|
return pubkey.encodeAsync({
|
||||||
|
ttl: 123,
|
||||||
|
from: from,
|
||||||
|
to: "BM-onhypnh1UMhbQpmvdiPuG6soLLytYJAfH",
|
||||||
|
}).then(pubkey.decodeAsync)
|
||||||
|
.then(function(res) {
|
||||||
|
expect(res.ttl).to.equal(123);
|
||||||
|
expect(res.type).to.equal(object.PUBKEY);
|
||||||
|
expect(res.version).to.equal(2);
|
||||||
|
expect(res.stream).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.length).to.equal(132);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should encode and decode pubkey v3", function() {
|
||||||
|
return pubkey.encodeAsync({
|
||||||
|
ttl: 456,
|
||||||
|
from: from,
|
||||||
|
to: "BM-2D8Jxw5yiepaQqxrx43iPPNfRqbvWoJLoU",
|
||||||
|
}).then(pubkey.decodeAsync)
|
||||||
|
.then(function(res) {
|
||||||
|
expect(res.ttl).to.equal(456);
|
||||||
|
expect(res.type).to.equal(object.PUBKEY);
|
||||||
|
expect(res.version).to.equal(3);
|
||||||
|
expect(res.stream).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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should encode and decode pubkey v4", function() {
|
||||||
|
return pubkey.encodeAsync({ttl: 789, from: from, to: from})
|
||||||
|
.then(function(buf) {
|
||||||
|
return pubkey.decodeAsync(buf, {neededPubkeys: from});
|
||||||
|
}).then(function(res) {
|
||||||
|
expect(res.ttl).to.equal(789);
|
||||||
|
expect(res.type).to.equal(object.PUBKEY);
|
||||||
|
expect(res.version).to.equal(4);
|
||||||
|
expect(res.stream).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.tag, from.getTag())).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("WIF", function() {
|
describe("WIF", function() {
|
||||||
|
@ -586,6 +680,11 @@ 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() {
|
||||||
|
var addr = Address.decode("BM-2cTux3PGRqHTEH6wyUP2sWeT4LrsGgy63z");
|
||||||
|
expect(addr.getPubkeyPrivateKey().toString("hex")).to.equal("15e516173769dc87d4a8e8ed90200362fa58c0228bb2b70b06f26c089a9823a4");
|
||||||
|
});
|
||||||
|
|
||||||
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