commit
58eadeb443
117
lib/hdpath.js
117
lib/hdpath.js
|
@ -1,117 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// 90.2% typed (by google's closure-compiler account)
|
|
||||||
|
|
||||||
var preconditions = require('preconditions').singleton();
|
|
||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @namespace
|
|
||||||
* @desc
|
|
||||||
* HDPath contains helper functions to handle BIP32 branches as
|
|
||||||
* Copay uses them.
|
|
||||||
* Based on https://github.com/maraoz/bips/blob/master/bip-NNNN.mediawiki
|
|
||||||
* <pre>
|
|
||||||
* m / purpose' / copayerIndex / change:boolean / addressIndex
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
var HDPath = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @desc Copay's BIP45 purpose code
|
|
||||||
* @const
|
|
||||||
* @type number
|
|
||||||
*/
|
|
||||||
HDPath.PURPOSE = 45;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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.checkArgument(_.isNumber(addressIndex));
|
|
||||||
preconditions.checkArgument(_.isBoolean(isChange));
|
|
||||||
|
|
||||||
var ret = 'm/' +
|
|
||||||
(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 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.checkArgument(_.isString(path));
|
|
||||||
|
|
||||||
var s = path.split('/');
|
|
||||||
var l = s.length;
|
|
||||||
return {
|
|
||||||
isChange: s[l - 2] === '1',
|
|
||||||
addressIndex: parseInt(s[l - 1], 10),
|
|
||||||
copayerIndex: parseInt(s[l - 3], 10)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
|
@ -13,6 +13,7 @@ Address.create = function(opts) {
|
||||||
|
|
||||||
x.createdOn = Math.floor(Date.now() / 1000);
|
x.createdOn = Math.floor(Date.now() / 1000);
|
||||||
x.address = opts.address;
|
x.address = opts.address;
|
||||||
|
x.isChange = opts.isChange;
|
||||||
x.path = opts.path;
|
x.path = opts.path;
|
||||||
x.publicKeys = opts.publicKeys;
|
x.publicKeys = opts.publicKeys;
|
||||||
return x;
|
return x;
|
||||||
|
@ -23,6 +24,7 @@ Address.fromObj = function(obj) {
|
||||||
|
|
||||||
x.createdOn = obj.createdOn;
|
x.createdOn = obj.createdOn;
|
||||||
x.address = obj.address;
|
x.address = obj.address;
|
||||||
|
x.isChange = obj.isChange;
|
||||||
x.path = obj.path;
|
x.path = obj.path;
|
||||||
x.publicKeys = obj.publicKeys;
|
x.publicKeys = obj.publicKeys;
|
||||||
return x;
|
return x;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var HDPath = require('../hdpath');
|
|
||||||
|
var SHARED_INDEX = 0x80000000 - 1;
|
||||||
|
|
||||||
function AddressManager() {
|
function AddressManager() {
|
||||||
this.version = '1.0.0';
|
this.version = '1.0.0';
|
||||||
|
@ -12,7 +13,7 @@ AddressManager.create = function(opts) {
|
||||||
|
|
||||||
x.receiveAddressIndex = 0;
|
x.receiveAddressIndex = 0;
|
||||||
x.changeAddressIndex = 0;
|
x.changeAddressIndex = 0;
|
||||||
x.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : HDPath.SHARED_INDEX;
|
x.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : SHARED_INDEX;
|
||||||
|
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
@ -28,6 +29,7 @@ AddressManager.fromObj = function(obj) {
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
AddressManager.prototype._incrementIndex = function(isChange) {
|
AddressManager.prototype._incrementIndex = function(isChange) {
|
||||||
if (isChange) {
|
if (isChange) {
|
||||||
this.changeAddressIndex++;
|
this.changeAddressIndex++;
|
||||||
|
@ -37,7 +39,10 @@ AddressManager.prototype._incrementIndex = function(isChange) {
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressManager.prototype.getCurrentAddressPath = function(isChange) {
|
AddressManager.prototype.getCurrentAddressPath = function(isChange) {
|
||||||
return HDPath.Branch(isChange ? this.changeAddressIndex : this.receiveAddressIndex, isChange, this.copayerIndex);
|
return 'm/' +
|
||||||
|
this.copayerIndex + '/' +
|
||||||
|
(isChange ? 1 : 0) + '/' +
|
||||||
|
(isChange ? this.changeAddressIndex : this.receiveAddressIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
AddressManager.prototype.getNewAddressPath = function(isChange) {
|
AddressManager.prototype.getNewAddressPath = function(isChange) {
|
||||||
|
|
|
@ -122,7 +122,9 @@ Wallet.prototype.createAddress = function(isChange) {
|
||||||
$.checkState(this.isComplete());
|
$.checkState(this.isComplete());
|
||||||
|
|
||||||
var path = this.addressManager.getNewAddressPath(isChange);
|
var path = this.addressManager.getNewAddressPath(isChange);
|
||||||
return Address.create(WalletUtils.deriveAddress(this.publicKeyRing, path, this.m, this.network));
|
var address = Address.create(WalletUtils.deriveAddress(this.publicKeyRing, path, this.m, this.network));
|
||||||
|
address.isChange = isChange;
|
||||||
|
return address;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ var Explorers = require('bitcore-explorers');
|
||||||
var ClientError = require('./clienterror');
|
var ClientError = require('./clienterror');
|
||||||
var Utils = require('./utils');
|
var Utils = require('./utils');
|
||||||
var Storage = require('./storage');
|
var Storage = require('./storage');
|
||||||
var HDPath = require('./hdpath');
|
|
||||||
var WalletUtils = require('./walletutils');
|
var WalletUtils = require('./walletutils');
|
||||||
|
|
||||||
var Wallet = require('./model/wallet');
|
var Wallet = require('./model/wallet');
|
||||||
|
@ -290,8 +289,8 @@ WalletService.prototype.getMainAddresses = function(opts, cb) {
|
||||||
|
|
||||||
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
|
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
var mainAddresses = _.filter(addresses, function(x) {
|
var mainAddresses = _.filter(addresses, {
|
||||||
return !HDPath.indexesForPath(x.path).isChange;
|
isChange: false
|
||||||
});
|
});
|
||||||
|
|
||||||
return cb(null, mainAddresses);
|
return cb(null, mainAddresses);
|
||||||
|
|
|
@ -10,7 +10,6 @@ var PrivateKey = Bitcore.PrivateKey;
|
||||||
var PublicKey = Bitcore.PublicKey;
|
var PublicKey = Bitcore.PublicKey;
|
||||||
var crypto = Bitcore.crypto;
|
var crypto = Bitcore.crypto;
|
||||||
var encoding = Bitcore.encoding;
|
var encoding = Bitcore.encoding;
|
||||||
var HDPath = require('./hdpath');
|
|
||||||
var Utils = require('./utils');
|
var Utils = require('./utils');
|
||||||
|
|
||||||
function WalletUtils() {};
|
function WalletUtils() {};
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var HDPath = require('../lib/hdpath');
|
|
||||||
|
|
||||||
describe('HDPath model', function() {
|
|
||||||
it('should have the correct constants', function() {
|
|
||||||
HDPath.MAX_NON_HARDENED.should.equal(Math.pow(2, 31) - 1);
|
|
||||||
HDPath.SHARED_INDEX.should.equal(HDPath.MAX_NON_HARDENED);
|
|
||||||
HDPath.ID_INDEX.should.equal(HDPath.SHARED_INDEX - 1);
|
|
||||||
HDPath.IdFullBranch.should.equal('m/45\'/2147483646/0/0');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get the correct branches', function() {
|
|
||||||
// shared branch (no cosigner index specified)
|
|
||||||
HDPath.FullBranch(0, false).should.equal('m/45\'/2147483647/0/0');
|
|
||||||
|
|
||||||
// copayer 0, address 0, external address (receiving)
|
|
||||||
HDPath.FullBranch(0, false, 0).should.equal('m/45\'/0/0/0');
|
|
||||||
|
|
||||||
// copayer 0, address 10, external address (receiving)
|
|
||||||
HDPath.FullBranch(0, false, 10).should.equal('m/45\'/10/0/0');
|
|
||||||
|
|
||||||
// copayer 0, address 0, internal address (change)
|
|
||||||
HDPath.FullBranch(0, true, 0).should.equal('m/45\'/0/1/0');
|
|
||||||
|
|
||||||
// copayer 0, address 10, internal address (change)
|
|
||||||
HDPath.FullBranch(10, true, 0).should.equal('m/45\'/0/1/10');
|
|
||||||
|
|
||||||
// copayer 7, address 10, internal address (change)
|
|
||||||
HDPath.FullBranch(10, true, 7).should.equal('m/45\'/7/1/10');
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
|
||||||
['m/45\'/0/0/0', {
|
|
||||||
index: 0,
|
|
||||||
isChange: false
|
|
||||||
}],
|
|
||||||
['m/45\'/0/0/1', {
|
|
||||||
index: 1,
|
|
||||||
isChange: false
|
|
||||||
}],
|
|
||||||
['m/45\'/0/0/2', {
|
|
||||||
index: 2,
|
|
||||||
isChange: false
|
|
||||||
}],
|
|
||||||
['m/45\'/0/1/0', {
|
|
||||||
index: 0,
|
|
||||||
isChange: true
|
|
||||||
}],
|
|
||||||
['m/45\'/0/1/1', {
|
|
||||||
index: 1,
|
|
||||||
isChange: true
|
|
||||||
}],
|
|
||||||
['m/45\'/0/1/2', {
|
|
||||||
index: 2,
|
|
||||||
isChange: true
|
|
||||||
}],
|
|
||||||
['m/45\'/0/0/900', {
|
|
||||||
index: 900,
|
|
||||||
isChange: false
|
|
||||||
}],
|
|
||||||
].forEach(function(datum) {
|
|
||||||
var path = datum[0];
|
|
||||||
var result = datum[1];
|
|
||||||
it('should get the correct indexes for path ' + path, function() {
|
|
||||||
var i = HDPath.indexesForPath(path);
|
|
||||||
i.addressIndex.should.equal(result.index);
|
|
||||||
i.isChange.should.equal(result.isChange);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -107,7 +107,9 @@ helpers.stubUtxos = function(server, wallet, amounts, cb) {
|
||||||
scriptPubKey: address.getScriptPubKey(wallet.m).toBuffer().toString('hex'),
|
scriptPubKey: address.getScriptPubKey(wallet.m).toBuffer().toString('hex'),
|
||||||
address: address.address,
|
address: address.address,
|
||||||
};
|
};
|
||||||
obj.toObject = function() {return obj;};
|
obj.toObject = function() {
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
blockExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
|
blockExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos);
|
||||||
|
@ -534,6 +536,7 @@ describe('Copay server', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
address.should.exist;
|
address.should.exist;
|
||||||
address.address.should.equal('38Jf1QE7ddXscW76ACgJrNkMWBwDAgMm6M');
|
address.address.should.equal('38Jf1QE7ddXscW76ACgJrNkMWBwDAgMm6M');
|
||||||
|
address.isChange.should.be.false;
|
||||||
address.path.should.equal('m/2147483647/0/0');
|
address.path.should.equal('m/2147483647/0/0');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -669,6 +672,13 @@ describe('Copay server', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
balance.totalAmount.should.equal(helpers.toSatoshi(300));
|
balance.totalAmount.should.equal(helpers.toSatoshi(300));
|
||||||
balance.lockedAmount.should.equal(helpers.toSatoshi(100));
|
balance.lockedAmount.should.equal(helpers.toSatoshi(100));
|
||||||
|
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
||||||
|
should.not.exist(err);
|
||||||
|
var change = _.filter(addresses, {
|
||||||
|
isChange: true
|
||||||
|
});
|
||||||
|
change.length.should.equal(1);
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue