HDParams beautified and jsdoc'd

This commit is contained in:
Esteban Ordano 2014-09-02 19:10:02 -03:00
parent 2a6b78c199
commit 49e7b2ae5f
1 changed files with 183 additions and 8 deletions

View File

@ -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:
* <pre> m/copay'/{copayer}/0/{index} </pre>
*
* 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:
* <pre> m/copay'/{copayer}/1/{changeIndex} </pre>
*
* There's a shared index, <tt>HDPath.SHARED_INDEX</tt>, 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 <tt>data</tt> 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 <tt>toObj()</tt> 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 <tt>toObj()</tt> 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 <tt>totalCopayers+1</tt> elements.
*
* Sets the first (corresponding to the parameters for the shared addresses)
* HDParams object to match <tt>shared</tt>'s values. Returns a serialized
* version of this set
*
* <pre>
* 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);
* </pre>
*
* @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 <tt>this</tt> instance if passed to the
* <tt>HDParams()</tt> 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 <tt>changeIndex</tt>
*/
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;