Merge pull request #57 from matiu/feature/wallet-refactor

Feature/wallet refactor
This commit is contained in:
Gustavo Maximiliano Cortez 2014-04-15 18:55:50 -03:00
commit 54f42f8278
13 changed files with 397 additions and 231 deletions

View file

@ -66,12 +66,11 @@
<div class="row"> <div class="row">
<div class="large-6 columns"> <div class="large-6 columns">
<h3>Open a Existing Wallet</h3> <h3>Open a Existing Wallet</h3>
<select class="form-control" > <select class="form-control" ng-model="selectedWalletId" ng-options="walletId for walletId in listWalletIds()">
<option ng-repeat="walletId in listWalletIds()" ng-model="sel" value="walletId">{{walletId}}</option>
</select> </select>
</div> </div>
<div class="large-3 columns"> <div class="large-3 columns">
<button class="button primary expand round" type="button" ng-click="open(sel || $scope.sel)">Open</button> <button class="button primary expand round" type="button" ng-click="open(selectedWalletId)">Open</button>
</div> </div>
</div> </div>
<hr> <hr>
@ -134,7 +133,7 @@
<div class="row"> <div class="row">
<div class="large-6 columns"> <div class="large-6 columns">
<a class="panel db" ng-repeat="addr in addrs" ng-click="selectAddr(addr.addrStr)">{{addr.addrStr}} <span class="right"> &gt;</span></a> <a class="panel db" ng-repeat="addr in addrs" ng-click="selectAddr(addr)">{{addr}} <span class="right"> &gt;</span></a>
</div> </div>
<div class="large-6 columns"> <div class="large-6 columns">
<qrcode size="160" data="{{selectedAddr}}"></qrcode> <qrcode size="160" data="{{selectedAddr}}"></qrcode>

View file

@ -5,17 +5,19 @@ var config = {
network: { network: {
apiKey: 'lwjd5qra8257b9', apiKey: 'lwjd5qra8257b9',
maxPeers: 3, maxPeers: 3,
debug: 0, debug: 3,
}, },
wallet: { wallet: {
requiredCopayers: 2, requiredCopayers: 2,
totalCopayers: 3, totalCopayers: 3,
}, },
insight: { blockchain: {
host: 'localhost', host: 'test.insight.is',
port: 3001 port: 80
// host: 'localhost',
// port: 3001
}, },
verbose: 0, verbose: 1,
}; };
var log = function () { var log = function () {

View file

@ -10,13 +10,13 @@ angular.module('copay.home').controller('HomeController',
$location.path('signin'); $location.path('signin');
} }
else { else {
$scope.addrs = $rootScope.wallet.publicKeyRing.getAddresses(); $scope.addrs = $rootScope.wallet.getAddressesStr();
$scope.selectedAddr = $scope.addrs[0]; $scope.selectedAddr = $scope.addrs[0];
} }
$scope.newAddr = function() { $scope.newAddr = function() {
var a = $rootScope.wallet.generateAddress(); var a = $rootScope.wallet.generateAddress();
$scope.addrs.push({ addrStr: a.toString('hex') }); $scope.addrs.push({ addrStr: a.toString() });
}; };
$scope.selectAddr = function(addr) { $scope.selectAddr = function(addr) {

View file

@ -16,29 +16,22 @@ angular.module('copay.send').controller('SendController',
var opts = {remainderOut: { address: pkr.generateAddress(true).toString() }}; var opts = {remainderOut: { address: pkr.generateAddress(true).toString() }};
// From @cmgustavo's wallet // From @cmgustavo's wallet
var unspentTest = [{ w.listUnspent(function (unspentTest) {
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", console.log('[send.js.19:unspentTest:]',unspentTest); //TODO
"vout": 1,
"amount": 10,
"confirmations":7
}];
unspentTest[0].address = pkr.generateAddress(false).toString(); txp.create(
unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(false); '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
console.log('[send.js.29:txp:] BEFORE',txp); //TODO unspentTest,
w.privateKey,
txp.create( opts
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', );
'123456789',
unspentTest,
w.privateKey,
opts
);
console.log('[send.js.29:txp:] READY:',txp); //TODO console.log('[send.js.29:txp:] READY:',txp); //TODO
Network.storeOpenWallet(); Network.storeOpenWallet();
Network.sendTxProposals(); Network.sendTxProposals();
$rootScope.$digest; $rootScope.$digest;
});
}; };
}); });

