You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

968 lines
33 KiB

10 years ago
'use strict';
var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var should = chai.should();
var levelup = require('levelup');
var memdown = require('memdown');
10 years ago
var async = require('async');
var request = require('supertest');
10 years ago
var Client = require('../../lib/client');
10 years ago
var AirGapped = Client.AirGapped;
10 years ago
var Bitcore = require('bitcore');
var WalletUtils = require('../../lib/walletutils');
var ExpressApp = require('../../lib/expressapp');
var Storage = require('../../lib/storage');
var TestData = require('../testdata');
10 years ago
10 years ago
var helpers = {};
helpers.getRequest = function(app) {
return function(args, cb) {
var req = request(app);
var r = req[args.method](args.relUrl);
if (args.headers) {
_.each(args.headers, function(v, k) {
r.set(k, v);
})
}
if (!_.isEmpty(args.body)) {
r.send(args.body);
};
r.end(function(err, res) {
return cb(err, res, res.body);
10 years ago
});
10 years ago
};
};
helpers.createAndJoinWallet = function(clients, m, n, cb) {
10 years ago
clients[0].createWallet('wallet name', 'creator', m, n, 'testnet',
10 years ago
function(err, secret) {
10 years ago
should.not.exist(err);
if (n > 1) {
should.exist(secret);
}
async.series([
function(next) {
async.each(_.range(1, n), function(i, cb) {
clients[i].joinWallet(secret, 'copayer ' + i, cb);
}, next);
},
function(next) {
async.each(_.range(n), function(i, cb) {
clients[i].openWallet(cb);
}, next);
},
],
function(err) {
10 years ago
should.not.exist(err);
10 years ago
return cb({
m: m,
n: n,
secret: secret,
});
10 years ago
});
10 years ago
});
10 years ago
};
10 years ago
helpers.tamperResponse = function(clients, method, url, args, tamper, cb) {
clients = [].concat(clients);
// Use first client to get a clean response from server
clients[0]._doRequest(method, url, args, function(err, result) {
should.not.exist(err);
tamper(result);
// Return tampered data for every client in the list
_.each(clients, function(client) {
client._doRequest = sinon.stub().withArgs(method, url).yields(null, result);
});
return cb();
});
10 years ago
};
10 years ago
var blockExplorerMock = {};
10 years ago
blockExplorerMock.getUnspentUtxos = function(dummy, cb) {
var ret = _.map(blockExplorerMock.utxos || [], function(x) {
var y = _.clone(x);
y.toObject = function() {
10 years ago
return this;
};
return y;
10 years ago
});
return cb(null, ret);
};
blockExplorerMock.setUtxo = function(address, amount, m) {
blockExplorerMock.utxos.push({
10 years ago
txid: Bitcore.crypto.Hash.sha256(new Buffer(Math.random() * 100000)).toString('hex'),
vout: Math.floor((Math.random() * 10) + 1),
amount: amount,
address: address.address,
scriptPubKey: Bitcore.Script.buildMultisigOut(address.publicKeys, m).toScriptHashOut().toString(),
10 years ago
});
};
blockExplorerMock.broadcast = function(raw, cb) {
blockExplorerMock.lastBroadcasted = raw;
return cb(null, (new Bitcore.Transaction(raw)).id);
};
blockExplorerMock.setHistory = function(txs) {
blockExplorerMock.txHistory = txs;
};
blockExplorerMock.getTransactions = function(addresses, cb) {
return cb(null, blockExplorerMock.txHistory || []);
};
10 years ago
blockExplorerMock.reset = function() {
blockExplorerMock.utxos = [];
blockExplorerMock.txHistory = [];
10 years ago
};
10 years ago
10 years ago
10 years ago
describe('client API ', function() {
10 years ago
var clients, app;
10 years ago
beforeEach(function() {
10 years ago
var db = levelup(memdown, {
valueEncoding: 'json'
});
var storage = new Storage({
db: db
});
10 years ago
app = ExpressApp.start({
10 years ago
WalletService: {
10 years ago
storage: storage,
blockExplorer: blockExplorerMock,
},
disableLogs: true,
10 years ago
});
10 years ago
// Generates 5 clients
10 years ago
clients = _.map(_.range(5), function(i) {
return new Client({
request: helpers.getRequest(app),
10 years ago
});
});
10 years ago
blockExplorerMock.reset();
10 years ago
});
describe('Server internals', function() {
it('should allow cors', function(done) {
10 years ago
clients[0].credentials = {};
clients[0]._doRequest('options', '/', {}, function(err, x, headers) {
headers['access-control-allow-origin'].should.equal('*');
should.exist(headers['access-control-allow-methods']);
should.exist(headers['access-control-allow-headers']);
done();
});
});
it('should handle critical errors', function(done) {
var s = sinon.stub();
s.storeWallet = sinon.stub().yields('bigerror');
s.fetchWallet = sinon.stub().yields(null);
app = ExpressApp.start({
WalletService: {
storage: s,
blockExplorer: blockExplorerMock,
},
disableLogs: true,
});
var s2 = sinon.stub();
s2.load = sinon.stub().yields(null);
var client = new Client({
storage: s2,
});
client.request = helpers.getRequest(app);
client.createWallet('1', '2', 1, 1, 'testnet',
function(err) {
err.code.should.equal('ERROR');
done();
});
});
it('should handle critical errors (Case2)', function(done) {
var s = sinon.stub();
s.storeWallet = sinon.stub().yields({
code: 501,
message: 'wow'
});
s.fetchWallet = sinon.stub().yields(null);
app = ExpressApp.start({
WalletService: {
storage: s,
blockExplorer: blockExplorerMock,
},
disableLogs: true,
});
var s2 = sinon.stub();
s2.load = sinon.stub().yields(null);
var client = new Client({
storage: s2,
});
client.request = helpers.getRequest(app);
client.createWallet('1', '2', 1, 1, 'testnet',
function(err) {
err.code.should.equal('ERROR');
done();
});
});
});
10 years ago
describe('Wallet Creation', function() {
10 years ago
it('should check balance in a 1-1 ', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function() {
10 years ago
clients[0].getBalance(function(err, x) {
should.not.exist(err);
done();
})
});
});
10 years ago
it('should be able to complete wallets in copayer that joined later', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 3, function() {
10 years ago
clients[0].getBalance(function(err, x) {
should.not.exist(err);
clients[1].getBalance(function(err, x) {
should.not.exist(err);
clients[2].getBalance(function(err, x) {
should.not.exist(err);
done();
})
})
})
});
});
10 years ago
10 years ago
it('should not allow to join a full wallet ', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 2, function(w) {
10 years ago
should.exist(w.secret);
clients[4].joinWallet(w.secret, 'copayer', function(err, result) {
err.code.should.contain('WFULL');
10 years ago
done();
});
});
});
it('should fail with an invalid secret', function(done) {
// Invalid
clients[0].joinWallet('dummy', 'copayer', function(err, result) {
err.message.should.contain('Invalid secret');
// Right length, invalid char for base 58
clients[0].joinWallet('DsZbqNQQ9LrTKU8EknR7gFKyCQMPg2UUHNPZ1BzM5EbJwjRZaUNBfNtdWLluuFc0f7f7sTCkh7T', 'copayer', function(err, result) {
err.message.should.contain('Invalid secret');
done();
});
});
});
it('should fail with an unknown secret', function(done) {
// Unknown walletId
var oldSecret = '3bJKRn1HkQTpwhVaJMaJ22KwsjN24ML9uKfkSrP7iDuq91vSsTEygfGMMpo6kWLp1pXG9wZSKcT';
10 years ago
clients[0].joinWallet(oldSecret, 'copayer', function(err, result) {
err.code.should.contain('BADREQUEST');
10 years ago
done();
});
});
10 years ago
10 years ago
it('should reject wallets with bad signatures', function(done) {
// Do not complete clients[1] pkr
var openWalletStub = sinon.stub(clients[1], 'openWallet').yields();
helpers.createAndJoinWallet(clients, 2, 3, function() {
helpers.tamperResponse([clients[0], clients[1]], 'get', '/v1/wallets/', {}, function(status) {
status.wallet.copayers[0].xPubKey = status.wallet.copayers[1].xPubKey;
}, function() {
openWalletStub.restore();
clients[1].openWallet(function(err, x) {
err.code.should.contain('SERVERCOMPROMISED');
done();
10 years ago
});
});
10 years ago
});
});
10 years ago
10 years ago
it('should reject wallets with missing signatures', function(done) {
10 years ago
// Do not complete clients[1] pkr
var openWalletStub = sinon.stub(clients[1], 'openWallet').yields();
helpers.createAndJoinWallet(clients, 2, 3, function() {
helpers.tamperResponse([clients[0], clients[1]], 'get', '/v1/wallets/', {}, function(status) {
delete status.wallet.copayers[1].xPubKey;
}, function() {
openWalletStub.restore();
clients[1].openWallet(function(err, x) {
err.code.should.contain('SERVERCOMPROMISED');
done();
});
});
});
});
10 years ago
10 years ago
it('should reject wallets missing callers pubkey', function(done) {
// Do not complete clients[1] pkr
var openWalletStub = sinon.stub(clients[1], 'openWallet').yields();
helpers.createAndJoinWallet(clients, 2, 3, function() {
helpers.tamperResponse([clients[0], clients[1]], 'get', '/v1/wallets/', {}, function(status) {
// Replace caller's pubkey
status.wallet.copayers[1].xPubKey = (new Bitcore.HDPrivateKey()).publicKey;
// Add a correct signature
status.wallet.copayers[1].xPubKeySignature = WalletUtils.signMessage(status.wallet.copayers[1].xPubKey, clients[0].credentials.walletPrivKey);
}, function() {
openWalletStub.restore();
clients[1].openWallet(function(err, x) {
err.code.should.contain('SERVERCOMPROMISED');
done();
});
});
});
});
10 years ago
});
10 years ago
10 years ago
describe('Address Creation', function() {
it('should be able to create address in all copayers in a 2-3 wallet', function(done) {
this.timeout(5000);
10 years ago
helpers.createAndJoinWallet(clients, 2, 3, function() {
10 years ago
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
clients[1].createAddress(function(err, x1) {
should.not.exist(err);
should.exist(x1.address);
clients[2].createAddress(function(err, x2) {
should.not.exist(err);
should.exist(x2.address);
done();
});
});
});
});
});
it('should see balance on address created by others', function(done) {
10 years ago
this.timeout(5000);
helpers.createAndJoinWallet(clients, 2, 2, function(w) {
10 years ago
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 10, w.m);
clients[0].getBalance(function(err, bal0) {
should.not.exist(err);
bal0.totalAmount.should.equal(10 * 1e8);
bal0.lockedAmount.should.equal(0);
clients[1].getBalance(function(err, bal1) {
bal1.totalAmount.should.equal(10 * 1e8);
bal1.lockedAmount.should.equal(0);
done();
});
});
});
});
});
it('should detect fake addresses', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function() {
helpers.tamperResponse(clients[0], 'post', '/v1/addresses/', {}, function(address) {
address.address = '2N86pNEpREGpwZyHVC5vrNUCbF9nM1Geh4K';
}, function() {
clients[0].createAddress(function(err, x0) {
err.code.should.contain('SERVERCOMPROMISED');
10 years ago
done();
});
});
});
});
10 years ago
it('should detect fake public keys', function(done) {
helpers.createAndJoinWallet(clients, 1, 1, function() {
helpers.tamperResponse(clients[0], 'post', '/v1/addresses/', {}, function(address) {
address.publicKeys = [
'0322defe0c3eb9fcd8bc01878e6dbca7a6846880908d214b50a752445040cc5c54',
'02bf3aadc17131ca8144829fa1883c1ac0a8839067af4bca47a90ccae63d0d8037'
];
}, function() {
10 years ago
clients[0].createAddress(function(err, x0) {
10 years ago
err.code.should.contain('SERVERCOMPROMISED');
done();
10 years ago
});
});
});
});
10 years ago
});
10 years ago
describe('Transaction Proposals Creation and Locked funds', function() {
10 years ago
it('Should lock and release funds through rejection', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 2, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 1, 2);
blockExplorerMock.setUtxo(x0, 1, 2);
var opts = {
amount: 120000000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
clients[0].sendTxProposal(opts, function(err, y) {
err.code.should.contain('INSUFFICIENTFUNDS');
clients[0].rejectTxProposal(x, 'no', function(err, z) {
should.not.exist(err);
z.status.should.equal('rejected');
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
done();
});
});
});
});
});
});
});
10 years ago
it('Should lock and release funds through removal', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 2, function(w) {
10 years ago
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 1, 2);
blockExplorerMock.setUtxo(x0, 1, 2);
var opts = {
amount: 120000000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
clients[0].sendTxProposal(opts, function(err, y) {
err.code.should.contain('INSUFFICIENTFUNDS');
clients[0].removeTxProposal(x, function(err) {
should.not.exist(err);
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
done();
});
});
});
});
});
});
});
it('Should keep message and refusal texts', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 3, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
blockExplorerMock.setUtxo(x0, 10, 2);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'some message',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
clients[1].rejectTxProposal(x, 'rejection comment', function(err, tx1) {
should.not.exist(err);
10 years ago
clients[2].getTxProposals({}, function(err, txs) {
should.not.exist(err);
txs[0].message.should.equal('some message');
txs[0].actions[0].comment.should.equal('rejection comment');
done();
});
});
});
});
});
});
it('Should encrypt proposal message', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 3, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
blockExplorerMock.setUtxo(x0, 10, 2);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'some message',
};
var spy = sinon.spy(clients[0], '_doPostRequest');
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
spy.calledOnce.should.be.true;
JSON.stringify(spy.getCall(0).args).should.not.contain('some message');
done();
});
});
});
});
it('Should encrypt proposal refusal comment', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 3, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
blockExplorerMock.setUtxo(x0, 10, 2);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
var spy = sinon.spy(clients[1], '_doPostRequest');
clients[1].rejectTxProposal(x, 'rejection comment', function(err, tx1) {
should.not.exist(err);
spy.calledOnce.should.be.true;
JSON.stringify(spy.getCall(0).args).should.not.contain('rejection comment');
done();
});
});
});
});
});
it('should detect fake tx proposals (wrong signature)', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function() {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
blockExplorerMock.setUtxo(x0, 10, 2);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
10 years ago
helpers.tamperResponse(clients[0], 'get', '/v1/txproposals/', {}, function(txps) {
txps[0].proposalSignature = '304402206e4a1db06e00068582d3be41cfc795dcf702451c132581e661e7241ef34ca19202203e17598b4764913309897d56446b51bc1dcd41a25d90fdb5f87a6b58fe3a6920';
}, function() {
clients[0].getTxProposals({}, function(err, txps) {
should.exist(err);
err.code.should.contain('SERVERCOMPROMISED');
done();
});
});
});
});
});
});
10 years ago
it('should detect fake tx proposals (tampered amount)', function(done) {
helpers.createAndJoinWallet(clients, 1, 1, function() {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
blockExplorerMock.setUtxo(x0, 10, 2);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
10 years ago
helpers.tamperResponse(clients[0], 'get', '/v1/txproposals/', {}, function(txps) {
txps[0].amount = 100000;
}, function() {
clients[0].getTxProposals({}, function(err, txps) {
should.exist(err);
err.code.should.contain('SERVERCOMPROMISED');
done();
});
});
});
});
});
});
it('should detect fake tx proposals (change address not it wallet)', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function() {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
blockExplorerMock.setUtxo(x0, 10, 2);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
10 years ago
helpers.tamperResponse(clients[0], 'get', '/v1/txproposals/', {}, function(txps) {
txps[0].changeAddress.address = 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5';
}, function() {
clients[0].getTxProposals({}, function(err, txps) {
should.exist(err);
err.code.should.contain('SERVERCOMPROMISED');
done();
});
});
});
});
});
});
it('Should return only main addresses (case 1)', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
blockExplorerMock.setUtxo(x0, 1, 1);
var opts = {
amount: 10000000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
clients[0].sendTxProposal(opts, function(err, x) {
should.not.exist(err);
clients[0].getMainAddresses({}, function(err, addr) {
should.not.exist(err);
addr.length.should.equal(1);
done();
});
});
});
});
});
it('Should return only main addresses (case 2)', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
10 years ago
clients[0].getMainAddresses({
doNotVerify: true
}, function(err, addr) {
should.not.exist(err);
addr.length.should.equal(2);
done();
});
});
});
});
});
});
describe('Transactions Signatures and Rejection', function() {
this.timeout(5000);
10 years ago
it('Send and broadcast in 1-1 wallet', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function(w) {
10 years ago
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 1, 1);
10 years ago
var opts = {
amount: 10000000,
10 years ago
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
10 years ago
};
10 years ago
clients[0].sendTxProposal(opts, function(err, txp) {
10 years ago
should.not.exist(err);
10 years ago
txp.requiredRejections.should.equal(1);
txp.requiredSignatures.should.equal(1);
txp.status.should.equal('pending');
txp.changeAddress.path.should.equal('m/2147483647/1/0');
clients[0].signTxProposal(txp, function(err, txp) {
should.not.exist(err);
10 years ago
txp.status.should.equal('accepted');
clients[0].broadcastTxProposal(txp, function(err, txp) {
txp.status.should.equal('broadcasted');
txp.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id);
done();
});
10 years ago
});
10 years ago
});
});
});
});
10 years ago
it('Send and broadcast in 2-3 wallet', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 3, function(w) {
10 years ago
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 10, 1);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
10 years ago
};
10 years ago
clients[0].sendTxProposal(opts, function(err, txp) {
10 years ago
should.not.exist(err);
clients[0].getStatus(function(err, st) {
should.not.exist(err);
10 years ago
var txp = st.pendingTxps[0];
txp.status.should.equal('pending');
txp.requiredRejections.should.equal(2);
txp.requiredSignatures.should.equal(2);
var w = st.wallet;
w.copayers.length.should.equal(3);
w.status.should.equal('complete');
var b = st.balance;
b.totalAmount.should.equal(1000000000);
b.lockedAmount.should.equal(1000000000);
10 years ago
clients[0].signTxProposal(txp, function(err, txp) {
should.not.exist(err, err);
10 years ago
txp.status.should.equal('pending');
clients[1].signTxProposal(txp, function(err, txp) {
should.not.exist(err);
10 years ago
txp.status.should.equal('accepted');
clients[1].broadcastTxProposal(txp, function(err, txp) {
txp.status.should.equal('broadcasted');
txp.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id);
done();
});
});
10 years ago
});
10 years ago
});
});
});
});
});
it('Send, reject, 2 signs and broadcast in 2-3 wallet', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 3, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 10, 1);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
10 years ago
clients[0].sendTxProposal(opts, function(err, txp) {
should.not.exist(err);
10 years ago
txp.status.should.equal('pending');
txp.requiredRejections.should.equal(2);
txp.requiredSignatures.should.equal(2);
clients[0].rejectTxProposal(txp, 'wont sign', function(err, txp) {
should.not.exist(err, err);
10 years ago
txp.status.should.equal('pending');
clients[1].signTxProposal(txp, function(err, txp) {
should.not.exist(err);
10 years ago
clients[2].signTxProposal(txp, function(err, txp) {
should.not.exist(err);
10 years ago
txp.status.should.equal('accepted');
clients[2].broadcastTxProposal(txp, function(err, txp) {
txp.status.should.equal('broadcasted');
txp.txid.should.equal((new Bitcore.Transaction(blockExplorerMock.lastBroadcasted)).id);
done();
});
});
});
});
});
});
});
});
it('Send, reject in 3-4 wallet', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 3, 4, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 10, 1);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
10 years ago
clients[0].sendTxProposal(opts, function(err, txp) {
should.not.exist(err);
10 years ago
txp.status.should.equal('pending');
txp.requiredRejections.should.equal(2);
txp.requiredSignatures.should.equal(3);
10 years ago
clients[0].rejectTxProposal(txp, 'wont sign', function(err, txp) {
should.not.exist(err, err);
10 years ago
txp.status.should.equal('pending');
clients[1].signTxProposal(txp, function(err, txp) {
should.not.exist(err);
10 years ago
txp.status.should.equal('pending');
clients[2].rejectTxProposal(txp, 'me neither', function(err, txp) {
should.not.exist(err);
10 years ago
txp.status.should.equal('rejected');
done();
});
});
});
});
});
});
});
it('Should not allow to reject or sign twice', function(done) {
10 years ago
helpers.createAndJoinWallet(clients, 2, 3, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
blockExplorerMock.setUtxo(x0, 10, 1);
var opts = {
amount: 10000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
10 years ago
clients[0].sendTxProposal(opts, function(err, txp) {
should.not.exist(err);
10 years ago
txp.status.should.equal('pending');
txp.requiredRejections.should.equal(2);
txp.requiredSignatures.should.equal(2);
clients[0].signTxProposal(txp, function(err, txp) {
should.not.exist(err);
txp.status.should.equal('pending');
clients[0].signTxProposal(txp, function(err) {
should.exist(err);
err.code.should.contain('CVOTED');
10 years ago
clients[1].rejectTxProposal(txp, 'xx', function(err, txp) {
should.not.exist(err);
10 years ago
clients[1].rejectTxProposal(txp, 'xx', function(err) {
should.exist(err);
err.code.should.contain('CVOTED');
done();
});
});
});
});
});
});
});
});
});
describe('Transaction history', function() {
it('should get transaction history', function(done) {
blockExplorerMock.setHistory(TestData.history);
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function(w) {
clients[0].createAddress(function(err, x0) {
should.not.exist(err);
should.exist(x0.address);
clients[0].getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(2);
done();
});
});
});
});
it('should get empty transaction history when there are no addresses', function(done) {
blockExplorerMock.setHistory(TestData.history);
10 years ago
helpers.createAndJoinWallet(clients, 1, 1, function(w) {
clients[0].getTxHistory({}, function(err, txs) {
should.not.exist(err);
should.exist(txs);
txs.length.should.equal(0);
done();
});
});
});
it.skip('should get transaction history decorated with proposal', function(done) {});
it.skip('should get paginated transaction history', function(done) {});
});
describe('Air gapped related flows', function() {
it('should create wallet in proxy from airgapped', function(done) {
var airgapped = new AirGapped({
network: 'testnet'
});
var seed = airgapped.getSeed();
var proxy = new Client({
request: helpers.getRequest(app),
});
proxy.seedFromAirGapped(seed);
should.not.exist(proxy.credentials.xPrivKey);
proxy.createWallet('wallet name', 'creator', 1, 1, 'testnet', function(err) {
should.not.exist(err);
proxy.getStatus(function(err, status) {
should.not.exist(err);
status.wallet.name.should.equal('wallet name');
done();
});
});
});
it('should be able to sign from airgapped client and broadcast from proxy', function(done) {
var airgapped = new AirGapped({
network: 'testnet'
});
var seed = airgapped.getSeed();
var proxy = new Client({
request: helpers.getRequest(app),
});
proxy.seedFromAirGapped(seed);
async.waterfall([
function(next) {
proxy.createWallet('wallet name', 'creator', 1, 1, 'testnet', function(err) {
should.not.exist(err);
proxy.createAddress(function(err, address) {
should.not.exist(err);
should.exist(address.address);
blockExplorerMock.setUtxo(address, 1, 1);
var opts = {
amount: 1200000,
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5',
message: 'hello 1-1',
};
proxy.sendTxProposal(opts, next);
});
});
},
function(txp, next) {
should.exist(txp);
proxy.signTxProposal(txp, function(err, txp) {
should.exist(err);
should.not.exist(txp);
err.message.should.equal('You do not have the required keys to sign transactions');
next(null, txp);
});
},
function(txp, next) {
proxy.getTxProposals({
10 years ago
forAirGapped: true
}, next);
},
10 years ago
function(bundle, next) {
var signatures = airgapped.signTxProposal(bundle.txps[0], bundle.publicKeyRing, bundle.m, bundle.n);
next(null, signatures);
},
function(signatures, next) {
proxy.getTxProposals({}, function(err, txps) {
should.not.exist(err);
var txp = txps[0];
txp.signatures = signatures;
async.each(txps, function(txp, cb) {
proxy.signTxProposal(txp, function(err, txp) {
should.not.exist(err);
proxy.broadcastTxProposal(txp, function(err, txp) {
should.not.exist(err);
txp.status.should.equal('broadcasted');
should.exist(txp.txid);
cb();
});
});
}, function(err) {
next(err);
});
});
},
],
function(err) {
should.not.exist(err);
done();
}
);
});
10 years ago
it.skip('should be able to detect tampered PKR when signing on airgapped client', function(done) {});
it.skip('should be able to detect tampered proposal when signing on airgapped client', function(done) {});
it.skip('should be able to detect tampered change address when signing on airgapped client', function(done) {});
});
10 years ago
});