diff --git a/js/models/network/Async.js b/js/models/network/Async.js index e63b2ecfc..e774d2eea 100644 --- a/js/models/network/Async.js +++ b/js/models/network/Async.js @@ -167,6 +167,7 @@ Network.prototype.iterateNonce = function() { var noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0); var noncep2 = this.networkNonce.slice(4, 8); noncep2.writeUInt32BE(noncep2uint + 1, 0); + this.networkNonce = Buffer.concat([noncep1, noncep2], 8); return this.networkNonce; }; diff --git a/js/models/storage/File.js b/js/models/storage/File.js deleted file mode 100644 index ec0880145..000000000 --- a/js/models/storage/File.js +++ /dev/null @@ -1,149 +0,0 @@ -'use strict'; -var fs = require('fs'); -var CryptoJS = require('node-cryptojs-aes').CryptoJS; - -var passwords = []; - -function Storage(opts) { - opts = opts || {}; - - this.data = {}; - passwords[0] = opts.password; -} - -Storage.prototype._encrypt = function(string) { - var encrypted = CryptoJS.AES.encrypt(string, passwords[0]); - var encryptedBase64 = encrypted.toString(); - return encryptedBase64; -}; - -Storage.prototype._encryptObj = function(obj) { - var string = JSON.stringify(obj); - return this._encrypt(string); -}; - -Storage.prototype._decrypt = function(base64) { - var decrypted = CryptoJS.AES.decrypt(base64, passwords[0]); - var decryptedStr = decrypted.toString(CryptoJS.enc.Utf8); - return decryptedStr; -}; - -Storage.prototype._decryptObj = function(base64) { - var decryptedStr = this._decrypt(base64); - return JSON.parse(decryptedStr); -}; - -Storage.prototype.load = function(walletId, callback) { - var self = this; - fs.readFile(walletId, function(err, base64) { - if (typeof base64 !== 'string') - base64 = base64.toString(); - var data = self._decryptObj(base64); - - if (err) return callback(err); - - try { - this.data[walletId] = JSON.parse(data); - } catch (err) { - if (callback) - return callback(err); - } - - if (callback) - return callback(null); - }); -}; - -Storage.prototype.save = function(walletId, callback) { - var obj = this.data[walletId]; - var encryptedBase64 = this._encryptObj(obj); - - //TODO: update to use a queue to ensure that saves are made sequentially - fs.writeFile(walletId, encryptedBase64, function(err) { - if (callback) - return callback(err); - }); -}; - -Storage.prototype._read = function(k) { - var split = k.split('::'); - var walletId = split[0]; - var key = split[1]; - return this.data[walletId][key]; -}; - -Storage.prototype._write = function(k, v, callback) { - var split = k.split('::'); - var walletId = split[0]; - var key = split[1]; - if (!this.data[walletId]) - this.data[walletId] = {}; - this.data[walletId][key] = v; - this.save(walletId, callback); -}; - -// get value by key -Storage.prototype.getGlobal = function(k) { - return this._read(k); -}; - -// set value for key -Storage.prototype.setGlobal = function(k, v, callback) { - this._write(k, v, callback); -}; - -// remove value for key -Storage.prototype.removeGlobal = function(k, callback) { - var split = k.split('::'); - var walletId = split[0]; - var key = split[1]; - delete this.data[walletId][key]; - this.save(walletId, callback); -}; - -Storage.prototype._key = function(walletId, k) { - return walletId + '::' + k; -}; - -// get value by key -Storage.prototype.get = function(walletId, k) { - return this.getGlobal(this._key(walletId, k)); -}; - -// set value for key -Storage.prototype.set = function(walletId, k, v, callback) { - this.setGlobal(this._key(walletId, k), v, callback); -}; - -// remove value for key -Storage.prototype.remove = function(walletId, k, callback) { - this.removeGlobal(this._key(walletId, k), callback); -}; - -Storage.prototype.getWalletIds = function() { - return []; -}; - -Storage.prototype.setFromObj = function(walletId, obj, callback) { - this.data[walletId] = obj; - this.save(walletId, callback); -}; - -Storage.prototype.setFromEncryptedObj = function(walletId, base64, callback) { - var obj = this._decryptObj(base64); - this.setFromObj(walletId, obj, callback); -}; - -Storage.prototype.getEncryptedObj = function(walletId) { - var encryptedBase64 = this._encryptObj(this.data[walletId]); - - return encryptedBase64; -}; - -// remove all values -Storage.prototype.clearAll = function(callback) { - this.data = {}; - this.save(callback); -}; - -module.exports = Storage; diff --git a/karma.conf.js b/karma.conf.js index ab1abaf1f..79314df62 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -59,7 +59,10 @@ module.exports = function(config) { 'test/mocha.conf.js', //test files - 'test/unit/**/*.js' + 'test/test.*.js', + + 'test/unit/**/*.js', + ], @@ -99,7 +102,7 @@ module.exports = function(config) { // start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Chrome'], + browsers: ['Chrome', 'Firefox'], // Continuous Integration mode diff --git a/package.json b/package.json index 26dcad40e..55572279e 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "setup-shell": "node shell/scripts/download-atom-shell.js", "start": "node server.js", "coverage": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --reporter spec test", - "test": "node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && ./node_modules/karma/bin/karma start --browsers Firefox --single-run", + "test": "sh test/run.sh", "dist": "node shell/scripts/dist.js", "sign": "gpg -u 1112CFA1 --output browser-extensions/firefox/copay.xpi.sig --detach-sig browser-extensions/firefox/copay.xpi; gpg -u 1112CFA1 --output browser-extensions/chrome/copay-chrome-extension.zip.sig --detach-sig browser-extensions/chrome/copay-chrome-extension.zip", "verify": "gpg --verify browser-extensions/firefox/copay.xpi.sig browser-extensions/firefox/copay.xpi; gpg --verify browser-extensions/chrome/copay-chrome-extension.zip.sig browser-extensions/chrome/copay-chrome-extension.zip" diff --git a/test/index.html b/test/index.html deleted file mode 100644 index e5aa76a6c..000000000 --- a/test/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - Mocha - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/run.sh b/test/run.sh new file mode 100644 index 000000000..c13caa84d --- /dev/null +++ b/test/run.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && \ + cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && \ + ./node_modules/karma/bin/karma start --browsers Firefox --single-run diff --git a/test/test.PayPro.js b/test/test.PayPro.js index 4f22917e2..0528864f3 100644 --- a/test/test.PayPro.js +++ b/test/test.PayPro.js @@ -3,14 +3,12 @@ var chai = chai || require('chai'); var should = chai.should(); var sinon = require('sinon'); -var is_browser = typeof process == 'undefined' - || typeof process.versions === 'undefined'; +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 copayConfig = require('../config'); var Wallet = copay.Wallet; var PrivateKey = copay.PrivateKey; var Storage = require('./mocks/FakeStorage'); @@ -26,7 +24,7 @@ var startServer = copay.FakePayProServer; // TODO should be require('./mocks/Fak var server; -var config = { +var walletConfig = { requiredCopayers: 1, totalCopayers: 1, spendUnconfirmed: true, @@ -36,861 +34,861 @@ var config = { var getNewEpk = function() { return new PrivateKey({ - networkName: config.networkName, - }) - .deriveBIP45Branch() - .extendedPublicKeyString(); + networkName: walletConfig.networkName, + }) + .deriveBIP45Branch() + .extendedPublicKeyString(); }; describe('PayPro (in Wallet) model', function() { - var createW = function(N, conf) { - var c = JSON.parse(JSON.stringify(conf || config)); - if (!N) N = c.totalCopayers; + if (!is_browser) { + var createW = function(N, conf) { + var c = JSON.parse(JSON.stringify(conf || walletConfig)); + if (!N) N = c.totalCopayers; - var mainPrivateKey = new copay.PrivateKey({ - networkName: config.networkName - }); - var mainCopayerEPK = mainPrivateKey.deriveBIP45Branch().extendedPublicKeyString(); - c.privateKey = mainPrivateKey; + var mainPrivateKey = new copay.PrivateKey({ + networkName: walletConfig.networkName + }); + var mainCopayerEPK = mainPrivateKey.deriveBIP45Branch().extendedPublicKeyString(); + c.privateKey = mainPrivateKey; - c.publicKeyRing = new copay.PublicKeyRing({ - networkName: c.networkName, - requiredCopayers: Math.min(N, c.requiredCopayers), - totalCopayers: N, - }); - c.publicKeyRing.addCopayer(mainCopayerEPK); + c.publicKeyRing = new copay.PublicKeyRing({ + networkName: c.networkName, + requiredCopayers: Math.min(N, c.requiredCopayers), + totalCopayers: N, + }); + c.publicKeyRing.addCopayer(mainCopayerEPK); - c.txProposals = new copay.TxProposals({ - networkName: c.networkName, - }); + c.txProposals = new copay.TxProposals({ + networkName: c.networkName, + }); - var storage = new Storage(config.storage); - var network = new Network(config.network); - var blockchain = new Blockchain(config.blockchain); - c.storage = storage; - c.network = network; - c.blockchain = blockchain; + var storage = new Storage(walletConfig.storage); + var network = new Network(walletConfig.network); + var blockchain = new Blockchain(walletConfig.blockchain); + c.storage = storage; + c.network = network; + c.blockchain = blockchain; - c.addressBook = { - '2NFR2kzH9NUdp8vsXTB4wWQtTtzhpKxsyoJ': { - label: 'John', - copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03', - createdTs: 1403102115, - hidden: false - }, - '2MtP8WyiwG7ZdVWM96CVsk2M1N8zyfiVQsY': { - label: 'Jennifer', - copayerId: '032991f836543a492bd6d0bb112552bfc7c5f3b7d5388fcbcbf2fbb893b44770d7', - createdTs: 1403103115, - hidden: false - } - }; - - c.networkName = config.networkName; - c.verbose = config.verbose; - c.version = '0.0.1'; - - return new Wallet(c); - } - - var unspentTest = [{ - "address": "dummy", - "scriptPubKey": "dummy", - "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", - "vout": 1, - "amount": 10, - "confirmations": 7 - }]; - - var createW2 = function(privateKeys, N, conf) { - if (!N) N = 3; - var w = createW(N, conf); - should.exist(w); - - var pkr = w.publicKeyRing; - - for (var i = 0; i < N - 1; i++) { - if (privateKeys) { - var k = privateKeys[i]; - pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : getNewEpk()); - } else { - pkr.addCopayer(getNewEpk()); - } - } - - return w; - }; - - var cachedW2 = null; - var cachedW2obj = null; - var cachedCreateW2 = function() { - if (!cachedW2) { - cachedW2 = createW2(); - cachedW2obj = cachedW2.toObj(); - cachedW2obj.opts.reconnectDelay = 100; - } - var w = Wallet.fromObj(cachedW2obj, cachedW2.storage, cachedW2.network, cachedW2.blockchain); - return w; - }; - - var createWallet = function() { - var w = cachedCreateW2(); - unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); - unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); - w.getUnspent = function(cb) { - return setTimeout(function() { - return cb(null, unspentTest, unspentTest); - }, 1); - }; - return w; - }; - - it('#start the example server', function(done) { - startServer(function(err, s) { - if (err) return done(err); - server = s; - server.uri = 'https://localhost:8080/-'; - done(); - }); - }); - - var pr; - var ppw; - - ppw = createWallet(); - - it('#retrieve a payment request message via http', function(done) { - var w = ppw; - should.exist(w); - - var req = { - headers: { - 'Host': 'localhost:8080', - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE - + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': 'application/octet-stream', - 'Content-Length': '0' - }, - socket: { - remoteAddress: 'localhost', - remotePort: 8080 - }, - body: {} - }; - - Object.keys(req.headers).forEach(function(key) { - req.headers[key.toLowerCase()] = req.headers[key]; - }); - - server.GET['/-/request'](req, function(err, res, body) { - var data = PayPro.PaymentRequest.decode(body); - pr = new PayPro(); - pr = pr.makePaymentRequest(data); - done(); - }); - }); - - it('#send a payment message via http', function(done) { - var w = ppw; - should.exist(w); - - var ver = pr.get('payment_details_version'); - var pki_type = pr.get('pki_type'); - var pki_data = pr.get('pki_data'); - var details = pr.get('serialized_payment_details'); - var sig = pr.get('signature'); - - var certs = PayPro.X509Certificates.decode(pki_data); - certs = certs.certificate; - - var verified = pr.verify(); - - if (!verified) { - return done(new Error('Server sent a bad signature.')); - } - - details = PayPro.PaymentDetails.decode(details); - var pd = new PayPro(); - pd = pd.makePaymentDetails(details); - - var network = pd.get('network'); - var outputs = pd.get('outputs'); - var time = pd.get('time'); - var expires = pd.get('expires'); - var memo = pd.get('memo'); - var payment_url = pd.get('payment_url'); - var merchant_data = pd.get('merchant_data'); - - var priv = w.privateKey; - var pkr = w.publicKeyRing; - - var opts = { - remainderOut: { - address: w._doGenerateAddress(true).toString() - } - }; - - var outs = []; - outputs.forEach(function(output) { - var amount = output.get('amount'); - var script = { - offset: output.get('script').offset, - limit: output.get('script').limit, - buffer: new Buffer(new Uint8Array( - output.get('script').buffer)) + c.addressBook = { + '2NFR2kzH9NUdp8vsXTB4wWQtTtzhpKxsyoJ': { + label: 'John', + copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03', + createdTs: 1403102115, + hidden: false + }, + '2MtP8WyiwG7ZdVWM96CVsk2M1N8zyfiVQsY': { + label: 'Jennifer', + copayerId: '032991f836543a492bd6d0bb112552bfc7c5f3b7d5388fcbcbf2fbb893b44770d7', + createdTs: 1403103115, + hidden: false + } }; - // big endian - var v = new Buffer(8); - v[0] = (amount.high >> 24) & 0xff; - v[1] = (amount.high >> 16) & 0xff; - v[2] = (amount.high >> 8) & 0xff; - v[3] = (amount.high >> 0) & 0xff; - v[4] = (amount.low >> 24) & 0xff; - v[5] = (amount.low >> 16) & 0xff; - v[6] = (amount.low >> 8) & 0xff; - v[7] = (amount.low >> 0) & 0xff; + c.networkName = walletConfig.networkName; + c.verbose = walletConfig.verbose; + c.version = '0.0.1'; - var s = script.buffer.slice(script.offset, script.limit); - var net = network === 'main' ? 'livenet' : 'testnet'; - var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net); + return new Wallet(c); + } - outs.push({ - address: addr[0].toString(), - amountSatStr: bitcore.Bignum.fromBuffer(v, { - // XXX for some reason, endian is ALWAYS 'big' - // in node (in the browser it behaves correctly) - endian: 'big', - size: 1 - }).toString(10) + var unspentTest = [{ + "address": "dummy", + "scriptPubKey": "dummy", + "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", + "vout": 1, + "amount": 10, + "confirmations": 7 + }]; + + var createW2 = function(privateKeys, N, conf) { + if (!N) N = 3; + var w = createW(N, conf); + should.exist(w); + + var pkr = w.publicKeyRing; + + for (var i = 0; i < N - 1; i++) { + if (privateKeys) { + var k = privateKeys[i]; + pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : getNewEpk()); + } else { + pkr.addCopayer(getNewEpk()); + } + } + + return w; + }; + + var cachedW2 = null; + var cachedW2obj = null; + var cachedCreateW2 = function() { + if (!cachedW2) { + cachedW2 = createW2(); + cachedW2obj = cachedW2.toObj(); + cachedW2obj.opts.reconnectDelay = 100; + } + var w = Wallet.fromObj(cachedW2obj, cachedW2.storage, cachedW2.network, cachedW2.blockchain); + return w; + }; + + var createWallet = function() { + var w = cachedCreateW2(); + unspentTest[0].address = w.publicKeyRing.getAddress(1, true, w.publicKey).toString(); + unspentTest[0].scriptPubKey = w.publicKeyRing.getScriptPubKeyHex(1, true, w.publicKey); + w.getUnspent = function(cb) { + return setTimeout(function() { + return cb(null, unspentTest, unspentTest); + }, 1); + }; + return w; + }; + + it('#start the example server', function(done) { + startServer(function(err, s) { + if (err) return done(err); + server = s; + server.uri = 'https://localhost:8080/-'; + done(); }); }); - var b = new bitcore.TransactionBuilder(opts) - .setUnspent(unspentTest) - .setOutputs(outs); + var pr; + var ppw; - outputs.forEach(function(output, i) { - var script = { - offset: output.get('script').offset, - limit: output.get('script').limit, - buffer: new Buffer(new Uint8Array( - output.get('script').buffer)) - }; - var s = script.buffer.slice(script.offset, script.limit); - b.tx.outs[i].s = s; - }); + ppw = createWallet(); - var selectedUtxos = b.getSelectedUnspent(); - var inputChainPaths = selectedUtxos.map(function(utxo) { - return pkr.pathForAddress(utxo.address); - }); + it('#retrieve a payment request message via http', function(done) { + var w = ppw; + should.exist(w); - b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - - if (priv) { - var keys = priv.getForPaths(inputChainPaths); - var signed = b.sign(keys); - } - - var tx = b.build(); - - var refund_outputs = []; - - var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0]; - - var total = outputs.reduce(function(total, _, i) { - // XXX reverse endianness to work around bignum bug: - var txv = tx.outs[i].v; - var v = new Buffer(8); - for (var j = 0; j < 8; j++) v[j] = txv[7 - j]; - return total.add(bignum.fromBuffer(v, { - endian: 'big', - size: 1 - })); - }, bitcore.Bignum('0', 10)); - - var rpo = new PayPro(); - rpo = rpo.makeOutput(); - - rpo.set('amount', +total.toString(10)); - - rpo.set('script', - Buffer.concat([ - new Buffer([ - 118, // OP_DUP - 169, // OP_HASH160 - 76, // OP_PUSHDATA1 - 20, // number of bytes - ]), - // needs to be ripesha'd - bitcore.util.sha256ripe160(refund_to), - new Buffer([ - 136, // OP_EQUALVERIFY - 172 // OP_CHECKSIG - ]) - ]) - ); - - refund_outputs.push(rpo.message); - - var pay = new PayPro(); - pay = pay.makePayment(); - pay.set('merchant_data', new Buffer([0, 1])); - pay.set('transactions', [tx.serialize()]); - pay.set('refund_to', refund_outputs); - pay.set('memo', 'Hi server, I would like to give you some money.'); - - pay = pay.serialize(); - - var req = { - headers: { - 'Host': 'localhost:8080', - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE - + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE, - 'Content-Length': pay.length + '' - }, - socket: { - remoteAddress: 'localhost', - remotePort: 8080 - }, - body: pay, - data: pay - }; - - Object.keys(req.headers).forEach(function(key) { - req.headers[key.toLowerCase()] = req.headers[key]; - }); - - server.POST['/-/pay'](req, function(err, res, body) { - if (err) return done(err); - - var data = PayPro.PaymentACK.decode(body); - var ack = new PayPro(); - ack = ack.makePaymentACK(data); - - var payment = ack.get('payment'); - var memo = ack.get('memo'); - - payment = PayPro.Payment.decode(payment); - var pay = new PayPro(); - payment = pay.makePayment(payment); - - var tx = payment.message.transactions[0]; - - if (!tx) { - return done(new Error('No tx in payment ACK.')); - } - - if (tx.buffer) { - tx.buffer = new Buffer(new Uint8Array(tx.buffer)); - tx.buffer = tx.buffer.slice(tx.offset, tx.limit); - var ptx = new bitcore.Transaction(); - ptx.parse(tx.buffer); - tx = ptx; - } - - var ackTotal = outputs.reduce(function(total, _, i) { - // XXX reverse endianness to work around bignum bug: - var txv = tx.outs[i].v; - var v = new Buffer(8); - for (var j = 0; j < 8; j++) v[j] = txv[7 - j]; - return total.add(bignum.fromBuffer(v, { - endian: 'big', - size: 1 - })); - }, bitcore.Bignum('0', 10)); - - ackTotal.toString(10).should.equal(total.toString(10)); - - done(); - }); - }); - - it('#retrieve a payment request message via http', function(done) { - var w = ppw; - should.exist(w); - - var req = { - headers: { - 'Host': 'localhost:8080', - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE - + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': 'application/octet-stream', - 'Content-Length': '0' - }, - socket: { - remoteAddress: 'localhost', - remotePort: 8080 - }, - body: {} - }; - - Object.keys(req.headers).forEach(function(key) { - req.headers[key.toLowerCase()] = req.headers[key]; - }); - - server.GET['/-/request'](req, function(err, res, body) { - var data = PayPro.PaymentRequest.decode(body); - pr = new PayPro(); - pr = pr.makePaymentRequest(data); - done(); - }); - }); - - it('#send a payment message via http', function(done) { - var w = ppw; - should.exist(w); - - var ver = pr.get('payment_details_version'); - var pki_type = pr.get('pki_type'); - var pki_data = pr.get('pki_data'); - var details = pr.get('serialized_payment_details'); - var sig = pr.get('signature'); - - var certs = PayPro.X509Certificates.decode(pki_data); - certs = certs.certificate; - - var verified = pr.verify(); - - if (!verified) { - return done(new Error('Server sent a bad signature.')); - } - - details = PayPro.PaymentDetails.decode(details); - var pd = new PayPro(); - pd = pd.makePaymentDetails(details); - - var network = pd.get('network'); - var outputs = pd.get('outputs'); - var time = pd.get('time'); - var expires = pd.get('expires'); - var memo = pd.get('memo'); - var payment_url = pd.get('payment_url'); - var merchant_data = pd.get('merchant_data'); - - var priv = w.privateKey; - var pkr = w.publicKeyRing; - - var opts = { - remainderOut: { - address: w._doGenerateAddress(true).toString() - } - }; - - var outs = []; - outputs.forEach(function(output) { - var amount = output.get('amount'); - var script = { - offset: output.get('script').offset, - limit: output.get('script').limit, - buffer: new Buffer(new Uint8Array( - output.get('script').buffer)) + var req = { + headers: { + 'Host': 'localhost:8080', + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Content-Type': 'application/octet-stream', + 'Content-Length': '0' + }, + socket: { + remoteAddress: 'localhost', + remotePort: 8080 + }, + body: {} }; - // big endian - var v = new Buffer(8); - v[0] = (amount.high >> 24) & 0xff; - v[1] = (amount.high >> 16) & 0xff; - v[2] = (amount.high >> 8) & 0xff; - v[3] = (amount.high >> 0) & 0xff; - v[4] = (amount.low >> 24) & 0xff; - v[5] = (amount.low >> 16) & 0xff; - v[6] = (amount.low >> 8) & 0xff; - v[7] = (amount.low >> 0) & 0xff; + Object.keys(req.headers).forEach(function(key) { + req.headers[key.toLowerCase()] = req.headers[key]; + }); - var s = script.buffer.slice(script.offset, script.limit); - var net = network === 'main' ? 'livenet' : 'testnet'; - var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net); - - outs.push({ - address: addr[0].toString(), - amountSatStr: bitcore.Bignum.fromBuffer(v, { - // XXX for some reason, endian is ALWAYS 'big' - // in node (in the browser it behaves correctly) - endian: 'big', - size: 1 - }).toString(10) + server.GET['/-/request'](req, function(err, res, body) { + var data = PayPro.PaymentRequest.decode(body); + pr = new PayPro(); + pr = pr.makePaymentRequest(data); + done(); }); }); - var b = new bitcore.TransactionBuilder(opts) - .setUnspent(unspentTest) - .setOutputs(outs); + it('#send a payment message via http', function(done) { + var w = ppw; + should.exist(w); - outputs.forEach(function(output, i) { - var script = { - offset: output.get('script').offset, - limit: output.get('script').limit, - buffer: new Buffer(new Uint8Array( - output.get('script').buffer)) - }; - var s = script.buffer.slice(script.offset, script.limit); - b.tx.outs[i].s = s; - }); - - var selectedUtxos = b.getSelectedUnspent(); - var inputChainPaths = selectedUtxos.map(function(utxo) { - return pkr.pathForAddress(utxo.address); - }); - - b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - - if (priv) { - var keys = priv.getForPaths(inputChainPaths); - var signed = b.sign(keys); - } - - var tx = b.build(); - - var refund_outputs = []; - - var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0]; - - var total = outputs.reduce(function(total, _, i) { - // XXX reverse endianness to work around bignum bug: - var txv = tx.outs[i].v; - var v = new Buffer(8); - for (var j = 0; j < 8; j++) v[j] = txv[7 - j]; - return total.add(bignum.fromBuffer(v, { - endian: 'big', - size: 1 - })); - }, bitcore.Bignum('0', 10)); - - var rpo = new PayPro(); - rpo = rpo.makeOutput(); - - rpo.set('amount', +total.toString(10)); - - rpo.set('script', - Buffer.concat([ - new Buffer([ - 118, // OP_DUP - 169, // OP_HASH160 - 76, // OP_PUSHDATA1 - 20, // number of bytes - ]), - // needs to be ripesha'd - bitcore.util.sha256ripe160(refund_to), - new Buffer([ - 136, // OP_EQUALVERIFY - 172 // OP_CHECKSIG - ]) - ]) - ); - - refund_outputs.push(rpo.message); - - var pay = new PayPro(); - pay = pay.makePayment(); - pay.set('merchant_data', new Buffer([0, 1])); - pay.set('transactions', [tx.serialize()]); - pay.set('refund_to', refund_outputs); - pay.set('memo', 'Hi server, I would like to give you some money.'); - - pay = pay.serialize(); - - var req = { - headers: { - 'Host': 'localhost:8080', - 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE - + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, - 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE, - 'Content-Length': pay.length + '' - }, - socket: { - remoteAddress: 'localhost', - remotePort: 8080 - }, - body: pay, - data: pay - }; - - Object.keys(req.headers).forEach(function(key) { - req.headers[key.toLowerCase()] = req.headers[key]; - }); - - server.POST['/-/pay'](req, function(err, res, body) { - if (err) return done(err); - - var data = PayPro.PaymentACK.decode(body); - var ack = new PayPro(); - ack = ack.makePaymentACK(data); - - var payment = ack.get('payment'); - var memo = ack.get('memo'); - - payment = PayPro.Payment.decode(payment); - var pay = new PayPro(); - payment = pay.makePayment(payment); - - var tx = payment.message.transactions[0]; - - if (!tx) { - return done(new Error('No tx in payment ACK.')); - } - - if (tx.buffer) { - tx.buffer = new Buffer(new Uint8Array(tx.buffer)); - tx.buffer = tx.buffer.slice(tx.offset, tx.limit); - var ptx = new bitcore.Transaction(); - ptx.parse(tx.buffer); - tx = ptx; - } - - var ackTotal = outputs.reduce(function(total, _, i) { - // XXX reverse endianness to work around bignum bug: - var txv = tx.outs[i].v; - var v = new Buffer(8); - for (var j = 0; j < 8; j++) v[j] = txv[7 - j]; - return total.add(bignum.fromBuffer(v, { - endian: 'big', - size: 1 - })); - }, bitcore.Bignum('0', 10)); - - ackTotal.toString(10).should.equal(total.toString(10)); - - should.exist(ack); - memo.should.equal('Thank you for your payment!'); - - done(); - }); - }); - - ppw = createWallet(); - - it('#retrieve a payment request message via model', function(done) { - var w = ppw; - should.exist(w); - // Caches Payment Request but does not add TX proposal - w.fetchPaymentTx({ - uri: 'https://localhost:8080/-/request' - }, function(err, merchantData) { - if (err) return done(err); - merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay'); - return done(); - }); - }); - - it('#add tx proposal based on payment message via model', function(done) { - var w = ppw; - should.exist(w); - var options = { - uri: 'https://localhost:8080/-/request' - }; - var req = w.paymentRequests[options.uri]; - should.exist(req); - delete w.paymentRequests[options.uri]; - w.receivePaymentRequest(options, req.pr, function(ntxid, merchantData) { - should.exist(ntxid); - should.exist(merchantData); - w._ntxid = ntxid; - merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay'); - return done(); - }); - }); - - it('#add tx proposal based on payment message via model', function(done) { - var w = ppw; - should.exist(w); - w.sendPaymentTx(w._ntxid, function(txid, merchantData) { - should.exist(txid); - should.exist(merchantData); - should.exist(merchantData.ack); - merchantData.ack.memo.should.equal('Thank you for your payment!'); - return done(); - }); - }); - - it('#send a payment request using payment api', function(done) { - var w = createWallet(); - should.exist(w); - var uri = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; - var memo = 'Hello, server. I\'d like to make a payment.'; - w.createPaymentTx({ - uri: uri, - memo: memo - }, function(ntxid, merchantData) { - should.exist(ntxid); - should.exist(merchantData); - if (w.isShared()) { - return done(); - } else { - w.sendPaymentTx(ntxid, { memo: memo }, function(txid, merchantData) { - should.exist(txid); - should.exist(merchantData); - return done(); - }); - } - }); - }); - - it('#send a payment request with merchant prefix', function(done) { - var w = createWallet(); - should.exist(w); - var address = 'Merchant: ' + server.uri + '/request\nMemo: foo'; - var commentText = 'Hello, server. I\'d like to make a payment.'; - var uri; - - // Replicates code in controllers/send.js: - if (address.indexOf('bitcoin:') === 0) { - uri = new bitcore.BIP21(address).data; - } else if (address.indexOf('Merchant: ') === 0) { - uri = address.split(/\s+/)[1]; - } - - w.createTx(uri, commentText, function(ntxid, merchantData) { - if (w.isShared()) { - should.exist(ntxid); - should.exist(merchantData); - return done(); - } else { - should.exist(merchantData); - w.sendTx(ntxid, function(txid, merchantData) { - should.exist(txid); - should.exist(merchantData); - return done(); - }); - } - }); - }); - - it('#send a payment request with bitcoin uri', function(done) { - var w = createWallet(); - should.exist(w); - var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; - var commentText = 'Hello, server. I\'d like to make a payment.'; - w.createTx(address, commentText, function(ntxid, merchantData) { - if (w.isShared()) { - should.exist(ntxid); - should.exist(merchantData); - return done(); - } else { - w.sendTx(ntxid, function(txid, merchantData) { - should.exist(txid); - should.exist(merchantData); - return done(); - }); - } - }); - }); - - it('#try to sign a tampered payment request (raw)', function(done) { - var w = createWallet(); - should.exist(w); - var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; - var commentText = 'Hello, server. I\'d like to make a payment.'; - w.createTx(address, commentText, function(ntxid, merchantData) { - should.exist(ntxid); - should.exist(merchantData); - - // Tamper with payment request in its raw form: - var data = new Buffer(merchantData.raw, 'hex'); - data = PayPro.PaymentRequest.decode(data); - var pr = new PayPro(); - pr = pr.makePaymentRequest(data); + var ver = pr.get('payment_details_version'); + var pki_type = pr.get('pki_type'); + var pki_data = pr.get('pki_data'); var details = pr.get('serialized_payment_details'); + var sig = pr.get('signature'); + + var certs = PayPro.X509Certificates.decode(pki_data); + certs = certs.certificate; + + var verified = pr.verify(); + + if (!verified) { + return done(new Error('Server sent a bad signature.')); + } + details = PayPro.PaymentDetails.decode(details); var pd = new PayPro(); pd = pd.makePaymentDetails(details); + + var network = pd.get('network'); var outputs = pd.get('outputs'); - outputs[outputs.length - 1].set('amount', 1000000000); - pd.set('outputs', outputs); - pr.set('serialized_payment_details', pd.serialize()); - merchantData.raw = pr.serialize().toString('hex'); + var time = pd.get('time'); + var expires = pd.get('expires'); + var memo = pd.get('memo'); + var payment_url = pd.get('payment_url'); + var merchant_data = pd.get('merchant_data'); - var myId = w.getMyCopayerId(); - var txp = w.txProposals.get(ntxid); - should.exist(txp); - should.exist(txp.signedBy[myId]); - should.not.exist(txp.rejectedBy[myId]); + var priv = w.privateKey; + var pkr = w.publicKeyRing; - w.verifyPaymentRequest(ntxid).should.equal(false); + var opts = { + remainderOut: { + address: w._doGenerateAddress(true).toString() + } + }; - return done(); + var outs = []; + outputs.forEach(function(output) { + var amount = output.get('amount'); + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)) + }; + + // big endian + var v = new Buffer(8); + v[0] = (amount.high >> 24) & 0xff; + v[1] = (amount.high >> 16) & 0xff; + v[2] = (amount.high >> 8) & 0xff; + v[3] = (amount.high >> 0) & 0xff; + v[4] = (amount.low >> 24) & 0xff; + v[5] = (amount.low >> 16) & 0xff; + v[6] = (amount.low >> 8) & 0xff; + v[7] = (amount.low >> 0) & 0xff; + + var s = script.buffer.slice(script.offset, script.limit); + var net = network === 'main' ? 'livenet' : 'testnet'; + var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net); + + outs.push({ + address: addr[0].toString(), + amountSatStr: bitcore.Bignum.fromBuffer(v, { + // XXX for some reason, endian is ALWAYS 'big' + // in node (in the browser it behaves correctly) + endian: 'big', + size: 1 + }).toString(10) + }); + }); + + var b = new bitcore.TransactionBuilder(opts) + .setUnspent(unspentTest) + .setOutputs(outs); + + outputs.forEach(function(output, i) { + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)) + }; + var s = script.buffer.slice(script.offset, script.limit); + b.tx.outs[i].s = s; + }); + + var selectedUtxos = b.getSelectedUnspent(); + var inputChainPaths = selectedUtxos.map(function(utxo) { + return pkr.pathForAddress(utxo.address); + }); + + b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); + + if (priv) { + var keys = priv.getForPaths(inputChainPaths); + var signed = b.sign(keys); + } + + var tx = b.build(); + + var refund_outputs = []; + + var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0]; + + var total = outputs.reduce(function(total, _, i) { + // XXX reverse endianness to work around bignum bug: + var txv = tx.outs[i].v; + var v = new Buffer(8); + for (var j = 0; j < 8; j++) v[j] = txv[7 - j]; + return total.add(bignum.fromBuffer(v, { + endian: 'big', + size: 1 + })); + }, bitcore.Bignum('0', 10)); + + var rpo = new PayPro(); + rpo = rpo.makeOutput(); + + rpo.set('amount', +total.toString(10)); + + rpo.set('script', + Buffer.concat([ + new Buffer([ + 118, // OP_DUP + 169, // OP_HASH160 + 76, // OP_PUSHDATA1 + 20, // number of bytes + ]), + // needs to be ripesha'd + bitcore.util.sha256ripe160(refund_to), + new Buffer([ + 136, // OP_EQUALVERIFY + 172 // OP_CHECKSIG + ]) + ]) + ); + + refund_outputs.push(rpo.message); + + var pay = new PayPro(); + pay = pay.makePayment(); + pay.set('merchant_data', new Buffer([0, 1])); + pay.set('transactions', [tx.serialize()]); + pay.set('refund_to', refund_outputs); + pay.set('memo', 'Hi server, I would like to give you some money.'); + + pay = pay.serialize(); + + var req = { + headers: { + 'Host': 'localhost:8080', + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE, + 'Content-Length': pay.length + '' + }, + socket: { + remoteAddress: 'localhost', + remotePort: 8080 + }, + body: pay, + data: pay + }; + + Object.keys(req.headers).forEach(function(key) { + req.headers[key.toLowerCase()] = req.headers[key]; + }); + + server.POST['/-/pay'](req, function(err, res, body) { + if (err) return done(err); + + var data = PayPro.PaymentACK.decode(body); + var ack = new PayPro(); + ack = ack.makePaymentACK(data); + + var payment = ack.get('payment'); + var memo = ack.get('memo'); + + payment = PayPro.Payment.decode(payment); + var pay = new PayPro(); + payment = pay.makePayment(payment); + + var tx = payment.message.transactions[0]; + + if (!tx) { + return done(new Error('No tx in payment ACK.')); + } + + if (tx.buffer) { + tx.buffer = new Buffer(new Uint8Array(tx.buffer)); + tx.buffer = tx.buffer.slice(tx.offset, tx.limit); + var ptx = new bitcore.Transaction(); + ptx.parse(tx.buffer); + tx = ptx; + } + + var ackTotal = outputs.reduce(function(total, _, i) { + // XXX reverse endianness to work around bignum bug: + var txv = tx.outs[i].v; + var v = new Buffer(8); + for (var j = 0; j < 8; j++) v[j] = txv[7 - j]; + return total.add(bignum.fromBuffer(v, { + endian: 'big', + size: 1 + })); + }, bitcore.Bignum('0', 10)); + + ackTotal.toString(10).should.equal(total.toString(10)); + + done(); + }); }); - }); - it('#try to sign a tampered payment request (abstract)', function(done) { - var w = createWallet(); - should.exist(w); - var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; - var commentText = 'Hello, server. I\'d like to make a payment.'; - w.createTx(address, commentText, function(ntxid, merchantData) { - should.exist(ntxid); - should.exist(merchantData); + it('#retrieve a payment request message via http', function(done) { + var w = ppw; + should.exist(w); - // Tamper with payment request in its abstract form: - var outputs = merchantData.pr.pd.outputs; - var output = outputs[outputs.length - 1]; - var amount = output.amount; - amount.low = 2; + var req = { + headers: { + 'Host': 'localhost:8080', + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Content-Type': 'application/octet-stream', + 'Content-Length': '0' + }, + socket: { + remoteAddress: 'localhost', + remotePort: 8080 + }, + body: {} + }; - var myId = w.getMyCopayerId(); - var txp = w.txProposals.get(ntxid); - should.exist(txp); - should.exist(txp.signedBy[myId]); - should.not.exist(txp.rejectedBy[myId]); + Object.keys(req.headers).forEach(function(key) { + req.headers[key.toLowerCase()] = req.headers[key]; + }); - w.verifyPaymentRequest(ntxid).should.equal(false); - - return done(); + server.GET['/-/request'](req, function(err, res, body) { + var data = PayPro.PaymentRequest.decode(body); + pr = new PayPro(); + pr = pr.makePaymentRequest(data); + done(); + }); }); - }); - it('#try to sign a tampered txp tx (abstract)', function(done) { - var w = createWallet(); - should.exist(w); - var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; - var commentText = 'Hello, server. I\'d like to make a payment.'; - w.createTx(address, commentText, function(ntxid, merchantData) { - should.exist(ntxid); - should.exist(merchantData); + it('#send a payment message via http', function(done) { + var w = ppw; + should.exist(w); - // Tamper with payment request in its abstract form: - var txp = w.txProposals.get(ntxid); - var tx = txp.builder.tx || txp.builder.build(); - tx.outs[0].v = new Buffer([2, 0, 0, 0, 0, 0, 0, 0]); + var ver = pr.get('payment_details_version'); + var pki_type = pr.get('pki_type'); + var pki_data = pr.get('pki_data'); + var details = pr.get('serialized_payment_details'); + var sig = pr.get('signature'); - var myId = w.getMyCopayerId(); - var txp = w.txProposals.get(ntxid); - should.exist(txp); - should.exist(txp.signedBy[myId]); - should.not.exist(txp.rejectedBy[myId]); + var certs = PayPro.X509Certificates.decode(pki_data); + certs = certs.certificate; - w.verifyPaymentRequest(ntxid).should.equal(false); + var verified = pr.verify(); - return done(); + if (!verified) { + return done(new Error('Server sent a bad signature.')); + } + + details = PayPro.PaymentDetails.decode(details); + var pd = new PayPro(); + pd = pd.makePaymentDetails(details); + + var network = pd.get('network'); + var outputs = pd.get('outputs'); + var time = pd.get('time'); + var expires = pd.get('expires'); + var memo = pd.get('memo'); + var payment_url = pd.get('payment_url'); + var merchant_data = pd.get('merchant_data'); + + var priv = w.privateKey; + var pkr = w.publicKeyRing; + + var opts = { + remainderOut: { + address: w._doGenerateAddress(true).toString() + } + }; + + var outs = []; + outputs.forEach(function(output) { + var amount = output.get('amount'); + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)) + }; + + // big endian + var v = new Buffer(8); + v[0] = (amount.high >> 24) & 0xff; + v[1] = (amount.high >> 16) & 0xff; + v[2] = (amount.high >> 8) & 0xff; + v[3] = (amount.high >> 0) & 0xff; + v[4] = (amount.low >> 24) & 0xff; + v[5] = (amount.low >> 16) & 0xff; + v[6] = (amount.low >> 8) & 0xff; + v[7] = (amount.low >> 0) & 0xff; + + var s = script.buffer.slice(script.offset, script.limit); + var net = network === 'main' ? 'livenet' : 'testnet'; + var addr = bitcore.Address.fromScriptPubKey(new bitcore.Script(s), net); + + outs.push({ + address: addr[0].toString(), + amountSatStr: bitcore.Bignum.fromBuffer(v, { + // XXX for some reason, endian is ALWAYS 'big' + // in node (in the browser it behaves correctly) + endian: 'big', + size: 1 + }).toString(10) + }); + }); + + var b = new bitcore.TransactionBuilder(opts) + .setUnspent(unspentTest) + .setOutputs(outs); + + outputs.forEach(function(output, i) { + var script = { + offset: output.get('script').offset, + limit: output.get('script').limit, + buffer: new Buffer(new Uint8Array( + output.get('script').buffer)) + }; + var s = script.buffer.slice(script.offset, script.limit); + b.tx.outs[i].s = s; + }); + + var selectedUtxos = b.getSelectedUnspent(); + var inputChainPaths = selectedUtxos.map(function(utxo) { + return pkr.pathForAddress(utxo.address); + }); + + b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); + + if (priv) { + var keys = priv.getForPaths(inputChainPaths); + var signed = b.sign(keys); + } + + var tx = b.build(); + + var refund_outputs = []; + + var refund_to = w.publicKeyRing.getPubKeys(0, false, w.getMyCopayerId())[0]; + + var total = outputs.reduce(function(total, _, i) { + // XXX reverse endianness to work around bignum bug: + var txv = tx.outs[i].v; + var v = new Buffer(8); + for (var j = 0; j < 8; j++) v[j] = txv[7 - j]; + return total.add(bignum.fromBuffer(v, { + endian: 'big', + size: 1 + })); + }, bitcore.Bignum('0', 10)); + + var rpo = new PayPro(); + rpo = rpo.makeOutput(); + + rpo.set('amount', +total.toString(10)); + + rpo.set('script', + Buffer.concat([ + new Buffer([ + 118, // OP_DUP + 169, // OP_HASH160 + 76, // OP_PUSHDATA1 + 20, // number of bytes + ]), + // needs to be ripesha'd + bitcore.util.sha256ripe160(refund_to), + new Buffer([ + 136, // OP_EQUALVERIFY + 172 // OP_CHECKSIG + ]) + ]) + ); + + refund_outputs.push(rpo.message); + + var pay = new PayPro(); + pay = pay.makePayment(); + pay.set('merchant_data', new Buffer([0, 1])); + pay.set('transactions', [tx.serialize()]); + pay.set('refund_to', refund_outputs); + pay.set('memo', 'Hi server, I would like to give you some money.'); + + pay = pay.serialize(); + + var req = { + headers: { + 'Host': 'localhost:8080', + 'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE + ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, + 'Content-Type': PayPro.PAYMENT_CONTENT_TYPE, + 'Content-Length': pay.length + '' + }, + socket: { + remoteAddress: 'localhost', + remotePort: 8080 + }, + body: pay, + data: pay + }; + + Object.keys(req.headers).forEach(function(key) { + req.headers[key.toLowerCase()] = req.headers[key]; + }); + + server.POST['/-/pay'](req, function(err, res, body) { + if (err) return done(err); + + var data = PayPro.PaymentACK.decode(body); + var ack = new PayPro(); + ack = ack.makePaymentACK(data); + + var payment = ack.get('payment'); + var memo = ack.get('memo'); + + payment = PayPro.Payment.decode(payment); + var pay = new PayPro(); + payment = pay.makePayment(payment); + + var tx = payment.message.transactions[0]; + + if (!tx) { + return done(new Error('No tx in payment ACK.')); + } + + if (tx.buffer) { + tx.buffer = new Buffer(new Uint8Array(tx.buffer)); + tx.buffer = tx.buffer.slice(tx.offset, tx.limit); + var ptx = new bitcore.Transaction(); + ptx.parse(tx.buffer); + tx = ptx; + } + + var ackTotal = outputs.reduce(function(total, _, i) { + // XXX reverse endianness to work around bignum bug: + var txv = tx.outs[i].v; + var v = new Buffer(8); + for (var j = 0; j < 8; j++) v[j] = txv[7 - j]; + return total.add(bignum.fromBuffer(v, { + endian: 'big', + size: 1 + })); + }, bitcore.Bignum('0', 10)); + + ackTotal.toString(10).should.equal(total.toString(10)); + + should.exist(ack); + memo.should.equal('Thank you for your payment!'); + + done(); + }); }); - }); - it('#sign an untampered payment request', function(done) { - var w = createWallet(); - should.exist(w); - var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; - var commentText = 'Hello, server. I\'d like to make a payment.'; - w.createTx(address, commentText, function(ntxid, merchantData) { - should.exist(ntxid); - should.exist(merchantData); + ppw = createWallet(); - var myId = w.getMyCopayerId(); - var txp = w.txProposals.get(ntxid); - should.exist(txp); - should.exist(txp.signedBy[myId]); - should.not.exist(txp.rejectedBy[myId]); - - w.verifyPaymentRequest(ntxid).should.equal(true); - - return done(); + it('#retrieve a payment request message via model', function(done) { + var w = ppw; + should.exist(w); + // Caches Payment Request but does not add TX proposal + w.fetchPaymentTx({ + uri: 'https://localhost:8080/-/request' + }, function(err, merchantData) { + if (err) return done(err); + merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay'); + return done(); + }); }); - }); - it('#close payment server', function(done) { - server.close(function() { - return done(); + it('#add tx proposal based on payment message via model', function(done) { + var w = ppw; + should.exist(w); + var options = { + uri: 'https://localhost:8080/-/request' + }; + var req = w.paymentRequests[options.uri]; + should.exist(req); + delete w.paymentRequests[options.uri]; + w.receivePaymentRequest(options, req.pr, function(ntxid, merchantData) { + should.exist(ntxid); + should.exist(merchantData); + w._ntxid = ntxid; + merchantData.pr.pd.payment_url.should.equal('https://localhost:8080/-/pay'); + return done(); + }); }); - }); + + it('#add tx proposal based on payment message via model', function(done) { + var w = ppw; + should.exist(w); + w.sendPaymentTx(w._ntxid, function(txid, merchantData) { + should.exist(txid); + should.exist(merchantData); + should.exist(merchantData.ack); + merchantData.ack.memo.should.equal('Thank you for your payment!'); + return done(); + }); + }); + + it('#send a payment request using payment api', function(done) { + var w = createWallet(); + should.exist(w); + var uri = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; + var memo = 'Hello, server. I\'d like to make a payment.'; + w.createPaymentTx({ + uri: uri, + memo: memo + }, function(ntxid, merchantData) { + should.exist(ntxid); + should.exist(merchantData); + if (w.isShared()) { + return done(); + } else { + w.sendPaymentTx(ntxid, { + memo: memo + }, function(txid, merchantData) { + should.exist(txid); + should.exist(merchantData); + return done(); + }); + } + }); + }); + + it('#send a payment request with merchant prefix', function(done) { + var w = createWallet(); + should.exist(w); + var address = 'Merchant: ' + server.uri + '/request\nMemo: foo'; + var commentText = 'Hello, server. I\'d like to make a payment.'; + var uri; + + // Replicates code in controllers/send.js: + if (address.indexOf('bitcoin:') === 0) { + uri = new bitcore.BIP21(address).data; + } else if (address.indexOf('Merchant: ') === 0) { + uri = address.split(/\s+/)[1]; + } + + w.createTx(uri, commentText, function(ntxid, merchantData) { + if (w.isShared()) { + should.exist(ntxid); + should.exist(merchantData); + return done(); + } else { + should.exist(merchantData); + w.sendTx(ntxid, function(txid, merchantData) { + should.exist(txid); + should.exist(merchantData); + return done(); + }); + } + }); + }); + + it('#send a payment request with bitcoin uri', function(done) { + var w = createWallet(); + should.exist(w); + var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; + var commentText = 'Hello, server. I\'d like to make a payment.'; + w.createTx(address, commentText, function(ntxid, merchantData) { + if (w.isShared()) { + should.exist(ntxid); + should.exist(merchantData); + return done(); + } else { + w.sendTx(ntxid, function(txid, merchantData) { + should.exist(txid); + should.exist(merchantData); + return done(); + }); + } + }); + }); + + it('#try to sign a tampered payment request (raw)', function(done) { + var w = createWallet(); + should.exist(w); + var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; + var commentText = 'Hello, server. I\'d like to make a payment.'; + w.createTx(address, commentText, function(ntxid, merchantData) { + should.exist(ntxid); + should.exist(merchantData); + + // Tamper with payment request in its raw form: + var data = new Buffer(merchantData.raw, 'hex'); + data = PayPro.PaymentRequest.decode(data); + var pr = new PayPro(); + pr = pr.makePaymentRequest(data); + var details = pr.get('serialized_payment_details'); + details = PayPro.PaymentDetails.decode(details); + var pd = new PayPro(); + pd = pd.makePaymentDetails(details); + var outputs = pd.get('outputs'); + outputs[outputs.length - 1].set('amount', 1000000000); + pd.set('outputs', outputs); + pr.set('serialized_payment_details', pd.serialize()); + merchantData.raw = pr.serialize().toString('hex'); + + var myId = w.getMyCopayerId(); + var txp = w.txProposals.get(ntxid); + should.exist(txp); + should.exist(txp.signedBy[myId]); + should.not.exist(txp.rejectedBy[myId]); + + w.verifyPaymentRequest(ntxid).should.equal(false); + + return done(); + }); + }); + + it('#try to sign a tampered payment request (abstract)', function(done) { + var w = createWallet(); + should.exist(w); + var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; + var commentText = 'Hello, server. I\'d like to make a payment.'; + w.createTx(address, commentText, function(ntxid, merchantData) { + should.exist(ntxid); + should.exist(merchantData); + + // Tamper with payment request in its abstract form: + var outputs = merchantData.pr.pd.outputs; + var output = outputs[outputs.length - 1]; + var amount = output.amount; + amount.low = 2; + + var myId = w.getMyCopayerId(); + var txp = w.txProposals.get(ntxid); + should.exist(txp); + should.exist(txp.signedBy[myId]); + should.not.exist(txp.rejectedBy[myId]); + + w.verifyPaymentRequest(ntxid).should.equal(false); + + return done(); + }); + }); + + it('#try to sign a tampered txp tx (abstract)', function(done) { + var w = createWallet(); + should.exist(w); + var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; + var commentText = 'Hello, server. I\'d like to make a payment.'; + w.createTx(address, commentText, function(ntxid, merchantData) { + should.exist(ntxid); + should.exist(merchantData); + + // Tamper with payment request in its abstract form: + var txp = w.txProposals.get(ntxid); + var tx = txp.builder.tx || txp.builder.build(); + tx.outs[0].v = new Buffer([2, 0, 0, 0, 0, 0, 0, 0]); + + var myId = w.getMyCopayerId(); + var txp = w.txProposals.get(ntxid); + should.exist(txp); + should.exist(txp.signedBy[myId]); + should.not.exist(txp.rejectedBy[myId]); + + w.verifyPaymentRequest(ntxid).should.equal(false); + + return done(); + }); + }); + + it('#sign an untampered payment request', function(done) { + var w = createWallet(); + should.exist(w); + var address = 'bitcoin:2NBzZdFBoQymDgfzH2Pmnthser1E71MmU47?amount=0.00003&r=' + server.uri + '/request'; + var commentText = 'Hello, server. I\'d like to make a payment.'; + w.createTx(address, commentText, function(ntxid, merchantData) { + should.exist(ntxid); + should.exist(merchantData); + + var myId = w.getMyCopayerId(); + var txp = w.txProposals.get(ntxid); + should.exist(txp); + should.exist(txp.signedBy[myId]); + should.not.exist(txp.rejectedBy[myId]); + + w.verifyPaymentRequest(ntxid).should.equal(true); + + return done(); + }); + }); + + it('#close payment server', function(done) { + server.close(function() { + return done(); + }); + }); + } }); diff --git a/test/test.PrivateKey.js b/test/test.PrivateKey.js index 60ca71664..69753234e 100644 --- a/test/test.PrivateKey.js +++ b/test/test.PrivateKey.js @@ -18,21 +18,21 @@ try { } var PrivateKey = copay.PrivateKey || require('../js/models/core/PrivateKey'); -var config = { +var pkConfig = { networkName: 'livenet', }; describe('PrivateKey model', function() { it('should create an instance', function() { - var w = new PrivateKey(config); + var w = new PrivateKey(pkConfig); should.exist(w); should.exist(w.bip); should.exist(w.bip.derive); }); it('should derive priv keys', function() { - var pk = new PrivateKey(config); + var pk = new PrivateKey(pkConfig); for (var j = false; !j; j=true) { for (var i = 0; i < 3; i++) { var wk = pk.get(i, j); @@ -51,7 +51,7 @@ describe('PrivateKey model', function() { } }); it('should derive priv keys array', function() { - var w = new PrivateKey(config); + var w = new PrivateKey(pkConfig); var wks = w.getAll(2, 3); wks.length.should.equal(5); for (var j = 0; j < wks.length; j++) { @@ -71,7 +71,7 @@ describe('PrivateKey model', function() { }); it('fromObj toObj roundtrip', function() { - var w1 = new PrivateKey(config); + var w1 = new PrivateKey(pkConfig); var o = JSON.parse(JSON.stringify(w1.toObj())) var w2 = PrivateKey.fromObj(o); @@ -86,7 +86,7 @@ describe('PrivateKey model', function() { describe('#getId', function() { it('should calculate the copayerId', function() { - var w1 = new PrivateKey(config); + var w1 = new PrivateKey(pkConfig); should.exist(w1.getId()); w1.getId().length.should.equal(33 * 2); }); @@ -94,7 +94,7 @@ describe('PrivateKey model', function() { describe('#getIdPriv', function() { it('should calculate .id', function() { - var w1 = new PrivateKey(config); + var w1 = new PrivateKey(pkConfig); should.exist(w1.getIdPriv()); w1.getIdPriv().length.should.equal(32 * 2); }); @@ -102,7 +102,7 @@ describe('PrivateKey model', function() { describe('#cacheId', function() { it('should set .id and .idpriv', function() { - var w1 = new PrivateKey(config); + var w1 = new PrivateKey(pkConfig); w1.cacheId(); var pub = w1.id; var priv = w1.idpriv; @@ -111,7 +111,7 @@ describe('PrivateKey model', function() { }); it('should set the id equal to the public key of the idpriv private key', function() { - var w1 = new PrivateKey(config); + var w1 = new PrivateKey(pkConfig); w1.cacheId(); var pub = w1.id; var priv = w1.idpriv; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 3406cf42c..df588df86 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -20,7 +20,7 @@ var TransactionBuilder = bitcore.TransactionBuilder; var Transaction = bitcore.Transaction; var Address = bitcore.Address; -var config = { +var walletConfig = { requiredCopayers: 3, totalCopayers: 5, spendUnconfirmed: true, @@ -30,7 +30,7 @@ var config = { var getNewEpk = function() { return new PrivateKey({ - networkName: config.networkName, + networkName: walletConfig.networkName, }) .deriveBIP45Branch() .extendedPublicKeyString(); @@ -46,7 +46,7 @@ describe('Wallet model', function() { it('should fail to create an instance', function() { (function() { - new Wallet(config) + new Wallet(walletConfig) }).should. throw(); }); @@ -58,11 +58,11 @@ describe('Wallet model', function() { var createW = function(N, conf) { - var c = JSON.parse(JSON.stringify(conf || config)); + var c = JSON.parse(JSON.stringify(conf || walletConfig)); if (!N) N = c.totalCopayers; var mainPrivateKey = new copay.PrivateKey({ - networkName: config.networkName + networkName: walletConfig.networkName }); var mainCopayerEPK = mainPrivateKey.deriveBIP45Branch().extendedPublicKeyString(); c.privateKey = mainPrivateKey; @@ -78,9 +78,9 @@ describe('Wallet model', function() { networkName: c.networkName, }); - var storage = new Storage(config.storage); - var network = new Network(config.network); - var blockchain = new Blockchain(config.blockchain); + var storage = new Storage(walletConfig.storage); + var network = new Network(walletConfig.network); + var blockchain = new Blockchain(walletConfig.blockchain); c.storage = storage; c.network = network; c.blockchain = blockchain; @@ -100,8 +100,8 @@ describe('Wallet model', function() { } }; - c.networkName = config.networkName; - c.verbose = config.verbose; + c.networkName = walletConfig.networkName; + c.verbose = walletConfig.verbose; c.version = '0.0.1'; return new Wallet(c); @@ -322,9 +322,9 @@ describe('Wallet model', function() { o.opts.reconnectDelay = 100; var w2 = Wallet.fromObj(o, - new Storage(config.storage), - new Network(config.network), - new Blockchain(config.blockchain)); + new Storage(walletConfig.storage), + new Network(walletConfig.network), + new Blockchain(walletConfig.blockchain)); should.exist(w2); w2.publicKeyRing.requiredCopayers.should.equal(w.publicKeyRing.requiredCopayers); should.exist(w2.publicKeyRing.getCopayerId); @@ -580,7 +580,7 @@ describe('Wallet model', function() { }); it('#getUnspent should honor spendUnconfirmed = false', function(done) { - var conf = JSON.parse(JSON.stringify(config)); + var conf = JSON.parse(JSON.stringify(walletConfig)); conf.spendUnconfirmed = false; var w = createW2(null, null, conf); w.getBalance(function(err, balance, balanceByAddr, safeBalance) { @@ -592,7 +592,7 @@ describe('Wallet model', function() { }); it('#getUnspent and spendUnconfirmed should count transactions with 1 confirmations', function(done) { - var conf = JSON.parse(JSON.stringify(config)); + var conf = JSON.parse(JSON.stringify(walletConfig)); conf.spendUnconfirmed = false; var w = cachedCreateW2(null, null, conf); w.blockchain.getUnspent = w.blockchain.getUnspent2; @@ -684,7 +684,7 @@ describe('Wallet model', function() { it('should create & sign transaction from received funds', function(done) { var k2 = new PrivateKey({ - networkName: config.networkName + networkName: walletConfig.networkName }); var w = createW2([k2]); @@ -1093,7 +1093,7 @@ describe('Wallet model', function() { it('should throw if network is different', function() { var backup = copayConfig.forceNetwork; copayConfig.forceNetwork = true; - config.networkName = 'livenet'; + walletConfig.networkName = 'livenet'; createW2.should.throw(Error); copayConfig.forceNetwork = backup; }); diff --git a/test/test.storage.File.js b/test/test.storage.File.js deleted file mode 100644 index ae1c516a2..000000000 --- a/test/test.storage.File.js +++ /dev/null @@ -1,204 +0,0 @@ -'use strict'; - -var chai = chai || require('chai'); -var should = chai.should(); -var Storage = require('../js/models/storage/File'); -var sinon = require('sinon'); -var CryptoJS = require('node-cryptojs-aes').CryptoJS; - -var mock = require('mock-fs'); - -describe('Storage/File', function() { - it('should exist', function() { - should.exist(Storage); - }); - - var mockFS = function() { - var obj = { - "test": "test" - }; - var encryptedStr = CryptoJS.AES.encrypt(JSON.stringify(obj), 'password').toString(); - mock({ - 'myfilename': encryptedStr - }); - }; - - describe('#load', function(done) { - it('should call fs.readFile', function(done) { - mockFS(); - var storage = new Storage({ - password: 'password' - }); - storage.load('myfilename', function(err) { - mock.restore(); - done(); - }); - }); - }); - - describe('#save', function(done) { - it('should call fs.writeFile', function(done) { - mockFS(); - var storage = new Storage({ - password: 'password' - }); - storage.save('myfilename', function(err) { - mock.restore(); - done(); - }); - }); - }); - - describe('#_read', function() { - it('should return the value of a key', function() { - var storage = new Storage(); - storage.data = { - 'walletId': { - 'test': 'data' - } - }; - storage._read('walletId::test').should.equal('data'); - }); - }); - - describe('#_write', function() { - it('should save the value of a key and then run save', function(done) { - var storage = new Storage(); - storage.save = function(walletId, callback) { - storage.data[walletId]['key'].should.equal('value'); - callback(); - }; - storage._write('walletId::key', 'value', function() { - done(); - }); - }); - }); - - describe('#getGlobal', function() { - it('should call storage._read', function() { - var storage = new Storage(); - storage.data = { - 'walletId': { - 'test': 'test' - } - }; - storage._read = sinon.spy(); - storage.getGlobal('walletId::test'); - storage._read.calledOnce.should.equal(true); - }); - }); - - describe('#setGlobal', function() { - it('should store a global key', function(done) { - var storage = new Storage(); - storage.save = function(walletId, callback) { - storage.data[walletId]['key'].should.equal('value'); - callback(); - }; - storage.setGlobal('walletId::key', 'value', function() { - done(); - }); - }); - }); - - describe('#removeGlobal', function() { - it('should remove a global key', function(done) { - var storage = new Storage(); - storage.data = { - 'walletId': { - 'key': 'value' - } - }; - storage.save = function(walletId, callback) { - should.not.exist(storage.data[walletId]['key']); - callback(); - }; - storage.removeGlobal('walletId::key', function() { - done(); - }); - }); - }); - - describe('#_key', function() { - it('should merge the wallet id and item key', function() { - var storage = new Storage(); - storage._key('wallet', 'key').should.equal('wallet::key'); - }); - }); - - describe('#get', function() { - it('should call getGlobal with the correct key', function() { - var storage = new Storage(); - storage.getGlobal = sinon.spy(); - storage.get('wallet', 'key'); - storage.getGlobal.calledOnce.should.equal(true); - storage.getGlobal.calledWith('wallet::key').should.equal(true); - }); - }); - - describe('#set', function() { - it('should call setGlobal with the correct key', function() { - var storage = new Storage(); - storage.setGlobal = sinon.spy(); - storage.set('wallet', 'key'); - storage.setGlobal.calledOnce.should.equal(true); - storage.setGlobal.calledWith('wallet::key').should.equal(true); - }); - }); - - describe('#remove', function() { - it('should call removeGlobal with the correct key', function() { - var storage = new Storage(); - storage.removeGlobal = sinon.spy(); - storage.remove('wallet', 'key'); - storage.removeGlobal.calledOnce.should.equal(true); - storage.removeGlobal.calledWith('wallet::key').should.equal(true); - }); - }); - - describe('#setFromObj', function() { - it('should set this object for a wallet', function(done) { - var obj = { - test: 'testval' - }; - var storage = new Storage(); - storage.save = function(walletId, callback) { - callback(); - }; - storage.setFromObj('walletId', obj, function() { - storage.data.walletId.test.should.equal('testval'); - done(); - }); - }); - }); - - describe('#getEncryptedObj', function() { - it('should give an encrypted object', function() { - var obj = { - test: 'testval' - }; - var data = JSON.stringify(obj); - var encrypted = CryptoJS.AES.encrypt(data, 'password'); - var base64 = encrypted.toString(); - - var storage = new Storage({ - password: 'password' - }); - storage.data['walletId'] = obj; - - var enc = storage.getEncryptedObj('walletId'); - //enc.length.should.equal(96); - enc.length.should.be.greaterThan(10); - enc.slice(0, 10).should.equal(base64.slice(0, 10)); - //enc.slice(0,6).should.equal("53616c"); - }); - }); - - describe('#clearAll', function() { - it('should set data to {}', function() { - - }); - }); - -}); - diff --git a/util/build.js b/util/build.js index 5495a04a2..db14b847d 100644 --- a/util/build.js +++ b/util/build.js @@ -59,9 +59,6 @@ var createBundle = function(opts) { b.require('./js/models/core/HDPath', { expose: '../js/models/core/HDPath' }); - b.require('./js/models/storage/File', { - expose: '../js/models/storage/File' - }); b.require('./config', { expose: '../config' });