View file

@ -7,6 +7,8 @@ angular.module('copay.signin').controller('SigninController',
// $rootScope.peerId = peerData ? peerData.peerId : null; // $rootScope.peerId = peerData ? peerData.peerId : null;
$scope.loading = false; $scope.loading = false;
$scope.selectedWalletId = false;
$scope.listWalletIds = function() { $scope.listWalletIds = function() {
return copay.Wallet.factory.getWalletIds(); return copay.Wallet.factory.getWalletIds();
}; };
@ -23,12 +25,20 @@ angular.module('copay.signin').controller('SigninController',
$scope.open = function(walletId) { $scope.open = function(walletId) {
$scope.loading = true; $scope.loading = true;
if (Network.openWallet(walletId)) {
Network.openWallet(walletId);
if ($rootScope.wallet && $rootScope.wallet.id) {
Network.init(function() { Network.init(function() {
$location.path('peer'); $location.path('peer');
$rootScope.$digest(); $rootScope.$digest();
}); });
} }
else {
$scope.loading = false;
$rootScope.flashMessage = {type:'error', message: 'Wallet not found'};
$location.path('signin');
}
}; };
$scope.join = function(cid) { $scope.join = function(cid) {

View file

@ -23,5 +23,5 @@ angular.module('copay.transactions').controller('TransactionsController',
} }
]; ];
$scope.txsoutput = $rootScope.wallet.txProposals.txps; $scope.txsoutput = $rootScope.wallet.getTxProposals();
}); });

View file

