txp* test passing
This commit is contained in:
parent
966818c53a
commit
753b890658
5 changed files with 417 additions and 193 deletions
|
|
@ -297,9 +297,10 @@ PublicKeyRing.prototype.forPaths = function(paths) {
|
|||
};
|
||||
|
||||
|
||||
// returns pubkey -> copayerId.
|
||||
PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) {
|
||||
|
||||
var inKeyMap = {}, ret = [];
|
||||
var inKeyMap = {}, ret = {};
|
||||
for(var i in pubkeys ){
|
||||
inKeyMap[pubkeys[i]] = 1;
|
||||
};
|
||||
|
|
@ -309,7 +310,7 @@ PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) {
|
|||
for(var copayerIndex in keys[i] ){
|
||||
var kHex = keys[i][copayerIndex].toString('hex');
|
||||
if (inKeyMap[kHex]) {
|
||||
ret.push(this.copayerIds[copayerIndex]);
|
||||
ret[kHex] =this.copayerIds[copayerIndex];
|
||||
delete inKeyMap[kHex];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,16 +11,16 @@ var buffertools = bitcore.buffertools;
|
|||
var preconditions = require('preconditions').instance();
|
||||
|
||||
var VERSION = 1;
|
||||
var CORE_FIELDS = ['builderObj','inputChainPaths', 'version'];
|
||||
var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version'];
|
||||
|
||||
|
||||
function TxProposal(opts) {
|
||||
preconditions.checkArgument(opts);
|
||||
preconditions.checkArgument(opts.inputChainPaths,'no inputChainPaths');
|
||||
preconditions.checkArgument(opts.creator,'no creator');
|
||||
preconditions.checkArgument(opts.createdTs,'no createdTs');
|
||||
preconditions.checkArgument(opts.builder,'no builder');
|
||||
preconditions.checkArgument(opts.inputChainPaths,'no inputChainPaths');
|
||||
preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths');
|
||||
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.inputChainPaths = opts.inputChainPaths;
|
||||
this.version = opts.version;
|
||||
|
|
@ -38,11 +38,63 @@ function TxProposal(opts) {
|
|||
this.sentTxid = opts.sentTxid || null;
|
||||
this.comment = opts.comment || null;
|
||||
this.readonly = opts.readonly || null;
|
||||
|
||||
this.sync();
|
||||
this._sync();
|
||||
}
|
||||
|
||||
|
||||
TxProposal.prototype._check = function() {
|
||||
|
||||
if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) {
|
||||
throw new Error('Invalid tx proposal');
|
||||
}
|
||||
|
||||
var tx = this.builder.build();
|
||||
if (!tx.ins.length)
|
||||
throw new Error('Invalid tx proposal: no ins');
|
||||
|
||||
for (var i in tx.ins) {
|
||||
var scriptSig = tx.ins[i].s;
|
||||
if (!scriptSig || !scriptSig.length) {
|
||||
throw new Error('Invalid tx proposal: no signatures');
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < tx.ins.length; i++) {
|
||||
var hashType = tx.getHashType(i);
|
||||
if (hashType && hashType !== Transaction.SIGHASH_ALL)
|
||||
throw new Error('Invalid tx proposal: bad signatures');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype._updateSignedBy = function() {
|
||||
this._inputSignatures = [];
|
||||
|
||||
var tx = this.builder.build();
|
||||
for (var i in tx.ins) {
|
||||
var scriptSig = new Script(tx.ins[i].s);
|
||||
var signatureCount = scriptSig.countSignatures();
|
||||
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
||||
var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL);
|
||||
var signatureIndexes = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash);
|
||||
if (signatureIndexes.length !== signatureCount)
|
||||
throw new Error('Invalid signature');
|
||||
this._inputSignatures[i] = signatureIndexes.map(function(i) {
|
||||
var r = info.keys[i].toString('hex');
|
||||
return r;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
TxProposal.prototype._sync = function() {
|
||||
this._check();
|
||||
this._updateSignedBy();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
TxProposal.prototype.getId = function() {
|
||||
preconditions.checkState(this.builder);
|
||||
return this.builder.build().getNormalizedHash().toString('hex');
|
||||
};
|
||||
|
||||
|
|
@ -54,23 +106,15 @@ TxProposal.prototype.toObj = function() {
|
|||
};
|
||||
|
||||
|
||||
TxProposal.prototype.toObjForNetwork = function() {
|
||||
var o = this.toObj;
|
||||
|
||||
var newOutput = {};
|
||||
CORE_FIELDS.forEach(function(k){
|
||||
newOutput[k] = o[k];
|
||||
TxProposal.trim = function() {
|
||||
var o = this.toObj();
|
||||
var ret = {};
|
||||
CORE_FIELDS.forEach(function(k) {
|
||||
ret[k] = o[k];
|
||||
});
|
||||
return newOutput;
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposal.prototype.sync = function() {
|
||||
this._check();
|
||||
this._updateSignedBy();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// fromObj => from a trusted source
|
||||
TxProposal.fromObj = function(o, forceOpts) {
|
||||
preconditions.checkArgument(o.builderObj);
|
||||
|
|
@ -93,17 +137,6 @@ TxProposal.fromObj = function(o, forceOpts) {
|
|||
return new TxProposal(o);
|
||||
};
|
||||
|
||||
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 TxProposal.fromObj(newInput, forceOpts, senderId);
|
||||
};
|
||||
|
||||
|
||||
|
||||
TxProposal._formatKeys = function(keys) {
|
||||
|
|
@ -158,49 +191,6 @@ TxProposal._infoFromRedeemScript = function(s) {
|
|||
};
|
||||
};
|
||||
|
||||
TxProposal.prototype._updateSignedBy = function() {
|
||||
this._inputSignatures = [];
|
||||
|
||||
var tx = this.builder.build();
|
||||
for (var i in tx.ins) {
|
||||
var scriptSig = new Script(tx.ins[i].s);
|
||||
var signatureCount = scriptSig.countSignatures();
|
||||
var info = TxProposal._infoFromRedeemScript(scriptSig);
|
||||
var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL);
|
||||
var signatureIndexes = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash);
|
||||
if (signatureIndexes.length !== signatureCount)
|
||||
throw new Error('Invalid signature');
|
||||
this._inputSignatures[i] = signatureIndexes.map(function(i) {
|
||||
var r = info.keys[i].toString('hex');
|
||||
return r;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
TxProposal.prototype._check = function() {
|
||||
|
||||
if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) {
|
||||
throw new Error('Invalid tx proposal');
|
||||
}
|
||||
|
||||
var tx = this.builder.build();
|
||||
if (!tx.ins.length)
|
||||
throw new Error('Invalid tx proposal: no ins');
|
||||
|
||||
for(var i in tx.ins){
|
||||
var scriptSig = tx.ins[i].s;
|
||||
if (!scriptSig || !scriptSig.length) {
|
||||
throw new Error('Invalid tx proposal: no signatures');
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < tx.ins.length; i++) {
|
||||
var hashType = tx.getHashType(i);
|
||||
if (hashType && hashType !== Transaction.SIGHASH_ALL)
|
||||
throw new Error('Invalid tx proposal: bad signatures');
|
||||
}
|
||||
};
|
||||
|
||||
TxProposal.prototype.mergeBuilder = function(incoming) {
|
||||
var b0 = this.builder;
|
||||
var b1 = incoming.builder;
|
||||
|
|
@ -213,12 +203,12 @@ TxProposal.prototype.mergeBuilder = function(incoming) {
|
|||
|
||||
|
||||
TxProposal.prototype.setSeen = function(copayerId) {
|
||||
if (!this.seenBy[copayerId])
|
||||
if (!this.seenBy[copayerId])
|
||||
this.seenBy[copayerId] = Date.now();
|
||||
};
|
||||
|
||||
TxProposal.prototype.setRejected = function(copayerId) {
|
||||
if (!this.rejectedBy[copayerId] && !this.signedBy)
|
||||
if (!this.rejectedBy[copayerId] && !this.signedBy)
|
||||
this.rejectedBy[copayerId] = Date.now();
|
||||
};
|
||||
|
||||
|
|
@ -227,55 +217,78 @@ TxProposal.prototype.setSent = function(sentTxid) {
|
|||
this.sentTs = Date.now();
|
||||
};
|
||||
|
||||
/* OTDO
|
||||
events.push({
|
||||
type: 'seen',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
events.push({
|
||||
type: 'signed',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
events.push({
|
||||
type: 'rejected',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
ret.events = this.mergeMetadata(incoming);
|
||||
*/
|
||||
|
||||
|
||||
TxProposal.prototype._allSignatures = function() {
|
||||
var ret = {};
|
||||
for(var i in this._inputSignatures)
|
||||
for (var i in this._inputSignatures)
|
||||
for (var j in this._inputSignatures[i])
|
||||
ret[this._inputSignatures[i][j]] = true;
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
||||
TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
||||
var newCopayers = {},
|
||||
oldCopayers = {}, newSignedBy = {}, readOnlyPeers = {}, isNew = 1;
|
||||
|
||||
for(var k in this.signedBy) {
|
||||
oldCopayers[k] = 1;
|
||||
isNew = 0;
|
||||
};
|
||||
|
||||
if (isNew == 0 && (!this.creator || !this.createdTs))
|
||||
throw new Error('Existing TX has no creator');
|
||||
|
||||
if (isNew == 0 && (!this.signedBy[this.creator]))
|
||||
throw new Error('Existing TX is not signed by creator');
|
||||
|
||||
var iSig = this._inputSignatures[0];
|
||||
for(var i in iSig){
|
||||
var copayerId = keyMap[iSig[i]];
|
||||
if (!copayerId)
|
||||
throw new Error('Found unknown signature')
|
||||
|
||||
if (oldCopayers[copayerId]) {
|
||||
//Already have it. Do nothing
|
||||
} else {
|
||||
newCopayers[copayerId] = Date.now();
|
||||
delete oldCopayers[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!newCopayers[senderId] && !readOnlyPeers[senderId])
|
||||
throw new Error('TX must have a (new) senders signature')
|
||||
|
||||
if (isNew && Object.keys(newCopayers).length>1)
|
||||
throw new Error('New TX must have only 1 signature');
|
||||
|
||||
// Handler creator / createdTs.
|
||||
// from senderId, and must be signed by senderId
|
||||
if (isNew) {
|
||||
this.creator = Object.keys(newCopayers)[0];
|
||||
this.createdTs = Date.now();
|
||||
}
|
||||
|
||||
//Ended. Update this.
|
||||
for(var i in newCopayers) {
|
||||
this.signedBy[i] = newCopayers[i];
|
||||
}
|
||||
|
||||
// signedBy has preference over rejectedBy
|
||||
for(var i in this.signedBy) {
|
||||
delete this.rejectedBy[i];
|
||||
}
|
||||
|
||||
return Object.keys(newCopayers);
|
||||
};
|
||||
|
||||
// merge will not merge any metadata.
|
||||
TxProposal.prototype.merge = function(incoming) {
|
||||
var ret = {};
|
||||
var newSignatures = [];
|
||||
incoming.sync();
|
||||
|
||||
var prevInputSignatures = this._allSignatures();
|
||||
|
||||
ret.hasChanged = this.mergeBuilder(incoming);
|
||||
this._updateSignedBy();
|
||||
|
||||
if (ret.hasChanged)
|
||||
for(var i in this._inputSignatures)
|
||||
for (var j in this._inputSignatures[i])
|
||||
if (!prevInputSignatures[this._inputSignatures[i][j]])
|
||||
newSignatures.push(this._inputSignatures[i][j]);
|
||||
|
||||
ret.newSignatures = newSignatures;
|
||||
|
||||
return ret;
|
||||
var hasChanged = this.mergeBuilder(incoming);
|
||||
this._sync();
|
||||
return hasChanged;
|
||||
};
|
||||
|
||||
//This should be on bitcore / Transaction
|
||||
|
|
|
|||
|
|
@ -56,21 +56,30 @@ TxProposals.prototype.toObj = function() {
|
|||
};
|
||||
|
||||
|
||||
TxProposals.prototype.merge = function(inTxp, allowedPubKeys) {
|
||||
var myTxps = this.txps;
|
||||
TxProposals.prototype.merge = function(inObj, senderId, copayersForPubkeys, builderOpts) {
|
||||
var safeObj = inObj.trimUntrustedObj();
|
||||
var incomingTx = TxProposal.fromObj(safeObj, builderOpts);
|
||||
incomingTx._sync();
|
||||
|
||||
var myTxps = this.txps;
|
||||
var ntxid = inTxp.getId();
|
||||
var ret = {};
|
||||
var ret = {
|
||||
ntxid: ntxid
|
||||
};
|
||||
|
||||
if (myTxps[ntxid]) {
|
||||
var v0 = myTxps[ntxid];
|
||||
var v1 = inTxp;
|
||||
ret = v0.merge(v1, allowedPubKeys);
|
||||
}
|
||||
else {
|
||||
this.txps[ntxid] = inTxp;
|
||||
|
||||
// Merge an existing txProposal
|
||||
ret.hasChanged = myTxps[ntxid].merge(inTxp, allowedPubKeys);
|
||||
|
||||
|
||||
} else {
|
||||
// Create a new one
|
||||
ret.new = 1;
|
||||
this.txps[ntxid] = inTxp;
|
||||
}
|
||||
|
||||
ret.txp = this.txps[ntxid];
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ function Wallet(opts) {
|
|||
}
|
||||
|
||||
|
||||
Wallet.builderOpts = {
|
||||
Wallet.builderOpts = {
|
||||
lockTime: null,
|
||||
signhash: bitcore.Transaction.SIGNHASH_ALL,
|
||||
fee: null,
|
||||
|
|
@ -132,72 +132,119 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
|
|||
|
||||
Wallet.prototype._processProposalEvents = function(mergeInfo) {
|
||||
var ev = [];
|
||||
if (mergeInfo.new) {
|
||||
ev = {
|
||||
type: 'new',
|
||||
cid: senderId
|
||||
if (mergeInfo) {
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var i in mergeInfo.newCopayers) {
|
||||
var copayerId = mergeInfo.newCopayers[i];
|
||||
ev.push({
|
||||
type: 'signed',
|
||||
cid: copayerId
|
||||
});
|
||||
}
|
||||
ev = {
|
||||
type: 'corrupt',
|
||||
cId: senderId,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
if (ev)
|
||||
this.emit('txProposalEvent', ev);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* OTDO
|
||||
events.push({
|
||||
type: 'signed',
|
||||
cId: k,
|
||||
txId: ntxid
|
||||
});
|
||||
*/
|
||||
Wallet.prototype._getKeyMap = function(tpx, senderId) {
|
||||
|
||||
this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.paths);
|
||||
|
||||
var keyMapStr = JSON.stringify(keyMap);
|
||||
// All inputs must be signed with the same copayers
|
||||
for (var i in m.txp._inputSignatures) {
|
||||
if (!i) continue;
|
||||
var inputKeyMapStr = JSON.stringify(
|
||||
this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[i], txp.paths));
|
||||
|
||||
if (inputKeyMapStr !== keyMapStr)
|
||||
throw new Error('found inputs with different signatures in Tx from:' + senderId);
|
||||
}
|
||||
if (ev)
|
||||
this.emit('txProposalEvent',ev);
|
||||
};
|
||||
|
||||
|
||||
Wallet.prototype._handleTxProposal = function(senderId, data) {
|
||||
this.log('RECV TXPROPOSAL: ', data);
|
||||
var mergeInfo, ntxid;
|
||||
var m;
|
||||
|
||||
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();
|
||||
m = this.txProposals.mergeObj(senderId, data.txProposal, Wallet.builderOpts);
|
||||
|
||||
var keyMap = this._getKeyMap(m.tpx,senderId);
|
||||
ret.newCopayers = m.txp.setCopayers(senderId, keyMap);
|
||||
|
||||
} catch (e) {
|
||||
var corruptEvent = {
|
||||
type: 'corrupt',
|
||||
cId: senderId,
|
||||
error: e,
|
||||
};
|
||||
this.emit('txProposalEvent', corruptEvent);
|
||||
return;
|
||||
this.log('Corrupt TX proposal received', senderId, e); //TODO
|
||||
}
|
||||
this.sendSeen(ntxid);
|
||||
|
||||
if (mergeInfo.hasChanged)
|
||||
this.sendTxProposal(ntxid);
|
||||
if (m) {
|
||||
this.emit('txProposalsUpdated');
|
||||
this.store();
|
||||
|
||||
this.emit('txProposalsUpdated');
|
||||
this.store();
|
||||
this._processProposalEvents(senderId, mergeInfo);
|
||||
this.sendSeen(m.ntxid);
|
||||
|
||||
if (m.hasChanged)
|
||||
this.sendTxProposal(m.ntxid);
|
||||
}
|
||||
|
||||
this._processProposalEvents(senderId, m);
|
||||
};
|
||||
|
||||
|
||||
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');
|
||||
|
||||
var txp = this.txProposals.txps[data.ntxid];
|
||||
|
||||
if (!txp)
|
||||
throw new Error('Received Reject for an unkwown TX from:' + senderId);
|
||||
|
||||
if (txp.signedBy[senderId])
|
||||
throw new Error('Received Reject for an already signed TX from:' + senderId);
|
||||
|
||||
txp.setRejected(senderId);
|
||||
this.store();
|
||||
|
||||
this.emit('txProposalsUpdated');
|
||||
this.emit('txProposalEvent', {
|
||||
type: 'rejected',
|
||||
cId: senderId,
|
||||
txId: data.ntxid,
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype._handleSeen = function(senderId, data, isInbound) {
|
||||
this.log('RECV SEEN:', data);
|
||||
this.txProposals.txps[data.ntxid].setSeen(senderId);
|
||||
this.emit('txProposalsUpdated');
|
||||
this.store();
|
||||
this.emit('txProposalsUpdated');
|
||||
this.emit('txProposalEvent', {
|
||||
type: 'seen',
|
||||
cId: senderId,
|
||||
txId: data.ntxid,
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -245,8 +292,10 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
|
|||
break;
|
||||
case 'reject':
|
||||
this._handleReject(senderId, data, isInbound);
|
||||
break;
|
||||
case 'seen':
|
||||
this._handleReject(senderId, data, isInbound);
|
||||
this._handleSeen(senderId, data, isInbound);
|
||||
break;
|
||||
case 'txProposal':
|
||||
this._handleTxProposal(senderId, data, isInbound);
|
||||
break;
|
||||
|
|
@ -796,7 +845,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
};
|
||||
}
|
||||
|
||||
for (var k in Wallet.builderOpts){
|
||||
for (var k in Wallet.builderOpts) {
|
||||
opts[k] = Wallet.builderOpts[k];
|
||||
}
|
||||
|
||||
|
|
@ -821,8 +870,8 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
|
||||
|
||||
var tx = b.build();
|
||||
if (!tx.countInputSignatures(0))
|
||||
throw new Error ('Could not sign generated tx');
|
||||
if (!tx.countInputSignatures(0))
|
||||
throw new Error('Could not sign generated tx');
|
||||
|
||||
var me = {};
|
||||
me[myId] = now;
|
||||
|
|
|
|||
|
|
@ -161,28 +161,30 @@ describe('TxProposal', function() {
|
|||
}).should.throw('script');
|
||||
});
|
||||
it('#_verifyScriptSig, no signatures', function() {
|
||||
var ret = TxProposal._verifySignatures( keyBuf, validScriptSig, new Buffer(32));
|
||||
var ret = TxProposal._verifySignatures(keyBuf, validScriptSig, new Buffer(32));
|
||||
ret.length.should.equal(0);
|
||||
});
|
||||
it('#_verifyScriptSig, two signatures', function() {
|
||||
// Data taken from bitcore's TransactionBuilder test
|
||||
var txp = dummyProposal;
|
||||
var tx = dummyProposal.builder.build();
|
||||
var ret = TxProposal._verifySignatures(pubkeys,validScriptSig, tx.hashForSignature());
|
||||
var ret = TxProposal._verifySignatures(pubkeys, validScriptSig, tx.hashForSignature());
|
||||
ret.should.deep.equal([0, 3]);
|
||||
});
|
||||
it('#_infoFromRedeemScript', function() {
|
||||
var info = TxProposal._infoFromRedeemScript(validScriptSig);
|
||||
var keys = info.keys;
|
||||
keys.length.should.equal(5);
|
||||
for(var i in keys){
|
||||
for (var i in keys) {
|
||||
keys[i].toString('hex').should.equal(pubkeys[i].toString('hex'));
|
||||
}
|
||||
Buffer.isBuffer(info.script.getBuffer()).should.equal(true);
|
||||
});
|
||||
it('#_updateSignedBy', function() {
|
||||
var txp = dummyProposal;
|
||||
txp._inputSignatures.should.deep.equal([[ '03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3' ]]);
|
||||
txp._inputSignatures.should.deep.equal([
|
||||
['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']
|
||||
]);
|
||||
});
|
||||
describe('#_check', function() {
|
||||
var txp = dummyProposal;
|
||||
|
|
@ -193,28 +195,38 @@ describe('TxProposal', function() {
|
|||
});
|
||||
it('FAIL ins', function() {
|
||||
txp.builder.tx.ins = [];
|
||||
(function() { txp._check();} ).should.throw('no ins');
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('no ins');
|
||||
txp.builder.tx.ins = backup;
|
||||
});
|
||||
it('FAIL signhash SINGLE', function() {
|
||||
sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_SINGLE);
|
||||
(function() { txp._check();} ).should.throw('signatures');
|
||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_SINGLE);
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('signatures');
|
||||
txp.builder.tx.getHashType.restore();
|
||||
});
|
||||
it('FAIL signhash NONE', function() {
|
||||
sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_NONE);
|
||||
(function() { txp._check();} ).should.throw('signatures');
|
||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_NONE);
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('signatures');
|
||||
txp.builder.tx.getHashType.restore();
|
||||
});
|
||||
it('FAIL signhash ANYONECANPAY', function() {
|
||||
sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_ANYONECANPAY);
|
||||
(function() { txp._check();} ).should.throw('signatures');
|
||||
sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_ANYONECANPAY);
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('signatures');
|
||||
txp.builder.tx.getHashType.restore();
|
||||
});
|
||||
it('FAIL no signatures', function() {
|
||||
var backup = txp.builder.tx.ins[0].s;
|
||||
txp.builder.tx.ins[0].s = undefined;
|
||||
(function() { txp._check();} ).should.throw('no signatures');
|
||||
(function() {
|
||||
txp._check();
|
||||
}).should.throw('no signatures');
|
||||
txp.builder.tx.ins[0].s = backup;
|
||||
});
|
||||
});
|
||||
|
|
@ -222,39 +234,179 @@ describe('TxProposal', function() {
|
|||
var txp = dummyProposal;
|
||||
var backup = txp.builder.tx.ins;
|
||||
it('with self', function() {
|
||||
var ret = txp.merge(txp);
|
||||
ret.newSignatures.length.should.equal(0);
|
||||
ret.hasChanged.should.equal(false);
|
||||
var hasChanged = txp.merge(txp);
|
||||
hasChanged.should.equal(false);
|
||||
});
|
||||
|
||||
it('with less signatures', function() {
|
||||
var backup = txp.builder.vanilla.scriptSig[0];
|
||||
txp.builder.merge = function() {
|
||||
// 3 signatures.
|
||||
this.vanilla.scriptSig=['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
||||
this.tx.ins[0].s=new Buffer(this.vanilla.scriptSig[0],'hex');
|
||||
this.vanilla.scriptSig = ['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
||||
this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex');
|
||||
};
|
||||
var ret = txp.merge(txp);
|
||||
ret.hasChanged.should.equal(true);
|
||||
ret.newSignatures.length.should.equal(0);
|
||||
var hasChanged = txp.merge(txp);
|
||||
hasChanged.should.equal(true);
|
||||
|
||||
txp.builder.vanilla.scriptSig = [backup];
|
||||
txp.builder.tx.ins[0].s = new Buffer(backup,'hex');
|
||||
txp.builder.tx.ins[0].s = new Buffer(backup, 'hex');
|
||||
});
|
||||
|
||||
|
||||
it('with more signatures', function() {
|
||||
txp.builder.merge = function() {
|
||||
// 3 signatures.
|
||||
this.vanilla.scriptSig=['00483045022100f75bd3eb92d8c9be9a94d848bbd1985fc0eaf4c47fb470a0b222881802a1f03802204eb239ae3082779b1ec4f2e69baa0362494071e707e1696c14ad23c8f2e184e20148304502201981482db0f369ce943293b6fec06a0347918663c766a79d4cbd0457801768d1022100aedf8d7c51d55a9ddbdcc0067ed6b648b77ce9660447bbcf4e2c209698efa0a30148304502203f0ddad47757f8705cb40e7c706590d2e2028a7027ffdb26dd208fd6155e0d28022100ccd206f9b969ab7f88ee4c5c6cee48c800a62dda024c5a8de7eb8612b833a0c0014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
||||
this.tx.ins[0].s=new Buffer(this.vanilla.scriptSig[0],'hex');
|
||||
this.vanilla.scriptSig = ['00483045022100f75bd3eb92d8c9be9a94d848bbd1985fc0eaf4c47fb470a0b222881802a1f03802204eb239ae3082779b1ec4f2e69baa0362494071e707e1696c14ad23c8f2e184e20148304502201981482db0f369ce943293b6fec06a0347918663c766a79d4cbd0457801768d1022100aedf8d7c51d55a9ddbdcc0067ed6b648b77ce9660447bbcf4e2c209698efa0a30148304502203f0ddad47757f8705cb40e7c706590d2e2028a7027ffdb26dd208fd6155e0d28022100ccd206f9b969ab7f88ee4c5c6cee48c800a62dda024c5a8de7eb8612b833a0c0014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
|
||||
this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex');
|
||||
};
|
||||
var ret = txp.merge(txp);
|
||||
ret.hasChanged.should.equal(true);
|
||||
ret.newSignatures.length.should.equal(1);
|
||||
ret.newSignatures[0].should.equal('0392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed03');
|
||||
var hasChanged = txp.merge(txp);
|
||||
hasChanged.should.equal(true);
|
||||
});
|
||||
});
|
||||
describe('#setCopayers', function() {
|
||||
it("should fails if Tx has no creator", function() {
|
||||
var txp = dummyProposal;
|
||||
txp.signedBy = {
|
||||
'hugo': 1
|
||||
};
|
||||
delete txp['creator'];
|
||||
(function() {
|
||||
txp.setCopayers('juan', {
|
||||
pk1: 'pepe'
|
||||
})
|
||||
}).should.throw('no creator');
|
||||
});
|
||||
it("should fails if Tx is not signed by creator", function() {
|
||||
var txp = dummyProposal;
|
||||
txp.creator = 'creator';
|
||||
txp.signedBy = {
|
||||
'hugo': 1
|
||||
};
|
||||
txp._inputSignatures = [
|
||||
['pkX']
|
||||
];
|
||||
(function() {
|
||||
txp.setCopayers('juan', {
|
||||
pk1: 'pepe'
|
||||
})
|
||||
}).should.throw('creator');
|
||||
});
|
||||
|
||||
|
||||
it("should fails if Tx has unmapped signatures", function() {
|
||||
var txp = dummyProposal;
|
||||
txp.creator = 'creator';
|
||||
txp.signedBy = {
|
||||
creator: 1
|
||||
};
|
||||
txp._inputSignatures = [
|
||||
['pk0', 'pkX']
|
||||
];
|
||||
(function() {
|
||||
txp.setCopayers('juan', {
|
||||
pk1: 'pepe'
|
||||
})
|
||||
}).should.throw('unknown sig');
|
||||
});
|
||||
|
||||
it("should be signed by sender", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp._inputSignatures = [
|
||||
['pk1', 'pk0']
|
||||
];
|
||||
txp.signedBy = {
|
||||
'creator': Date.now()
|
||||
};
|
||||
(function() {
|
||||
txp.setCopayers('juan', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
})
|
||||
}).should.throw('senders sig');
|
||||
});
|
||||
|
||||
|
||||
it("should set signedBy (trivial case)", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp._inputSignatures = [
|
||||
['pk1', 'pk0']
|
||||
];
|
||||
txp.signedBy = {
|
||||
'creator': Date.now()
|
||||
};
|
||||
txp.setCopayers('pepe', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
})
|
||||
Object.keys(txp.signedBy).length.should.equal(2);
|
||||
txp.signedBy['pepe'].should.gte(ts);
|
||||
txp.signedBy['creator'].should.gte(ts);
|
||||
});
|
||||
it("should assign creator", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp._inputSignatures = [
|
||||
['pk0']
|
||||
];
|
||||
txp.signedBy = {};
|
||||
delete txp['creator'];
|
||||
delete txp['creatorTs'];
|
||||
txp.setCopayers('creator', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
})
|
||||
Object.keys(txp.signedBy).length.should.equal(1);
|
||||
txp.creator.should.equal('creator');
|
||||
txp.createdTs.should.gte(ts);
|
||||
})
|
||||
it("New tx should have only 1 signature", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp.signedBy = {};
|
||||
delete txp['creator'];
|
||||
delete txp['creatorTs'];
|
||||
txp._inputSignatures = [
|
||||
['pk0', 'pk1']
|
||||
];
|
||||
(function() {
|
||||
txp.setCopayers(
|
||||
'creator', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
}, {
|
||||
'creator2': 1
|
||||
}
|
||||
);
|
||||
}).should.throw('only 1');
|
||||
})
|
||||
|
||||
it("if signed, should not change ts", function() {
|
||||
var txp = dummyProposal;
|
||||
var ts = Date.now();
|
||||
txp._inputSignatures = [
|
||||
['pk0', 'pk1']
|
||||
];
|
||||
txp.creator = 'creator';
|
||||
txp.signedBy = {
|
||||
'creator': 1
|
||||
};
|
||||
txp.setCopayers('pepe', {
|
||||
pk0: 'creator',
|
||||
pk1: 'pepe',
|
||||
pk2: 'john'
|
||||
})
|
||||
Object.keys(txp.signedBy).length.should.equal(2);
|
||||
txp.creator.should.equal('creator');
|
||||
txp.signedBy['creator'].should.equal(1);
|
||||
txp.signedBy['pepe'].should.gte(ts);
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue