|
|
@ -39,7 +39,7 @@ helpers.getRequest = function(app) { |
|
|
|
}; |
|
|
|
|
|
|
|
helpers.createAndJoinWallet = function(clients, m, n, cb) { |
|
|
|
clients[0].createWallet('wallet name', 'creator copayer', m, n, 'testnet', |
|
|
|
clients[0].createWallet('wallet name', 'creator', m, n, 'testnet', |
|
|
|
function(err, secret) { |
|
|
|
if (err) return cb(err); |
|
|
|
if (n == 1) return cb(); |
|
|
@ -51,12 +51,17 @@ helpers.createAndJoinWallet = function(clients, m, n, cb) { |
|
|
|
return cb(err); |
|
|
|
}); |
|
|
|
}, function(err) { |
|
|
|
if (err) return new Error('Could not generate wallet'); |
|
|
|
return cb(); |
|
|
|
if (err) return cb(err); |
|
|
|
return cb(null, { |
|
|
|
m: m, |
|
|
|
n: n, |
|
|
|
secret: secret, |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
var fsmock = {}; |
|
|
|
var content = {}; |
|
|
|
fsmock.readFile = function(name, enc, cb) { |
|
|
@ -69,9 +74,47 @@ fsmock.writeFile = function(name, data, cb) { |
|
|
|
content[name] = data; |
|
|
|
return cb(); |
|
|
|
}; |
|
|
|
fsmock.reset = function() { |
|
|
|
content = {}; |
|
|
|
}; |
|
|
|
|
|
|
|
fsmock._get = function(name) { |
|
|
|
return content[name]; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
var utxos = []; |
|
|
|
var blockExplorerMock = {}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
blockExplorerMock.getUnspentUtxos = function(dummy, cb) { |
|
|
|
var ret = _.map(utxos || [], function(x) { |
|
|
|
x.toObject = function() { |
|
|
|
return this; |
|
|
|
}; |
|
|
|
return x; |
|
|
|
}); |
|
|
|
return cb(null, ret); |
|
|
|
}; |
|
|
|
|
|
|
|
blockExplorerMock.setUtxo = function(address, amount, m) { |
|
|
|
utxos.push({ |
|
|
|
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(), |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
blockExplorerMock.reset = function() { |
|
|
|
utxos = []; |
|
|
|
}; |
|
|
|
|
|
|
|
describe('client API ', function() { |
|
|
|
var clients; |
|
|
|
var clients, app; |
|
|
|
|
|
|
|
beforeEach(function() { |
|
|
|
clients = []; |
|
|
@ -81,9 +124,10 @@ describe('client API ', function() { |
|
|
|
var storage = new Storage({ |
|
|
|
db: db |
|
|
|
}); |
|
|
|
var app = ExpressApp.start({ |
|
|
|
app = ExpressApp.start({ |
|
|
|
CopayServer: { |
|
|
|
storage: storage |
|
|
|
storage: storage, |
|
|
|
blockExplorer: blockExplorerMock, |
|
|
|
} |
|
|
|
}); |
|
|
|
// Generates 5 clients
|
|
|
@ -99,10 +143,11 @@ describe('client API ', function() { |
|
|
|
client.request = helpers.getRequest(app); |
|
|
|
clients.push(client); |
|
|
|
}); |
|
|
|
content = {}; |
|
|
|
fsmock.reset(); |
|
|
|
blockExplorerMock.reset(); |
|
|
|
}); |
|
|
|
|
|
|
|
describe.only('#getBalance', function() { |
|
|
|
describe('Wallet Creation', function() { |
|
|
|
it('should check balance in a 1-1 ', function(done) { |
|
|
|
helpers.createAndJoinWallet(clients, 1, 1, function(err) { |
|
|
|
should.not.exist(err); |
|
|
@ -112,7 +157,7 @@ describe('client API ', function() { |
|
|
|
}) |
|
|
|
}); |
|
|
|
}); |
|
|
|
it('should be able to check balance in a 2-3 wallet ', function(done) { |
|
|
|
it('should be able to complete wallets in copayer that joined later', function(done) { |
|
|
|
helpers.createAndJoinWallet(clients, 2, 3, function(err) { |
|
|
|
should.not.exist(err); |
|
|
|
clients[0].getBalance(function(err, x) { |
|
|
@ -127,105 +172,212 @@ describe('client API ', function() { |
|
|
|
}) |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
describe('#_tryToComplete ', function() { |
|
|
|
it('should complete a wallet ', function(done) { |
|
|
|
client.storage.fs.readFile = |
|
|
|
sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); |
|
|
|
|
|
|
|
|
|
|
|
client.getBalance(function(err, x) { |
|
|
|
it('should not allow to join a full wallet ', function(done) { |
|
|
|
helpers.createAndJoinWallet(clients, 2, 2, function(err, w) { |
|
|
|
should.not.exist(err); |
|
|
|
should.exist(w.secret); |
|
|
|
clients[4].joinWallet(w.secret, 'copayer', function(err, result) { |
|
|
|
err.should.contain('Request error'); |
|
|
|
done(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
it('should fail with a unknown secret', function(done) { |
|
|
|
var oldSecret = '3f8e5acb-ceeb-4aae-134f-692d934e3b1c:L2gohj8s2fLKqVU5cQutAVGciutUxczFxLxxXHFsjzLh71ZjkFQQ:T'; |
|
|
|
clients[0].joinWallet(oldSecret, 'copayer', function(err, result) { |
|
|
|
err.should.contain('Request error'); |
|
|
|
done(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
it('should reject wallets with bad signatures', function(done) { |
|
|
|
helpers.createAndJoinWallet(clients, 2, 3, function(err) { |
|
|
|
should.not.exist(err); |
|
|
|
|
|
|
|
// Get right response
|
|
|
|
var data = clients[0]._load(function(err, data) { |
|
|
|
var url = '/v1/wallets/'; |
|
|
|
clients[0]._doGetRequest(url, data, function(err, x) { |
|
|
|
|
|
|
|
it('should handle incomple wallets', function(done) { |
|
|
|
var request = sinon.stub(); |
|
|
|
// Tamper data
|
|
|
|
x.wallet.copayers[0].xPubKey = x.wallet.copayers[1].xPubKey; |
|
|
|
|
|
|
|
// Wallet request
|
|
|
|
request.onCall(0).yields(null, { |
|
|
|
statusCode: 200, |
|
|
|
}, TestData.serverResponse.incompleteWallet); |
|
|
|
// Tamper response
|
|
|
|
clients[1]._doGetRequest = sinon.stub().yields(null, x); |
|
|
|
|
|
|
|
client.request = request; |
|
|
|
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); |
|
|
|
client.createAddress(function(err, x) { |
|
|
|
err.should.contain('Incomplete'); |
|
|
|
done(); |
|
|
|
clients[1].getBalance(function(err, x) { |
|
|
|
err.should.contain('verified'); |
|
|
|
done(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
it('should reject wallets with bad signatures', function(done) { |
|
|
|
var request = sinon.stub(); |
|
|
|
// Wallet request
|
|
|
|
request.onCall(0).yields(null, { |
|
|
|
statusCode: 200, |
|
|
|
}, TestData.serverResponse.corruptWallet22); |
|
|
|
it('should reject wallets with missing signatures', function(done) { |
|
|
|
helpers.createAndJoinWallet(clients, 2, 3, function(err) { |
|
|
|
should.not.exist(err); |
|
|
|
|
|
|
|
client.request = request; |
|
|
|
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); |
|
|
|
client.createAddress(function(err, x) { |
|
|
|
err.should.contain('verified'); |
|
|
|
done(); |
|
|
|
// Get right response
|
|
|
|
var data = clients[0]._load(function(err, data) { |
|
|
|
var url = '/v1/wallets/'; |
|
|
|
clients[0]._doGetRequest(url, data, function(err, x) { |
|
|
|
|
|
|
|
// Tamper data
|
|
|
|
delete x.wallet.copayers[1].xPubKey; |
|
|
|
|
|
|
|
// Tamper response
|
|
|
|
clients[1]._doGetRequest = sinon.stub().yields(null, x); |
|
|
|
|
|
|
|
clients[1].getBalance(function(err, x) { |
|
|
|
err.should.contain('verified'); |
|
|
|
done(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
it('should reject wallets with missing signatures ', function(done) { |
|
|
|
var request = sinon.stub(); |
|
|
|
// Wallet request
|
|
|
|
request.onCall(0).yields(null, { |
|
|
|
statusCode: 200, |
|
|
|
}, TestData.serverResponse.corruptWallet222); |
|
|
|
|
|
|
|
client.request = request; |
|
|
|
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); |
|
|
|
client.createAddress(function(err, x) { |
|
|
|
err.should.contain('verified'); |
|
|
|
done(); |
|
|
|
it('should reject wallets missing caller"s pubkey', function(done) { |
|
|
|
helpers.createAndJoinWallet(clients, 2, 3, function(err) { |
|
|
|
should.not.exist(err); |
|
|
|
|
|
|
|
// Get right response
|
|
|
|
var data = clients[0]._load(function(err, data) { |
|
|
|
var url = '/v1/wallets/'; |
|
|
|
clients[0]._doGetRequest(url, data, function(err, x) { |
|
|
|
|
|
|
|
// Tamper data. Replace caller's pubkey
|
|
|
|
x.wallet.copayers[1].xPubKey = (new Bitcore.HDPrivateKey()).publicKey; |
|
|
|
// Add a correct signature
|
|
|
|
x.wallet.copayers[1].xPubKeySignature = WalletUtils.signMessage( |
|
|
|
x.wallet.copayers[1].xPubKey, data.walletPrivKey), |
|
|
|
|
|
|
|
// Tamper response
|
|
|
|
clients[1]._doGetRequest = sinon.stub().yields(null, x); |
|
|
|
|
|
|
|
clients[1].getBalance(function(err, x) { |
|
|
|
err.should.contain('verified'); |
|
|
|
done(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
it('should reject wallets missing caller"s pubkey', function(done) { |
|
|
|
var request = sinon.stub(); |
|
|
|
// Wallet request
|
|
|
|
request.onCall(0).yields(null, { |
|
|
|
statusCode: 200, |
|
|
|
}, TestData.serverResponse.missingMyPubKey); |
|
|
|
|
|
|
|
client.request = request; |
|
|
|
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.incompleteWallet22)); |
|
|
|
client.createAddress(function(err, x) { |
|
|
|
err.should.contain('verified'); |
|
|
|
done(); |
|
|
|
describe('Address Creation', function() { |
|
|
|
it('should be able to create address in all copayers in a 2-3 wallet', function(done) { |
|
|
|
this.timeout(5000); |
|
|
|
helpers.createAndJoinWallet(clients, 2, 3, function(err) { |
|
|
|
should.not.exist(err); |
|
|
|
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) { |
|
|
|
helpers.createAndJoinWallet(clients, 2, 2, function(err, w) { |
|
|
|
should.not.exist(err); |
|
|
|
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(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
describe('#createAddress ', function() { |
|
|
|
it('should check address ', function(done) { |
|
|
|
|
|
|
|
var response = { |
|
|
|
createdOn: 1424105995, |
|
|
|
address: '2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq', |
|
|
|
path: 'm/2147483647/0/7', |
|
|
|
publicKeys: ['03f6a5fe8db51bfbaf26ece22a3e3bc242891a47d3048fc70bc0e8c03a071ad76f'] |
|
|
|
}; |
|
|
|
var request = sinon.mock().yields(null, { |
|
|
|
statusCode: 200 |
|
|
|
}, response); |
|
|
|
client.request = request; |
|
|
|
describe('Wallet Backups and Mobility', function() { |
|
|
|
|
|
|
|
it('round trip #import #export', function(done) { |
|
|
|
helpers.createAndJoinWallet(clients, 2, 2, function(err, w) { |
|
|
|
should.not.exist(err); |
|
|
|
clients[0].export(function(err, str) { |
|
|
|
should.not.exist(err); |
|
|
|
var original = JSON.parse(fsmock._get('client0')); |
|
|
|
clients[2].import(str, function(err, wallet) { |
|
|
|
should.not.exist(err); |
|
|
|
var clone = JSON.parse(fsmock._get('client2')); |
|
|
|
delete original.walletPrivKey; // no need to persist it.
|
|
|
|
clone.should.deep.equal(original); |
|
|
|
done(); |
|
|
|
}); |
|
|
|
|
|
|
|
client.createAddress(function(err, x) { |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
it('should recreate a wallet, create addresses and receive money', function(done) { |
|
|
|
var backup = '["tprv8ZgxMBicQKsPehCdj4HM1MZbKVXBFt5Dj9nQ44M99EdmdiUfGtQBDTSZsKmzdUrB1vEuP6ipuoa39UXwPS2CvnjE1erk5aUjc5vQZkWvH4B",2,["tpubD6NzVbkrYhZ4XCNDPDtyRWPxvJzvTkvUE2cMPB8jcUr9Dkicv6cYQmA18DBAid6eRK1BGCU9nzgxxVdQUGLYJ34XsPXPW4bxnH4PH6oQBF3"],"sd0kzXmlXBgTGHrKaBW4aA=="]'; |
|
|
|
clients[0].import(backup, function(err, wallet) { |
|
|
|
should.not.exist(err); |
|
|
|
x.address.should.equal('2N3fA6wDtnebzywPkGuNK9KkFaEzgbPRRTq'); |
|
|
|
done(); |
|
|
|
clients[0].reCreateWallet('pepe', function(err, wallet) { |
|
|
|
should.not.exist(err); |
|
|
|
|
|
|
|
clients[0].createAddress(function(err, x0) { |
|
|
|
should.not.exist(err); |
|
|
|
should.exist(x0.address); |
|
|
|
blockExplorerMock.setUtxo(x0, 10, 2); |
|
|
|
clients[0].getBalance(function(err, bal0) { |
|
|
|
should.not.exist(err); |
|
|
|
bal0.totalAmount.should.equal(10 * 1e8); |
|
|
|
bal0.lockedAmount.should.equal(0); |
|
|
|
done(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe.only('Send TXs', function() { |
|
|
|
it('Send and broadcast in 1-1 wallet', function(done) { |
|
|
|
helpers.createAndJoinWallet(clients, 1, 1, function(err, w) { |
|
|
|
clients[0].createAddress(function(err, x0) { |
|
|
|
should.not.exist(err); |
|
|
|
should.exist(x0.address); |
|
|
|
blockExplorerMock.setUtxo(x0, 10, 1); |
|
|
|
var opts = { |
|
|
|
amount: 1000, |
|
|
|
toAddress: 'n2TBMPzPECGUfcT2EByiTJ12TPZkhN2mN5', |
|
|
|
message: 'hola 1-1', |
|
|
|
}; |
|
|
|
clients[0].sendTxProposal(opts, function(err, x) { |
|
|
|
should.not.exist(err); |
|
|
|
console.log('[clientApi.js.372]',x); //TODO
|
|
|
|
done(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
/* |
|
|
|
describe('TODO', function(x) { |
|
|
|
it('should detect fake addresses ', function(done) { |
|
|
|
var response = { |
|
|
|
createdOn: 1424105995, |
|
|
@ -246,25 +398,6 @@ describe('client API ', function() { |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('#export & #import 2-2 wallet', function() { |
|
|
|
it('round trip ', function(done) { |
|
|
|
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.complete22)); |
|
|
|
client.export(function(err, str) { |
|
|
|
should.not.exist(err); |
|
|
|
|
|
|
|
client.storage.fs.readFile = sinon.stub().yields(null); |
|
|
|
client.import(str, function(err, wallet) { |
|
|
|
should.not.exist(err); |
|
|
|
var wallet = JSON.parse(client.storage.fs.writeFile.getCall(0).args[1]); |
|
|
|
TestData.storage.complete22.should.deep.equal(wallet); |
|
|
|
|
|
|
|
done(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('#getTxProposals', function() { |
|
|
|
it('should return tx proposals and decrypt message', function(done) { |
|
|
|
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.complete11)); |
|
|
@ -283,10 +416,6 @@ describe('client API ', function() { |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
describe('#recreate', function() { |
|
|
|
it.skip('Should recreate a wallet acording stored data', function(done) {}); |
|
|
|
}); |
|
|
|
|
|
|
|
describe('#sendTxProposal ', function() { |
|
|
|
it('should send tx proposal with encrypted message', function(done) { |
|
|
|
client.storage.fs.readFile = sinon.stub().yields(null, JSON.stringify(TestData.storage.complete11)); |
|
|
@ -356,4 +485,5 @@ describe('client API ', function() { |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
*/ |
|
|
|
}); |
|
|
|