enable both bip44/45 & p2sh/pkh

This commit is contained in:
Ivan Socolsky 2015-09-07 17:18:32 -03:00
parent 8d026b85a5
commit 97e63f9c6f
3 changed files with 145 additions and 45 deletions

View File

@ -130,10 +130,10 @@ ExpressApp.prototype.start = function(opts, cb) {
});
};
// DEPRECATED
router.post('/v1/wallets/', function(req, res) {
var server = getServer(req, res);
req.body.supportBIP44 = false;
req.body.supportP2PKH = false;
req.body.supportBIP44AndP2PKH = false;
server.createWallet(req.body, function(err, walletId) {
if (err) return returnError(err, res, req);
res.json({
@ -144,8 +144,7 @@ ExpressApp.prototype.start = function(opts, cb) {
router.post('/v2/wallets/', function(req, res) {
var server = getServer(req, res);
req.body.supportBIP44 = true;
req.body.supportP2PKH = true;
req.body.supportBIP44AndP2PKH = true;
server.createWallet(req.body, function(err, walletId) {
if (err) return returnError(err, res, req);
res.json({
@ -163,6 +162,7 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
// DEPRECATED
router.post('/v1/wallets/:id/copayers/', function(req, res) {
req.body.walletId = req.params['id'];
var server = getServer(req, res);
@ -173,6 +173,17 @@ ExpressApp.prototype.start = function(opts, cb) {
});
});
router.post('/v2/wallets/:id/copayers/', function(req, res) {
req.body.walletId = req.params['id'];
req.body.supportBIP44AndP2PKH = true;
var server = getServer(req, res);
server.joinWallet(req.body, function(err, result) {
if (err) return returnError(err, res, req);
res.json(result);
});
});
// DEPRECATED
router.get('/v1/wallets/', function(req, res) {
getServerWithAuth(req, res, function(server) {

View File

@ -203,8 +203,7 @@ WalletService.prototype._runLocked = function(cb, task) {
* @param {number} opts.n - Total copayers.
* @param {string} opts.pubKey - Public key to verify copayers joining have access to the wallet secret.
* @param {string} [opts.network = 'livenet'] - The Bitcoin network for this wallet.
* @param {string} [opts.supportBIP44 = false] - Client supports BIP44 paths for 1-of-1 wallets.
* @param {string} [opts.supportP2PKH = false] - Client supports P2PKH address type for 1-of-1 wallets.
* @param {string} [opts.supportBIP44AndP2PKH = false] - Client supports BIP44 & P2PKH for new wallets.
*/
WalletService.prototype.createWallet = function(opts, cb) {
var self = this,
@ -221,11 +220,8 @@ WalletService.prototype.createWallet = function(opts, cb) {
if (!_.contains(['livenet', 'testnet'], opts.network))
return cb(new ClientError('Invalid network'));
var derivationStrategy = (opts.n == 1 && opts.supportBIP44) ?
WalletUtils.DERIVATION_STRATEGIES.BIP44 : WalletUtils.DERIVATION_STRATEGIES.BIP45;
var addressType = (opts.n == 1 && opts.supportP2PKH) ?
WalletUtils.SCRIPT_TYPES.P2PKH : WalletUtils.SCRIPT_TYPES.P2SH;
var derivationStrategy = opts.supportBIP44AndP2PKH ? WalletUtils.DERIVATION_STRATEGIES.BIP44 : WalletUtils.DERIVATION_STRATEGIES.BIP45;
var addressType = (opts.n == 1 && opts.supportBIP44AndP2PKH) ? WalletUtils.SCRIPT_TYPES.P2PKH : WalletUtils.SCRIPT_TYPES.P2SH;
try {
pubKey = new PublicKey.fromString(opts.pubKey);
@ -412,8 +408,6 @@ WalletService.prototype._notify = function(type, data, opts, cb) {
WalletService.prototype._addCopayerToWallet = function(wallet, opts, cb) {
var self = this;
if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL);
var copayer = Model.Copayer.create({
name: opts.name,
copayerIndex: wallet.copayers.length,
@ -559,6 +553,7 @@ WalletService.prototype._clientSupportsTXPv2 = function() {
* @param {string} opts.requestPubKey - Public Key used to check requests from this copayer.
* @param {string} opts.copayerSignature - S(name|xPubKey|requestPubKey). Used by other copayers to verify that the copayer joining knows the wallet secret.
* @param {string} opts.customData - (optional) Custom data for this copayer.
* @param {string} [opts.supportBIP44AndP2PKH = false] - Client supports BIP44 & P2PKH for joining wallets.
*/
WalletService.prototype.joinWallet = function(opts, cb) {
var self = this;
@ -575,11 +570,25 @@ WalletService.prototype.joinWallet = function(opts, cb) {
if (err) return cb(err);
if (!wallet) return cb(Errors.WALLET_NOT_FOUND);
if (opts.supportBIP44AndP2PKH) {
// New client trying to join legacy wallet
if (wallet.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP45) {
return cb(new ClientError('The wallet you are trying to join was created with an older version of the client app.'));
}
} else {
// Legacy client trying to join new wallet
if (wallet.derivationStrategy == WalletUtils.DERIVATION_STRATEGIES.BIP44) {
return cb(new ClientError(Errors.codes.UPGRADE_NEEDED, 'To join this wallet you need to upgrade your client app.'));
}
}
var hash = WalletUtils.getCopayerHash(opts.name, opts.xPubKey, opts.requestPubKey);
if (!self._verifySignature(hash, opts.copayerSignature, wallet.pubKey)) {
return cb(new ClientError());
}
if (wallet.copayers.length == wallet.n) return cb(Errors.WALLET_FULL);
if (_.find(wallet.copayers, {
xPubKey: opts.xPubKey
})) return cb(Errors.COPAYER_IN_WALLET);
@ -1209,7 +1218,7 @@ WalletService.prototype.createTx = function(opts, cb) {
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos,
derivationStrategy: wallet.derivationStrategy,
addressType: wallet.addressType,
customData: opts.customData
customData: opts.customData,
};
if (signingKey.selfSigned) {

View File

@ -96,15 +96,13 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
var copayerIds = [];
var offset = opts.offset || 0;
var supportBIP44 = _.isBoolean(opts.supportBIP44) ? opts.supportBIP44 : true
var supportP2PKH = _.isBoolean(opts.supportP2PKH) ? opts.supportP2PKH : true
var supportBIP44AndP2PKH = _.isBoolean(opts.supportBIP44AndP2PKH) ? opts.supportBIP44AndP2PKH : true;
var walletOpts = {
name: 'a wallet',
m: m,
n: n,
pubKey: TestData.keyPair.pub,
supportBIP44: supportBIP44,
supportP2PKH: supportP2PKH,
supportBIP44AndP2PKH: supportBIP44AndP2PKH,
};
server.createWallet(walletOpts, function(err, walletId) {
if (err) return cb(err);
@ -114,9 +112,10 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'copayer ' + (i + 1),
xPubKey: (n == 1 && supportBIP44) ? copayerData.xPubKey_44H_0H_0H : copayerData.xPubKey_45H,
xPubKey: supportBIP44AndP2PKH ? copayerData.xPubKey_44H_0H_0H : copayerData.xPubKey_45H,
requestPubKey: copayerData.pubKey_1H_0,
customData: 'custom data ' + (i + 1),
supportBIP44AndP2PKH: supportBIP44AndP2PKH,
});
server.joinWallet(copayerOpts, function(err, result) {
@ -135,6 +134,7 @@ helpers.createAndJoinWallet = function(m, n, opts, cb) {
});
};
helpers.randomTXID = function() {
return Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex');;
};
@ -509,7 +509,7 @@ describe('Wallet service', function() {
txpId = txp.id;
async.eachSeries(_.range(2), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id45, function(server) {
helpers.getAuthServer(copayer.id44, function(server) {
var signatures = helpers.clientSign(txp, copayer.xPrivKey);
server.signTx({
txProposalId: txp.id,
@ -567,7 +567,7 @@ describe('Wallet service', function() {
txpId = txp.id;
async.eachSeries(_.range(1, 3), function(i, next) {
var copayer = TestData.copayers[i];
helpers.getAuthServer(copayer.id45, function(server) {
helpers.getAuthServer(copayer.id44, function(server) {
server.rejectTx({
txProposalId: txp.id,
}, next);
@ -952,6 +952,21 @@ describe('Wallet service', function() {
});
});
it('should fail to join with mismatching address derivation strategy', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
name: 'me',
xPubKey: TestData.copayers[0].xPubKey_45H,
requestPubKey: TestData.copayers[0].pubKey_1H_0,
supportBIP44AndP2PKH: true,
});
server.joinWallet(copayerOpts, function(err, result) {
should.exist(err);
err.message.should.contain('The wallet you are trying to join was created with an older version of the client app');
done();
});
});
it('should join existing wallet', function(done) {
var copayerOpts = helpers.getSignedCopayerOpts({
walletId: walletId,
@ -1028,6 +1043,7 @@ describe('Wallet service', function() {
name: 'me',
xPubKey: TestData.copayers[1].xPubKey_44H_0H_0H,
requestPubKey: TestData.copayers[1].pubKey_1H_0,
supportBIP44AndP2PKH: true,
});
server.joinWallet(copayerOpts, function(err) {
should.exist(err);
@ -1176,8 +1192,7 @@ describe('Wallet service', function() {
m: 1,
n: 1,
pubKey: TestData.keyPair.pub,
supportBIP44: true,
supportP2PKH: true,
supportBIP44AndP2PKH: true,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
@ -1195,6 +1210,7 @@ describe('Wallet service', function() {
m: 1,
n: 1,
pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
@ -1206,14 +1222,31 @@ describe('Wallet service', function() {
});
});
});
it('should always use BIP45 & P2SH for shared wallets', function(done) {
it('should use BIP44 & P2SH for shared wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
supportBIP44: true,
supportP2PKH: true,
supportBIP44AndP2PKH: true,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
server.storage.fetchWallet(wid, function(err, wallet) {
should.not.exist(err);
wallet.derivationStrategy.should.equal('BIP44');
wallet.addressType.should.equal('P2SH');
done();
});
});
});
it('should use BIP45 & P2SH for shared wallet if supported', function(done) {
var walletOpts = {
name: 'my wallet',
m: 2,
n: 3,
pubKey: TestData.keyPair.pub,
supportBIP44AndP2PKH: false,
};
server.createWallet(walletOpts, function(err, wid) {
should.not.exist(err);
@ -1278,7 +1311,6 @@ describe('Wallet service', function() {
should.exist(status.wallet.copayers[0].requestPubKey);
should.exist(status.wallet.copayers[0].signature);
should.exist(status.wallet.copayers[0].requestPubKey);
should.exist(status.wallet.copayers[0].addressManager);
should.exist(status.wallet.copayers[0].customData);
// Do not return other copayer's custom data
_.each(_.rest(status.wallet.copayers), function(copayer) {
@ -1353,7 +1385,9 @@ describe('Wallet service', function() {
describe('shared wallets (BIP45)', function() {
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
helpers.createAndJoinWallet(2, 2, {
supportBIP44AndP2PKH: false
}, function(s, w) {
server = s;
wallet = w;
done();
@ -1395,6 +1429,52 @@ describe('Wallet service', function() {
done();
});
});
});
describe('shared wallets (BIP44)', function() {
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 2, function(s, w) {
server = s;
wallet = w;
done();
});
});
it('should create address', function(done) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
should.exist(address);
address.walletId.should.equal(wallet.id);
address.network.should.equal('livenet');
address.address.should.equal('36q2G5FMGvJbPgAVEaiyAsFGmpkhPKwk2r');
address.isChange.should.be.false;
address.path.should.equal('m/0/0');
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notif = _.find(notifications, {
type: 'NewAddress'
});
should.exist(notif);
notif.data.address.should.equal(address.address);
done();
});
});
});
it('should create many addresses on simultaneous requests', function(done) {
var N = 5;
async.map(_.range(N), function(i, cb) {
server.createAddress({}, cb);
}, function(err, addresses) {
addresses.length.should.equal(N);
_.each(_.range(N), function(i) {
addresses[i].path.should.equal('m/0/' + i);
});
// No two identical addresses
_.uniq(_.pluck(addresses, 'address')).length.should.equal(N);
done();
});
});
it('should not create address if unable to store it', function(done) {
sinon.stub(server.storage, 'storeAddressAndWallet').yields('dummy error');
@ -1667,10 +1747,10 @@ describe('Wallet service', function() {
reqPrivKey = new Bitcore.PrivateKey();
var requestPubKey = reqPrivKey.toPublicKey();
var xPrivKey = TestData.copayers[0].xPrivKey_45H;
var xPrivKey = TestData.copayers[0].xPrivKey_44H_0H_0H;
var sig = WalletUtils.signRequestPubKey(requestPubKey, xPrivKey);
var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_45H);
var copayerId = WalletUtils.xPubToCopayerId(TestData.copayers[0].xPubKey_44H_0H_0H);
opts = {
copayerId: copayerId,
requestPubKey: requestPubKey,
@ -1681,9 +1761,7 @@ describe('Wallet service', function() {
describe('#addAccess 1-1', function() {
beforeEach(function(done) {
helpers.createAndJoinWallet(1, 1, {
supportBIP44: false
}, function(s, w) {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
@ -4496,7 +4574,9 @@ describe('Wallet service', function() {
WalletService.SCAN_CONFIG.scanWindow = 2;
WalletService.SCAN_CONFIG.derivationDelay = 0;
helpers.createAndJoinWallet(1, 2, function(s, w) {
helpers.createAndJoinWallet(1, 2, {
supportBIP44AndP2PKH: false
}, function(s, w) {
server = s;
wallet = w;
done();
@ -4584,7 +4664,7 @@ describe('Wallet service', function() {
WalletService.SCAN_CONFIG.derivationDelay = 0;
helpers.createAndJoinWallet(1, 1, {
supportP2PKH: false
supportBIP44AndP2PKH: false
}, function(s, w) {
server = s;
wallet = w;
@ -4598,17 +4678,17 @@ describe('Wallet service', function() {
it('should start an asynchronous scan', function(done) {
helpers.stubAddressActivity(
['3J4J9nkFpzQjUGDh5hLKMKztFSPWMKejKE', // m/0/0
'384JHSf9kVBs3yXsPwCzEScRs395u8hwxj', // m/0/2
'3NgXBiMQvwcRU8khVoPFJ6gsbGg9ZYrRzH', // m/1/0
['3GvvHimEMk2GBZnPxTF89GHZL6QhZjUZVs', // m/2147483647/0/0
'37pd1jjTUiGBh8JL2hKLDgsyrhBoiz5vsi', // m/2147483647/0/2
'3C3tBn8Sr1wHTp2brMgYsj9ncB7R7paYuB', // m/2147483647/1/0
]);
var expectedPaths = [
'm/0/0',
'm/0/1',
'm/0/2',
'm/0/3',
'm/1/0',
'm/1/1',
'm/2147483647/0/0',
'm/2147483647/0/1',
'm/2147483647/0/2',
'm/2147483647/0/3',
'm/2147483647/1/0',
'm/2147483647/1/1',
];
server.messageBroker.onMessage(function(n) {
if (n.type == 'ScanFinished') {
@ -4623,7 +4703,7 @@ describe('Wallet service', function() {
_.difference(paths, expectedPaths).length.should.equal(0);
server.createAddress({}, function(err, address) {
should.not.exist(err);
address.path.should.equal('m/0/4');
address.path.should.equal('m/2147483647/0/4');
done();
});
})