mirror of https://github.com/BTCPrivate/copay.git
rebased
This commit is contained in:
commit
550d82cacd
|
@ -24,7 +24,7 @@
|
|||
"zeroclipboard": "~1.3.5",
|
||||
"ng-idle": "*",
|
||||
"underscore": "~1.7.0",
|
||||
"assert": "~0.1.0"
|
||||
"inherits": "~0.0.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "=1.2.19"
|
||||
|
|
|
@ -17,7 +17,7 @@ angular.module('copayApp.controllers').controller('JoinController',
|
|||
|
||||
$scope.hideAdv=true;
|
||||
|
||||
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
||||
|
||||
var _scan = function(evt) {
|
||||
if (localMediaStream) {
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var Transaction = bitcore.Transaction;
|
||||
|
||||
function BuilderMockV0 (data) {
|
||||
this.vanilla = data;
|
||||
this.tx = new Transaction();
|
||||
this.tx.parse(new Buffer(data.tx, 'hex'));
|
||||
};
|
||||
|
||||
BuilderMockV0.prototype.build = function() {
|
||||
return this.tx;
|
||||
};
|
||||
|
||||
|
||||
BuilderMockV0.prototype.getSelectedUnspent = function() {
|
||||
return [];
|
||||
};
|
||||
|
||||
BuilderMockV0.prototype.toObj = function() {
|
||||
return this.vanilla;
|
||||
};
|
||||
|
||||
module.exports = BuilderMockV0;
|
|
@ -1,53 +1,117 @@
|
|||
'use strict';
|
||||
|
||||
// 90.2% typed (by google's closure-compiler account)
|
||||
|
||||
var preconditions = require('preconditions').singleton();
|
||||
var _ = require('underscore');
|
||||
|
||||
function HDPath() {}
|
||||
|
||||
/*
|
||||
/**
|
||||
* @namespace
|
||||
*
|
||||
* HDPath contains helper functions to handle BIP32 branches as
|
||||
* Copay uses them.
|
||||
*
|
||||
* Based on https://github.com/maraoz/bips/blob/master/bip-NNNN.mediawiki
|
||||
* m / purpose' / copayerIndex / change / addressIndex
|
||||
* <pre>
|
||||
* m / purpose' / copayerIndex / change:boolean / addressIndex
|
||||
* </pre>
|
||||
*/
|
||||
var PURPOSE = 45;
|
||||
var MAX_NON_HARDENED = 0x80000000 - 1;
|
||||
var HDPath = {};
|
||||
|
||||
var SHARED_INDEX = MAX_NON_HARDENED - 0;
|
||||
var ID_INDEX = MAX_NON_HARDENED - 1;
|
||||
/**
|
||||
* @desc Copay's BIP45 purpose code
|
||||
* @const
|
||||
* @type number
|
||||
*/
|
||||
HDPath.PURPOSE = 45;
|
||||
|
||||
var BIP45_PUBLIC_PREFIX = 'm/' + PURPOSE + '\'';
|
||||
HDPath.BIP45_PUBLIC_PREFIX = BIP45_PUBLIC_PREFIX;
|
||||
/**
|
||||
* @desc Maximum number for non-hardened values (BIP32)
|
||||
* @const
|
||||
* @type number
|
||||
*/
|
||||
HDPath.MAX_NON_HARDENED = 0x80000000 - 1;
|
||||
|
||||
/**
|
||||
* @desc Shared Index: used for creating addresses for no particular purpose
|
||||
* @const
|
||||
* @type number
|
||||
*/
|
||||
HDPath.SHARED_INDEX = HDPath.MAX_NON_HARDENED - 0;
|
||||
|
||||
/**
|
||||
* @desc ???
|
||||
* @const
|
||||
* @type number
|
||||
*/
|
||||
HDPath.ID_INDEX = HDPath.MAX_NON_HARDENED - 1;
|
||||
|
||||
/**
|
||||
* @desc BIP45 prefix for COPAY
|
||||
* @const
|
||||
* @type string
|
||||
*/
|
||||
HDPath.BIP45_PUBLIC_PREFIX = 'm/' + HDPath.PURPOSE + '\'';
|
||||
|
||||
/**
|
||||
* @desc Retrieve a string to be used with bitcore representing a Copay branch
|
||||
* @param {number} addressIndex - the last value of the HD derivation
|
||||
* @param {boolean} isChange - whether this is a change address or a receive
|
||||
* @param {number} copayerIndex - the index of the copayer in the pubkeyring
|
||||
* @return {string} - the path for the HD derivation
|
||||
*/
|
||||
HDPath.Branch = function(addressIndex, isChange, copayerIndex) {
|
||||
preconditions.shouldBeNumber(addressIndex);
|
||||
preconditions.shouldBeBoolean(isChange);
|
||||
preconditions.checkArgument(_.isNumber(addressIndex));
|
||||
preconditions.checkArgument(_.isBoolean(isChange));
|
||||
|
||||
var ret = 'm/' +
|
||||
(typeof copayerIndex !== 'undefined' ? copayerIndex : SHARED_INDEX) + '/' +
|
||||
(typeof copayerIndex !== 'undefined' ? copayerIndex : HDPath.SHARED_INDEX) + '/' +
|
||||
(isChange ? 1 : 0) + '/' +
|
||||
addressIndex;
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc ???
|
||||
* @param {number} addressIndex - the last value of the HD derivation
|
||||
* @param {boolean} isChange - whether this is a change address or a receive
|
||||
* @param {number} copayerIndex - the index of the copayer in the pubkeyring
|
||||
* @return {string} - the path for the HD derivation
|
||||
*/
|
||||
HDPath.FullBranch = function(addressIndex, isChange, copayerIndex) {
|
||||
preconditions.checkArgument(_.isNumber(addressIndex));
|
||||
preconditions.checkArgument(_.isBoolean(isChange));
|
||||
|
||||
var sub = HDPath.Branch(addressIndex, isChange, copayerIndex);
|
||||
sub = sub.substring(2);
|
||||
return BIP45_PUBLIC_PREFIX + '/' + sub;
|
||||
return HDPath.BIP45_PUBLIC_PREFIX + '/' + sub;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Decompose a string and retrieve its arguments as if it where a Copay address.
|
||||
* @param {string} path - the HD path
|
||||
* @returns {Object} an object with three keys: addressIndex, isChange, and
|
||||
* copayerIndex
|
||||
*/
|
||||
HDPath.indexesForPath = function(path) {
|
||||
preconditions.shouldBeString(path);
|
||||
preconditions.checkArgument(_.isString(path));
|
||||
|
||||
var s = path.split('/');
|
||||
return {
|
||||
isChange: s[3] === '1',
|
||||
addressIndex: parseInt(s[4]),
|
||||
copayerIndex: parseInt(s[2])
|
||||
addressIndex: parseInt(s[4], 10),
|
||||
copayerIndex: parseInt(s[2], 10)
|
||||
};
|
||||
};
|
||||
|
||||
HDPath.IdFullBranch = HDPath.FullBranch(0, false, ID_INDEX);
|
||||
HDPath.IdBranch = HDPath.Branch(0, false, ID_INDEX);
|
||||
HDPath.PURPOSE = PURPOSE;
|
||||
HDPath.MAX_NON_HARDENED = MAX_NON_HARDENED;
|
||||
HDPath.SHARED_INDEX = SHARED_INDEX;
|
||||
HDPath.ID_INDEX = ID_INDEX;
|
||||
/**
|
||||
* @desc The ID for a shared branch
|
||||
*/
|
||||
HDPath.IdFullBranch = HDPath.FullBranch(0, false, HDPath.ID_INDEX);
|
||||
/**
|
||||
* @desc Partial ID for a shared branch
|
||||
*/
|
||||
HDPath.IdBranch = HDPath.Branch(0, false, HDPath.ID_INDEX);
|
||||
|
||||
module.exports = HDPath;
|
||||
|
|
|
@ -1,14 +1,30 @@
|
|||
'use strict';
|
||||
|
||||
var preconditions = require('preconditions').instance();
|
||||
var _ = require('underscore');
|
||||
var log = require('../../log');
|
||||
var bitcore = require('bitcore');
|
||||
var HK = bitcore.HierarchicalKey;
|
||||
var Address = bitcore.Address;
|
||||
var Script = bitcore.Script;
|
||||
var PrivateKey = require('./PrivateKey');
|
||||
var HDPath = require('./HDPath');
|
||||
var HDParams = require('./HDParams');
|
||||
var Address = bitcore.Address;
|
||||
var Script = bitcore.Script;
|
||||
|
||||
/**
|
||||
* @desc
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.walletId
|
||||
* @param {string} opts.network 'livenet' to signal the bitcoin main network, all others are testnet
|
||||
* @param {number=} opts.requiredCopayers - defaults to 3
|
||||
* @param {number=} opts.totalCopayers - defaults to 5
|
||||
* @param {Object[]=} opts.indexes - an array to be deserialized using {@link HDParams#fromList}
|
||||
* (defaults to all indexes in zero)
|
||||
* @param {Object=} opts.nicknameFor - nicknames for other copayers
|
||||
* @param {boolean[]=} opts.copayersBackup - whether other copayers have backed up their wallets
|
||||
*/
|
||||
function PublicKeyRing(opts) {
|
||||
opts = opts || {};
|
||||
|
||||
|
@ -29,22 +45,50 @@ function PublicKeyRing(opts) {
|
|||
this.copayerIds = [];
|
||||
this.copayersBackup = opts.copayersBackup || [];
|
||||
this.addressToPath = {};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Returns an object with only the keys needed to rebuild a PublicKeyRing
|
||||
*
|
||||
* @TODO: Figure out if this is the correct pattern
|
||||
* This is a static method and is probably used for serialization.
|
||||
*
|
||||
* @static
|
||||
* @param {Object} data
|
||||
* @param {string} data.walletId - a string to identify a wallet
|
||||
* @param {string} data.networkName - the name of the bitcoin network
|
||||
* @param {number} data.requiredCopayers - the number of required copayers
|
||||
* @param {number} data.totalCopayers - the number of copayers in the ring
|
||||
* @param {Object[]} data.indexes - an array of objects that can be turned into
|
||||
* an array of HDParams
|
||||
* @param {Object} data.nicknameFor - a registry of nicknames for other copayers
|
||||
* @param {boolean[]} data.copayersBackup - whether copayers have backed up their wallets
|
||||
* @param {string[]} data.copayersExtPubKeys - the extended public keys of copayers
|
||||
* @returns {Object} a trimmed down version of PublicKeyRing that can be used
|
||||
* as a parameter
|
||||
*/
|
||||
PublicKeyRing.trim = function(data) {
|
||||
var opts = {};
|
||||
['walletId', 'networkName', 'requiredCopayers', 'totalCopayers','indexes','nicknameFor','copayersBackup', 'copayersExtPubKeys' ].forEach(function(k){
|
||||
['walletId', 'networkName', 'requiredCopayers', 'totalCopayers',
|
||||
'indexes','nicknameFor','copayersBackup', 'copayersExtPubKeys'
|
||||
].forEach(function(k){
|
||||
opts[k] = data[k];
|
||||
});
|
||||
|
||||
return opts;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Deserializes a PublicKeyRing from a plain object
|
||||
*
|
||||
* If the <tt>data</tt> parameter is an instance of PublicKeyRing already,
|
||||
* it will fail, throwing an assertion error.
|
||||
*
|
||||
* @static
|
||||
* @param {object} data - a serialized version of PublicKeyRing {@see PublicKeyRing#trim}
|
||||
* @return {PublicKeyRing} - the deserialized object
|
||||
*/
|
||||
PublicKeyRing.fromObj = function(data) {
|
||||
preconditions.checkArgument(!(data instanceof PublicKeyRing), 'bad data format: Did you use .toObj()?');
|
||||
|
||||
|
||||
var opts = PublicKeyRing.trim(data);
|
||||
|
||||
// Support old indexes schema
|
||||
|
@ -61,6 +105,12 @@ PublicKeyRing.fromObj = function(data) {
|
|||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Serialize this object to a plain object with all the data needed to
|
||||
* rebuild it
|
||||
*
|
||||
* @return {Object} a serialized version of a PublicKeyRing
|
||||
*/
|
||||
PublicKeyRing.prototype.toObj = function() {
|
||||
return {
|
||||
walletId: this.walletId,
|
||||
|
@ -73,91 +123,175 @@ PublicKeyRing.prototype.toObj = function() {
|
|||
copayersExtPubKeys: this.copayersHK.map(function(b) {
|
||||
return b.extendedPublicKeyString();
|
||||
}),
|
||||
nicknameFor: this.nicknameFor,
|
||||
nicknameFor: this.nicknameFor
|
||||
};
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.getCopayerId = function(i) {
|
||||
preconditions.checkArgument(typeof i !== 'undefined');
|
||||
return this.copayerIds[i];
|
||||
/**
|
||||
* @desc
|
||||
* Retrieve a copayer's public key as a hexadecimal encoded string
|
||||
*
|
||||
* @param {number} copayerId - the copayer id
|
||||
* @returns {string} the extended public key of the i-th copayer
|
||||
*/
|
||||
PublicKeyRing.prototype.getCopayerId = function(copayerId) {
|
||||
preconditions.checkArgument(!_.isUndefined(copayerId))
|
||||
preconditions.checkArgument(_.isNumber(copayerId));
|
||||
|
||||
return this.copayerIds[copayerId];
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Get the amount of registered copayers in this PubKeyRing
|
||||
*
|
||||
* @returns {number} amount of copayers present
|
||||
*/
|
||||
PublicKeyRing.prototype.registeredCopayers = function() {
|
||||
return this.copayersHK.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Returns true if all the needed copayers have joined the public key ring
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PublicKeyRing.prototype.isComplete = function() {
|
||||
return this.remainingCopayers() == 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Returns the number of copayers yet to join to make the public key ring complete
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
PublicKeyRing.prototype.remainingCopayers = function() {
|
||||
return this.totalCopayers - this.registeredCopayers();
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Returns an array of copayer's public keys
|
||||
*
|
||||
* @returns {string[]} a list of hexadecimal strings with the public keys for
|
||||
* the copayers in this ring
|
||||
*/
|
||||
PublicKeyRing.prototype.getAllCopayerIds = function() {
|
||||
return this.copayerIds;
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.myCopayerId = function(i) {
|
||||
/**
|
||||
* @desc
|
||||
* Gets the current user's copayerId
|
||||
*
|
||||
* @returns {string} the extended public key hexadecimal-encoded
|
||||
*/
|
||||
PublicKeyRing.prototype.myCopayerId = function() {
|
||||
return this.getCopayerId(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Throws an error if the public key ring isn't complete
|
||||
*/
|
||||
PublicKeyRing.prototype._checkKeys = function() {
|
||||
|
||||
if (!this.isComplete())
|
||||
throw new Error('dont have required keys yet');
|
||||
if (!this.isComplete()) throw new Error('dont have required keys yet');
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Updates the internal register of the public hex string for a copayer, based
|
||||
* on the value of the hierarchical key stored in copayersHK
|
||||
*
|
||||
* @private
|
||||
* @param {number} index - the index of the copayer to update
|
||||
*/
|
||||
PublicKeyRing.prototype._updateBip = function(index) {
|
||||
var hk = this.copayersHK[index].derive(HDPath.IdBranch);
|
||||
this.copayerIds[index] = hk.eckey.public.toString('hex');
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Sets a nickname for one of the copayers
|
||||
*
|
||||
* @private
|
||||
* @param {number} index - the index of the copayer to update
|
||||
* @param {string} nickname - the new nickname for that copayer
|
||||
*/
|
||||
PublicKeyRing.prototype._setNicknameForIndex = function(index, nickname) {
|
||||
this.nicknameFor[this.copayerIds[index]] = nickname;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Fetch the name of a copayer
|
||||
*
|
||||
* @param {number} index - the index of the copayer
|
||||
* @return {string} the nickname of the index-th copayer
|
||||
*/
|
||||
PublicKeyRing.prototype.nicknameForIndex = function(index) {
|
||||
return this.nicknameFor[this.copayerIds[index]];
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Fetch the name of a copayer using its public key
|
||||
*
|
||||
* @param {string} copayerId - the public key ring of a copayer, hex encoded
|
||||
* @return {string} the nickname of the copayer with such pubkey
|
||||
*/
|
||||
PublicKeyRing.prototype.nicknameForCopayer = function(copayerId) {
|
||||
return this.nicknameFor[copayerId] || 'NN';
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
|
||||
preconditions.checkArgument(newEpk);
|
||||
/**
|
||||
* @desc
|
||||
* Add a copayer into the public key ring.
|
||||
*
|
||||
* @param {string} newHexaExtendedPublicKey - an hex encoded string with the copayer's pubkey
|
||||
* @param {string} nickname - a nickname for this copayer
|
||||
* @return {string} the newHexaExtendedPublicKey parameter
|
||||
*/
|
||||
PublicKeyRing.prototype.addCopayer = function(newHexaExtendedPublicKey, nickname) {
|
||||
preconditions.checkArgument(newHexaExtendedPublicKey && _.isString(newHexaExtendedPublicKey));
|
||||
preconditions.checkArgument(!this.isComplete());
|
||||
preconditions.checkArgument(!nickname || _.isString(nickname));
|
||||
preconditions.checkArgument(!_.any(this.copayersHK,
|
||||
function(copayer) { return copayer.extendedPublicKeyString === newHexaExtendedPublicKey; }
|
||||
));
|
||||
|
||||
if (this.isComplete())
|
||||
throw new Error('PKR already has all required key:' + this.totalCopayers);
|
||||
var newCopayerIndex = this.copayersHK.length;
|
||||
var hierarchicalKey = new HK(newHexaExtendedPublicKey);
|
||||
|
||||
this.copayersHK.forEach(function(b) {
|
||||
if (b.extendedPublicKeyString() === newEpk)
|
||||
throw new Error('PKR already has that key');
|
||||
});
|
||||
this.copayersHK.push(hierarchicalKey);
|
||||
this._updateBip(newCopayerIndex);
|
||||
|
||||
var i = this.copayersHK.length;
|
||||
var bip = new HK(newEpk);
|
||||
this.copayersHK.push(bip);
|
||||
this._updateBip(i);
|
||||
if (nickname) {
|
||||
this._setNicknameForIndex(i, nickname);
|
||||
this._setNicknameForIndex(newCopayerIndex, nickname);
|
||||
}
|
||||
return newEpk;
|
||||
return newHexaExtendedPublicKey;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Get all the public keys for the copayers in this ring, for a given branch of Copay
|
||||
*
|
||||
* @param {number} index - the index for the shared address
|
||||
* @param {boolean} isChange - whether to derive a change address o receive address
|
||||
* @param {number} copayerIndex - the index of the copayer that requested the derivation
|
||||
* @return {Buffer[]} an array of derived public keys in hexa format
|
||||
*/
|
||||
PublicKeyRing.prototype.getPubKeys = function(index, isChange, copayerIndex) {
|
||||
this._checkKeys();
|
||||
|
||||
var path = HDPath.Branch(index, isChange, copayerIndex);
|
||||
var pubKeys = this.publicKeysCache[path];
|
||||
if (!pubKeys) {
|
||||
pubKeys = [];
|
||||
var l = this.copayersHK.length;
|
||||
for (var i = 0; i < l; i++) {
|
||||
var hk = this.copayersHK[i].derive(path);
|
||||
pubKeys[i] = hk.eckey.public;
|
||||
}
|
||||
pubKeys = _.map(this.copayersHK, function(hdKey) {
|
||||
return hdKey.derive(path).eckey.public;
|
||||
});
|
||||
this.publicKeysCache[path] = pubKeys.map(function(pk) {
|
||||
return pk.toString('hex');
|
||||
});
|
||||
|
@ -166,19 +300,39 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange, copayerIndex) {
|
|||
return new Buffer(s, 'hex');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return pubKeys;
|
||||
};
|
||||
|
||||
// TODO this could be cached
|
||||
/**
|
||||
* @desc
|
||||
* Generate a new Script for a copay address generated by index, isChange, and copayerIndex
|
||||
*
|
||||
* @TODO this could be cached
|
||||
*
|
||||
* @param {number} index - the index for the shared address
|
||||
* @param {boolean} isChange - whether to derive a change address o receive address
|
||||
* @param {number} copayerIndex - the index of the copayer that requested the derivation
|
||||
* @returns {bitcore.Script}
|
||||
*/
|
||||
PublicKeyRing.prototype.getRedeemScript = function(index, isChange, copayerIndex) {
|
||||
var pubKeys = this.getPubKeys(index, isChange, copayerIndex);
|
||||
var script = Script.createMultisig(this.requiredCopayers, pubKeys);
|
||||
return script;
|
||||
};
|
||||
|
||||
// TODO this could be cached
|
||||
/**
|
||||
* @desc
|
||||
* Get the address for a multisig based on the given params.
|
||||
*
|
||||
* Caches the address to the branch in the member addressToPath
|
||||
*
|
||||
* @TODO this could be cached
|
||||
*
|
||||
* @param {number} index - the index for the shared address
|
||||
* @param {boolean} isChange - whether to derive a change address o receive address
|
||||
* @param {number} copayerIndex - the index of the copayer that requested the derivation
|
||||
* @returns {bitcore.Address}
|
||||
*/
|
||||
PublicKeyRing.prototype.getAddress = function(index, isChange, id) {
|
||||
var copayerIndex = this.getCosigner(id);
|
||||
var script = this.getRedeemScript(index, isChange, copayerIndex);
|
||||
|
@ -187,7 +341,17 @@ PublicKeyRing.prototype.getAddress = function(index, isChange, id) {
|
|||
return address;
|
||||
};
|
||||
|
||||
// Overloaded to receive a PubkeyString or a consigner index
|
||||
/**
|
||||
* @desc
|
||||
* Get the parameters used to derive a pubkey or a cosigner index
|
||||
*
|
||||
* Overloaded to receive a PubkeyString or a consigner index
|
||||
*
|
||||
* @TODO: Couldn't really figure out what does this do
|
||||
*
|
||||
* @param {number|string} id public key in hex format, or the copayer's index
|
||||
* @return ????
|
||||
*/
|
||||
PublicKeyRing.prototype.getHDParams = function(id) {
|
||||
var copayerIndex = this.getCosigner(id);
|
||||
var index = this.indexes.filter(function(i) {
|
||||
|
@ -198,20 +362,44 @@ PublicKeyRing.prototype.getHDParams = function(id) {
|
|||
return index[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Get the path used to derive a pubkey or a cosigner index for an address
|
||||
*
|
||||
* @param {string} address a multisig p2sh address
|
||||
* @return {HDPath}
|
||||
*/
|
||||
PublicKeyRing.prototype.pathForAddress = function(address) {
|
||||
var path = this.addressToPath[address];
|
||||
if (!path) throw new Error('Couldn\'t find path for address ' + address);
|
||||
return path;
|
||||
};
|
||||
|
||||
// TODO this could be cached
|
||||
/**
|
||||
* @desc
|
||||
* Get the hexadecimal representation of a P2SH script
|
||||
*
|
||||
* @param {number} index - index to use when generating the address
|
||||
* @param {boolean} isChange - generate a change address or a receive addres
|
||||
* @param {number|string} pubkey - index of the copayer, or his public key
|
||||
* @returns {string} hexadecimal encoded P2SH hash
|
||||
*/
|
||||
PublicKeyRing.prototype.getScriptPubKeyHex = function(index, isChange, pubkey) {
|
||||
var copayerIndex = this.getCosigner(pubkey);
|
||||
var addr = this.getAddress(index, isChange, copayerIndex);
|
||||
return Script.createP2SH(addr.payload()).getBuffer().toString('hex');
|
||||
};
|
||||
|
||||
//generate a new address, update index.
|
||||
/**
|
||||
* @desc
|
||||
* Generates a new address and updates the last index used
|
||||
*
|
||||
* @param {truthy} isChange - generate a change address if true, otherwise
|
||||
* generates a receive
|
||||
* @param {number|string} pubkey - the pubkey for the copayer that generates the
|
||||
* address (or index in the keyring)
|
||||
* @returns {bitpay.Address}
|
||||
*/
|
||||
PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) {
|
||||
isChange = !!isChange;
|
||||
var HDParams = this.getHDParams(pubkey);
|
||||
|
@ -221,15 +409,31 @@ PublicKeyRing.prototype.generateAddress = function(isChange, pubkey) {
|
|||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Retrieve the addresses from a getAddressInfo return object
|
||||
*
|
||||
* {@see PublicKeyRing#getAddressInfo}
|
||||
* @returns {string[]} the result of retrieving the addresses from calling
|
||||
*/
|
||||
PublicKeyRing.prototype.getAddresses = function(opts) {
|
||||
return this.getAddressesInfo(opts).map(function(info) {
|
||||
return info.address;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Maps a copayer's public key to his index in the keyring
|
||||
*
|
||||
* @param {number|string|undefined} pubKey - if undefined, returns the SHARED_INDEX
|
||||
* - if a number, just return it
|
||||
* - if a string, assume is the hex encoded public key
|
||||
* @returns {number} the index of the copayer with the given pubkey
|
||||
*/
|
||||
PublicKeyRing.prototype.getCosigner = function(pubKey) {
|
||||
if (typeof pubKey == 'undefined') return HDPath.SHARED_INDEX;
|
||||
if (typeof pubKey == 'number') return pubKey;
|
||||
if (_.isUndefined(pubKey)) return HDPath.SHARED_INDEX;
|
||||
if (_.isNumber(pubKey)) return pubKey;
|
||||
|
||||
var sorted = this.copayersHK.map(function(h, i) {
|
||||
return h.eckey.public.toString('hex');
|
||||
|
@ -241,9 +445,17 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) {
|
|||
if (index == -1) throw new Error('public key is not on the ring');
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Gets information about addresses for a copayer
|
||||
*
|
||||
* @see PublicKeyRing#getAddressesInfoForIndex
|
||||
* @param {Object} opts
|
||||
* @param {string|number} pubkey - the pubkey or index of a copayer in the ring
|
||||
* @returns {AddressInfo[]}
|
||||
*/
|
||||
PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) {
|
||||
var ret = [];
|
||||
var self = this;
|
||||
|
@ -252,53 +464,88 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) {
|
|||
ret = ret.concat(self.getAddressesInfoForIndex(index, opts, copayerIndex));
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef AddressInfo
|
||||
* @property {bitcore.Address} address - the address generated
|
||||
* @property {string} addressStr - the base58 encoded address
|
||||
* @property {boolean} isChange - true if it's a change address
|
||||
* @property {boolean} owned - true if it's an address generated by a copayer
|
||||
*/
|
||||
/**
|
||||
* @desc
|
||||
* Retrieves info about addresses generated by a copayer
|
||||
*
|
||||
* @param {HDParams} index - HDParams of the copayer
|
||||
* @param {Object} opts
|
||||
* @param {boolean} opts.excludeChange - don't append information about change addresses
|
||||
* @param {boolean} opts.excludeMain - don't append information about receive addresses
|
||||
* @param {string|number|undefined} copayerIndex - copayer index, pubkey, or undefined to fetch info
|
||||
* about shared addresses
|
||||
* @return {AddressInfo[]} a list of AddressInfo
|
||||
*/
|
||||
PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayerIndex) {
|
||||
opts = opts || {};
|
||||
|
||||
var isOwned = index.copayerIndex == HDPath.SHARED_INDEX || index.copayerIndex == copayerIndex;
|
||||
|
||||
var isOwned = index.copayerIndex === HDPath.SHARED_INDEX || index.copayerIndex === copayerIndex;
|
||||
var ret = [];
|
||||
if (!opts.excludeChange) {
|
||||
for (var i = 0; i < index.changeIndex; i++) {
|
||||
var a = this.getAddress(i, true, index.copayerIndex);
|
||||
var appendAddressInfo = function(address, isChange) {
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: true,
|
||||
address: address,
|
||||
addressStr: address.toString(),
|
||||
isChange: isChange,
|
||||
owned: isOwned
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!opts.excludeMain) {
|
||||
for (var i = 0; i < index.receiveIndex; i++) {
|
||||
var a = this.getAddress(i, false, index.copayerIndex);
|
||||
ret.unshift({
|
||||
address: a,
|
||||
addressStr: a.toString(),
|
||||
isChange: false,
|
||||
owned: isOwned
|
||||
});
|
||||
for (var i = 0; !opts.excludeChange && i < index.changeIndex; i++) {
|
||||
appendAddressInfo(this.getAddress(i, true, index.copayerIndex), true);
|
||||
}
|
||||
for (var i = 0; !opts.excludeMain && i < index.receiveIndex; i++) {
|
||||
appendAddressInfo(this.getAddress(i, false, index.copayerIndex), false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Retrieve the public keys for all cosigners for a given path
|
||||
*
|
||||
* @param {string} path - the BIP32 path
|
||||
* @return {Buffer[]} the public keys, in buffer format
|
||||
*/
|
||||
PublicKeyRing.prototype.getForPath = function(path) {
|
||||
var p = HDPath.indexesForPath(path);
|
||||
var pubKeys = this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex);
|
||||
return pubKeys;
|
||||
return this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex);
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Retrieve the public keys for all cosigners for multiple paths
|
||||
* @see PublicKeyRing#getForPath
|
||||
*
|
||||
* @param {string[]} paths - the BIP32 paths
|
||||
* @return {Buffer[][]} the public keys, in buffer format
|
||||
*/
|
||||
PublicKeyRing.prototype.getForPaths = function(paths) {
|
||||
preconditions.checkArgument(paths);
|
||||
preconditions.checkArgument(!_.isUndefined(paths));
|
||||
preconditions.checkArgument(_.isArray(paths));
|
||||
preconditions.checkArgument(_.all(paths, _.isString));
|
||||
|
||||
return paths.map(this.getForPath.bind(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Retrieve the public keys for derived addresses and the public keys for copayers
|
||||
*
|
||||
* @TODO: Should this exist? A user should just call getForPath(paths)
|
||||
*
|
||||
* @param {string[]} paths - the paths to be derived
|
||||
* @return {Object} with keys pubKeys and copayerIds
|
||||
*/
|
||||
PublicKeyRing.prototype.forPaths = function(paths) {
|
||||
return {
|
||||
pubKeys: paths.map(this.getForPath.bind(this)),
|
||||
|
@ -306,8 +553,13 @@ PublicKeyRing.prototype.forPaths = function(paths) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
// returns pubkey -> copayerId.
|
||||
/**
|
||||
* @desc
|
||||
* Returns a map from a pubkey of an address to the id that generated it
|
||||
*
|
||||
* @param {string[]} pubkeys - the pubkeys to query
|
||||
* @param {string[]} paths - the paths to query
|
||||
*/
|
||||
PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) {
|
||||
preconditions.checkArgument(pubkeys);
|
||||
preconditions.checkArgument(paths);
|
||||
|
@ -328,71 +580,87 @@ PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
for(var i in inKeyMap)
|
||||
throw new Error('Pubkey not identified')
|
||||
if (_.size(inKeyMap)) {
|
||||
for (var i in inKeyMap) {
|
||||
log.error('Pubkey ' + i + ' not identified');
|
||||
}
|
||||
throw new Error('Pubkeys not identified');
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
// TODO this could be cached
|
||||
PublicKeyRing.prototype._addScriptMap = function(map, path) {
|
||||
var p = HDPath.indexesForPath(path);
|
||||
var script = this.getRedeemScript(p.addressIndex, p.isChange, p.copayerIndex);
|
||||
map[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex');
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Returns a map from address -> public key needed
|
||||
*
|
||||
* @param {HDPath[]} paths - paths to be solved
|
||||
* @returns {Object} a map from addresses to Buffer with the hex pubkeys
|
||||
*/
|
||||
PublicKeyRing.prototype.getRedeemScriptMap = function(paths) {
|
||||
var ret = {};
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = paths[i];
|
||||
this._addScriptMap(ret, path);
|
||||
var p = HDPath.indexesForPath(path);
|
||||
var script = this.getRedeemScript(p.addressIndex, p.isChange, p.copayerIndex);
|
||||
ret[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex');
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Check if another PubKeyRing is similar to this one (checks network name,
|
||||
* requiredCopayers, and totalCopayers). If ignoreId is falsy, also check that
|
||||
* both walletIds match.
|
||||
*
|
||||
* @private
|
||||
* @param {PubKeyRing} inPKR - the other PubKeyRing
|
||||
* @param {boolean} ignoreId - whether to ignore checking for equal walletId
|
||||
* @throws {Error} if the wallets mismatch
|
||||
* @return true
|
||||
*/
|
||||
|
||||
PublicKeyRing.prototype._checkInPKR = function(inPKR, ignoreId) {
|
||||
|
||||
if (!ignoreId && this.walletId !== inPKR.walletId) {
|
||||
if (!ignoreId && this.walletId !== inPKR.walletId)
|
||||
throw new Error('inPKR walletId mismatch');
|
||||
}
|
||||
|
||||
if (this.network.name !== inPKR.network.name) {
|
||||
if (this.network.name !== inPKR.network.name)
|
||||
throw new Error('Network mismatch. Should be ' + this.network.name +
|
||||
' and found ' + inPKR.network.name);
|
||||
}
|
||||
|
||||
if (
|
||||
this.requiredCopayers && inPKR.requiredCopayers &&
|
||||
if (this.requiredCopayers && inPKR.requiredCopayers &&
|
||||
(this.requiredCopayers !== inPKR.requiredCopayers))
|
||||
throw new Error('inPKR requiredCopayers mismatch ' + this.requiredCopayers + '!=' + inPKR.requiredCopayers);
|
||||
throw new Error('inPKR requiredCopayers mismatch ' + this.requiredCopayers +
|
||||
'!=' + inPKR.requiredCopayers);
|
||||
|
||||
if (
|
||||
this.totalCopayers && inPKR.totalCopayers &&
|
||||
(this.totalCopayers !== inPKR.totalCopayers))
|
||||
throw new Error('inPKR totalCopayers mismatch' + this.totalCopayers + '!=' + inPKR.requiredCopayers);
|
||||
if (this.totalCopayers && inPKR.totalCopayers &&
|
||||
this.totalCopayers !== inPKR.totalCopayers)
|
||||
throw new Error('inPKR totalCopayers mismatch' + this.totalCopayers +
|
||||
'!=' + inPKR.requiredCopayers);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Merges the public keys of the wallet passed in as a parameter with ours.
|
||||
*
|
||||
* @param {PublicKeyRing} inPKR
|
||||
* @return {boolean} true if there where changes in our internal state
|
||||
*/
|
||||
PublicKeyRing.prototype._mergePubkeys = function(inPKR) {
|
||||
var self = this;
|
||||
|
||||
var hasChanged = false;
|
||||
var l = self.copayersHK.length;
|
||||
|
||||
if (self.isComplete())
|
||||
return;
|
||||
|
||||
inPKR.copayersHK.forEach(function(b) {
|
||||
var haveIt = false;
|
||||
var epk = b.extendedPublicKeyString();
|
||||
for (var j = 0; j < l; j++) {
|
||||
if (self.copayersHK[j].extendedPublicKeyString() === epk) {
|
||||
haveIt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var haveIt = _.any(self.copayersHK, function(hk) { return hk.extendedPublicKeyString() === epk; });
|
||||
|
||||
if (!haveIt) {
|
||||
if (self.isComplete()) {
|
||||
throw new Error('trying to add more pubkeys, when PKR isComplete at merge');
|
||||
|
@ -408,27 +676,57 @@ PublicKeyRing.prototype._mergePubkeys = function(inPKR) {
|
|||
return hasChanged;
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.setBackupReady = function(copayerId) {
|
||||
/**
|
||||
* @desc
|
||||
* Mark backup as done for us
|
||||
*
|
||||
* @TODO: REVIEW FUNCTIONALITY - it used to have a parameter that was not used at all!
|
||||
*
|
||||
* @return {boolean} true if everybody has backed up their wallet
|
||||
*/
|
||||
PublicKeyRing.prototype.setBackupReady = function() {
|
||||
if (this.isBackupReady()) return false;
|
||||
|
||||
var cid = this.myCopayerId();
|
||||
this.copayersBackup.push(cid);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc returns true if a copayer has backed up his wallet
|
||||
* @param {string=} copayerId - the pubkey of a copayer, defaults to our own's
|
||||
* @return {boolean} if this copayer has backed up
|
||||
*/
|
||||
PublicKeyRing.prototype.isBackupReady = function(copayerId) {
|
||||
var cid = copayerId || this.myCopayerId();
|
||||
return this.copayersBackup.indexOf(cid) != -1;
|
||||
}
|
||||
};
|
||||
|
||||
PublicKeyRing.prototype.isFullyBackup = function(copayerId) {
|
||||
/**
|
||||
* @desc returns true if all copayers have backed up their wallets
|
||||
* @return {boolean}
|
||||
*/
|
||||
PublicKeyRing.prototype.isFullyBackup = function() {
|
||||
return this.remainingBackups() == 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc returns the amount of backups remaining
|
||||
* @return {boolean}
|
||||
*/
|
||||
PublicKeyRing.prototype.remainingBackups = function() {
|
||||
return this.totalCopayers - this.copayersBackup.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Merges this public key ring with another one, optionally ignoring the
|
||||
* wallet id
|
||||
*
|
||||
* @param {PublicKeyRing} inPkr
|
||||
* @param {boolean} ignoreId
|
||||
* @return {boolean} true if the internal state has changed
|
||||
*/
|
||||
PublicKeyRing.prototype.merge = function(inPKR, ignoreId) {
|
||||
this._checkInPKR(inPKR, ignoreId);
|
||||
|
||||
|
@ -440,6 +738,15 @@ PublicKeyRing.prototype.merge = function(inPKR, ignoreId) {
|
|||
return !!hasChanged;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Merges the indexes for addresses generated with another copy of a list of
|
||||
* HDParams
|
||||
*
|
||||
* @param {HDParams[]} indexes - indexes as received from another sources
|
||||
* @return {boolean} true if the internal state has changed
|
||||
*/
|
||||
PublicKeyRing.prototype.mergeIndexes = function(indexes) {
|
||||
var self = this;
|
||||
var hasChanged = false;
|
||||
|
@ -452,6 +759,11 @@ PublicKeyRing.prototype.mergeIndexes = function(indexes) {
|
|||
return !!hasChanged
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc merges information about backups done by another copy of PublicKeyRing
|
||||
* @param {string[]} backups - another copy of backups
|
||||
* @return {boolean} true if the internal state has changed
|
||||
*/
|
||||
PublicKeyRing.prototype._mergeBackups = function(backups) {
|
||||
var self = this;
|
||||
var hasChanged = false;
|
||||
|
@ -463,7 +775,6 @@ PublicKeyRing.prototype._mergeBackups = function(backups) {
|
|||
});
|
||||
|
||||
return !!hasChanged
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = PublicKeyRing;
|
||||
|
|
|
@ -4,7 +4,6 @@ var bitcore = require('bitcore');
|
|||
var _ = require('underscore');
|
||||
var util = bitcore.util;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var TransactionBuilder = bitcore.TransactionBuilder;
|
||||
var Script = bitcore.Script;
|
||||
var Key = bitcore.Key;
|
||||
|
@ -134,12 +133,7 @@ TxProposal.fromObj = function(o, forceOpts) {
|
|||
}
|
||||
o.builder = TransactionBuilder.fromObj(o.builderObj);
|
||||
} catch (e) {
|
||||
|
||||
// backwards (V0) compatatibility fix.
|
||||
if (!o.version) {
|
||||
o.builder = new BuilderMockV0(o.builderObj);
|
||||
o.readonly = 1;
|
||||
};
|
||||
throw new Error("Invalid or Incompatible Backup Detected.");
|
||||
}
|
||||
return new TxProposal(o);
|
||||
};
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var bitcore = require('bitcore');
|
||||
var util = bitcore.util;
|
||||
var Transaction = bitcore.Transaction;
|
||||
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||
var TxProposal = require('./TxProposal');;
|
||||
var TxProposal = require('./TxProposal');
|
||||
var Script = bitcore.Script;
|
||||
var Key = bitcore.Key;
|
||||
var buffertools = bitcore.buffertools;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,18 +4,34 @@ var TxProposals = require('./TxProposals');
|
|||
var PublicKeyRing = require('./PublicKeyRing');
|
||||
var PrivateKey = require('./PrivateKey');
|
||||
var Wallet = require('./Wallet');
|
||||
var preconditions = require('preconditions').instance();
|
||||
|
||||
var _ = require('underscore');
|
||||
var log = require('../../log');
|
||||
|
||||
var Async = module.exports.Async = require('../network/Async');
|
||||
var Insight = module.exports.Insight = require('../blockchain/Insight');
|
||||
var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('../storage/LocalEncrypted');
|
||||
|
||||
/*
|
||||
* WalletFactory
|
||||
/**
|
||||
* @desc
|
||||
* WalletFactory - stores the state for a wallet in creation
|
||||
*
|
||||
* @param {Object} config - configuration for this wallet
|
||||
*
|
||||
* @TODO: Don't pass a class for these three components
|
||||
* -- send a factory or instance, the 'new' call considered harmful for refactoring
|
||||
* -- arguable, since all of them is called with an object as argument.
|
||||
* -- Still, could it be hard to refactor? (for example, what if we want to fail hard if a network call gets interrupted?)
|
||||
* @param {Storage} config.Storage - the class to instantiate to store the wallet (StorageLocalEncrypted by default)
|
||||
* @param {Object} config.storage - the configuration to be sent to the Storage constructor
|
||||
* @param {Network} config.Network - the class to instantiate to make network requests to copayers (the Async module by default)
|
||||
* @param {Object} config.network - the configuration to be sent to the Network constructor
|
||||
* @param {Blockchain} config.Blockchain - the class to instantiate to get information about the blockchain (Insight by default)
|
||||
* @param {Object} config.blockchain - the configuration to be sent to the Blockchain constructor
|
||||
* @param {string} config.networkName - the name of the bitcoin network to use ('testnet' or 'livenet')
|
||||
* @TODO: Investigate what parameters go inside this object
|
||||
* @param {Object} config.wallet - default configuration for the wallet
|
||||
* @TODO: put `version` inside of the config object
|
||||
* @param {string} version - the version of copay for which this wallet was generated (for example, 0.4.7)
|
||||
*/
|
||||
|
||||
function WalletFactory(config, version) {
|
||||
var self = this;
|
||||
config = config || {};
|
||||
|
@ -31,8 +47,20 @@ function WalletFactory(config, version) {
|
|||
this.networkName = config.networkName;
|
||||
this.walletDefaults = config.wallet;
|
||||
this.version = version;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc
|
||||
* Returns true if the storage instance can retrieve the following keys using a given walletId
|
||||
* <ul>
|
||||
* <li><tt>publicKeyRing</tt></li>
|
||||
* <li><tt>txProposals</tt></li>
|
||||
* <li><tt>opts</tt></li>
|
||||
* <li><tt>privateKey</tt></li>
|
||||
* </ul>
|
||||
* @param {string} walletId
|
||||
* @return {boolean} true if all the keys are present in the storage instance
|
||||
*/
|
||||
WalletFactory.prototype._checkRead = function(walletId) {
|
||||
var s = this.storage;
|
||||
var ret =
|
||||
|
@ -43,6 +71,12 @@ WalletFactory.prototype._checkRead = function(walletId) {
|
|||
return !!ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Deserialize an object to a Wallet
|
||||
* @param {Object} obj
|
||||
* @param {string[]} skipFields - fields to skip when importing
|
||||
* @return {Wallet}
|
||||
*/
|
||||
WalletFactory.prototype.fromObj = function(obj, skipFields) {
|
||||
|
||||
// not stored options
|
||||
|
@ -67,6 +101,13 @@ WalletFactory.prototype.fromObj = function(obj, skipFields) {
|
|||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Imports a wallet from an encrypted base64 object
|
||||
* @param {string} base64 - the base64 encoded object
|
||||
* @param {string} password - password to decrypt it
|
||||
* @param {string[]} skipFields - fields to ignore when importing
|
||||
* @return {Wallet}
|
||||
*/
|
||||
WalletFactory.prototype.fromEncryptedObj = function(base64, password, skipFields) {
|
||||
this.storage._setPassphrase(password);
|
||||
var walletObj = this.storage.import(base64);
|
||||
|
@ -75,14 +116,29 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password, skipFields
|
|||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* @TODO: import is a reserved keyword! DONT USE IT
|
||||
* @TODO: this is essentialy the same method as {@link WalletFactory#fromEncryptedObj}!
|
||||
* @desc Imports a wallet from an encrypted base64 object
|
||||
* @param {string} base64 - the base64 encoded object
|
||||
* @param {string} password - password to decrypt it
|
||||
* @param {string[]} skipFields - fields to ignore when importing
|
||||
* @return {Wallet}
|
||||
*/
|
||||
WalletFactory.prototype.import = function(base64, password, skipFields) {
|
||||
var self = this;
|
||||
var w = self.fromEncryptedObj(base64, password, skipFields);
|
||||
|
||||
if (!w) throw new Error('Wrong password');
|
||||
return w;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Retrieve a wallet from storage
|
||||
* @param {string} walletId - the wallet id
|
||||
* @param {string[]} skipFields - parameters to ignore when importing
|
||||
* @return {Wallet}
|
||||
*/
|
||||
WalletFactory.prototype.read = function(walletId, skipFields) {
|
||||
if (!this._checkRead(walletId))
|
||||
return false;
|
||||
|
@ -103,6 +159,25 @@ WalletFactory.prototype.read = function(walletId, skipFields) {
|
|||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc This method instantiates a wallet
|
||||
*
|
||||
* @param {Object} opts
|
||||
* @param {string} opts.id
|
||||
* @param {PrivateKey=} opts.privateKey
|
||||
* @param {string=} opts.privateKeyHex
|
||||
* @param {number} opts.requiredCopayers
|
||||
* @param {number} opts.totalCopayers
|
||||
* @param {PublicKeyRing=} opts.publicKeyRing
|
||||
* @param {string} opts.nickname
|
||||
* @param {string} opts.passphrase
|
||||
* @TODO: Figure out what is this parameter
|
||||
* @param {?} opts.spendUnconfirmed this.walletDefaults.spendUnconfirmed ??
|
||||
* @TODO: Figure out in what unit is this reconnect delay.
|
||||
* @param {number} opts.reconnectDelay milliseconds?
|
||||
* @param {number=} opts.version
|
||||
* @return {Wallet}
|
||||
*/
|
||||
WalletFactory.prototype.create = function(opts) {
|
||||
opts = opts || {};
|
||||
log.debug('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey'));
|
||||
|
@ -156,7 +231,11 @@ WalletFactory.prototype.create = function(opts) {
|
|||
return w;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc Checks if a version is compatible with the current version
|
||||
* @param {string} inVersion - a version, with major, minor, and revision, period-separated (x.y.z)
|
||||
* @throws {Error} if there's a major version difference
|
||||
*/
|
||||
WalletFactory.prototype._checkVersion = function(inVersion) {
|
||||
var thisV = this.version.split('.');
|
||||
var thisV0 = parseInt(thisV[0]);
|
||||
|
@ -172,14 +251,23 @@ WalletFactory.prototype._checkVersion = function(inVersion) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc Throw an error if the network name is different to {@link WalletFactory#networkName}
|
||||
* @param {string} inNetworkName - the network name to check
|
||||
* @throws {Error}
|
||||
*/
|
||||
WalletFactory.prototype._checkNetwork = function(inNetworkName) {
|
||||
if (this.networkName !== inNetworkName) {
|
||||
throw new Error('This Wallet is configured for ' + inNetworkName + ' while currently Copay is configured for: ' + this.networkName + '. Check your settings.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @desc Retrieve a wallet from the storage
|
||||
* @param {string} walletId - the id of the wallet
|
||||
* @param {string} passphrase - the passphrase to decode it
|
||||
* @return {Wallet}
|
||||
*/
|
||||
WalletFactory.prototype.open = function(walletId, passphrase) {
|
||||
this.storage._setPassphrase(passphrase);
|
||||
var w = this.read(walletId);
|
||||
|
@ -190,6 +278,10 @@ WalletFactory.prototype.open = function(walletId, passphrase) {
|
|||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Retrieve all wallets stored without encription in the storage instance
|
||||
* @returns {Wallet[]}
|
||||
*/
|
||||
WalletFactory.prototype.getWallets = function() {
|
||||
var ret = this.storage.getWallets();
|
||||
ret.forEach(function(i) {
|
||||
|
@ -198,6 +290,14 @@ WalletFactory.prototype.getWallets = function() {
|
|||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Deletes this wallet. This involves removing it from the storage instance
|
||||
* @TODO: delete is a reserved javascript keyword. NEVER USE IT.
|
||||
* @param {string} walletId
|
||||
* @TODO: Why is there a callback?
|
||||
* @callback cb
|
||||
* @return {?} the result of the callback
|
||||
*/
|
||||
WalletFactory.prototype.delete = function(walletId, cb) {
|
||||
var s = this.storage;
|
||||
s.deleteWallet(walletId);
|
||||
|
@ -205,6 +305,9 @@ WalletFactory.prototype.delete = function(walletId, cb) {
|
|||
return cb();
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc Pass through to {@link Wallet#secret}
|
||||
*/
|
||||
WalletFactory.prototype.decodeSecret = function(secret) {
|
||||
try {
|
||||
return Wallet.decodeSecret(secret);
|
||||
|
@ -213,7 +316,26 @@ WalletFactory.prototype.decodeSecret = function(secret) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @callback walletCreationCallback
|
||||
* @param {?=} err - an error, if any, that happened during the wallet creation
|
||||
* @param {Wallet=} wallet - the wallet created
|
||||
*/
|
||||
|
||||
/**
|
||||
* @desc Start the network functionality.
|
||||
*
|
||||
* Start up the Network instance and try to join a wallet defined by the
|
||||
* parameter <tt>secret</tt> using the parameter <tt>nickname</tt>. Encode
|
||||
* information locally using <tt>passphrase</tt>. <tt>privateHex</tt> is the
|
||||
* private extended master key. <tt>cb</tt> has two params: error and wallet.
|
||||
*
|
||||
* @param {string} secret - the wallet secret
|
||||
* @param {string} nickname - a nickname for the current user
|
||||
* @param {string} passphrase - a passphrase to use to encrypt the wallet for persistance
|
||||
* @param {string} privateHex - the private extended master key
|
||||
* @param {walletCreationCallback} cb - a callback
|
||||
*/
|
||||
WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphrase, privateHex, cb) {
|
||||
var self = this;
|
||||
var s = self.decodeSecret(secret);
|
||||
|
|
|
@ -235,7 +235,8 @@ Network.prototype._setupConnectionHandlers = function(cb) {
|
|||
self.socket.on('connect', function() {
|
||||
|
||||
self.socket.on('disconnect', function() {
|
||||
self.cleanUp();
|
||||
var pubKey = self.getKey().public.toString('hex');
|
||||
self.socket.emit('subscribe', pubKey);
|
||||
});
|
||||
|
||||
if (typeof cb === 'function') cb();
|
||||
|
|
|
@ -29,7 +29,7 @@ module.exports = function(config) {
|
|||
'lib/angular-foundation/mm-foundation.min.js',
|
||||
'lib/angular-foundation/mm-foundation-tpls.min.js',
|
||||
'lib/angular-gettext/dist/angular-gettext.min.js',
|
||||
'lib/assert/assert.js',
|
||||
'lib/inherits/inherits.js',
|
||||
'lib/bitcore.js',
|
||||
'lib/underscore/underscore.js',
|
||||
'lib/crypto-js/rollups/sha256.js',
|
||||
|
|
19
package.json
19
package.json
|
@ -11,8 +11,8 @@
|
|||
},
|
||||
"version": "0.4.7",
|
||||
"dependencies": {
|
||||
"assert": "^1.1.2",
|
||||
"browser-request": "^0.3.2",
|
||||
"inherits": "^2.0.1",
|
||||
"mocha": "^1.18.2",
|
||||
"mocha-lcov-reporter": "0.0.1",
|
||||
"optimist": "^0.6.1",
|
||||
|
@ -31,7 +31,8 @@
|
|||
"test": "sh test/run.sh",
|
||||
"dist": "node shell/scripts/dist.js",
|
||||
"sign": "gpg -u 1112CFA1 --output browser-extensions/firefox/copay.xpi.sig --detach-sig browser-extensions/firefox/copay.xpi; gpg -u 1112CFA1 --output browser-extensions/chrome/copay-chrome-extension.zip.sig --detach-sig browser-extensions/chrome/copay-chrome-extension.zip",
|
||||
"verify": "gpg --verify browser-extensions/firefox/copay.xpi.sig browser-extensions/firefox/copay.xpi; gpg --verify browser-extensions/chrome/copay-chrome-extension.zip.sig browser-extensions/chrome/copay-chrome-extension.zip"
|
||||
"verify": "gpg --verify browser-extensions/firefox/copay.xpi.sig browser-extensions/firefox/copay.xpi; gpg --verify browser-extensions/chrome/copay-chrome-extension.zip.sig browser-extensions/chrome/copay-chrome-extension.zip",
|
||||
"postinstall": "./node_modules/.bin/grunt"
|
||||
},
|
||||
"keywords": [
|
||||
"wallet",
|
||||
|
@ -41,23 +42,27 @@
|
|||
],
|
||||
"devDependencies": {
|
||||
"async": "0.9.0",
|
||||
"bitcore": "0.1.35",
|
||||
"blanket": "1.1.6",
|
||||
"browser-pack": "2.0.1",
|
||||
"browser-request": "0.3.2",
|
||||
"browserify": "3.32.1",
|
||||
"buffertools": "2.0.1",
|
||||
"chai": "1.9.1",
|
||||
"cli-color": "0.3.2",
|
||||
"commander": "2.1.0",
|
||||
"coveralls": "2.10.0",
|
||||
"crypto-js": "3.1.2",
|
||||
"express": "4.0.0",
|
||||
"github-releases": "0.2.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-browserify": "2.0.8",
|
||||
"grunt-cli": "^0.1.13",
|
||||
"grunt-contrib-concat": "0.5.0",
|
||||
"grunt-contrib-cssmin": "0.10.0",
|
||||
"grunt-contrib-uglify": "^0.5.1",
|
||||
"grunt-contrib-watch": "0.5.3",
|
||||
"grunt-markdown": "0.5.0",
|
||||
"bitcore": "0.1.35",
|
||||
"grunt-mocha-test": "0.8.2",
|
||||
"grunt-shell": "0.6.4",
|
||||
"grunt-angular-gettext": "^0.2.15",
|
||||
|
@ -71,13 +76,11 @@
|
|||
"mocha-lcov-reporter": "0.0.1",
|
||||
"mock-fs": "^2.3.1",
|
||||
"node-cryptojs-aes": "0.4.0",
|
||||
"request": "2.40.0",
|
||||
"shelljs": "0.3.0",
|
||||
"socket.io-client": "1.0.6",
|
||||
"travis-cov": "0.2.5",
|
||||
"uglifyify": "1.2.3",
|
||||
"crypto-js": "3.1.2",
|
||||
"shelljs": "0.3.0",
|
||||
"browser-request": "0.3.2",
|
||||
"request": "2.40.0"
|
||||
"uglifyify": "1.2.3"
|
||||
},
|
||||
"main": "app.js",
|
||||
"homepage": "https://github.com/bitpay/copay",
|
||||
|
|
|
@ -133,7 +133,7 @@ describe('TxProposal', function() {
|
|||
builderObj: b.toObj(),
|
||||
inputChainPaths: ['m/1'],
|
||||
});
|
||||
}).should.throw('Invalid');
|
||||
}).should.throw('Invalid or Incompatible Backup Detected');
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -432,16 +432,13 @@ describe('WalletFactory model', function() {
|
|||
wf.network.start.getCall(0).args[0].privkey.length.should.equal(64); //privkey is hex of private key buffer
|
||||
});
|
||||
});
|
||||
describe('dont break backwards compatibility of wallets', function() {
|
||||
it('should be able to import unencrypted legacy wallet TxProposal: v0', function() {
|
||||
describe('break backwards compatibility with older versions', function() {
|
||||
it('should\'nt be able to import unencrypted legacy wallet TxProposal: v0', function() {
|
||||
|
||||
(function() {
|
||||
var wf = new WalletFactory(config, '0.0.5');
|
||||
var w = wf.fromObj(JSON.parse(legacyO));
|
||||
|
||||
should.exist(w);
|
||||
w.id.should.equal('55d4bd062d32f90a');
|
||||
should.exist(w.publicKeyRing.getCopayerId);
|
||||
should.exist(w.txProposals.toObj());
|
||||
should.exist(w.privateKey.toObj());
|
||||
}).should.throw('Invalid or Incompatible Backup Detected');
|
||||
});
|
||||
|
||||
it('should be able to import simple 1-of-1 encrypted legacy testnet wallet', function(done) {
|
||||
|
|
|
@ -46,9 +46,6 @@ var createBundle = function(opts) {
|
|||
b.require('underscore', {
|
||||
expose: 'underscore'
|
||||
});
|
||||
b.require('assert', {
|
||||
expose: 'assert'
|
||||
});
|
||||
|
||||
b.require('./copay', {
|
||||
expose: 'copay'
|
||||
|
|
Loading…
Reference in New Issue