diff --git a/js/models/core/HDParams.js b/js/models/core/HDParams.js index 6f045e679..04872eedd 100644 --- a/js/models/core/HDParams.js +++ b/js/models/core/HDParams.js @@ -1,45 +1,164 @@ 'use strict'; +// 83.8% typed (by google's closure-compiler account) + var preconditions = require('preconditions').singleton(); var HDPath = require('./HDPath'); +var _ = require('underscore'); +/** + * @desc + * HDParams is a class that encapsulates information about the current indexes + * of a copayer + * + * When a copayer creates a new wallet, his receiveIndex gets updated, and an + * address is generated from everybody's public key, using this BIP32 path: + *
m/copay'/{copayer}/0/{index}
+ *
+ * When a copayer generates a transaction proposal, his changeIndex gets
+ * updated, and all funds from that transaction proposal go to the multisig
+ * address generated from this BIP32 path for all the copayers:
+ * m/copay'/{copayer}/1/{changeIndex}
+ *
+ * There's a shared index, HDPath.SHARED_INDEX, that serves to
+ * generate addresses common to everybody.
+ *
+ * @TODO: Should opts.cosigner go?
+ *
+ * @constructor
+ *
+ * @param {Object} opts - options for the construction of this object
+ * @param {number=} opts.cosigner - backwards compatible index of a copayer
+ * @param {number} opts.copayerIndex - the copayer that generated this branch
+ * of addresses
+ * @param {number} opts.receiveIndex - the current index for a the last receive
+ * address generated for this copayer
+ * @param {number} opts.changeIndex - the current index for a the last change
+ * address generated for this copayer
+ */
function HDParams(opts) {
opts = opts || {};
//opts.cosigner is for backwards compatibility only
- this.copayerIndex = typeof opts.copayerIndex === 'undefined' ? opts.cosigner : opts.copayerIndex;
+
+ /**
+ * @public
+ * @type number
+ */
+ this.copayerIndex = _.isUndefined(opts.copayerIndex) ? opts.cosigner : opts.copayerIndex;
+ /**
+ * @public
+ * @type number
+ */
this.changeIndex = opts.changeIndex || 0;
+ /**
+ * @public
+ * @type number
+ */
this.receiveIndex = opts.receiveIndex || 0;
- if (typeof this.copayerIndex === 'undefined') {
+ if (_.isUndefined(this.copayerIndex)) {
this.copayerIndex = HDPath.SHARED_INDEX;
}
}
+/**
+ * @desc
+ * Creates a set of HDParams, with one HDParams structure for each copayer and
+ * a shared path.
+ *
+ * @static
+ * @param {number} totalCopayers - the number of copayers in a wallet
+ * @returns {HDParams[]} a list of HDParams generated for a new empty wallet
+ */
HDParams.init = function(totalCopayers) {
preconditions.shouldBeNumber(totalCopayers);
+
var ret = [new HDParams({receiveIndex: 1})];
for (var i = 0 ; i < totalCopayers ; i++) {
ret.push(new HDParams({copayerIndex: i}));
}
return ret;
-}
+};
+/**
+ * @desc
+ * Generates a set of HDParams from a list with a object-serialized version of
+ * HDParams.
+ *
+ * @static
+ * @param {Object[]} hdParams - a list with objects
+ * @returns {HDParams[]} builds a HDParams for each object literal found in the list
+ */
HDParams.fromList = function(hdParams) {
return hdParams.map(function(i) { return HDParams.fromObj(i); });
-}
+};
+/**
+ * @desc
+ * Generate a HDParams from an object.
+ *
+ * This essentialy only calls new HDParams(data), but it's the interface being
+ * used everywhere to encode/decode.
+ *
+ * @TODO: This should be clarified - Or abstracted away - as it's a pattern
+ * used in multiple places.
+ *
+ * @static
+ * @param {Object} data - a serialized version of HDParams
+ * @return {HDParams}
+ * @throws {BADDATA} - when the parameter data already is an instance of
+ * HDParams.
+ */
HDParams.fromObj = function(data) {
if (data instanceof HDParams) {
- throw new Error('bad data format: Did you use .toObj()?');
+ throw new Error('BADDATA', 'bad data format: Did you use .toObj()?');
}
return new HDParams(data);
};
+/**
+ * @desc
+ * Serializes a list of HDParams to a list of "plain" objects, according to each
+ * element's toObj() method.
+ *
+ * @TODO: There should be a list of Classes that share this behaviour.
+ *
+ * @static
+ * @param {HDParams[]} hdParams - a list of HDParams objects.
+ * @returns {Array} an array with the toObj() serialization of each
+ * element in hdParams
+ */
HDParams.serialize = function(hdParams) {
return hdParams.map(function(i) { return i.toObj(); });
-}
+};
+/**
+ * @desc
+ * Creates a new HDParams set with totalCopayers+1 elements.
+ *
+ * Sets the first (corresponding to the parameters for the shared addresses)
+ * HDParams object to match shared's values. Returns a serialized
+ * version of this set
+ *
+ *
+ * var updateResult = HDParams.update({changeIndex: 1, receiveIndex: 2}, 5);
+ * // All the following asserts succeed
+ * assert(_.isArray(updateResult));
+ * assert(_.all(updateResult, function (hd) { return !(hd instanceOf HDParams); }));
+ * assert(_.size(updateResult) === 6);
+ * assert(updateResult[0].changeIndex === 1);
+ * assert(updateResult[0].receiveIndex === 2);
+ *
+ *
+ * @TODO: This method is badly coded, it does something that is very specific
+ * and kind of strange. I couldn't figure out why would it be needed.
+ *
+ * @static
+ * @param {HDParams} shared - an instance of HDParams
+ * @param {number} totalCopayers
+ * @return {Object[]}
+ */
HDParams.update = function(shared, totalCopayers) {
var hdParams = this.init(totalCopayers);
hdParams[0].changeIndex = shared.changeIndex;
@@ -47,6 +166,18 @@ HDParams.update = function(shared, totalCopayers) {
return this.serialize(hdParams);
};
+/**
+ * @desc
+ * Serializes this object
+ *
+ * @TODO: I couldn't realize why would this be needed - calling, for example,
+ * JSON.stringify would have the same result on this object than on the
+ * original instance
+ *
+ * @returns {Object} a serialized version that should be equal (in a deep object
+ * comparison) to the this instance if passed to the
+ * HDParams() constructor.
+ */
HDParams.prototype.toObj = function() {
return {
copayerIndex: this.copayerIndex,
@@ -55,6 +186,16 @@ HDParams.prototype.toObj = function() {
};
};
+/**
+ * @desc
+ * Throws an error if a given index falls out of the range for known addresses.
+ *
+ * @TODO: This is not a good pattern, exceptions should be for exceptional things.
+ *
+ * @param {number} index the index to check for
+ * @param {boolean} isChange whether to check for the change index or the
+ * receive address index
+ */
HDParams.prototype.checkRange = function(index, isChange) {
if ((isChange && index > this.changeIndex) ||
(!isChange && index > this.receiveIndex)) {
@@ -62,14 +203,38 @@ HDParams.prototype.checkRange = function(index, isChange) {
}
};
+/**
+ * @desc
+ * Return this instance's changeIndex value
+ *
+ * @TODO: Somebody did a lot of java. Not sure if we need to be so verbose. If
+ * anything, let's just declare changeIndex as a read only variable
+ * @returns {number} this HDParams current index to generate a change address
+ */
HDParams.prototype.getChangeIndex = function() {
return this.changeIndex;
};
+/**
+ * @desc
+ * Return this instance's receiveIndex value
+ *
+ * @TODO: Somebody did a lot of java. Not sure if we need to be so verbose. If
+ * anything, let's just declare changeIndex as a read only variable
+ * @returns {number} this HDParams current index to generate a receive address
+ */
HDParams.prototype.getReceiveIndex = function() {
return this.receiveIndex;
};
+/**
+ * @desc
+ * Increment this instance's changeIndex or receiveIndex value
+ *
+ * @TODO: Somebody did a lot of java. Not sure if we need to be so verbose.
+ *
+ * @param {boolean} isChange - if true, change changeIndex
+ */
HDParams.prototype.increment = function(isChange) {
if (isChange) {
this.changeIndex++;
@@ -78,9 +243,19 @@ HDParams.prototype.increment = function(isChange) {
}
};
+/**
+ * @desc
+ * Merge this instance with another HDParams instance.
+ *
+ * @TODO: Device a general approach to merges.
+ *
+ * @param {Object} inHDParams - the object to merge to
+ * @param {number} inHDParams.copayerIndex - the object to merge to
+ * @returns {boolean} true if this object has changed
+ */
HDParams.prototype.merge = function(inHDParams) {
- preconditions.shouldBeObject(inHDParams)
- .checkArgument(this.copayerIndex == inHDParams.copayerIndex);
+ preconditions.shouldBeObject(inHDParams);
+ preconditions.checkArgument(this.copayerIndex == inHDParams.copayerIndex);
var hasChanged = false;