Merge pull request #6 from isocolsky/addressable
refactor address manager
This commit is contained in:
commit
cf6acad277
|
@ -0,0 +1,38 @@
|
||||||
|
var _ = require('lodash');
|
||||||
|
var HDPath = require('./hdpath');
|
||||||
|
|
||||||
|
function AddressManager(opts) {
|
||||||
|
this.receiveAddressIndex = 0;
|
||||||
|
this.changeAddressIndex = 0;
|
||||||
|
this.copayerIndex = (opts && _.isNumber(opts.copayerIndex)) ? opts.copayerIndex : HDPath.SHARED_INDEX;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
AddressManager.fromObj = function (obj) {
|
||||||
|
var x = new AddressManager();
|
||||||
|
|
||||||
|
x.receiveAddressIndex = obj.receiveAddressIndex;
|
||||||
|
x.changeAddressIndex = obj.changeAddressIndex;
|
||||||
|
x.copayerIndex = obj.copayerIndex;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
};
|
||||||
|
|
||||||
|
AddressManager.prototype._incrementIndex = function (isChange) {
|
||||||
|
if (isChange) {
|
||||||
|
this.changeAddressIndex++;
|
||||||
|
} else {
|
||||||
|
this.receiveAddressIndex++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AddressManager.prototype.getCurrentAddressPath = function (isChange) {
|
||||||
|
return HDPath.Branch(isChange ? this.changeAddressIndex : this.receiveAddressIndex, isChange, this.copayerIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
AddressManager.prototype.getNewAddressPath = function (isChange) {
|
||||||
|
this._incrementIndex(isChange);
|
||||||
|
return this.getCurrentAddressPath(isChange);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = AddressManager;
|
|
@ -6,7 +6,7 @@ var util = require('util');
|
||||||
var Bitcore = require('bitcore');
|
var Bitcore = require('bitcore');
|
||||||
var HDPublicKey = Bitcore.HDPublicKey;
|
var HDPublicKey = Bitcore.HDPublicKey;
|
||||||
|
|
||||||
var Addressable = require('./Addressable');
|
var AddressManager = require('./addressmanager');
|
||||||
|
|
||||||
|
|
||||||
var VERSION = '1.0.0';
|
var VERSION = '1.0.0';
|
||||||
|
@ -15,7 +15,6 @@ var MESSAGE_SIGNING_PATH = "m/1/0";
|
||||||
function Copayer(opts) {
|
function Copayer(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
opts.copayerIndex = opts.copayerIndex || 0;
|
opts.copayerIndex = opts.copayerIndex || 0;
|
||||||
Copayer.super_.apply(this, [opts]);
|
|
||||||
|
|
||||||
this.version = VERSION;
|
this.version = VERSION;
|
||||||
this.createdOn = Math.floor(Date.now() / 1000);
|
this.createdOn = Math.floor(Date.now() / 1000);
|
||||||
|
@ -24,10 +23,9 @@ function Copayer(opts) {
|
||||||
this.xPubKey = opts.xPubKey;
|
this.xPubKey = opts.xPubKey;
|
||||||
this.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently
|
this.xPubKeySignature = opts.xPubKeySignature; // So third parties can check independently
|
||||||
this.signingPubKey = this.getSigningPubKey();
|
this.signingPubKey = this.getSigningPubKey();
|
||||||
|
this.addressManager = new AddressManager({ copayerIndex: opts.copayerIndex });
|
||||||
};
|
};
|
||||||
|
|
||||||
util.inherits(Copayer, Addressable);
|
|
||||||
|
|
||||||
Copayer.prototype.getSigningPubKey = function() {
|
Copayer.prototype.getSigningPubKey = function() {
|
||||||
if (!this.xPubKey) return null;
|
if (!this.xPubKey) return null;
|
||||||
return HDPublicKey.fromString(this.xPubKey).derive(MESSAGE_SIGNING_PATH).publicKey.toString();
|
return HDPublicKey.fromString(this.xPubKey).derive(MESSAGE_SIGNING_PATH).publicKey.toString();
|
||||||
|
@ -42,8 +40,8 @@ Copayer.fromObj = function(obj) {
|
||||||
x.xPubKey = obj.xPubKey;
|
x.xPubKey = obj.xPubKey;
|
||||||
x.xPubKeySignature = obj.xPubKeySignature;
|
x.xPubKeySignature = obj.xPubKeySignature;
|
||||||
x.signingPubKey = obj.signingPubKey;
|
x.signingPubKey = obj.signingPubKey;
|
||||||
|
x.addressManager = AddressManager.fromObj(obj.addressManager);
|
||||||
|
|
||||||
Wallet.super_.prototype.fromObj.apply(this, [obj]);
|
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,11 @@ var BitcoreAddress = Bitcore.Address;
|
||||||
|
|
||||||
var Address = require('./address');
|
var Address = require('./address');
|
||||||
var Copayer = require('./copayer');
|
var Copayer = require('./copayer');
|
||||||
var Addressable = require('./Addressable');
|
var AddressManager = require('./addressmanager');
|
||||||
|
|
||||||
var VERSION = '1.0.0';
|
var VERSION = '1.0.0';
|
||||||
|
|
||||||
function Wallet(opts) {
|
function Wallet(opts) {
|
||||||
Wallet.super_.apply(this, arguments);
|
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
this.version = VERSION;
|
this.version = VERSION;
|
||||||
|
@ -28,6 +27,7 @@ function Wallet(opts) {
|
||||||
this.copayers = [];
|
this.copayers = [];
|
||||||
this.pubKey = opts.pubKey;
|
this.pubKey = opts.pubKey;
|
||||||
this.isTestnet = false;
|
this.isTestnet = false;
|
||||||
|
this.addressManager = new AddressManager();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* For compressed keys, m*73 + n*34 <= 496 */
|
/* For compressed keys, m*73 + n*34 <= 496 */
|
||||||
|
@ -45,7 +45,6 @@ Wallet.COPAYER_PAIR_LIMITS = {
|
||||||
11: 1,
|
11: 1,
|
||||||
12: 1,
|
12: 1,
|
||||||
};
|
};
|
||||||
util.inherits(Wallet, Addressable);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the maximum allowed number of required copayers.
|
* Get the maximum allowed number of required copayers.
|
||||||
|
@ -76,8 +75,8 @@ Wallet.fromObj = function(obj) {
|
||||||
});
|
});
|
||||||
x.pubKey = obj.pubKey;
|
x.pubKey = obj.pubKey;
|
||||||
x.isTestnet = obj.isTestnet;
|
x.isTestnet = obj.isTestnet;
|
||||||
|
x.addressManager = AddressManager.fromObj(obj.addressManager);
|
||||||
|
|
||||||
Wallet.super_.prototype.fromObj.apply(this, [obj]);
|
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -101,9 +100,9 @@ Wallet.prototype._getBitcoreNetwork = function() {
|
||||||
return this.isTestnet ? Bitcore.Networks.testnet : Bitcore.Networks.livenet;
|
return this.isTestnet ? Bitcore.Networks.testnet : Bitcore.Networks.livenet;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wallet.prototype.createAddress = function(isChange) {
|
||||||
Wallet.prototype.createAddress = function(path) {
|
var path = this.addressManager.getNewAddressPath(isChange);
|
||||||
|
|
||||||
var publicKeys = _.map(this.copayers, function(copayer) {
|
var publicKeys = _.map(this.copayers, function(copayer) {
|
||||||
var xpub = new Bitcore.HDPublicKey(copayer.xPubKey);
|
var xpub = new Bitcore.HDPublicKey(copayer.xPubKey);
|
||||||
return xpub.derive(path).publicKey;
|
return xpub.derive(path).publicKey;
|
||||||
|
|
|
@ -179,15 +179,11 @@ CopayServer.prototype.createAddress = function(opts, cb) {
|
||||||
}, function(err, wallet) {
|
}, function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var copayer = wallet.copayers[0]; // TODO: Assign copayer from authentication.
|
var address = wallet.createAddress(opts.isChange);
|
||||||
|
|
||||||
var path = copayer.getNewAddressPath(isChange);
|
|
||||||
|
|
||||||
self.storage.storeWallet(wallet, function(err) {
|
self.storage.storeWallet(wallet, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var address = wallet.createAddress(path);
|
|
||||||
|
|
||||||
self.storage.storeAddress(opts.walletId, address, function(err) {
|
self.storage.storeAddress(opts.walletId, address, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
|
var chai = require('chai');
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var should = chai.should();
|
||||||
|
var AddressManager = require('../lib/model/addressmanager');
|
||||||
|
|
||||||
|
|
||||||
|
describe('AddressManager', function() {
|
||||||
|
describe('#getCurrentAddressPath', function() {
|
||||||
|
it('should return a valid BIP32 path for given index', function() {
|
||||||
|
var am = new AddressManager({
|
||||||
|
copayerIndex: 4
|
||||||
|
});
|
||||||
|
am.getCurrentAddressPath(false).should.equal('m/4/0/0');
|
||||||
|
am.getCurrentAddressPath(true).should.equal('m/4/1/0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#getCurrentAddressPath', function() {
|
||||||
|
it('should return a valid BIP32 path for defaut Index', function() {
|
||||||
|
var am = new AddressManager();
|
||||||
|
am.getCurrentAddressPath(false).should.equal('m/2147483647/0/0');
|
||||||
|
am.getCurrentAddressPath(true).should.equal('m/2147483647/1/0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#getNewAddressPath', function() {
|
||||||
|
it('should return a new valid BIP32 path for given index', function() {
|
||||||
|
var am = new AddressManager({
|
||||||
|
copayerIndex: 2
|
||||||
|
});
|
||||||
|
am.getNewAddressPath(false).should.equal('m/2/0/1');
|
||||||
|
am.getNewAddressPath(true).should.equal('m/2/1/1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,28 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var _ = require('lodash');
|
|
||||||
var chai = require('chai');
|
|
||||||
var sinon = require('sinon');
|
|
||||||
var should = chai.should();
|
|
||||||
var Copayer = require('../lib/model/copayer');
|
|
||||||
|
|
||||||
|
|
||||||
describe('Copayer', function() {
|
|
||||||
|
|
||||||
describe('#getCurrentAddressPath', function() {
|
|
||||||
it('return a valid BIP32 path for defaut copayer Index', function() {
|
|
||||||
var c = new Copayer();
|
|
||||||
c.getCurrentAddressPath(false).should.equal('m/0/0/0');
|
|
||||||
c.getCurrentAddressPath(true).should.equal('m/0/1/0');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('return a valid BIP32 path for given index', function() {
|
|
||||||
var c = new Copayer({
|
|
||||||
copayerIndex: 4
|
|
||||||
});
|
|
||||||
c.getCurrentAddressPath(false).should.equal('m/4/0/0');
|
|
||||||
c.getCurrentAddressPath(true).should.equal('m/4/1/0');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -494,21 +494,6 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set index in 1-1 wallet creation.', function(done) {
|
|
||||||
helpers.createAndJoinWallet('123', 1, 1, function(err, wallet) {
|
|
||||||
wallet.receiveAddressIndex.should.equal(0);
|
|
||||||
wallet.changeAddressIndex.should.equal(0);
|
|
||||||
wallet.copayerIndex.should.equal(0x80000000 - 1);
|
|
||||||
|
|
||||||
var copayer = wallet.copayers[0];
|
|
||||||
copayer.receiveAddressIndex.should.equal(0);
|
|
||||||
copayer.changeAddressIndex.should.equal(0);
|
|
||||||
copayer.copayerIndex.should.equal(0);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should set pkr and status = complete on last copayer joining (2-3)', function(done) {
|
it('should set pkr and status = complete on last copayer joining (2-3)', function(done) {
|
||||||
helpers.createAndJoinWallet('123', 2, 3, function(err, wallet) {
|
helpers.createAndJoinWallet('123', 2, 3, function(err, wallet) {
|
||||||
server.getWallet({
|
server.getWallet({
|
||||||
|
@ -517,12 +502,6 @@ describe('Copay server', function() {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
wallet.status.should.equal('complete');
|
wallet.status.should.equal('complete');
|
||||||
wallet.publicKeyRing.length.should.equal(3);
|
wallet.publicKeyRing.length.should.equal(3);
|
||||||
_.each([0, 1, 2], function(i) {
|
|
||||||
var copayer = wallet.copayers[i];
|
|
||||||
copayer.receiveAddressIndex.should.equal(0);
|
|
||||||
copayer.changeAddressIndex.should.equal(0);
|
|
||||||
copayer.copayerIndex.should.equal(i);
|
|
||||||
});
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -584,8 +563,8 @@ describe('Copay server', function() {
|
||||||
}, function(err, address) {
|
}, function(err, address) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
address.should.exist;
|
address.should.exist;
|
||||||
address.address.should.equal('3BPfHzwq5j72TBYtYv3Uggk3vyHFHX3QpA');
|
address.address.should.equal('3H4pNP6J4PW4NnvdrTg37VvZ7h2QWuAwtA');
|
||||||
address.path.should.equal('m/0/0/1');
|
address.path.should.equal('m/2147483647/0/1');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -600,8 +579,8 @@ describe('Copay server', function() {
|
||||||
}, function(err, address) {
|
}, function(err, address) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
address.should.exist;
|
address.should.exist;
|
||||||
address.address.should.equal('39Dzj5mBJWvzH7bDfmYzXDvTbZS5HdQ4a4');
|
address.address.should.equal('3GesnvqTsw3PQbyZwf4D96ZZiFrhVkYsJn');
|
||||||
address.path.should.equal('m/0/1/1');
|
address.path.should.equal('m/2147483647/1/1');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -626,7 +605,7 @@ describe('Copay server', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only('should create tx', function(done) {
|
it.skip('should create tx', function(done) {
|
||||||
var bc = sinon.stub();
|
var bc = sinon.stub();
|
||||||
bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, helpers.createUtxos(wallet, [100, 200]));
|
bc.getUnspentUtxos = sinon.stub().callsArgWith(1, null, helpers.createUtxos(wallet, [100, 200]));
|
||||||
server._getBlockExplorer = sinon.stub().returns(bc);
|
server._getBlockExplorer = sinon.stub().returns(bc);
|
||||||
|
|
|
@ -18,29 +18,21 @@ describe('Wallet', function() {
|
||||||
describe('#createAddress', function() {
|
describe('#createAddress', function() {
|
||||||
it('create an address', function() {
|
it('create an address', function() {
|
||||||
var w = Wallet.fromObj(testWallet);
|
var w = Wallet.fromObj(testWallet);
|
||||||
var a = w.createAddress('m/1/1');
|
var a = w.createAddress(false);
|
||||||
a.address.should.equal('32HG4C9tWMhWoDoTHFvjmbV5sUJMjWs4vL');
|
a.address.should.equal('35Du8JgkFoiN5znoETnkGZAv99v6eCwGMB');
|
||||||
a.path.should.equal('m/1/1');
|
a.path.should.equal('m/2147483647/0/1');
|
||||||
a.createdOn.should.be.above(1);
|
a.createdOn.should.be.above(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#getCurrentAddressPath', function() {
|
|
||||||
it('return a valid BIP32 path for defaut wallet Index', function() {
|
|
||||||
var w = new Wallet();
|
|
||||||
w.getCurrentAddressPath(false).should.equal('m/2147483647/0/0');
|
|
||||||
w.getCurrentAddressPath(true).should.equal('m/2147483647/1/0');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var testWallet = {
|
var testWallet = {
|
||||||
receiveAddressIndex: 0,
|
addressManager: {
|
||||||
changeAddressIndex: 0,
|
receiveAddressIndex: 0,
|
||||||
copayerIndex: 2147483647,
|
changeAddressIndex: 0,
|
||||||
|
copayerIndex: 2147483647,
|
||||||
|
},
|
||||||
createdOn: 1422904188,
|
createdOn: 1422904188,
|
||||||
id: '123',
|
id: '123',
|
||||||
name: '123 wallet',
|
name: '123 wallet',
|
||||||
|
@ -52,9 +44,11 @@ var testWallet = {
|
||||||
'xpub661MyMwAqRbcFXUfkjfSaRwxJbAPpzNUvTiNFjgZwDJ8sZuhyodkP24L4LvsrgThYAAwKkVVSSmL7Ts7o9EHEHPB3EE89roAra7njoSeiMd'
|
'xpub661MyMwAqRbcFXUfkjfSaRwxJbAPpzNUvTiNFjgZwDJ8sZuhyodkP24L4LvsrgThYAAwKkVVSSmL7Ts7o9EHEHPB3EE89roAra7njoSeiMd'
|
||||||
],
|
],
|
||||||
copayers: [{
|
copayers: [{
|
||||||
receiveAddressIndex: 0,
|
addressManager: {
|
||||||
changeAddressIndex: 0,
|
receiveAddressIndex: 0,
|
||||||
copayerIndex: 0,
|
changeAddressIndex: 0,
|
||||||
|
copayerIndex: 0,
|
||||||
|
},
|
||||||
createdOn: 1422904189,
|
createdOn: 1422904189,
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'copayer 1',
|
name: 'copayer 1',
|
||||||
|
@ -63,9 +57,11 @@ var testWallet = {
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
signingPubKey: '03814ac7decf64321a3c6967bfb746112fdb5b583531cd512cc3787eaf578947dc'
|
signingPubKey: '03814ac7decf64321a3c6967bfb746112fdb5b583531cd512cc3787eaf578947dc'
|
||||||
}, {
|
}, {
|
||||||
receiveAddressIndex: 0,
|
addressManager: {
|
||||||
changeAddressIndex: 0,
|
receiveAddressIndex: 0,
|
||||||
copayerIndex: 1,
|
changeAddressIndex: 0,
|
||||||
|
copayerIndex: 1,
|
||||||
|
},
|
||||||
createdOn: 1422904189,
|
createdOn: 1422904189,
|
||||||
id: '2',
|
id: '2',
|
||||||
name: 'copayer 2',
|
name: 'copayer 2',
|
||||||
|
@ -74,9 +70,11 @@ var testWallet = {
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
signingPubKey: '03fc086d2bd8b6507b1909b24c198c946e68775d745492ea4ca70adfce7be92a60'
|
signingPubKey: '03fc086d2bd8b6507b1909b24c198c946e68775d745492ea4ca70adfce7be92a60'
|
||||||
}, {
|
}, {
|
||||||
receiveAddressIndex: 0,
|
addressManager: {
|
||||||
changeAddressIndex: 0,
|
receiveAddressIndex: 0,
|
||||||
copayerIndex: 2,
|
changeAddressIndex: 0,
|
||||||
|
copayerIndex: 2,
|
||||||
|
},
|
||||||
createdOn: 1422904189,
|
createdOn: 1422904189,
|
||||||
id: '3',
|
id: '3',
|
||||||
name: 'copayer 3',
|
name: 'copayer 3',
|
||||||
|
|
Loading…
Reference in New Issue