copay/test/test.TxProposal.js

464 lines
15 KiB
JavaScript

'use strict';
var chai = chai || require('chai');
var should = chai.should();
var bitcore = bitcore || require('bitcore');
var Transaction = bitcore.Transaction;
var buffertools = bitcore.buffertools;
var WalletKey = bitcore.WalletKey;
var Key = bitcore.Key;
var bignum = bitcore.Bignum;
var Script = bitcore.Script;
var TransactionBuilder = bitcore.TransactionBuilder;
var util = bitcore.util;
var networks = bitcore.networks;
var sinon = require('sinon');
var is_browser = typeof process == 'undefined'
|| typeof process.versions === 'undefined';
if (is_browser) {
var copay = require('copay'); //browser
} else {
var copay = require('../copay'); //node
}
var FakeBuilder = require('./mocks/FakeBuilder');
var TxProposal = copay.TxProposal;
var dummyProposal = new TxProposal({
creator: 1,
createdTs: 1,
builder: new FakeBuilder(),
inputChainPaths: ['m/1'],
});
var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"];
describe('TxProposal', function() {
describe('new', function() {
it('should fail to create an instance with wrong arguments', function() {
(function() {
var txp = new TxProposal();
}).should.throw('Illegal Argument');
(function() {
var txp = new TxProposal({
creator: 1
});
}).should.throw('no inputChainPaths');
});
it('should create an instance', function() {
var txp = new TxProposal({
creator: 1,
createdTs: 1,
builder: new FakeBuilder(),
inputChainPaths: 'm/1',
});
should.exist(txp);
txp.creator.should.equal(1);
should.exist(txp.builder);
txp.inputChainPaths.should.equal('m/1');
});
});
describe('#getId', function() {
it('should return id', function() {
var b = new FakeBuilder();
var spy = sinon.spy(b.tx, 'getNormalizedHash');
var txp = new TxProposal({
creator: 1,
createdTs: 1,
builder: b,
inputChainPaths: 'm/1',
});
txp.getId().should.equal('123456');;
sinon.assert.callCount(spy, 1);
});
});
describe('#toObj', function() {
it('should return an object and remove builder', function() {
var b = new FakeBuilder();
var txp = new TxProposal({
creator: 1,
createdTs: 1,
builder: b,
inputChainPaths: 'm/1',
});
var o = txp.toObj();
should.exist(o);
o.creator.should.equal(1);
should.not.exist(o.builder);
should.exist(o.builderObj);
});
it('toObjTrim', function() {
var b = new FakeBuilder();
var txp = new TxProposal({
creator: 1,
createdTs: 1,
builder: b,
inputChainPaths: 'm/1',
comment: 'hola',
});
var o = txp.toObjTrim();
should.exist(o);
should.not.exist(o.creator);
should.not.exist(o.builder);
should.exist(o.comment);
should.exist(o.builderObj);
});
});
describe('#fromObj', function() {
it.skip('should create from Object', function() {
var b = new FakeBuilder();
var txp = TxProposal.fromObj({
creator: 1,
createdTs: 1,
builderObj: b.toObj(),
inputChainPaths: ['m/1'],
});
should.exist(txp);
});
it('should fail to create from wrong object', function() {
var b = new FakeBuilder();
(function() {
var txp = TxProposal.fromObj({
creator: 1,
createdTs: 1,
builderObj: b.toObj(),
inputChainPaths: ['m/1'],
});
}).should.throw('Invalid or Incompatible Backup Detected');
});
});
describe('#setSent', function() {
it('should set txid and timestamp', function() {
var now = Date.now();
var txp = dummyProposal;
txp.setSent('3a42');
txp.sentTs.should.gte(now);
txp.sentTxid.should.equal('3a42');
});
});
describe('Signature verification', function() {
var validScriptSig = new bitcore.Script(FakeBuilder.VALID_SCRIPTSIG_BUF);
var pubkeys = [
'03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d',
'0380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127',
'0392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed03',
'03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3',
'03e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e4'
].map(function(hex) {
return new Buffer(hex, 'hex');
});
var keyBuf = someKeys.map(function(hex) {
return new Buffer(hex, 'hex');
});
it('#_formatKeys', function() {
(function() {
TxProposal._formatKeys(someKeys);
}).should.throw('buffers');
var res = TxProposal._formatKeys(keyBuf);
});
it('#_verifyScriptSig arg checks', function() {
(function() {
TxProposal._verifySignatures(
keyBuf,
new bitcore.Script(new Buffer('112233', 'hex')),
new Buffer('1a', 'hex'));
}).should.throw('script');
});
it('#_verifyScriptSig, no signatures', function() {
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());
ret.should.deep.equal(['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']);
});
it('#_infoFromRedeemScript', function() {
var info = TxProposal._infoFromRedeemScript(validScriptSig);
var keys = info.keys;
keys.length.should.equal(5);
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._inputSigners.should.deep.equal([
['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']
]);
});
describe('#_check', function() {
var txp = dummyProposal;
var backup = txp.builder.tx.ins;
it('OK', function() {
txp._check();
});
it('FAIL ins', function() {
txp.builder.tx.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');
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');
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');
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');
txp.builder.tx.ins[0].s = backup;
});
});
describe('#merge', function() {
var txp = dummyProposal;
var backup = txp.builder.tx.ins;
it('with self', function() {
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() {
// 2 signatures.
this.vanilla.scriptSig = ['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'];
this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex');
};
var hasChanged = txp.merge(txp);
hasChanged.should.equal(true);
txp.builder.vanilla.scriptSig = [backup];
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');
};
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._inputSigners = [
['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._inputSigners = [
['pk0', 'pkX']
];
(function() {
txp.setCopayers('juan', {
pk1: 'pepe'
})
}).should.throw('unknown sig');
});
// This was disabled. Unnecessary to check this.
it.skip("should be signed by sender", function() {
var txp = dummyProposal;
var ts = Date.now();
txp._inputSigners = [
['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._inputSigners = [
['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._inputSigners = [
['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);
txp.seenBy['creator'].should.equal(txp.createdTs);
})
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._inputSigners = [
['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._inputSigners = [
['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);
})
});
});
describe('micelaneous functions', function() {
it('should report rejectCount', function() {
var txp = dummyProposal;
txp.rejectCount().should.equal(0);
txp.setRejected(['juan'])
txp.rejectCount().should.equal(1);
});
it('should report isPending 1', function() {
var txp = dummyProposal;
txp.rejectedBy=[];
txp.sentTxid=1;
txp.isPending(3).should.equal(false);
});
it('should report isPending 2', function() {
var txp = dummyProposal;
txp.rejectedBy=[];
txp.sentTxid=null;
txp.isPending(3).should.equal(true);
});
it('should report isPending 3', function() {
var txp = dummyProposal;
txp.rejectedBy=[1,2,3,4];
txp.sentTxid=null;
txp.isPending(3).should.equal(false);
});
});
});