@ -5,8 +5,9 @@ var bitcore = require('bitcore');
function Insight(opts) { function Insight(opts) {
opts = opts || {}; opts = opts || {};
this.host = 'test.insight.is'; this.host = opts.host || 'localhost';
this.port = '80'; this.port = opts.port || '3001';
this.scheme = opts.scheme || 'http';
} }
function _asyncForEach(array, fn, callback) { function _asyncForEach(array, fn, callback) {
@ -40,7 +41,7 @@ Insight.prototype.getBalance = function(unspent) {
Insight.prototype.listUnspent = function(addresses, cb) { Insight.prototype.listUnspent = function(addresses, cb) {
var self = this; var self = this;
if (!addresses || !addresses.length) return cb(); if (!addresses || !addresses.length) return cb([]);
var all = []; var all = [];
@ -48,8 +49,11 @@ Insight.prototype.listUnspent = function(addresses, cb) {
var options = { var options = {
host: self.host, host: self.host,
port: self.port, port: self.port,
scheme: self.scheme,
method: 'GET', method: 'GET',
path: '/api/addr/' + addr + '/utxo' path: '/api/addr/' + addr + '/utxo',
headers: { 'Access-Control-Request-Headers' : '' }
}; };
self._request(options, function(err, res) { self._request(options, function(err, res) {
@ -74,7 +78,6 @@ Insight.prototype.sendRawTransaction = function(rawtx, cb) {
data: 'rawtx='+rawtx, data: 'rawtx='+rawtx,
headers: { 'content-type' : 'application/x-www-form-urlencoded' } headers: { 'content-type' : 'application/x-www-form-urlencoded' }
}; };
this._request(options, function(err,res) { this._request(options, function(err,res) {
if (err) return cb(); if (err) return cb();
return cb(res.txid); return cb(res.txid);

View file

@ -275,7 +275,7 @@ PublicKeyRing.prototype._mergePubkeys = function(inPKR) {
} }
if (!haveIt) { if (!haveIt) {
if (self.isComplete()) { if (self.isComplete()) {
console.log('[PublicKeyRing.js.318] REPEATED KEY', epk); //TODO //console.log('[PublicKeyRing.js.318] REPEATED KEY', epk); //TODO
throw new Error('trying to add more pubkeys, when PKR isComplete at merge'); throw new Error('trying to add more pubkeys, when PKR isComplete at merge');
} }
self.copayersBIP32.push(new BIP32(epk)); self.copayersBIP32.push(new BIP32(epk));

View file

@ -26,7 +26,6 @@ function TxProposals(opts) {
this.walletId = opts.walletId; this.walletId = opts.walletId;
this.network = opts.networkName === 'livenet' ? this.network = opts.networkName === 'livenet' ?
bitcore.networks.livenet : bitcore.networks.testnet; bitcore.networks.livenet : bitcore.networks.testnet;
this.publicKeyRing = opts.publicKeyRing;
this.txps = []; this.txps = [];
} }
@ -136,6 +135,10 @@ TxProposals.prototype._mergeBuilder = function(myTxps, theirTxps, mergeInfo) {
}); });
}; };
TxProposals.prototype.add = function(data) {
this.txps.push( new TxProposal(data) );
};
TxProposals.prototype.merge = function(t) { TxProposals.prototype.merge = function(t) {
if (this.network.name !== t.network.name) if (this.network.name !== t.network.name)
throw new Error('network mismatch in:', t); throw new Error('network mismatch in:', t);
@ -157,44 +160,6 @@ TxProposals.prototype.merge = function(t) {
return mergeInfo.stats; return mergeInfo.stats;
}; };
TxProposals.prototype.create = function(toAddress, amountSatStr, utxos, priv, opts) {
var pkr = this.publicKeyRing;
opts = opts || {};
var amountSat = bitcore.bignum(amountSatStr);
if (! pkr.isComplete() ) {
throw new Error('publicKeyRing is not complete');
}
var opts = {
remainderOut: opts.remainderOut || { address: pkr.generateAddress(true).toString() }
};
var b = new Builder(opts)
.setUnspent(utxos)
.setHashToScriptMap(pkr.getRedeemScriptMap())
.setOutputs([{address: toAddress, amountSat: amountSat}])
;
var signRet;
if (priv) {
b.sign( priv.getAll(pkr.addressIndex, pkr.changeAddressIndex) );
}
var me = {};
if (priv) me[priv.id] = Date.now();
this.txps.push(
new TxProposal({
signedBy: priv && b.signaturesAdded ? me : {},
seenBy: priv ? me : {},
builder: b,
})
);
return 1;
};
TxProposals.prototype.sign = function(index) {
};
module.exports = require('soop')(TxProposals); module.exports = require('soop')(TxProposals);

View file

@ -1,17 +1,18 @@
'use strict'; 'use strict';
var imports = require('soop').imports(); var imports = require('soop').imports();
var bitcore = require('bitcore'); var bitcore = require('bitcore');
var coinUtil = bitcore.util; var coinUtil = bitcore.util;
var buffertools = bitcore.buffertools; var buffertools = bitcore.buffertools;
var http = require('http'); var Builder = bitcore.TransactionBuilder;
var http = require('http');
var Storage = imports.Storage; var Storage = imports.Storage;
var Network = imports.Network; var Network = imports.Network;
var Blockchain = imports.Blockchain; var Blockchain = imports.Blockchain;
var copay = copay || require('../../../copay'); var copay = copay || require('../../../copay');
function Wallet(config) { function Wallet(config) {
this._startInterface(config); this._startInterface(config);
@ -35,6 +36,7 @@ Wallet.prototype._startInterface = function(config) {
Wallet.prototype.create = function(opts) { Wallet.prototype.create = function(opts) {
opts = opts || {};
this.id = opts.id || Wallet.getRandomId(); this.id = opts.id || Wallet.getRandomId();
this.log('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID')); this.log('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID'));
@ -55,7 +57,6 @@ Wallet.prototype.create = function(opts) {
this.txProposals = new copay.TxProposals({ this.txProposals = new copay.TxProposals({
walletId: this.id, walletId: this.id,
publicKeyRing: this.publicKeyRing,
networkName: this.networkName, networkName: this.networkName,
}); });
this.log('\t### TxProposals Initialized'); this.log('\t### TxProposals Initialized');
@ -63,11 +64,11 @@ Wallet.prototype.create = function(opts) {
Wallet.prototype._checkLoad = function(walletId) { Wallet.prototype._checkLoad = function(walletId) {
return ( var ret = this.storage.get(walletId, 'publicKeyRing') &&
this.storage.get(walletId, 'publicKeyRing') &&
this.storage.get(walletId, 'txProposals') && this.storage.get(walletId, 'txProposals') &&
this.storage.get(walletId, 'privateKey') this.storage.get(walletId, 'privateKey')
); ;
return ret;
} }
Wallet.prototype.load = function(walletId) { Wallet.prototype.load = function(walletId) {
@ -138,6 +139,86 @@ Wallet.prototype.generateAddress = function() {
return addr; return addr;
}; };
Wallet.prototype.getTxProposals = function() {
var ret = [];
this.txProposals.txps.forEach(function(txp) {
var i = {txp:txp};
i.signedByUs = txp.signedBy[this.privateKey.id]?true:false;
ret.push(i);
});
return ret;
};
Wallet.prototype.addSeenToTxProposals = function() {
var ret=false;
this.txProposals.txps.forEach(function(txp) {
if (!txp.seenBy[this.privateKey.id]) {
txp.seenBy[this.privateKey.id] = Date.now();
ret = true;
}
});
return ret;
};
Wallet.prototype.getAddresses = function() {
return this.publicKeyRing.getAddresses();
};
Wallet.prototype.getAddressesStr = function() {
var ret = [];
this.publicKeyRing.getAddresses().forEach(function(a) {
ret.push(a.toString());
});
return ret;
};
Wallet.prototype.listUnspent = function(cb) {
this.blockchain.listUnspent(this.getAddressesStr(), cb);
};
Wallet.prototype.createTx = function(toAddress, amountSatStr, utxos, opts) {
var pkr = this.publicKeyRing;
var priv = this.privateKey;
opts = opts || {};
var amountSat = bitcore.bignum(amountSatStr);
if (! pkr.isComplete() ) {
throw new Error('publicKeyRing is not complete');
}
if (!opts.remainderOut) {
opts.remainderOut ={ address: pkr.generateAddress(true).toString() };
};
var b = new Builder(opts)
.setUnspent(utxos)
.setHashToScriptMap(pkr.getRedeemScriptMap())
.setOutputs([{address: toAddress, amountSat: amountSat}])
;
var signRet;
if (priv) {
b.sign( priv.getAll(pkr.addressIndex, pkr.changeAddressIndex) );
}
var me = {};
if (priv) me[priv.id] = Date.now();
this.txProposals.add({
signedBy: priv && b.signaturesAdded ? me : {},
seenBy: priv ? me : {},
builder: b,
});
};
Wallet.prototype.sign = function(txp) {
};
// // HERE? not sure // // HERE? not sure
// Wallet.prototype.cleanPeers = function() { // Wallet.prototype.cleanPeers = function() {
// this.storage.remove('peerData'); // this.storage.remove('peerData');

View file

@ -114,8 +114,10 @@ angular.module('copay.network')
var recipients; var recipients;
var inTxProposals = copay.TxProposals.fromObj(data.txProposals); var inTxProposals = copay.TxProposals.fromObj(data.txProposals);
var mergeInfo = w.txProposals.merge(inTxProposals, true); var mergeInfo = w.txProposals.merge(inTxProposals, true);
if ( mergeInfo.merged && !data.isBroadcast) {
log('### BROADCASTING txProposals'); var addSeen = w.addSeenToTxProposals();
if ((mergeInfo.merged && !data.isBroadcast) || addSeen) {
log('### BROADCASTING txProposals. ' );
recipients = null; recipients = null;
shouldSend = true; shouldSend = true;
} }

View file

@ -10,6 +10,7 @@ var Key = bitcore.Key;
var BIP32 = bitcore.BIP32; var BIP32 = bitcore.BIP32;
var bignum = bitcore.bignum; var bignum = bitcore.bignum;
var Script = bitcore.Script; var Script = bitcore.Script;
var Builder = bitcore.TransactionBuilder;
var util = bitcore.util; var util = bitcore.util;
var networks = bitcore.networks; var networks = bitcore.networks;
var copay = copay || require('../copay'); var copay = copay || require('../copay');
@ -69,106 +70,60 @@ describe('TxProposals model', function() {
w.network.name.should.equal('livenet'); w.network.name.should.equal('livenet');
}); });
it('#create, no signing', function () { function createTx(toAddress, amountSatStr, utxos, opts, priv, pkr) {
var w = new TxProposals({ opts = opts || {};
networkName: config.networkName,
publicKeyRing: createPKR(),
});
should.exist(w);
w.network.name.should.equal('livenet');
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString(); var amountSat = bitcore.bignum(amountSatStr);
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
w.create( if(! pkr.isComplete() ) {
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', throw new Error('publicKeyRing is not complete');
'123456789',
unspentTest
);
var tx = w.txps[0].builder.build();
should.exist(tx);
tx.isComplete().should.equal(false);
Object.keys(w.txps[0].signedBy).length.should.equal(0);
Object.keys(w.txps[0].seenBy).length.should.equal(0);
});
it('#create, signing with wrong key', function () {
var w = new TxProposals({
networkName: config.networkName,
publicKeyRing: createPKR(),
});
should.exist(w);
w.network.name.should.equal('livenet');
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
var priv = new PrivateKey(config);
w.create(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
unspentTest,
priv
);
var tx = w.txps[0].builder.build();
should.exist(tx);
tx.isComplete().should.equal(false);
Object.keys(w.txps[0].signedBy).length.should.equal(0);
Object.keys(w.txps[0].seenBy).length.should.equal(1);
});
it('#create. Signing with derivate keys', function () {
var priv = new PrivateKey(config);
var w = new TxProposals({
networkName: config.networkName,
publicKeyRing: createPKR([priv.getBIP32()]),
});
var ts = Date.now();
for (var isChange=0; isChange<2; isChange++) {
for (var index=0; index<3; index++) {
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange);
w.create(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
unspentTest,
priv
);
var tx = w.txps[0].builder.build();
should.exist(tx);
tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2);
(w.txps[0].signedBy[priv.id] - ts > 0).should.equal(true);
(w.txps[0].seenBy[priv.id] - ts > 0).should.equal(true);
}
} }
});
if (!opts.remainderOut) {
opts.remainderOut ={ address: pkr.generateAddress(true).toString() };
};
var b = new Builder(opts)
.setUnspent(utxos)
.setHashToScriptMap(pkr.getRedeemScriptMap())
.setOutputs([{address: toAddress, amountSat: amountSat}])
;
var signRet;
if (priv) {
b.sign( priv.getAll(pkr.addressIndex, pkr.changeAddressIndex) );
}
var me = {};
if (priv) me[priv.id] = Date.now();
return {
signedBy: priv && b.signaturesAdded ? me : {},
seenBy: priv ? me : {},
builder: b,
};
};
it('#merge with self', function () { it('#merge with self', function () {
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: createPKR([priv.getBIP32()]),
}); });
var pkr=createPKR([priv.getBIP32()]);
var ts = Date.now(); var ts = Date.now();
var isChange=0; var isChange=0;
var index=0; var index=0;
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w.create( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
priv {},
); priv,
pkr
));
var tx = w.txps[0].builder.build(); var tx = w.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2); tx.countInputMissingSignatures(0).should.equal(2);
@ -184,13 +139,11 @@ describe('TxProposals model', function() {
(w.txps[0].signedBy[priv.id] - ts > 0).should.equal(true); (w.txps[0].signedBy[priv.id] - ts > 0).should.equal(true);
(w.txps[0].seenBy[priv.id] - ts > 0).should.equal(true); (w.txps[0].seenBy[priv.id] - ts > 0).should.equal(true);
}); });
it('#merge, merge signatures case 1', function () { it('#merge, merge signatures case 1', function () {
var priv2 = new PrivateKey(config); var priv2 = new PrivateKey(config);
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var ts = Date.now(); var ts = Date.now();
@ -202,17 +155,17 @@ describe('TxProposals model', function() {
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: pkr,
}); });
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w.create( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
opts,
priv2, priv2,
opts pkr
); ));
var tx = w.txps[0].builder.build(); var tx = w.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
@ -226,15 +179,16 @@ describe('TxProposals model', function() {
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: w.publicKeyRing, publicKeyRing: w.publicKeyRing,
}); });
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w2.create( w2.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
opts,
priv, priv,
opts pkr
); ));
var tx = w2.txps[0].builder.build(); var tx = w2.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
@ -275,17 +229,18 @@ var _dumpChunks = function (scriptSig, label) {
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: pkr,
}); });
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w.create(
w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
opts,
priv3, priv3,
opts pkr
); ));
var tx = w.txps[0].builder.build(); var tx = w.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
@ -297,17 +252,18 @@ var _dumpChunks = function (scriptSig, label) {
var w2 = new TxProposals({ var w2 = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: pkr,
}); });
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
w2.create( unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w2.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
opts,
priv, priv,
opts pkr
); ));
tx = w2.txps[0].builder.build(); tx = w2.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2); tx.countInputMissingSignatures(0).should.equal(2);
@ -329,15 +285,16 @@ var _dumpChunks = function (scriptSig, label) {
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: pkr, publicKeyRing: pkr,
}); });
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w3.create( w3.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
opts,
priv2, priv2,
opts pkr
); ));
tx = w3.txps[0].builder.build(); tx = w3.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2); tx.countInputMissingSignatures(0).should.equal(2);
@ -372,17 +329,17 @@ var _dumpChunks = function (scriptSig, label) {
var w = new TxProposals({ var w = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: pkr,
}); });
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w.create( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
opts,
priv, priv,
opts pkr
); ));
var tx = w.txps[0].builder.build(); var tx = w.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2); tx.countInputMissingSignatures(0).should.equal(2);
@ -392,17 +349,17 @@ var _dumpChunks = function (scriptSig, label) {
var w2 = new TxProposals({ var w2 = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: pkr,
}); });
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w2.create( w2.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
opts,
priv2, priv2,
opts pkr
); ));
var tx = w2.txps[0].builder.build(); var tx = w2.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2); tx.countInputMissingSignatures(0).should.equal(2);
@ -412,17 +369,17 @@ var _dumpChunks = function (scriptSig, label) {
var w3 = new TxProposals({ var w3 = new TxProposals({
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: pkr,
}); });
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w3.create( w3.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
opts,
priv3, priv3,
opts pkr
); ));
var tx = w3.txps[0].builder.build(); var tx = w3.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2); tx.countInputMissingSignatures(0).should.equal(2);
@ -458,23 +415,25 @@ var _dumpChunks = function (scriptSig, label) {
it('#toObj #fromObj roundtrip', function () { it('#toObj #fromObj roundtrip', function () {
var priv = new PrivateKey(config); var priv = new PrivateKey(config);
var pkr = createPKR([priv.getBIP32()]);
var w = new TxProposals({ var w = new TxProposals({
walletId: 'qwerty', walletId: 'qwerty',
networkName: config.networkName, networkName: config.networkName,
publicKeyRing: createPKR([priv.getBIP32()]),
}); });
var ts = Date.now(); var ts = Date.now();
var isChange=0; var isChange=0;
var index=0; var index=0;
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString(); unspentTest[0].address = pkr.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange); unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(index, isChange);
w.create( w.add(createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789', '123456789',
unspentTest, unspentTest,
priv {},
); priv,
pkr
));
var tx = w.txps[0].builder.build(); var tx = w.txps[0].builder.build();
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2); tx.countInputMissingSignatures(0).should.equal(2);

View file

@ -9,23 +9,68 @@ var Wallet = require('soop').load('../js/models/core/Wallet', {
Blockchain: copay.Insight Blockchain: copay.Insight
}); });
var addCopayers = function (w) {
for(var i=0; i<4; i++) {
w.publicKeyRing.addCopayer();
}
};
describe('Wallet model', function() { describe('Wallet model', function() {
var config = { var config = {
wallet: { wallet: {
requiredCopayers: 3, requiredCopayers: 3,
totalCopayers: 5, totalCopayers: 5,
} },
blockchain: {
host: 'test.insight.is',
port: 80
},
}; };
var opts = {}; var opts = {};
it.skip('should create an instance', function () { it('should create an instance', function () {
var opts = {}; var opts = {};
var w = Wallet.create(config, opts); var w = new Wallet(config);
should.exist(w); should.exist(w);
}); });
it('should fail to load', function () {
var opts = {};
var w = new Wallet(config);
w.load(123);
should.not.exist(w.id);
});
it('should create', function () {
var opts = {};
var w = new Wallet(config);
w.create();
should.exist(w.id);
should.exist(w.publicKeyRing);
should.exist(w.privateKey);
should.exist(w.txProposals);
});
it('should list unspent', function (done) {
var opts = {};
var w = new Wallet(config);
w.create();
addCopayers(w);
w.publicKeyRing.generateAddress(false);
should.exist(w.id);
w.publicKeyRing.isComplete().should.equal(true);
w.listUnspent(function(utxos) {
utxos.length.should.equal(0);
done();
});
});
describe('factory', function() { describe('factory', function() {
it('should create the factory', function() { it('should create the factory', function() {
should.exist(Wallet.factory); should.exist(Wallet.factory);
@ -41,4 +86,111 @@ describe('Wallet model', function() {
}); });
}); });
var unspentTest = [
{
"address": "dummy",
"scriptPubKey": "dummy",
"txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
"vout": 1,
"amount": 10,
"confirmations":7
}
];
var createWallet = function (bip32s) {
var w = new Wallet(config);
w.create();
should.exist(w);
var pkr = w.publicKeyRing;
for(var i=0; i<4; i++) {
if (bip32s) {
var b=bip32s[i];
pkr.addCopayer(b?b.extendedPublicKeyString():null);
}
else
pkr.addCopayer();
}
pkr.generateAddress(true);
pkr.generateAddress(true);
pkr.generateAddress(true);
pkr.generateAddress(false);
pkr.generateAddress(false);
pkr.generateAddress(false);
//3x3 indexes
return w;
};
it('#create, 1 sign', function () {
var w = createWallet();
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
w.createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
unspentTest
);
var t = w.txProposals;
var tx = t.txps[0].builder.build();
should.exist(tx);
tx.isComplete().should.equal(false);
Object.keys(t.txps[0].signedBy).length.should.equal(1);
Object.keys(t.txps[0].seenBy).length.should.equal(1);
});
it('#create. Signing with derivate keys', function () {
var w = createWallet();
var ts = Date.now();
for (var isChange=0; isChange<2; isChange++) {
for (var index=0; index<3; index++) {
unspentTest[0].address = w.publicKeyRing.getAddress(index, isChange).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(index, isChange);
w.createTx(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
unspentTest
);
var t = w.txProposals;
var tx = t.txps[0].builder.build();
should.exist(tx);
tx.isComplete().should.equal(false);
tx.countInputMissingSignatures(0).should.equal(2);
( t.txps[0].signedBy[w.privateKey.id] - ts > 0).should.equal(true);
( t.txps[0].seenBy[w.privateKey.id] - ts > 0).should.equal(true);
}
}
});
// TODO: when sign is implemented
it.skip('#create, signing with wrong key', function () {
var w1 = createWallet();
unspentTest[0].address = w.publicKeyRing.getAddress(1, true).toString();
unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true);
var priv = new PrivateKey(config);
w.create(
'15q6HKjWHAksHcH91JW23BJEuzZgFwydBt',
'123456789',
unspentTest
);
var tx = w.txps[0].builder.build();
should.exist(tx);
tx.isComplete().should.equal(false);
Object.keys(w.txps[0].signedBy).length.should.equal(0);
Object.keys(w.txps[0].seenBy).length.should.equal(1);
});
}); });