add different toObj/fromObj fn for networking

This commit is contained in:
Matias Alejo Garcia 2014-08-02 20:51:31 -03:00
parent c8917fccd6
commit 966818c53a
6 changed files with 234 additions and 117 deletions

View File

@ -99,14 +99,6 @@ PublicKeyRing.prototype._checkKeys = function() {
throw new Error('dont have required keys yet');
};
PublicKeyRing.prototype._newExtendedPublicKey = function() {
return new PrivateKey({
networkName: this.network.name
})
.deriveBIP45Branch()
.extendedPublicKeyString();
};
PublicKeyRing.prototype._updateBip = function(index) {
var hk = this.copayersHK[index].derive(HDPath.IdBranch);
this.copayerIds[index] = hk.eckey.public.toString('hex');
@ -125,6 +117,8 @@ PublicKeyRing.prototype.nicknameForCopayer = function(copayerId) {
};
PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
preconditions.checkArgument(newEpk);
if (this.isComplete())
throw new Error('PKR already has all required key:' + this.totalCopayers);
@ -133,10 +127,6 @@ PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) {
throw new Error('PKR already has that key');
});
if (!newEpk) {
newEpk = this._newExtendedPublicKey();
}
var i = this.copayersHK.length;
var bip = new HK(newEpk);
this.copayersHK.push(bip);
@ -307,6 +297,30 @@ PublicKeyRing.prototype.forPaths = function(paths) {
};
PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) {
var inKeyMap = {}, ret = [];
for(var i in pubkeys ){
inKeyMap[pubkeys[i]] = 1;
};
var keys = this.getForPaths(paths);
for(var i in keys ){
for(var copayerIndex in keys[i] ){
var kHex = keys[i][copayerIndex].toString('hex');
if (inKeyMap[kHex]) {
ret.push(this.copayerIds[copayerIndex]);
delete inKeyMap[kHex];
}
}
}
for(var i in inKeyMap)
throw new Error('Pubkey not identified')
return ret;
};
// TODO this could be cached
PublicKeyRing.prototype._addScriptMap = function(map, path) {
var p = HDPath.indexesForPath(path);

View File

@ -10,6 +10,9 @@ var Key = bitcore.Key;
var buffertools = bitcore.buffertools;
var preconditions = require('preconditions').instance();
var VERSION = 1;
var CORE_FIELDS = ['builderObj','inputChainPaths', 'version'];
function TxProposal(opts) {
preconditions.checkArgument(opts);
@ -17,22 +20,26 @@ function TxProposal(opts) {
preconditions.checkArgument(opts.creator,'no creator');
preconditions.checkArgument(opts.createdTs,'no createdTs');
preconditions.checkArgument(opts.builder,'no builder');
preconditions.checkArgument(opts.inputChainPaths,'no inputChainPaths');
this.creator = opts.creator;
this.createdTs = opts.createdTs;
this.builder = opts.builder;
this.inputChainPaths = opts.inputChainPaths;
this.version = opts.version;
this.builder = opts.builder;
this.createdTs = opts.createdTs;
this.createdTs = opts.createdTs;
this._inputSignatures = [];
this.seenBy = opts.seenBy || {};
// CopayerIds
this.creator = opts.creator;
this.signedBy = opts.signedBy || {};
this.seenBy = opts.seenBy || {};
this.rejectedBy = opts.rejectedBy || {};
this.sentTs = opts.sentTs || null;
this.sentTxid = opts.sentTxid || null;
this.comment = opts.comment || null;
this.readonly = opts.readonly || null;
// this._updateSignedBy();
this.sync();
}
TxProposal.prototype.getId = function() {
@ -47,11 +54,24 @@ TxProposal.prototype.toObj = function() {
};
TxProposal.prototype.setSent = function(sentTxid) {
this.sentTxid = sentTxid;
this.sentTs = Date.now();
TxProposal.prototype.toObjForNetwork = function() {
var o = this.toObj;
var newOutput = {};
CORE_FIELDS.forEach(function(k){
newOutput[k] = o[k];
});
return newOutput;
};
TxProposal.prototype.sync = function() {
this._check();
this._updateSignedBy();
return this;
}
// fromObj => from a trusted source
TxProposal.fromObj = function(o, forceOpts) {
preconditions.checkArgument(o.builderObj);
delete o['builder'];
@ -64,17 +84,24 @@ 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;
};
}
return new TxProposal(o);
};
var t = new TxProposal(o);
t._check();
t._updateSignedBy();
TxProposal.fromObjUntrusted = function(o, forceOpts, senderId) {
var newInput = {};
CORE_FIELDS.forEach(function(k){
newInput[k] = o[k];
});
if (newInput.version !== VERSION)
throw new Error('Peer using different version');
return t;
return TxProposal.fromObj(newInput, forceOpts, senderId);
};
@ -144,7 +171,8 @@ TxProposal.prototype._updateSignedBy = function() {
if (signatureIndexes.length !== signatureCount)
throw new Error('Invalid signature');
this._inputSignatures[i] = signatureIndexes.map(function(i) {
return info.keys[i].toString('hex');
var r = info.keys[i].toString('hex');
return r;
});
};
};
@ -184,6 +212,21 @@ TxProposal.prototype.mergeBuilder = function(incoming) {
};
TxProposal.prototype.setSeen = function(copayerId) {
if (!this.seenBy[copayerId])
this.seenBy[copayerId] = Date.now();
};
TxProposal.prototype.setRejected = function(copayerId) {
if (!this.rejectedBy[copayerId] && !this.signedBy)
this.rejectedBy[copayerId] = Date.now();
};
TxProposal.prototype.setSent = function(sentTxid) {
this.sentTxid = sentTxid;
this.sentTs = Date.now();
};
/* OTDO
events.push({
type: 'seen',
@ -213,12 +256,11 @@ TxProposal.prototype._allSignatures = function() {
return ret;
};
// merge will not merge any metadata.
TxProposal.prototype.merge = function(incoming) {
var ret = {};
var newSignatures = [];
incoming._check();
incoming._updateSignedBy();
incoming.sync();
var prevInputSignatures = this._allSignatures();

View File

@ -20,6 +20,7 @@ function TxProposals(opts) {
this.txps = {};
}
// fromObj => from a trusted source
TxProposals.fromObj = function(o, forceOpts) {
var ret = new TxProposals({
networkName: o.networkName,
@ -60,8 +61,6 @@ TxProposals.prototype.merge = function(inTxp, allowedPubKeys) {
var ntxid = inTxp.getId();
var ret = {};
ret.events = [];
ret.events.hasChanged = false;
if (myTxps[ntxid]) {
var v0 = myTxps[ntxid];
@ -70,12 +69,7 @@ TxProposals.prototype.merge = function(inTxp, allowedPubKeys) {
}
else {
this.txps[ntxid] = inTxp;
ret.hasChanged = true;
ret.events.push({
type: 'new',
cid: inTxp.creator,
tx: ntxid
});
ret.new = 1;
}
return ret;
};
@ -88,22 +82,14 @@ TxProposals.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, opt
};
// Add a LOCALLY CREATED (trusted) tx proposal
TxProposals.prototype.add = function(data) {
var txp = new TxProposal(data);
TxProposals.prototype.add = function(txp) {
txp.sync();
var ntxid = txp.getId();
this.txps[ntxid] = txp;
return ntxid;
};
TxProposals.prototype.setSent = function(ntxid, txid) {
//sent TxProposals are local an not broadcasted.
this.txps[ntxid].setSent(txid);
};
TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
var txp = this.txps[ntxid];
var i = JSON.parse(JSON.stringify(txp));

View File

@ -17,6 +17,7 @@ var Address = bitcore.Address;
var HDParams = require('./HDParams');
var PublicKeyRing = require('./PublicKeyRing');
var TxProposal = require('./TxProposal');
var TxProposals = require('./TxProposals');
var PrivateKey = require('./PrivateKey');
var copayConfig = require('../../../config');
@ -129,11 +130,39 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
};
Wallet.prototype._processProposalEvents = function(mergeInfo) {
var ev = [];
if (mergeInfo.new) {
ev = {
type: 'new',
cid: senderId
}
} else {
for (var i in mergeInfo.newCopayers) {
var copayerId = mergeInfo.newCopayers[i];
ev.push({
type: 'signed',
cid: copayerId
});
}
}
if (ev)
this.emit('txProposalEvent',ev);
};
Wallet.prototype._handleTxProposal = function(senderId, data) {
this.log('RECV TXPROPOSAL: ', data);
var mergeInfo;
var mergeInfo, ntxid;
try {
mergeInfo = this.txProposals.mergeFromObj(data.txProposal, senderId, Wallet.builderOpts);
mergeInfo.newCopayers=[];
for (var i in mergeInfo.newSignatures) {
var k = mergeInfo.newSignatures[i];
mergeInfo.newCopayers.push(this.getCopayerIdFromPubKey(k));
};
ntxid = mergeInfo.inTxp.getId();
} catch (e) {
var corruptEvent = {
type: 'corrupt',
@ -143,21 +172,36 @@ Wallet.prototype._handleTxProposal = function(senderId, data) {
this.emit('txProposalEvent', corruptEvent);
return;
}
this.sendSeen(ntxid);
var added = this.addSeenToTxProposals();
if (added) {
this.log('### BROADCASTING txProposals with my seenBy updated.');
this.sendTxProposal(mergeInfo.inTxp.getId());
}
if (mergeInfo.hasChanged)
this.sendTxProposal(ntxid);
this.emit('txProposalsUpdated');
this.store();
for (var i = 0; i < mergeInfo.events.length; i++) {
this.emit('txProposalEvent', mergeInfo.events[i]);
}
this._processProposalEvents(senderId, mergeInfo);
};
Wallet.prototype._handleReject = function(senderId, data, isInbound) {
this.log('RECV REJECT:', data);
// TODO check that has not signed.
//
this.txProposals.txps[data.ntxid].setRejected(senderId);
this.emit('txProposalsUpdated');
this.store();
};
Wallet.prototype._handleSeen = function(senderId, data, isInbound) {
this.log('RECV SEEN:', data);
this.txProposals.txps[data.ntxid].setSeen(senderId);
this.emit('txProposalsUpdated');
this.store();
};
Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) {
this.log('RECV ADDRESSBOOK:', data);
var rcv = data.addressBook;
@ -199,6 +243,10 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
case 'publicKeyRing':
this._handlePublicKeyRing(senderId, data, isInbound);
break;
case 'reject':
this._handleReject(senderId, data, isInbound);
case 'seen':
this._handleReject(senderId, data, isInbound);
case 'txProposal':
this._handleTxProposal(senderId, data, isInbound);
break;
@ -381,6 +429,7 @@ Wallet.prototype.toObj = function() {
return walletObj;
};
// fromObj => from a trusted source
Wallet.fromObj = function(o, storage, network, blockchain) {
var opts = JSON.parse(JSON.stringify(o.opts));
opts.addressBook = o.addressBook;
@ -424,6 +473,26 @@ Wallet.prototype.sendTxProposal = function(ntxid, recipients) {
});
};
Wallet.prototype.sendSeen = function(ntxid) {
preconditions.checkArgument(ntxid);
this.log('### SENDING seen: ' + ntxid + ' TO: All');
this.send(null, {
type: 'seen',
ntxid: ntxid,
walletId: this.id,
});
};
Wallet.prototype.sendReject = function(ntxid) {
preconditions.checkArgument(ntxid);
this.log('### SENDING reject: ' + ntxid + ' TO: All');
this.send(null, {
type: 'reject',
ntxid: ntxid,
walletId: this.id,
});
};
Wallet.prototype.sendWalletReady = function(recipients) {
this.log('### SENDING WalletReady TO:', recipients);
@ -521,7 +590,7 @@ Wallet.prototype.reject = function(ntxid) {
}
txp.rejectedBy[myId] = Date.now();
this.sendTxProposal(ntxid);
this.sendReject(ntxid);
this.store();
this.emit('txProposalsUpdated');
};
@ -534,10 +603,10 @@ Wallet.prototype.sign = function(ntxid, cb) {
setTimeout(function() {
var myId = self.getMyCopayerId();
var txp = self.txProposals.txps[ntxid];
if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
if (cb) cb(false);
}
// if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
// if (cb) cb(false);
// }
//
var keys = self.privateKey.getForPaths(txp.inputChainPaths);
var b = txp.builder;
@ -574,7 +643,7 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
this.blockchain.sendRawTransaction(txHex, function(txid) {
self.log('BITCOIND txid:', txid);
if (txid) {
self.txProposals.setSent(ntxid, txid);
self.txProposals.txps[ntxid].setSent(txid);
self.sendTxProposal(ntxid);
self.store();
}
@ -582,20 +651,20 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
});
};
Wallet.prototype.addSeenToTxProposals = function() {
var ret = false;
var myId = this.getMyCopayerId();
for (var k in this.txProposals.txps) {
var txp = this.txProposals.txps[k];
if (!txp.seenBy[myId]) {
txp.seenBy[myId] = Date.now();
ret = true;
}
}
return ret;
};
// Wallet.prototype.addSeenToTxProposals = function() {
// var ret = false;
// var myId = this.getMyCopayerId();
//
// for (var k in this.txProposals.txps) {
// var txp = this.txProposals.txps[k];
// if (!txp.seenBy[myId]) {
//
// txp.seenBy[myId] = Date.now();
// ret = true;
// }
// }
// return ret;
// };
// TODO: remove this method and use getAddressesInfo everywhere
Wallet.prototype.getAddresses = function(opts) {
@ -718,6 +787,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName());
preconditions.checkState(pkr.isComplete());
preconditions.checkState(priv);
if (comment) preconditions.checkArgument(comment.length <= 100);
if (!opts.remainderOut) {
@ -744,22 +814,23 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths));
if (priv) {
var keys = priv.getForPaths(inputChainPaths);
var signed = b.sign(keys);
}
var keys = priv.getForPaths(inputChainPaths);
var signed = b.sign(keys);
var myId = this.getMyCopayerId();
var now = Date.now();
var me = {};
var tx = b.build();
if (priv && tx.countInputSignatures(0)) me[myId] = now;
if (!tx.countInputSignatures(0))
throw new Error ('Could not sign generated tx');
var me = {};
me[myId] = now;
var meSeen = {};
if (priv) meSeen[myId] = now;
var data = {
var ntxid = this.txProposals.add(new TxProposal({
inputChainPaths: inputChainPaths,
signedBy: me,
seenBy: meSeen,
@ -767,9 +838,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
createdTs: now,
builder: b,
comment: comment
};
var ntxid = this.txProposals.add(data);
}));
return ntxid;
};

View File

@ -182,8 +182,6 @@ describe('TxProposal', function() {
});
it('#_updateSignedBy', function() {
var txp = dummyProposal;
txp._inputSignatures.should.deep.equal([]);
txp._updateSignedBy();
txp._inputSignatures.should.deep.equal([[ '03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3' ]]);
});
describe('#_check', function() {

View File

@ -10,7 +10,7 @@ try {
}
var copayConfig = require('../config');
var Wallet = require('../js/models/core/Wallet');
var Structure = copay.Structure;
var PrivateKey = copay.PrivateKey;
var Storage = require('./mocks/FakeStorage');
var Network = require('./mocks/FakeNetwork');
var Blockchain = require('./mocks/FakeBlockchain');
@ -19,22 +19,30 @@ var TransactionBuilder = bitcore.TransactionBuilder;
var Transaction = bitcore.Transaction;
var Address = bitcore.Address;
var config = {
requiredCopayers: 3,
totalCopayers: 5,
spendUnconfirmed: true,
reconnectDelay: 100,
networkName: 'testnet',
};
var getNewEpk = function() {
return new PrivateKey({
networkName: config.networkName,
})
.deriveBIP45Branch()
.extendedPublicKeyString();
}
var addCopayers = function(w) {
for (var i = 0; i < 4; i++) {
w.publicKeyRing.addCopayer();
w.publicKeyRing.addCopayer(getNewEpk());
}
};
describe('Wallet model', function() {
var config = {
requiredCopayers: 3,
totalCopayers: 5,
spendUnconfirmed: true,
reconnectDelay: 100,
networkName: 'testnet',
};
it('should fail to create an instance', function() {
(function() {
new Wallet(config)
@ -47,12 +55,11 @@ describe('Wallet model', function() {
});
var createW = function(netKey, N, conf) {
var createW = function(N, conf) {
var c = JSON.parse(JSON.stringify(conf || config));
if (!N) N = c.totalCopayers;
if (netKey) c.netKey = netKey;
var mainPrivateKey = new copay.PrivateKey({
networkName: config.networkName
});
@ -148,8 +155,7 @@ describe('Wallet model', function() {
var createW2 = function(privateKeys, N, conf) {
if (!N) N = 3;
var netKey = 'T0FbU2JLby0=';
var w = createW(netKey, N, conf);
var w = createW(N, conf);
should.exist(w);
var pkr = w.publicKeyRing;
@ -157,9 +163,9 @@ describe('Wallet model', function() {
for (var i = 0; i < N - 1; i++) {
if (privateKeys) {
var k = privateKeys[i];
pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : null);
pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : getNewEpk());
} else {
pkr.addCopayer();
pkr.addCopayer(getNewEpk());
}
}
@ -212,12 +218,12 @@ describe('Wallet model', function() {
var t = w.txProposals;
var txp = t.txps[ntxid];
Object.keys(txp._inputSignatures).length.should.equal(1);
var tx = txp.builder.build();
should.exist(tx);
chai.expect(txp.comment).to.be.null;
tx.isComplete().should.equal(false);
Object.keys(txp.seenBy).length.should.equal(1);
Object.keys(txp.signedBy).length.should.equal(1);
});
it('#create with comment', function() {
@ -502,7 +508,8 @@ describe('Wallet model', function() {
var w = createW();
var r = w.getRegisteredCopayerIds();
r.length.should.equal(1);
w.publicKeyRing.addCopayer();
w.publicKeyRing.addCopayer(getNewEpk());
r = w.getRegisteredCopayerIds();
r.length.should.equal(2);
r[0].should.not.equal(r[1]);
@ -512,7 +519,7 @@ describe('Wallet model', function() {
var w = createW();
var r = w.getRegisteredPeerIds();
r.length.should.equal(1);
w.publicKeyRing.addCopayer();
w.publicKeyRing.addCopayer(getNewEpk());
r = w.getRegisteredPeerIds();
r.length.should.equal(2);
r[0].should.not.equal(r[1]);
@ -642,10 +649,11 @@ describe('Wallet model', function() {
});
});
it('should create & sign transaction from received funds', function(done) {
this.timeout(10000);
var w = cachedCreateW2();
var pk = w.privateKey;
w.privateKey = null;
var k2 = new PrivateKey({
networkName: config.networkName
});
var w = createW2([k2]);
var utxo = createUTXO(w);
w.blockchain.fixUnspent(utxo);
w.createTx(toAddress, amountSatStr, null, function(ntxid) {
@ -654,7 +662,7 @@ describe('Wallet model', function() {
w.getTxProposals()[0].rejectedByUs.should.equal(false);
done();
});
w.privateKey = pk;
w.privateKey = k2;
w.sign(ntxid, function(success) {
success.should.equal(true);
});
@ -1031,9 +1039,9 @@ describe('Wallet model', function() {
e.type.should.equal(result);
done();
});
var txp = {
'txProposal': { dummy: 1}
};
var txp = {dummy:1};
// txp.prototype.getId = function() {return 'aa'};
var txp = { 'txProposal': txp };
var merge = sinon.stub(w.txProposals, 'mergeFromObj', function() {
if (shouldThrow) throw new Error();
return {events: [{type:'new'}]};